@thi.ng/oquery
Version:
Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects
337 lines (336 loc) • 8.93 kB
JavaScript
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
};