Skip to main content

omgbase_surface/
reference.rs

1//! The `query_syntax` text (`spec/surface/README.md` §4: reference prose,
2//! unpinned). A condensed distillation of `docs/query-language.md`.
3
4/// The agent-facing OQX syntax reference the `query_syntax` tool returns.
5pub const QUERY_SYNTAX: &str = r##"# query — OQX syntax reference
6
7The `query` tool takes ONE plain OQX string (+ optional `limit`/`cursor`). There
8is no {from, filter, order} envelope: every concern is a clause of the string.
9
10  [select <items>] from <target> [where <pred>] [follow <dest> [{ … }], …]
11                   [order by <expr> [asc|desc], …] [limit N] [offset N]
12  <target> count|exists|none|first|single { <block> }     (scalar/one-row form: a bare target at the root scope)
13
14CLAUSE ORDER IS FIXED: select, from, where, follow, order by, limit, offset —
15each at most once; an out-of-order clause is a parse error naming the order.
16Only `select` may drop its keyword, and only as the first clause
17(`path, era from docs where …`). A top-level predicate needs `where`, because
18`where` follows `from` (`era > 1600 from docs` is the clause-order error); a
19block may LEAD with a predicate (see Sugar), but a predicate after a projection
20still needs the keyword (`{ name where kind == "md:task" }`, not
21`{ name, kind == "md:task" }`); `from docs count` is an error (write
22`docs count { … }`). `where` and later `select` items may use the same body's
23`select` aliases (`select path, old: era < 1000 from docs where old`; an alias
24shadows a same-named field there). `and` / `or` / `not` are the connectives;
25the symbols `&&` / `||` / `!` are accepted synonyms (the words are canonical and
26what `print` writes).
27
28## Sugar (OQX 0.17)
29
30Every form here is shorthand for an explicit directive (the AST is the explicit form):
31  nodes { kind == "md:task" }        ≡ nodes collect { where kind == "md:task" } — a receiver block with
32                                       no consumer is collect; a block whose LEADING expression is a predicate
33                                       (comparison, call, !/is/not, in, literal, (…), a consumer test — anything
34                                       but a bare name, a dotted path or a ^lift) is where-first. Bare names still
35                                       project: nodes { name } ≡ nodes collect { name }; filter a bare field with
36                                       nodes { is checked }. Wherever a value stands (0.20), call arguments included:
37                                       size(children { is checked }); x distinct { … } ≡ x collect distinct { … }. After a
38                                       follow destination too: follow children { depth 2 } is that destination's block.
39  (a, b)                              a tuple (0.20): two or more expressions, concatenated one level flat — a value
40                                       (size((tags, aliases)), "x" in (tags, aliases)) and a receiver ((a, b) { … },
41                                       follow (before, after) { depth 2 }); (a) alone is grouping
42  refs(x)[0]                          ≡ refs(x) first { offset 0 }   positional (integer literal or ${binding});
43                                       out of range ⇒ absent; refs(x)[0].path navigates the row
44  ^docs[path == ^company]             ≡ ^docs first { where path == ^company }   first match or absent
45  ^docs[path == ^company]!            ≡ … single { … } required: exactly one, else filter_invalid
46  title!                              required: title, or an error naming the expression (and the row's $key — its id)
47                                       — never a filter, never a coercion (0!, ""! are values); tightest: a!.b vs a.b!
48  is x / not x                        truthiness of x, and its negation (prefix; `not` ≡ `!`)
49  x is y / x is not y                 identity ($key: a store row's id, else structural) — compares rows, which == does not;
50                                       x is null = absent; for scalars is ≡ ==; comparison precedence, no chaining
51  a and b / a or b                    the connectives (≡ && / ||: same precedence, short-circuit, value: data.title or path coalesces)
52  boss: ^docs[path == ^manager], bossName: boss.title   a select item may use the items to its LEFT
53  Reserved words (never a bare field name): from where select is not and or, true false null.
54  Contextual words (limit order offset follow collect count first distinct desc by in frontier depth) parse as
55  fields in expression position (select o: order from docs).
56
57Results are LEAN hits — { id, path } + whatever `select` projects (or `values`,
58`count`, `exists`, `none`). The envelope keys ARE the attributes: copy a hit's
59`id` straight into `where id == "…"` (no translation); `select id, path` is simply
60the envelope (no duplicate columns); any OTHER projection whose output key would
61be `id` or `path` must be aliased — `select data.path` is refused ("projection
62`path` collides with the hit envelope — alias it (`p: data.path`)"); a row
63surfacing as a value (an empty `collect { }`) is the same `{ id, path }`. Every
64other tool spells a row the same way (`docs_read` → `{ id, path, rev, properties,
65raw }`, `blocks_read` → `{ id, type, text, raw, hash, … }`, `docs_list` → `{ id,
66path, updated_at, blocks }`, a revision → `{ id, path, hash }`): hydrate by id via
67blocks_read/docs_read.
68
69## Paths
70
71Every path the surface returns is `/`-rooted — the form a reference is
72written in (`[x](/projects/oqx.md)`, `before: [/timeline/kickoff.md]`): a
73hit's `path`, an edge's `dst_path`, every tool's paths. Every path it
74accepts tolerates both forms (`/a.md` or `a.md`): tool arguments, `refs(x)`,
75`within(...)`, and a string LITERAL compared with `path`/`dst_path` (bare, or
76through a row handle: `doc.path`) by `==`/`!=` or passed to their
77`.startsWith(...)` (it is rooted first, so `path == "a.md"` and `path == "/a.md"`
78both match). A property holding a reference compares directly — and is never
79rooted: `where ^path in list(after)`, `where customer == ^path`,
80`where data.path == "/x.md"` (the author's `path:` key, as written) — no
81`"/" + ...` glue.
82
83## Targets & field namespaces
84
85THE NAME RULE: `$` belongs to OQX — $it, $key, $depth, $stop, $leaf, $frontier,
86$ordinal are values the LANGUAGE injects into a scope; omgbase adds NO $ names
87(a `$id` in a query is an unknown name, absent). Every engine attribute is a BARE
88snake_case name that WINS on collision — the convention the relations (children,
89blocks, out, in, doc, frontmatter, …) always followed: a bare name that is an
90engine attribute reads the engine's value, never yours. Your data is bare too;
91where a key of yours collides with an engine name, reach it through a source
92handle: `data.<k>` (the merged authored view — frontmatter + inline, exactly the
93read a bare non-colliding key performs), `frontmatter.<k>`, `inline.<k>`. So
94`path` is the engine's /-rooted location and `data.path` your `path:` key;
95`tags` is your tags ∪ the body #hashtags and `data.tags` the authored list alone;
96`title` the computed title (the first H1) and `data.title` your `title:`;
97`entries(data)` lists every authored top-level key. Same value, same name
98everywhere: a hit's `id` is docs_read's `id` and the `id` argument of blocks_read.
99
100  Reserved bare names per kind (engine attributes; handles; self; relations):
101    docs    id path raw hash updated_at title tags format | data frontmatter inline | doc | blocks nodes out in out_links in_links
102    blocks  id path doc_id raw hash updated_at type text attrs depth ordinal | block doc | children nodes out_links section
103    nodes   id path doc_id block_id kind name value attrs | section doc block | blocks subsections children
104    links   id path doc_id block_id via_id dst_id dst_path uri field dst_kind predicate provenance anchor | doc
105  A key of yours with any of these names is `data.<k>` (or frontmatter.<k> /
106  inline.<k> by source; attrs.<k> on a block or node).
107
108docs:
109  - bare identifier  = a document PROPERTY (frontmatter + inline `key:: value`,
110                       unioned; nested via dots: meta.owner). YAML/JSON docs
111                       expose the parsed object's keys the same way.
112  - source handles   = data.<k> (merged authored view) / frontmatter.<k> /
113                       inline.<k>; the whole bag as a collection via entries(data)
114                       / entries(frontmatter) / entries(inline) (see entries()).
115  - attributes       = id, path (/-rooted, see Paths), updated_at (ISO-8601,
116                       sorts chronologically), raw (the whole file, frontmatter
117                       included), hash (its sha256, 64 hex), title (the computed
118                       title: the first H1; data.title is the authored key), tags
119                       (authored tags ∪ body #hashtags, deduped, authored first;
120                       data.tags is the authored list), format (the doc's format —
121                       "markdown"/"yaml"/"json"; present on EVERY doc, so
122                       has(format) is always true and `format == "yaml"` is the
123                       filter).
124  - relations        = nodes, blocks, doc.out_links / doc.in_links (a doc's
125                       links as rows; the documents at their other end are
126                       out_links collect distinct { from dst } and
127                       in_links collect distinct { from doc } — there is no
128                       out / in relation).
129
130blocks:
131  - bare fields      = doc_id (the owning doc), type, text (normalized), ordinal
132                       (0-based among siblings), depth (0 at the top level), and
133                       every attrs key FLATTENED onto the row (bare `checked`
134                       reads attrs.checked; `attrs.<k>` still works; the
135                       structural fields win on collision; absent ⇒ false).
136  - attributes       = id, path (the owning doc's path), raw (the block's raw
137                       source — a container's raw holds its children), hash
138                       (its sha256 — the value update/split expect as
139                       expect.content_hash), updated_at.
140  - reach-through    = doc.<key> / doc.path / doc.id / doc.format = the
141                       CONTAINING doc (doc.id and doc.path cost nothing — they
142                       read the row's own doc_id / joined path, no lookup).
143  - relations        = block.children, block.nodes, block.out_links, section.
144  NOTE: inside a `follow`, $depth/$ordinal are ALWAYS the walk's metadata; a
145  block's own nesting is `depth` / `ordinal` (no $) everywhere.
146
147nodes:
148  - bare fields      = doc_id, block_id (null for a block-less node), kind
149                       (md:task / md:link / md:section / yaml:…), name, value,
150                       + attrs keys flattened (checked, level).
151  - attributes       = id, path.
152  - reach-through    = doc.<key> / doc.id / doc.path, block.type / block.text
153                       / block.id / block.path.
154  - relations        = section.blocks, section.children, section.subsections.
155
156links:  (the authored link graph as rows — one per open link: the relation a link
157        establishes; the md:link NODE is its occurrence in the text and via_id
158        points at it — a frontmatter link has a row and no node)
159  - bare fields      = doc_id (the SOURCE doc), block_id (the source block; null
160                       for a frontmatter edge), via_id, dst_id (the destination
161                       node id: a doc id, an external node id or phantom:<path>),
162                       dst_path (target doc path, /-rooted; NULL when
163                       dangling/external), uri (external URL; NULL otherwise),
164                       predicate ("references"/"embeds"/freeform), provenance
165                       ("link"/"frontmatter"/"inline_field"/…), dst_kind
166                       ("document"/"external"/"collection"), anchor, field (the
167                       frontmatter key / inline field it was authored in).
168  - attributes       = id, path (the SOURCE doc's path); doc.<key> reaches the
169                       source document. There is no from_commit: a query sees
170                       open edges only (history is blocks_history/changes_since).
171  - text()/semantic() are N/A on links.
172
173## Scoping (ADR-015; the root row since surface 2.0): bare names are LOCAL
174
175  name          the CURRENT row only — never falls through to an enclosing row
176                or the repository; an absent field is simply absent.
177  ^name         one scope OUT (the enclosing row's fields/intrinsics/lifts);
178                ^^name two scopes out. `^path` = the enclosing row's path.
179  ^docs ^nodes ^blocks ^links
180                THE ROOT ROW (surface 2.0, oqx 0.18): the repository is the root
181                scope's row, one scope out from a top-level row, so ^docs there is
182                the whole documents scan (uncorrelated until you add a ^ predicate);
183                one caret per enclosing block (^^docs from depth two), or the
184                absolute 0^docs from ANY depth. ^id / 0^id = the repository id.
185                ^$it = the root object: ^$it.docs ≡ ^docs, entries(^$it) names the
186                four collections. At the root scope itself a bare target is the scan
187                (docs count { … }, from docs, entries(docs)).
188                ERRORS: a bare `docs` INSIDE a block reads a property of the current
189                row, which has none — refused ("did you mean ^docs"); `^docs` AT THE
190                TOP LEVEL reaches past the root — refused (write the bare `docs`);
191                so is `$repo` (surface < 2.0; the message names the replacement).
192                nodes/blocks on a doc row are that doc's RELATIONS, not the root.
193  $it           the current item itself (a row, or each ELEMENT when the
194                receiver is a list property); ^$it = the enclosing row.
195                $value is NOT an intrinsic (since oqx 0.15): it reads a
196                property literally named $value, absent when there is none.
197  $key          the row's identity (0.20): a store row's id (doc/block/node/edge), an entries(x)
198                entry's key, a follow occurrence's walk identity; project $key: <expr> to rekey the
199                rows a block yields (a destination block's $key: is not a column).
200
201## Scalar semantics (@omgbase/oqx)
202
203  comparisons  ==  !=  <  <=  >  >=    strict & typed (5 == "5" is false); either
204                                       side may be a field (field-vs-field OK).
205  absence      null and a missing field are the same "absent" value; two absents
206               are EQUAL, so `absent != "x"` is TRUE and `absent == null` is TRUE.
207               Relational (< <= > >=) on an absent operand → FALSE. Bare `f` in
208               boolean position uses JS truthiness (absent/false/0/"" ⇒ false;
209               NOTE: "false", [] and {} are truthy). has(f) = explicit presence.
210  boolean      and  or  not    grouping ( … )   — && || ! are accepted synonyms
211  arithmetic   + - * / %    (+ concatenates if either side is a string)
212  membership   x in y       array → typed membership; string → substring;
213                            object → key exists; range → coverage. Any list
214                            property works directly: "pricing" in tags. list(f)
215                            coerces scalar-or-list (absent → []) — use it when a
216                            key is sometimes scalar, sometimes a list.
217  ranges       lo..hi (inclusive)  lo...hi (exclusive hi)  ..hi  lo..
218               era in 800..1680; works over ISO dates. range(prop) reads a
219               range-shaped STRING property as an interval: "2026-01-15" in range(window).
220  strings      x.contains("s") x.startsWith("p") x.endsWith("s") x.matches("^re$")
221               (real regex) x.lower() x.upper() size(x). ALL CASE-SENSITIVE —
222               fold with .lower(): title.lower().contains("aurora").
223  functions    has(f)  size(x)  list(x)  entries(x)  range(s)
224  literals     "str" 'str' 123 1.5 true false null
225  determinism  no now()/clock/random. Lists/structs are not literals; ternary is
226               not supported. Errors surface as filter_invalid.
227  scalars vs lists  a list-authored key never == a scalar (tags == "x" is false
228               when tags is a list) — use `"x" in tags` / `in list(tags)`.
229
230## Domain predicates (omgbase)
231
232  text("terms")        FTS5 PRUNING predicate (docs match via their blocks, nodes
233                       via the node index); composes anywhere, incl. nested scopes.
234  semantic("phrase")   embedding cosine SCORE (docs/blocks): threshold it
235                       (semantic("x") > 0.6), project it, or `order by semantic("x")
236                       desc` for top-K. Needs a provider that has embedded the repository; else provider_unavailable.
237  blocks target only:
238    under(id_or_heading)      block in the containment subtree / section range
239    under_heading("Launch")   an ancestor section heading contains the text (ci)
240    under_kind(type[, name])  an ancestor block has that type (name: its text
241                              contains / its key equals name)
242    within("path" | "d_..")   containing doc = id, exact path, or path glob (*);
243                              either path form
244    yaml_path("a.b")          YAML docs: the block at that key path
245    json_pointer("#/a/b")     JSON docs: the block at that pointer
246    has_edge("pred"[, target])  an open authored edge with that predicate leaves here
247    has_anchor()              block carries an authored ^ref
248    parent_type() == "x"      parent block's type (must be compared)
249    child_count() > 0         number of direct children (must be compared)
250  refs(x)              the live documents a property's document references name:
251                       x is a string, a list or absent; each "/a/b.md", "a/b.md"
252                       or "d_…" element resolves to that doc's row; dangling and
253                       non-string elements are dropped. Yields docs rows, so it is
254                       a source, a receiver or a follow destination:
255                       follow refs(before), refs(after) walks a timeline both ways.
256                       The reverse direction needs no function: a document's
257                       path is already the reference form, so
258                       ^docs { where ^path in list(after) } is
259                       "the documents whose `after` names me".
260  A HIT IS A STORE ROW: a top-level row that is not a doc/block/node/edge fails
261                       (filter_invalid). `follow before` over a list of paths
262                       reaches the STRINGS — write `follow refs(before)`.
263
264## Nested queries & consumers
265
266  <receiver> collect|exists|none|count|first|single { <block> }
267    receiver = a relation (nodes, blocks, doc.out_links, section.blocks, …), a
268               reach-through row (dst, doc: one row — dst { title }), a
269    list property (tags), entries(x), or a root collection (^docs; 0^docs at depth ≥ 2).
270    block    = [select …] [where …] [follow …] [order by …] [limit N] [offset N]
271               — the same fixed order as the top level (`from` optional; the
272               receiver supplies the rows). A leading name/alias list is the
273               projection (`select` dropped); a predicate always needs `where`.
274  exists    ≥1 row      nodes exists { where kind == "md:task" and not checked }
275  none      0 rows      nodes none { where kind == "md:task" and not checked }  ("every task done")
276  count     a number    nodes count { where kind == "md:task" } >= 2
277  collect   rows (default) — in select for nested results; in where with a lift
278  first / single   zero-or-one (single errors on >1) — lookups in select
279  distinct  on any consumer: dedup by projected value — nodes count distinct { select kind }
280  limit/offset  bound the block's rows AFTER where/order/distinct, BEFORE the
281            consumer reduces: nodes exists { offset 1 } = at least two;
282            first { … offset 1 } = the second.
283  lift      `^name:` in a where-collect binds values outward:
284            select path, open from docs where nodes collect { ^open: value where kind == "md:task" and not checked }
285  joins     ^<t> exists { where slug == ^ref }  semi-join; not … exists  anti-join;
286            ^nodes single { where kind == "person" and attrs.id == ^owner_id }  lookup;
287            x in ^keys  membership over a lifted set.
288
289## select — projection
290
291  select a, alias: expr, nested: nodes collect { … }
292  bare key → the attribute when it is one (id, path, title, …), else the property
293  (scalar-authored → scalar; list → array); data.<k> forces the property;
294  a call/arithmetic needs an alias (n: size(tags)) unless in values mode;
295  an output key of id/path other than the attribute itself needs an alias.
296  raw         whole file bytes (docs) — docs_read is cheaper for one doc.
297  distinct    select distinct type   (dedup hits by projected value)
298  values      select <ONE item> values → bare values instead of records:
299                from docs select distinct type values          → values: ["hub","lab-note",…]
300                select path values from docs                   → values: ["/index.md", …]
301                tags: tags collect { $it values }             → a plain array
302                h: nodes first { name values where kind == "md:section" order by first_ordinal }
303  entries(x)  a record as a collection ($key / $it per entry), key order:
304                select fm: entries(data) collect { k: $key, v: $it }
305                where entries(frontmatter) exists { where $key == "era" and $it > 1600 }
306                entries(inline), entries(attrs), a nested map, or a list (index keys)
307  $semantic_score is not a field — project semantic("…") under an alias instead.
308
309## order by, limit/offset, pagination
310
311  order by era desc, path asc      absent values sort LAST (asc); ties → (path, id)
312  limit N / offset N               define the result SET (integer literals);
313                                   the tool's limit/cursor then page WITHIN it
314  cursor                           opaque keyset on (path,id); a custom order by
315                                   disables it (you get the top page + truncated)
316
317## Graph traversal (follow) and links
318
319  The citation graph is the LINK ROWS walked through a destination block:
320  dst is a link's destination document (absent for an external or dangling
321  link — it then contributes no successor), doc its source.
322  from docs where <seed> follow out_links collect { from dst }     docs the seed links TO
323  from docs where <seed> follow in_links collect { from doc }      backlinks
324    follow out_links collect { from dst depth 3 }                  bound (1..8, default 8) — a clause of the block
325    follow out_links collect { from dst where layer == "canon" }   keep only matching successors
326    follow in_links collect { from doc where ^path in list(before) }   ^ = the row being expanded
327                                                   (^^ = the walk's enclosing scope)
328    follow out_links collect { from dst where predicate == "depends_on" }   one predicate only
329    follow out_links collect { from dst }, in_links collect { from doc }
330                                                   several destinations: their union
331                                                   (one step dedups by identity)
332    follow ^docs { where out_links exists { where dst_path == ^^path } }
333                                                   a destination block over the root's
334                                                   documents, re-read per frontier row
335                                                   (^ = that row) = the backlinks walk
336    follow out_links collect { from dst frontier type == "practitioner" }   cut, keeping the frontier row
337    from docs where type == "process" frontier !verified follow out_links collect { from dst }
338                                                   the BODY's frontier applies to the seeds:
339                                                   an unverified seed is kept, not expanded
340    follow distinct out_links collect { from dst } / … { $key: group from dst }   identity control ($key)
341    follow refs(before), ^docs { where ^path in list(after) }    forward over `before`, backward
342                                                   over every doc whose `after` names me
343  Per-occurrence metadata: $depth (seed = 1), $stop (interior|leaf|frontier|
344  depth|cycle), $leaf/$frontier, $ordinal, $key — usable in select/order by and
345  the top-level post-walk where, NOT metadata of the candidates a destination
346  block's where reads.
347  Destination blocks (OQX 0.20): a brace after ANY destination opens that
348  destination's block — children { depth 2 }, refs(x) { depth 2 }, out_links
349  collect { from dst depth 2 }, ^docs { where ^path in list(after) } — with the
350  0.17 body rules (where-first, projections) and two clauses of its own after
351  `where`: frontier <pred> (occurrences reached through this destination that
352  match are frontiers; ^ there is the walk's enclosing scope) and depth <n>
353  (this destination expands a frontier row only while $depth < n). The body
354  that carries the follow may write frontier <pred> too, after its where and
355  before follow: it applies to the SEEDS (read like the body's where) — a
356  matching seed stays in the result with $stop == "frontier" and is not
357  expanded; each destination's frontier is its own. There is no options block;
358  the old `by` is $key. A bare follow ^docs (no brace, no consumer) is an
359  error naming both block spellings.
360  Type-preserving relations: block.children, section.children, section.subsections;
361  the citation graph is out_links collect { from dst } / in_links collect { from doc }.
362  from links where dst_path == "/notes/x.md"       inbound links to a doc, as rows
363  select d: dst.title, k: dst_kind from links       the destination document's title
364  from docs where out_links exists { where dst.layer == "draft" }   links into drafts
365  select b: in_links collect distinct { from doc } from docs     backlinks as { id, path }
366  from links where predicate == "depends_on" select doc_id, dst_path
367  from docs where not doc.in_links exists { }      orphans (nothing links in)
368  (The `graph` tool is a convenience wrapper that compiles to a follow query.)
369
370## semantic grain
371
372from docs ranks whole DOCUMENTS (one hit per file); from blocks ranks PASSAGES.
373text()/where prune the candidate set; only order by reweights.
374
375## Examples
376
377  from docs where layer == "working"
378  from docs where path.startsWith("/guides/") and updated_at >= "2026-08-01"
379  from docs where "pricing" in list(tags) select layer, tags
380  from docs where inline.owner == "alice"
381  from docs where title.lower().contains("q3 plan")
382  from docs where format == "yaml"
383  from docs where type == "practitioner" order by era desc limit 2
384  from docs where nodes none { where kind == "md:task" and not checked }
385  from docs where tags exists { where $it == "tria-prima" }
386  from docs where path == "/x.md" select fm: entries(data) collect { k: $key, v: $it }
387  from docs select owner_id, owner: ^nodes single { where kind == "person" and attrs.id == ^owner_id }
388  from blocks where type == "task" and not checked and under_heading("Launch") and doc.layer == "working"
389  from blocks where type == "paragraph" and has_edge("references", "d_92aaaaa")
390  select path, $depth from docs where path == "/timeline/review.md" follow refs(before), refs(after)
391  select path, next: ^docs collect { path where ^path in list(before) } from docs where type == "milestone"
392  select path, n: size(^docs), repo: ^id from docs limit 1
393  select p: data.path, t: data.title from docs where has(data.path)
394  docs { type == "substance" and not verified }
395  select subject, process: ^docs[slug == ^subject]!.path from docs where type == "lab-note"
396  select path, back: nodes first { select p: 0^docs collect { path where ^^path in list(after) } values } from docs where type == "milestone"
397  select path, prior: refs(before) collect { path, when } from docs where type == "milestone"
398  from blocks order by semantic("identity preservation across edits") desc limit 10
399  from links where dst_kind == "external" select uri
400  docs count { where layer == "canon" }
401
402Common mistake: `$path`/`$id` (the 2.x spelling) — omgbase has no $ names; write
403`path`/`id`. Another: `where tags == "x"` on a list-valued key is false — write
404`"x" in tags`. Another: `select data.path` — alias it (`p: data.path`).
405"##;