Skip to content

Instantly share code, notes, and snippets.

@postspectacular
Created November 6, 2025 22:21
Show Gist options
  • Select an option

  • Save postspectacular/ff997a4f1016b17bbfe9beb989984ac3 to your computer and use it in GitHub Desktop.

Select an option

Save postspectacular/ff997a4f1016b17bbfe9beb989984ac3 to your computer and use it in GitHub Desktop.
Query engine example using a Lisp-like syntax to perform nested tag queries with Set-semantics (TODO insert URL)
import type { FnU2, Maybe, Predicate2 } from "@thi.ng/api";
import { difference, intersection, union } from "@thi.ng/associative";
import { isArray, isString } from "@thi.ng/checks";
import { illegalArgs } from "@thi.ng/errors";
import { evalSource } from "@thi.ng/lispy";
import { defQuery, type QueryOpts } from "@thi.ng/oquery";
import { expect, test } from "bun:test";
type Note = { id: number; tags: string[] };
// example collection of tagged notes/items for querying
const DB = <Note[]>[
{ id: 0, tags: ["a", "b"] },
{ id: 1, tags: ["a", "foo"] },
{ id: 2, tags: ["b", "bar"] },
{ id: 3, tags: ["c"] },
];
// helper function for testing
const expectQuery = (src: string, ...expectedIDs: number[]) =>
expect(executeQuery(DB, src)).toEqual(expectedIDs.map((i) => DB[i]));
// test various query expressions...
test("and", () => expectQuery("(and 'a' 'b')", 0));
test("or", () => expectQuery("(or 'a' 'b')", 0, 1, 2));
test("not", () => expectQuery("(not 'a')", 2, 3));
test("nested and", () => expectQuery("(and (not 'a') 'c')", 3));
test("nested or", () => expectQuery("(not (or 'foo' 'bar'))", 0, 3));
test("double negative", () => expectQuery("(not (not 'a'))", 0, 1));
// main query function, evaluating (potentially nested) query expressions in
// Lisp-style syntax, e.g. `(not (or 'tag1' 'tag2'))` to select items which
// have neither a `tag1` nor `tag2`...
const executeQuery = (db: Note[], src: string) =>
// evalSource() is the thi.ng/lispy interpreter which takes the query
// expression sourcecode and a custom environment of available built-in
// functions, in this case limited to only the following three ops...
evalSource(
src,
{
// implementation of AND (aka intersection) query (see below)
and: defOp(
db,
intersection,
(terms, tags) => terms.every((x) => tags.includes(x)),
{ intersect: true, cwise: false }
),
// implementation of OR (aka union) query
or: defOp(
db,
union,
(terms, tags) => terms.some((x) => tags.includes(x)),
{ cwise: false }
),
// implementation of NOT (aka negation/difference) query
not: (term: any) =>
isString(term)
? defQuery<Note[]>({ intersect: true, cwise: false })(
db,
"tags",
(x: string[]) => !x.includes(term)
)
: isArray(term)
? [...difference(new Set(db), new Set(term))]
: illegalArgs("wrong NOT query term"),
},
// custom syntax option for our query DSL, see https://thi.ng/lispy
{ string: "'" }
);
// higher-order query operation (used for AND/OR queries).
// delegates to https://thi.ng/oquery for querying
// applies chosen Set semantics to combine any sub-results
const defOp =
(
db: Note[],
op: FnU2<Set<Note>>,
pred: Predicate2<string[]>,
opts: Partial<QueryOpts>
) =>
(...terms: any[]) => {
let items: Maybe<Set<Note>>;
const tags: string[] = [];
for (let x of terms) {
if (isString(x)) tags.push(String(x));
else if (isArray(x))
items = items ? op(items, new Set(x)) : new Set(x);
else illegalArgs("wrong query term: " + x);
}
const sel = items ? [...items] : db;
return tags.length
? defQuery<Note[]>(opts)(sel, "tags", (x: string[]) =>
pred(tags, x)
)
: sel;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment