UNPKG

@thi.ng/oquery

Version:

Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects

337 lines (336 loc) 8.93 kB
import { isArray } from "@thi.ng/checks/is-array"; import { isFunction } from "@thi.ng/checks/is-function"; import { isSet } from "@thi.ng/checks/is-set"; import { compare } from "@thi.ng/compare/compare"; import { compareByKey } from "@thi.ng/compare/keys"; import { reverse } from "@thi.ng/compare/reverse"; import { defmulti } from "@thi.ng/defmulti/defmulti"; import { equiv } from "@thi.ng/equiv"; const __classify = (x) => x != null ? isFunction(x) ? "f" : "l" : "n"; const __ensureArray = (src) => isArray(src) ? src : [...src]; const __ensureSet = (src) => isArray(src) ? new Set(src) : src; const __intersect = (src) => { const a = __ensureArray(src); const num = a.length; return (b) => { const $b = __ensureSet(b); for (let i = num; i-- > 0; ) { if (!$b.has(a[i])) return false; } return true; }; }; const __coerce = (x, isec = false) => isArray(x) ? isec ? __intersect(x) : (y) => x.includes(y) : isSet(x) ? isec ? __intersect(x) : (y) => x.has(y) : x; const __coerceStr = (x) => isArray(x) ? __coerce(x.map((y) => String(y))) : isSet(x) ? __coerce(new Set([...x].map((y) => String(y)))) : x == null || isFunction(x) ? x : String(x); const __addTriple = (acc, s, p, o) => { const sval = acc[s]; sval ? sval[p] = o : acc[s] = { [p]: o }; }; const __match = (o, val, opts) => { if (val != null) { const pred = isFunction(o) ? o : ($) => opts.equiv(o, $); return opts.cwise && isArray(val) ? val.some(pred) : pred(val); } return false; }; const __collect = (acc, s, p, o, val, opts) => { if (val != null) { const pred = isFunction(o) ? o : ($) => opts.equiv(o, $); if (opts.cwise && isArray(val)) { val = val.filter(pred); val.length && __addTriple(acc, s, p, val); } else if (pred(val)) { __addTriple(acc, s, p, val); } } }; const __collectFull = (res, s, val) => res[s] = val; const __collectSP = (res, sval, s, p, o, opts) => { if (opts.partial) { for (let $p in sval) { p($p) && __collect(res, s, $p, o, sval[$p], opts); } } else { for (let $p in sval) { if (p($p) && __match(o, sval[$p], opts)) { __collectFull(res, s, sval); return; } } } }; const __collectSO = (res, sval, s, o, opts) => { if (opts.partial) { for (let p in sval) { __collect(res, s, p, o, sval[p], opts); } } else { for (let p in sval) { if (__match(o, sval[p], opts)) { __collectFull(res, s, sval); return; } } } }; const __queryLL = (res, db, s, p, o, opts) => { const sval = db[s]; const val = sval?.[p]; if (opts.partial) { __collect(res, s, p, o, val, opts); } else { __match(o, val, opts) && __collectFull(res, s, sval); } }; const __queryLF = (res, db, s, p, o, opts) => { const sval = db[s]; sval != null && __collectSP(res, sval, s, p, o, opts); }; const __queryLN = (res, db, s, _, o, opts) => { const sval = db[s]; sval != null && __collectSO(res, sval, s, o, opts); }; const __queryFL = (res, db, s, p, o, opts) => { if (opts.partial) { for (let $s in db) { s($s) && __collect(res, $s, p, o, db[$s]?.[p], opts); } } else { for (let $s in db) { const sval = db[$s]; s($s) && __match(o, sval?.[p], opts) && __collectFull(res, $s, sval); } } }; const __queryFF = (res, db, s, p, o, opts) => { for (let $s in db) { s($s) && __collectSP(res, db[$s], $s, p, o, opts); } }; const __queryFFNPartial = (res, db, s, p) => { for (let $s in db) { if (s($s)) { const sval = db[$s]; for (let $p in sval) { p($p) && __addTriple(res, $s, $p, sval[$p]); } } } }; const __queryFFN = (res, db, s, p) => { for (let $s in db) { if (s($s)) { const sval = db[$s]; for (let $p in sval) { if (p($p)) { __collectFull(res, $s, sval); break; } } } } }; const __queryFN = (res, db, s, _, o, opts) => { for (let $s in db) { s($s) && __collectSO(res, db[$s], $s, o, opts); } }; const __queryNL = (res, db, _, p, o, opts) => { if (opts.partial) { for (let s in db) { __collect(res, s, p, o, db[s][p], opts); } } else { for (let s in db) { const sval = db[s]; __match(o, sval[p], opts) && __collectFull(res, s, sval); } } }; const __queryNF = (res, db, _, p, o, opts) => { for (let s in db) { __collectSP(res, db[s], s, p, o, opts); } }; const __queryNN = (res, db, _, __, o, opts) => { for (let s in db) { __collectSO(res, db[s], s, o, opts); } }; const __queryNLNPartial = (res, db, _, p) => { for (let s in db) { const val = db[s][p]; val != null && __addTriple(res, s, p, val); } }; const __queryNLN = (res, db, _, p) => { for (let s in db) { const sval = db[s]; const val = sval[p]; val != null && __collectFull(res, s, sval); } }; const __querySP = (res, sval, s, p, _, opts) => { if (opts.partial) { for (let q in sval) { if (p(q)) { const val = sval[q]; val != null && __addTriple(res, s, q, val); } } } else { for (let q in sval) { if (p(q)) { __collectFull(res, s, sval); return; } } } }; const __queryO = (res, db, s, p, _, opts) => { const sval = db[s]; const val = sval?.[p]; val != null && (opts.partial ? __addTriple(res, s, p, val) : __collectFull(res, s, sval)); }; const IMPLS = { lll: __queryLL, llf: __queryLL, lln: __queryO, lfl: __queryLF, lff: __queryLF, lfn: (res, db, s, p, _, opts) => { const sval = db[s]; sval != null && __querySP(res, sval, s, p, null, opts); }, lnl: __queryLN, lnf: __queryLN, lnn: (res, db, s) => { const sval = db[s]; sval != null && __collectFull(res, s, sval); }, fll: __queryFL, flf: __queryFL, fln: (res, db, s, p, _, opts) => { for (let $s in db) { s($s) && __queryO(res, db, $s, p, null, opts); } }, ffl: __queryFF, fff: __queryFF, ffn: (res, db, s, p, o, opts) => (opts.partial ? __queryFFNPartial : __queryFFN)(res, db, s, p, o, opts), fnl: __queryFN, fnf: __queryFN, fnn: (res, db, s) => { for (let $s in db) { if (s($s)) { const sval = db[$s]; sval != null && __collectFull(res, $s, sval); } } }, nll: __queryNL, nlf: __queryNL, nln: (res, db, s, p, o, opts) => (opts.partial ? __queryNLNPartial : __queryNLN)(res, db, s, p, o, opts), nfl: __queryNF, nff: __queryNF, nfn: (res, db, _, p, __, opts) => { for (let s in db) { __querySP(res, db[s], s, p, null, opts); } }, nnl: __queryNN, nnf: __queryNN, nnn: (res, db) => Object.assign(res, db) }; const __impl = defmulti((_, __, s, p, o) => __classify(s) + __classify(p) + __classify(o), {}, IMPLS); const __objQuery = (src, opts, args) => { const isIsec = opts.cwise && opts.intersect; isIsec && (opts.cwise = false); let [s, p, o, out] = args; out = out || {}; __impl( out, src, __coerceStr(s), __coerceStr(p), __coerce(o, isIsec), opts ); return out; }; const __arrayQuery = (src, opts, p, o, collect) => { const isIsec = opts.cwise && opts.intersect; isIsec && (opts.cwise = false); const $p = __coerceStr(p); const $o = __coerce(o, isIsec); const impl = IMPLS["n" + __classify($p) + __classify($o)]; for (let i = 0, n = src.length; i < n; i++) { const res = {}; impl(res, { _: src[i] }, "_", $p, $o, opts); res._ && collect(res._, i); } }; const DEFAULT_OPTS = { partial: false, cwise: true, intersect: false, equiv }; const defQuery = (opts) => { const $opts = { ...DEFAULT_OPTS, ...opts }; return ((src, ...args) => { if (isArray(src)) { const out = args[2] || []; __arrayQuery(src, $opts, args[0], args[1], (x) => out.push(x)); return out; } else { return __objQuery(src, $opts, args); } }); }; const defKeyQuery = (opts) => { const $opts = { ...DEFAULT_OPTS, ...opts }; return ((src, ...args) => { if (isArray(src)) { const out = args[2] || /* @__PURE__ */ new Set(); __arrayQuery(src, $opts, args[0], args[1], (_, i) => out.add(i)); return out; } else { const res = __objQuery(src, $opts, args.slice(0, 3)); const out = args[3]; if (!out) return new Set(Object.keys(res)); for (let k in res) out.add(k); return out; } }); }; const query = (db, terms, opts = {}) => { for (let term of terms) { if (!term) continue; db = defQuery(term.opts)( db, term.q[0], term.q[1] ); term.post && (db = term.post(db)); if (!db.length) return db; } const limit = opts.limit || 0; if (limit > 0 && limit < db.length) { db.length = limit; } if (opts.sort) { db.sort( compareByKey( opts.sort, opts.reverse ? reverse(compare) : compare ) ); } return db; }; export { defKeyQuery, defQuery, query };