Class reference
Query
Querying a document
query() turns a Document into something you interrogate. It returns a lazy, composable Query: you chain filters and ordering, then end with a terminal that walks the document. Nothing is evaluated until the terminal runs, and filter-only pipelines stop early. It is read-only — a query never mutates the document.
auto open_highs = doc.query("/tracker/*")
.where(byType("issue") && field("status").eq("open") && field("sev").eq("high"))
.orderBy("area");
for (const Element* e : open_highs.toList()) std::puts(e->path().str().c_str());# Every open HIGH issue, by area — the whole pipeline is lazy until .to_list()/iteration.
open_highs = doc.query("/tracker/*").where(type="issue", status="open", sev="high").order_by("area")
for issue in open_highs:
print(issue.path)query() is additive: find() still returns its eager list. Pass a path glob to scope the source, or no argument for the whole document. A Query borrows its document — do not keep one past the document's life, and do not mutate the document while iterating. (In C++, query() on a temporary document is a compile error.)
where — filtering
where narrows the result. Chain it more than once; the conditions combine with AND.
Python accepts three forms, freely mixed:
doc.query().where(type="issue", status="open") # keyword sugar: type token + property equality
doc.query().where(kg.field("sev") == "high") # a field expression
doc.query().where(kg.field("weight") > 3) # comparisons
doc.query().where(lambda h: h.get_int("weight") > 3) # an escape-hatch lambda, given a Handle
C++ uses predicate builders composed with &&, ||, !:
doc.query().where(byType("issue") && field("status").eq("open"));
doc.query().where(field("sev").eq("high") || field("sev").eq("med"));
doc.query().where([](const Element& e){ return e.type() == "issue"; }); // escape hatch
Field predicates
field("name") builds a predicate from a property. Methods are canonical in both languages; Python also allows the operators.
| | reads | |---|---| | eq(v) / == | equal | | ne(v) / != | present and unequal | | lt le gt ge / < <= > >= | ordered comparison | | in_([...]) (Python) / in({...}) (C++) | equal to any | | contains(s) · startswith(s) | substring / prefix (string properties) | | exists() | the property is present |
Other builders: by_type(t) / byType(t) (the element's type token), under(path) (strict descendants), name_is(name) / nameIs, meta(key).eq(v), has_meta(key) / hasMeta.
Comparison semantics (the rules worth knowing)
- Numbers compare by value, across dtypes.
field("scale").eq(1)matches a property stored as a float1.0— the query layer widens both sides. (Prism'sValueonly compares exactly; the query layer adds this.) - Strings compare as text. A number-vs-string comparison never matches (it is not an error).
- Missing is not a match. A missing or wrong-typed property makes a positive predicate false — it never throws. So
field("sev").ne("high")matches elements that have asevand it isn'thigh; an element with nosevis not matched. To also catch the missing ones, negate the equality:~field("sev").eq("high")(Python) /!field("sev").eq("high")(C++) is true whensevis absent. - Time: animated properties are read at time 0 in v1.
Ordering, limiting
doc.query().where(type="issue").order_by("weight") # ascending, stable
doc.query().where(type="issue").order_by("weight").reverse() # descending
doc.query().where(type="issue").limit(10)
order_by(field) is ascending and stable; chain reverse() for descending. Elements whose key is missing or incomparable sort last, keeping document order among themselves. limit(n) takes the first n.
Terminals
A terminal evaluates the pipeline against the document as of that call (a Query is a recipe, not a frozen snapshot — call to_list() to freeze).
| terminal | returns | |---|---| | iterate / to_list() | the matched elements (Handles in Python, const Element* in C++) | | paths() | their paths | | first() | the first match, or None/nullptr | | one() | exactly one match, else raises (PrismError / std::invalid_argument) | | count() | how many matched | | any() | whether any matched (short-circuits) | | group_by(field) | a dict/map of string key → matched elements (string properties or the type token) |
In Python a Query is list-like — len(q), q[0], and iteration all work.
n_open = doc.query().where(type="issue", status="open").count()
by_status = doc.query().where(type="issue").group_by("status") # {"open": [...], "done": [...]}
first_bug = doc.query().where(type="issue", kind="bug").first()
Laziness
Building a query scans nothing. A filter-only pipeline runs in a single pass and short-circuits — first() and any() stop at the first match. A buffering stage (order_by, reverse) consumes the whole result before the terminal. Re-running a terminal re-walks the document, so two terminals on one query can differ if the document changed between them; to_list() freezes a snapshot.
The query is implemented natively in
kinogaki-core(C++) and mirrored in the Python package; both are exercised by one shared fixture so they stay in lockstep.