Expand description
dr-strange’s query language: an openCypher subset that parses into core’s
LogicalPlan (arch/00 §5 — “a Cypher/GQL-subset parser lands in v2 on top
of the same logical plan layer”). The nom grammar and the plan compiler
live here so the parser-combinator dependency never reaches the lean core;
the crate depends on core only for the plan types it targets.
MATCH (p:Paper)-[:CITES]->(q:Paper)
WHERE p.year >= 2020
RETURN q
ORDER BY q.year DESC
LIMIT 10§Supported
Every source below binds one node pattern and may continue with a
relationship tail, so a typed hop chains off a retrieval seed exactly as it
does off a MATCH node (ROADMAP §7).
MATCHone linear path:(a:Label),-[:TYPE]->/<-[:TYPE]-/-[:T]-(in/out/both), bare-->/--, and bounded variable-length-[:T*1..3]->.SEARCH (v:Label) [ON prop] NEAR "text"|[..] [METRIC m] [TOPK k]— an indexed vector seed (Source::VectorTopK)."text"is embedded server-side viaparse_with_embedder;[..]is a literal escape hatch.ONmay be omitted: everyNEARdefaults to theembeddingproperty, which is what the digest pipeline writes.SEARCH (v:Label) ON prop MATCHING "text" [TOPK k]— a BM25 keyword seed (Source::KeywordTopK) over a declared keyword index. Same verb as the vector seed, different operator:NEARcompares meaning,MATCHINGcompares words.HYBRID (v:Label) [VECTOR [ON p] NEAR q [METRIC m] [WEIGHT w]] [KEYWORD ON p MATCHING "text" [WEIGHT w]] [GRAPH HOPS h [DECAY d] [SEEDS n] [WEIGHT w]] [CANDIDATES n] [TOPK k]— fused retrieval (Source::Hybrid); channels in any order, at least one of VECTOR/KEYWORD.CALL <pagerank|components|shortest_path|louvain>(arg: v, …) ON (v[:Label])— a graph algorithm as a source (Source::Algo).ONboth scopes the algorithm and binds the variable; the per-node result ridesscore().BEAM (result[:Label]) <OUT|IN|BOTH> [:TYPE] ON prop NEAR "text"|[..] [METRIC m] WIDTH w DEPTH d— similarity-guided beam traversal (Step::ExpandBeam) from the current frontier; chains after any source.WHEREover=,<>,!=,<,<=,>,>=,AND/OR/NOT,+ - * /,IS [NOT] NULL,x IN [a, b], property accessa.key, the label predicatea:Label, the external keykey(a), and the scoring termsscore(),hops(),similarity(a.prop, "text"|[..][, metric]),distance(...)(usable inORDER BYtoo — a brute-force rank).RETURN [DISTINCT] <var|*>— node records — or a projection:RETURN a.name, count(*) AS n, …, each item a column named by its alias or its own text, over any variable the pattern bound (an earlier one compiles toExpr::At). Folds:count/sum/avg/min/max/collect, each optionallyDISTINCT, grouped by every column that isn’t one.ORDER BY expr [ASC|DESC], …,SKIP n,LIMIT n— steps over node rows; on the projection tail otherwise, whereORDER BYnames a returned column (by alias, by the expression it returned, or by the aggregate it folds:ORDER BY count(*)).AS OF <seq|"RFC-3339"|TIME <ms>>— last clause; reads a past snapshot (native backend). Not a plan node: it rides onReadQueryfor the surface to apply withPlaneHandle::as_of.
key(n) = "…" (or key(n) IN […]) on a scanned source compiles to a
SeekKeys seek rather than a scan-and-filter — an index lookup, so an LLM
can anchor on an entity it knows by key.
§Writes (via parse_statement, applied with WriteStatement::apply)
CREATE (n:L {k: v, …}), (a)-[:T {…}]->(b), …— a stringkey:sets the external key; edges are directed (->/<-).MERGE (n:L {key: "…", …}) [ON CREATE SET …] [ON MATCH SET …]— upsert a node by its external key; a pathMERGE (a {key})-[:T]->(b {key})upserts each keyed node and ensures the edge (idempotent, element-wise — also against what earlier clauses of the same statement created).MATCH pattern [WHERE …] SET n.p = v, n:Label, n += {…}/REMOVE n.p, n:Label/[DETACH] DELETE n— find-then-mutate on the pattern’s terminal variable, once per distinct matched node however many paths reach it (plainDELETErefuses a connected node).MATCH pattern [WHERE …] CREATE (n)-[:T]->(x {…})/MERGE (n)-[:T]->(x {key})— once per matched row, with the terminal variable pre-bound so(n)anchors to the matched node.
§Parameters
$name placeholders in value positions (WHERE/ORDER literals, SET/CREATE/
MERGE props, and a node’s key:) are resolved from a caller-supplied
Params map via parse_statement_full — the SDK-safe way to pass
values (no string interpolation).
§Null
Core’s evaluator is two-valued: a missing property makes a predicate
false, so left alone n.p <> 1, NOT n.p = 1 and NOT n.p IN [..] would
be true for a node with no p. The compiler guards those — <>, NOT,
and = with no constant side — with IS NOT NULL on every property (or
channel, or literal null) they read, so for a predicate over one
property the rows match openCypher: a missing value satisfies neither the
test nor its negation, n.p = null keeps nothing, and IS [NOT] NULL is
how absence is asked about. Where this is stricter than openCypher: under
NOT, a compound predicate whose false branch would have absorbed a null
(NOT (n.a = 1 AND n.b = 2) keeps a node with b = 3 and no a there,
not here); and x IN [1, null] is null here even when x = 1. Ordered
comparisons, IN and the string predicates are already false on a
missing value and stay unguarded.
§Lexical rules
Keywords and function names are case-insensitive. Identifiers are Unicode
words, or anything between backticks. Strings take either quote with the
escapes \' \" \\ \n \t \r \b \f \uXXXX; numbers are
ints or floats (1.5, 1e9). Expressions nest at most
parse::MAX_NESTING levels, an operator of an AND/OR/arithmetic chain
counting one; deeper is a syntax error, not a stack overflow.
§Not yet (each a clear error naming the rewrite, never a silent miscompile)
- cross-variable predicates (
p.year < q.year); - returning the rows of a non-terminal variable (
RETURN pafter a hop); its values project (RETURN p.name); WITHpipelining: a projection is a tail, so nothing follows it;- unbounded variable-length (
*,*n..); - a second
MATCH,OPTIONAL MATCH,UNION,UNWIND, and a pattern with several paths ((a)-->(b), (a)-->(c)): a query holds one linear path — run one query per pattern or branch; - relationship variables (
-[r:T]->): an edge can’t be bound, returned or filtered on — drop the variable; - inline property predicates in a
MATCHnode ((n {name: "x"})): write them inWHERE; - list and map literals as values (
RETURN [1, 2],n.x = {a: 1}); a list is only sugar afterIN(and a vector afterNEAR), a map only aCREATE/SETproperty map; - the
%and^operators.
Errors point at the fault: a mistyped clause is reported from the reading of the statement that got furthest, not from the first token.
§Completing a prefix
complete answers the other question an editor asks: not “is this a
statement” but “what could this become”. It reads a half-typed query
against a plane’s Vocab — its labels, edge types and properties, with
the counts — so the guess after MATCH is the label that plane holds most
of, and the guess after a node is the edge that most often leaves it. It is
a second, smaller reading of this same grammar; see complete for why a
nom parser cannot do it.
Structs§
- Completion
- What may come next, best first.
- Connection
src -[:type]-> dst, and how many such edges there are.- Edge
Info - One edge type: how many the plane holds, and between which labels.
- Label
Info - One node label: how many nodes carry it, and the properties they hold.
- Read
Query - A compiled read: the plan to run, and the snapshot to run it against.
- Suggestion
- One thing the caret could become.
- Vocab
- What a plane holds, as far as completing a query needs to know: the soft-schema catalog, which a server has already computed.
- Write
Statement - A compiled, executable write statement: an optional bound
MATCH(variable name + its read plan) and the mutation ops. Build with [compile]. - Write
Summary - What a write statement changed. Returned so a surface can report
2 nodes, 1 edge created, etc.
Enums§
- AsOf
Spec - The point
AS OFpins reads to. A bare integer is a commit sequence; a quoted RFC-3339 instant andTIME <ms>are both wall-clock addresses. - Expect
- What the caret sits at — the question a suggestion answers.
- Kind
- What kind of thing a suggestion is, for a caller that shows them differently.
- Parse
Error - Why a query didn’t turn into a plan.
- Statement
- A parsed statement: a read (a runnable plan) or a write (applied with
WriteStatement::apply). Surfaces branch on this to run a query.
Traits§
- Embedder
- Resolves the text in a
SEARCH … NEAR "text"(orsimilarity(p, "text")) into a query vector. The pure parser owns no embedding machinery — a surface (web/MCP/CLI) supplies this, backed by the server’s configured provider (API key from the environment, never the client). Keeping it a one-method trait keepsdr-strange-parserdependency-free.
Functions§
- complete
- What may follow
prefix— the query text from its start to the caret — in a plane whose vocabulary isvocab. - grammar_
hint - The grammar a failed statement was reaching for, chosen by the keywords it contains. Sections are joined by blank lines; the result stands on its own after an error message.
- parse
- Parse a read query into a runnable
ReadQuery. A write statement (CREATE, …) is an error here — useparse_statement. A textSEARCH … NEAR "…"also errors (no embedder); useparse_with_embedderor a literal vector. - parse_
statement - Parse a statement — either a read query or a write (
CREATE, …). This is the entry point a surface uses when it wants to run either. - parse_
statement_ full - The general entry point: parse a statement, resolving text
SEARCHvia an optionalembedderand$nameplaceholders viaparams. The surfaces that accept parameters (CLI/MCP/plane.cypher) use this. - parse_
statement_ with_ embedder - Like
parse_statement, but resolves a textSEARCH … NEAR "…"viaembedder. - parse_
with_ embedder - Like
parse, but resolves textNEAR "…"terms into query vectors viaembedder— the semantic-search entry point the surfaces use for reads.
Type Aliases§
- Params
- Values for
$nameplaceholders in a query, supplied by the caller and resolved at parse time (so no string interpolation — the SDK-safe way to pass values). Keyed by the bare name (no$).