quarb/adapter.rs
1//! The adapter surface: how a data source plugs into the engine.
2//!
3//! The live methods — those the engine currently drives — are
4//! the navigation set ([`root`](AstAdapter::root),
5//! [`children`](AstAdapter::children), [`name`](AstAdapter::name),
6//! [`parent`](AstAdapter::parent)) plus the projection set
7//! ([`traits`](AstAdapter::traits), [`property`](AstAdapter::property),
8//! [`default_value`](AstAdapter::default_value),
9//! [`metadata`](AstAdapter::metadata)). The projection methods have
10//! defaults, so an adapter can implement only what its domain
11//! supports. The reference cluster (`->`, `<-`, `-->`, `<--`,
12//! `=>`), the search fast paths and provenance are documented on
13//! the trait, group by group. See `doc/impl.tex`.
14
15use crate::value::Value;
16
17/// An opaque handle to a node in an arbor.
18///
19/// The engine treats a `NodeId` as an opaque token: it is minted and
20/// interpreted solely by the adapter that produced it. The `u64`
21/// payload is an adapter-private index or key.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct NodeId(pub u64);
24
25/// Per-node data provenance: the three optional components behind
26/// the `:::source` / `:::instant` / `:::dpid` core-metadata keys and
27/// their composite `:::provenance` (`?src@ts#dpid`, kaiv's
28/// spelling). A component is present only where the source genuinely
29/// records it; wrapper adapters layer them ([`or`](Self::or), inner
30/// wins per component).
31#[derive(Debug, Clone, Default, PartialEq)]
32pub struct Provenance {
33 /// Where the datum came from: the document, file, store or
34 /// page — the innermost source that holds the node (a grafted
35 /// node's source is the file the graft opened, not the
36 /// directory above it).
37 pub source: Option<String>,
38 /// The node's path within its source — its name-path from the
39 /// source's root — so a source and a path name the node
40 /// without the engine's id. Filled by the layer that knows
41 /// where the source begins (a mount, a graft), else by the
42 /// engine from the arbor root.
43 pub path: Option<String>,
44 /// When the datum was observed — `(secs, nanos, offset_min)`,
45 /// the shape `Value::Instant` carries — by the rung
46 /// [`instant_from`](Self::instant_from) names.
47 pub instant: Option<(i64, u32, Option<i16>)>,
48 /// Which rung answered the instant: the datum's own
49 /// timestamp, its source's modification time, or the moment of
50 /// reading. `None` with no instant.
51 pub instant_from: Option<InstantFrom>,
52 /// The source-assigned data-point identifier (kaiv `#dpid`).
53 pub dpid: Option<String>,
54}
55
56/// The rung an instant came from — three facts that share one
57/// field: a row's own timestamp, a file's mtime, the clock at the
58/// read.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum InstantFrom {
61 /// The datum's own timestamp (a kaiv `@ts`, a commit date, a
62 /// row's `modified`).
63 Node,
64 /// The source's modification time (a file's mtime).
65 Source,
66 /// The moment the query read it: the invocation instant.
67 Access,
68}
69
70impl InstantFrom {
71 pub fn name(self) -> &'static str {
72 match self {
73 InstantFrom::Node => "node",
74 InstantFrom::Source => "source",
75 InstantFrom::Access => "access",
76 }
77 }
78}
79
80impl Provenance {
81 /// Component-wise layering: `self` (inner, more specific) wins;
82 /// missing components fill from `outer`. The instant and its
83 /// rung travel together.
84 pub fn or(self, outer: Provenance) -> Provenance {
85 let (instant, instant_from) = if self.instant.is_some() {
86 (self.instant, self.instant_from)
87 } else {
88 (outer.instant, outer.instant_from)
89 };
90 Provenance {
91 source: self.source.or(outer.source),
92 path: self.path.or(outer.path),
93 instant,
94 instant_from,
95 dpid: self.dpid.or(outer.dpid),
96 }
97 }
98
99 /// Nothing recorded — the path does not count, it is derivable.
100 pub fn is_empty(&self) -> bool {
101 self.source.is_none() && self.instant.is_none() && self.dpid.is_none()
102 }
103
104 /// The instant re-labelled as the source's — what a graft does
105 /// to the file's own mtime when it fills a node inside the file.
106 pub fn as_source_rung(mut self) -> Provenance {
107 if self.instant.is_some() {
108 self.instant_from = Some(InstantFrom::Source);
109 }
110 self
111 }
112
113 /// The canonical text of a list: `?a@ts#x;b#y;+N` — one `?`,
114 /// the entries separated by `;` (kaiv's list separator), each
115 /// in its own optionality grammar, the elision marker `+N` last
116 /// when `elided > 0`. `None` when no entry has a component.
117 pub fn canonical_list(entries: &[Provenance], elided: u32) -> Option<String> {
118 // Entries that write alike are one in the text (the path is
119 // not part of it).
120 let mut parts: Vec<String> = Vec::new();
121 for c in entries.iter().filter_map(|e| e.canonical()) {
122 let c = c.trim_start_matches('?').to_string();
123 if !parts.contains(&c) {
124 parts.push(c);
125 }
126 }
127 if parts.is_empty() {
128 return None;
129 }
130 let mut out = format!("?{}", parts.join(";"));
131 if elided > 0 {
132 out.push_str(&format!(";+{elided}"));
133 }
134 Some(out)
135 }
136
137 /// The composite canonical text `?src@ts#dpid` — kaiv's
138 /// optionality grammar (`?src`, `?src@ts`, `?@ts#dpid`, …), the
139 /// instant in Quarb's dashed-extended display form. `None` when
140 /// fully empty.
141 pub fn canonical(&self) -> Option<String> {
142 if self.is_empty() {
143 return None;
144 }
145 let mut out = String::from("?");
146 if let Some(src) = &self.source {
147 out.push_str(src);
148 }
149 if let Some((secs, _, offset)) = self.instant {
150 // kaiv's instant has no sub-second field: whole seconds,
151 // whatever the rung recorded.
152 out.push('@');
153 out.push_str(&crate::temporal::format_instant(secs, 0, offset));
154 }
155 if let Some(dpid) = &self.dpid {
156 out.push('#');
157 out.push_str(dpid);
158 }
159 Some(out)
160 }
161}
162
163/// A node's provenance as a list: several entries where the datum
164/// was recorded from several sources (a kaiv leaf's `?a;b`), and how
165/// many further sources the record elides (its `;+N`). The singular
166/// [`AstAdapter::provenance`] is the list's anchoring first entry.
167#[derive(Debug, Clone, Default, PartialEq)]
168pub struct ProvenanceList {
169 pub entries: Vec<Provenance>,
170 pub elided: u32,
171}
172
173impl ProvenanceList {
174 /// The one-entry list — empty when the entry is.
175 pub fn single(p: Provenance) -> ProvenanceList {
176 ProvenanceList {
177 entries: if p.is_empty() { Vec::new() } else { vec![p] },
178 elided: 0,
179 }
180 }
181
182 /// The anchoring entry (the first), empty when there is none.
183 pub fn first(&self) -> Provenance {
184 self.entries.first().cloned().unwrap_or_default()
185 }
186
187 /// The latest instant any entry carries: a datum with several
188 /// sources is as fresh as its newest.
189 pub fn instant_max(&self) -> Option<(i64, u32, Option<i16>)> {
190 self.entries
191 .iter()
192 .filter_map(|p| p.instant)
193 .max_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)))
194 }
195
196 /// The canonical list text, `?a@ts#x;b#y;+N`.
197 pub fn canonical(&self) -> Option<String> {
198 Provenance::canonical_list(&self.entries, self.elided)
199 }
200
201 /// Component-wise layering of every entry over `outer` (see
202 /// [`Provenance::or`]); an empty list takes `outer` as its one
203 /// entry.
204 pub fn or(self, outer: Provenance) -> ProvenanceList {
205 if self.entries.is_empty() {
206 return ProvenanceList {
207 entries: ProvenanceList::single(outer).entries,
208 elided: self.elided,
209 };
210 }
211 ProvenanceList {
212 entries: self
213 .entries
214 .into_iter()
215 .map(|p| p.or(outer.clone()))
216 .collect(),
217 elided: self.elided,
218 }
219 }
220}
221
222/// The direction of a crosslink walk, for the batching hint
223/// [`AstAdapter::prefetch_links`].
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
225pub enum LinkDir {
226 /// `->`: the links a node carries.
227 Out,
228 /// `<-`: the links that reach a node.
229 In,
230}
231
232/// A reference to a document outside the arbor, as an adapter read
233/// it from its own reference syntax: which document, and where in
234/// it. The engine treats both as opaque.
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct ExternalRef {
237 /// The document's identity (a URL, a path, a key — whatever
238 /// the mounts are registered under).
239 pub document: String,
240 /// The location within that document, for the adapter that
241 /// owns it to resolve; `None` for the document itself.
242 pub within: Option<String>,
243}
244
245impl ExternalRef {
246 /// Read a URI reference: everything before the first `#` is the
247 /// document, a non-empty fragment after it the location. A
248 /// convenience for adapters whose references are URIs (html,
249 /// the text level, a JSON `$ref`); an adapter with another
250 /// syntax builds the two parts its own way.
251 pub fn uri(reference: &str) -> Self {
252 match reference.split_once('#') {
253 Some((document, fragment)) => ExternalRef {
254 document: document.to_string(),
255 within: (!fragment.is_empty()).then(|| fragment.to_string()),
256 },
257 None => ExternalRef {
258 document: reference.to_string(),
259 within: None,
260 },
261 }
262 }
263
264 /// The reference as one URI again.
265 pub fn to_uri(&self) -> String {
266 match &self.within {
267 Some(w) => format!("{}#{w}", self.document),
268 None => self.document.clone(),
269 }
270 }
271}
272
273/// What one navigation step asks of an adapter, lowered for
274/// [`AstAdapter::select`]: the axis, the name matcher, the
275/// predicate conjuncts the engine can express as plain comparisons,
276/// and — when the command after the step consumes them — a limit and
277/// an order. An adapter answers from an index what it can and names
278/// what it applied; the engine tests the rest.
279#[derive(Debug, Clone)]
280pub struct StepPlan<'a> {
281 /// The axis: a child hop or a descendant search.
282 pub axis: PlanAxis,
283 /// The name the step matches, when it is one literal name.
284 pub name: Option<&'a str>,
285 /// The leading predicates lowered to comparisons, in order.
286 pub conjuncts: Vec<Conjunct<'a>>,
287 /// The step's `<trait>` filters, each one literal trait name
288 /// (a clause with alternatives or a negation is not lowered).
289 pub traits: Vec<&'a str>,
290 /// How many results the command after the step keeps, when it
291 /// keeps a prefix (`[..n]`, `@| first(n)`).
292 pub limit: Option<usize>,
293 /// Where an estimator may stop counting: past this many nodes
294 /// or bytes the answer already exceeds the run's budget, so a
295 /// listing, a header walk or an arena count stops there and
296 /// reports `(counted, u64::MAX)`. Unbounded when no budget.
297 pub cap: Cap,
298 /// The property the command after the step orders by, and whether
299 /// ascending.
300 pub order: Option<(&'a str, bool)>,
301}
302
303/// Where counting may stop: the most nodes and bytes a budget
304/// allows, so an estimator short-circuits at the first entry past
305/// either ([`StepPlan::cap`], [`Catalog`] producers).
306#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
307pub struct Cap {
308 pub nodes: Option<u64>,
309 pub bytes: Option<u64>,
310}
311
312impl Cap {
313 /// No cap: count everything (within the walk's own budget).
314 pub const NONE: Cap = Cap {
315 nodes: None,
316 bytes: None,
317 };
318
319 /// Whether `nodes` counted or `bytes` summed so far already
320 /// pass the cap: the walk may stop.
321 pub fn past(&self, nodes: u64, bytes: u64) -> bool {
322 self.nodes.is_some_and(|max| nodes > max) || self.bytes.is_some_and(|max| bytes > max)
323 }
324}
325
326/// The axis of a [`StepPlan`].
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub enum PlanAxis {
329 /// `/name`: the children.
330 Child,
331 /// `//name`: the descendants, each with its depth.
332 Descendant,
333}
334
335/// One lowered predicate of a [`StepPlan`].
336#[derive(Debug, Clone)]
337pub enum Conjunct<'a> {
338 /// `[::prop = value]` under the engine's standard equality, with
339 /// a text value: what an index or a keyed read answers.
340 Equals(&'a str, &'a Value),
341 /// `[::prop OP literal]`: a comparison on one property, lowered
342 /// for pricing (a listing's sizes, a store's statistics). An
343 /// adapter never applies it in [`AstAdapter::select`]; the
344 /// engine runs it. `Cmp::Eq` here is an equality on a
345 /// non-text literal.
346 Compare(&'a str, Cmp, &'a Value),
347}
348
349/// A comparison a [`Conjunct::Compare`] lowers.
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351pub enum Cmp {
352 Eq,
353 Ne,
354 Lt,
355 Le,
356 Gt,
357 Ge,
358 /// `*=`: the property's text contains the literal.
359 Contains,
360}
361
362impl Cmp {
363 /// The operator as written.
364 pub fn text(self) -> &'static str {
365 match self {
366 Cmp::Eq => "=",
367 Cmp::Ne => "!=",
368 Cmp::Lt => "<",
369 Cmp::Le => "<=",
370 Cmp::Gt => ">",
371 Cmp::Ge => ">=",
372 Cmp::Contains => "*=",
373 }
374 }
375
376 /// Whether `value` stands to `literal` as the operator says,
377 /// under the engine's value ordering.
378 pub fn holds(self, value: &Value, literal: &Value) -> bool {
379 use std::cmp::Ordering::*;
380 match self {
381 Cmp::Contains => match (value, literal) {
382 (Value::Str(a), Value::Str(b)) => a.contains(b.as_str()),
383 _ => false,
384 },
385 _ => {
386 let o = value.compare(literal);
387 match self {
388 Cmp::Eq => o == Equal,
389 Cmp::Ne => o != Equal,
390 Cmp::Lt => o == Less,
391 Cmp::Le => o != Greater,
392 Cmp::Gt => o == Greater,
393 Cmp::Ge => o != Less,
394 Cmp::Contains => unreachable!(),
395 }
396 }
397 }
398 }
399}
400
401/// What an adapter answers to a [`StepPlan`]: the nodes (each with
402/// its depth from the step's base; children are depth 1) in
403/// document order, and which conjuncts the adapter already applied
404/// (the engine runs the rest).
405#[derive(Debug, Clone, Default)]
406pub struct Selection {
407 pub nodes: Vec<(NodeId, usize)>,
408 pub applied: Vec<bool>,
409}
410
411/// What reaching one step's nodes would cost, as an adapter can
412/// say before running it ([`AstAdapter::estimate`]): intervals, a
413/// request count, a latency class and how sure the adapter is. A
414/// host composes these along a query ([`crate::plan::Plan`]) and
415/// refuses before running when a budget is exceeded.
416#[derive(Debug, Clone, PartialEq)]
417pub struct Estimate {
418 /// The nodes the step yields, from the least to the most.
419 pub nodes: (u64, u64),
420 /// The bytes read to yield them, when the adapter can say (a
421 /// warehouse's dry run, a file's size).
422 pub bytes: Option<(u64, u64)>,
423 /// Round trips to a remote, when any.
424 pub requests: Option<u32>,
425 pub latency: Latency,
426 pub confidence: Confidence,
427}
428
429/// Where a step's cost is paid.
430#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
431pub enum Latency {
432 /// The nodes are in memory.
433 Memory,
434 /// A local file or store.
435 Local,
436 /// A network round trip.
437 Remote,
438}
439
440/// How much an [`Estimate`] is worth.
441#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
442pub enum Confidence {
443 /// A guess from statistics or defaults.
444 Guess,
445 /// An upper bound (a clustered table's dry run, a partition's
446 /// size): the actual may come in lower.
447 Bound,
448 /// Exact: the catalog or the store knows.
449 Exact,
450}
451
452/// What a source knows about itself before it is read: how many
453/// entries it holds (files under a directory, members of an
454/// archive, rows of a table, documents on a shelf) and how many
455/// bytes they come to — from a listing, a central directory, a
456/// `stat`, a store's catalog, never from the contents. A latent
457/// mount answers the plan from it; an [`Unopened`] source is only
458/// it; an opened arena answers from its nodes instead.
459#[derive(Debug, Clone, PartialEq)]
460pub struct Catalog {
461 /// The entries, from the least to the most. A listing that
462 /// stopped early reports `(counted, u64::MAX)`: unbounded.
463 pub entries: (u64, u64),
464 /// Their bytes, when the listing says.
465 pub bytes: Option<(u64, u64)>,
466 pub latency: Latency,
467 pub confidence: Confidence,
468 /// Which of the asked conjuncts the listing applied before
469 /// counting (empty: none; a catalog that applies filters sets
470 /// one flag per conjunct asked).
471 pub applied: Vec<bool>,
472 /// Whether the asked trait filters were all applied.
473 pub traits_applied: bool,
474}
475
476impl Catalog {
477 /// A catalog that applied nothing.
478 pub fn plain(
479 entries: (u64, u64),
480 bytes: Option<(u64, u64)>,
481 latency: Latency,
482 confidence: Confidence,
483 ) -> Catalog {
484 Catalog {
485 entries,
486 bytes,
487 latency,
488 confidence,
489 applied: Vec::new(),
490 traits_applied: false,
491 }
492 }
493}
494
495/// What a catalog is asked for: the literal path under the source,
496/// where counting may stop, and the step's filters a listing may
497/// apply before counting (it says which in [`Catalog::applied`]).
498pub struct Ask<'a> {
499 pub scope: &'a [&'a str],
500 pub cap: Cap,
501 pub conjuncts: &'a [Conjunct<'a>],
502 pub traits: &'a [&'a str],
503}
504
505impl<'a> Ask<'a> {
506 /// A bare ask: the path and the cap, no filters.
507 pub fn at(scope: &'a [&'a str], cap: Cap) -> Ask<'a> {
508 Ask {
509 scope,
510 cap,
511 conjuncts: &[],
512 traits: &[],
513 }
514 }
515}
516
517/// A source known only by its catalog: a root with nothing under
518/// it, answering the plan's estimate for a step from the root out
519/// of the catalog. A front end plans and budgets a query against
520/// it before paying to open the source — a directory of a hundred
521/// thousand files, a tarball of books — and opens only within
522/// budget.
523pub struct Unopened(pub CatalogLookup);
524
525/// A catalog by literal path from a source's root: `&[]` is the
526/// whole source, `&["shelf", "emma.txt"]` one entry under it (a
527/// folder's own listing, one member's header), `None` where the
528/// path names nothing the catalog can see. The cap says where
529/// counting may stop.
530pub type CatalogLookup = Box<dyn Fn(&Ask<'_>) -> Option<Catalog>>;
531
532impl Unopened {
533 /// A source with one catalog, for its root alone.
534 pub fn fixed(c: Catalog) -> Unopened {
535 Unopened(Box::new(move |ask| ask.scope.is_empty().then(|| c.clone())))
536 }
537}
538
539/// The bare root (spec: the expression head): the source a
540/// calculator query runs against — one node, no name, no children,
541/// no properties. `qua '= expr'` without an input and `qc` run on
542/// it, so the two agree by construction.
543pub struct Bare;
544
545impl AstAdapter for Bare {
546 fn root(&self) -> NodeId {
547 NodeId(0)
548 }
549 fn children(&self, _: NodeId) -> Vec<NodeId> {
550 Vec::new()
551 }
552 fn name(&self, _: NodeId) -> Option<String> {
553 None
554 }
555}
556
557impl AstAdapter for Unopened {
558 fn root(&self) -> NodeId {
559 NodeId(0)
560 }
561 fn children(&self, _: NodeId) -> Vec<NodeId> {
562 Vec::new()
563 }
564 fn name(&self, _: NodeId) -> Option<String> {
565 None
566 }
567 fn estimate(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Estimate> {
568 Estimate::at_catalog(&*self.0, scope, plan)
569 }
570}
571
572impl Estimate {
573 /// The estimate a catalog gives a step from the source's root.
574 /// The entries are files or members, the step yields nodes,
575 /// and the two meet only as bounds: a child step reaches at most
576 /// the entries (`/*`, or a named one, a subset), an unfiltered
577 /// descendant step reaches at least every entry (`//*`: each is
578 /// a node, with what the reading finds under it), anything else
579 /// is open. The bytes are the source's: what opening it reads.
580 pub fn from_catalog(c: &Catalog, plan: &StepPlan<'_>) -> Estimate {
581 // Filters the listing applied make the count a bound on the
582 // matches: nothing under an entry answers a listing's
583 // property, so a filtered descendant step is bounded too.
584 let filtered = !plan.conjuncts.is_empty() || !plan.traits.is_empty();
585 let applied = filtered
586 && c.applied.len() == plan.conjuncts.len()
587 && c.applied.iter().all(|&b| b)
588 && (plan.traits.is_empty() || c.traits_applied);
589 let rows = match (plan.axis, plan.name, filtered) {
590 (PlanAxis::Child, _, _) => (0, c.entries.1),
591 (PlanAxis::Descendant, None, false) => (c.entries.0, u64::MAX),
592 (PlanAxis::Descendant, None, true) if applied => (0, c.entries.1),
593 _ => (0, u64::MAX),
594 };
595 Estimate {
596 nodes: rows,
597 bytes: c.bytes,
598 requests: Some(0),
599 latency: c.latency,
600 confidence: c.confidence.min(Confidence::Bound),
601 }
602 }
603
604 /// The estimate a catalogued source gives a step at a literal
605 /// `scope`, narrowing to the path the plan records: a bare hop
606 /// by name is one entry when the catalog finds it (and costs
607 /// nothing; the step that reads under it is priced by that
608 /// entry's own catalog) and may read the whole when it does not
609 /// (a node inside a document); a reading step at a path the
610 /// catalog resolves is priced by it, and one the catalog cannot
611 /// place is a bounded unknown under the source. `None` when the
612 /// source itself has no catalog.
613 pub fn at_catalog(
614 lookup: &dyn Fn(&Ask<'_>) -> Option<Catalog>,
615 scope: &[&str],
616 plan: &StepPlan<'_>,
617 ) -> Option<Estimate> {
618 let whole = |scope: &[&str]| lookup(&Ask::at(scope, plan.cap));
619 let bare =
620 plan.conjuncts.is_empty() && plan.traits.is_empty() && plan.axis == PlanAxis::Child;
621 if bare && let Some(name) = plan.name {
622 let mut sub: Vec<&str> = scope.to_vec();
623 sub.push(name);
624 return Some(match whole(&sub) {
625 Some(c) => Estimate {
626 nodes: (1, 1),
627 bytes: None,
628 requests: Some(0),
629 latency: c.latency,
630 confidence: Confidence::Exact,
631 },
632 // Not an entry the catalog sees (a node inside a
633 // document): reaching it from the root may read the
634 // whole; deeper, the whole was charged already.
635 None => {
636 let whole = whole(&[])?;
637 if scope.is_empty() {
638 Estimate::from_catalog(&whole, plan)
639 } else {
640 Estimate::beneath_catalog(&whole)
641 }
642 }
643 });
644 }
645 // A reading step asks with its filters: a listing that can
646 // apply them counts the matches alone.
647 let ask = Ask {
648 scope,
649 cap: plan.cap,
650 conjuncts: &plan.conjuncts,
651 traits: &plan.traits,
652 };
653 Some(match lookup(&ask) {
654 Some(c) => Estimate::from_catalog(&c, plan),
655 None => Estimate::beneath_catalog(&whole(&[])?),
656 })
657 }
658
659 /// The estimate a catalog gives a step deeper than the root:
660 /// nothing is known of the nodes, and the bytes were counted at
661 /// the root (`None` here, so the branch keeps them).
662 pub fn beneath_catalog(c: &Catalog) -> Estimate {
663 Estimate {
664 nodes: (0, u64::MAX),
665 bytes: None,
666 requests: Some(0),
667 latency: c.latency,
668 confidence: Confidence::Bound,
669 }
670 }
671
672 /// `self` followed by `next` along one branch: the nodes are
673 /// the next step's, the bytes and requests add, the latency is
674 /// the slower, the confidence the weaker.
675 pub fn then(&self, next: &Estimate) -> Estimate {
676 Estimate {
677 nodes: next.nodes,
678 bytes: match (self.bytes, next.bytes) {
679 (Some(a), Some(b)) => Some((a.0.saturating_add(b.0), a.1.saturating_add(b.1))),
680 (a, None) => a,
681 (None, b) => b,
682 },
683 requests: match (self.requests, next.requests) {
684 (Some(a), Some(b)) => Some(a + b),
685 (a, None) => a,
686 (None, b) => b,
687 },
688 latency: self.latency.max(next.latency),
689 confidence: self.confidence.min(next.confidence),
690 }
691 }
692
693 /// `self` beside `other` (two branches of one query): nodes,
694 /// bytes and requests add.
695 pub fn beside(&self, other: &Estimate) -> Estimate {
696 Estimate {
697 nodes: (
698 self.nodes.0.saturating_add(other.nodes.0),
699 self.nodes.1.saturating_add(other.nodes.1),
700 ),
701 ..self.then(other)
702 }
703 }
704}
705
706/// Nodes an arena walk may touch while estimating before it stops
707/// counting: past this many the count is a lower bound.
708pub const ARENA_ESTIMATE_BUDGET: u64 = 2_000_000;
709
710/// An estimate for an adapter whose arbor is in memory (a parsed
711/// document, a walked directory, an archive's member table, a
712/// shelf of documents): the plan's base found by the literal
713/// `scope` from the root (one hit per name, else unknown), then the
714/// step's nodes counted — the children answering to the name, or
715/// the descendants — with `bytes_of` summing what the adapter knows
716/// per node. Exact; a bound under a conjunct or past the walk's
717/// budget. Not the trait's default: for a lazy or remote adapter a
718/// walk is a read.
719pub fn arena_estimate<A: AstAdapter + ?Sized>(
720 adapter: &A,
721 scope: &[&str],
722 plan: &StepPlan<'_>,
723 latency: Latency,
724 bytes_of: Option<&dyn Fn(NodeId) -> Option<u64>>,
725) -> Option<Estimate> {
726 let mut at = adapter.root();
727 for name in scope {
728 let mut hits = adapter.children_named(at, name).into_iter();
729 at = hits.next()?;
730 if hits.next().is_some() {
731 return None;
732 }
733 }
734 let matches = |c: NodeId| plan.name.is_none_or(|n| adapter.answers_to(c, n));
735 let mut n: u64 = 0;
736 let mut bytes: u64 = 0;
737 let mut cut = false;
738 // Counts one node; `true` once the cap is passed: stop.
739 let mut count = |c: NodeId| {
740 if matches(c) {
741 n += 1;
742 if let Some(f) = bytes_of {
743 bytes += f(c).unwrap_or(0);
744 }
745 }
746 plan.cap.past(n, bytes)
747 };
748 match plan.axis {
749 PlanAxis::Child => {
750 for c in adapter.children(at) {
751 if count(c) {
752 cut = true;
753 break;
754 }
755 }
756 }
757 PlanAxis::Descendant => {
758 let mut visited: u64 = 0;
759 let mut stack = adapter.children(at);
760 while let Some(c) = stack.pop() {
761 visited += 1;
762 if visited > ARENA_ESTIMATE_BUDGET || count(c) {
763 cut = true;
764 break;
765 }
766 stack.extend(adapter.children(c));
767 }
768 }
769 }
770 let filtered = !plan.conjuncts.is_empty();
771 Some(Estimate {
772 nodes: if cut {
773 (n, u64::MAX)
774 } else if filtered {
775 (0, n)
776 } else {
777 (n, n)
778 },
779 bytes: bytes_of.map(|_| if cut { (bytes, u64::MAX) } else { (0, bytes) }),
780 requests: Some(0),
781 latency,
782 confidence: if cut || filtered {
783 Confidence::Bound
784 } else {
785 Confidence::Exact
786 },
787 })
788}
789
790/// [`AstAdapter::select`]'s default, through the individual hooks:
791/// a child hop by `children_named`; a descendant search by
792/// `descendants_where` when the plan leads with an equality the
793/// adapter indexes, else `descendants_named`. A wrapper that
794/// translates node ids forwards `select` to its inner adapter and
795/// falls back to this over its own hooks.
796pub fn select_by_hooks<A: AstAdapter + ?Sized>(
797 adapter: &A,
798 from: NodeId,
799 plan: &StepPlan<'_>,
800) -> Option<Selection> {
801 let name = plan.name?;
802 match plan.axis {
803 PlanAxis::Child => Some(Selection {
804 nodes: adapter
805 .children_named(from, name)
806 .into_iter()
807 .map(|n| (n, 1))
808 .collect(),
809 applied: vec![false; plan.conjuncts.len()],
810 }),
811 PlanAxis::Descendant => {
812 if let Some(Conjunct::Equals(prop, value)) = plan.conjuncts.first()
813 && let Some(nodes) = adapter.descendants_where(from, name, prop, value)
814 {
815 let mut applied = vec![false; plan.conjuncts.len()];
816 applied[0] = true;
817 return Some(Selection { nodes, applied });
818 }
819 let nodes = adapter.descendants_named(from, name)?;
820 Some(Selection {
821 nodes,
822 applied: vec![false; plan.conjuncts.len()],
823 })
824 }
825 }
826}
827
828/// The probe behind [`AstAdapter::has_node`]'s default: run a
829/// closure that may panic on a foreign node id, answering `false`
830/// when it does, without the panic reaching stderr. The process's
831/// hook is wrapped once (the first probe installs the wrapper,
832/// which defers to whatever hook was there) and a thread-local
833/// flag silences it only on the probing thread.
834mod probe {
835 use std::cell::Cell;
836 use std::sync::Once;
837
838 thread_local! {
839 static SILENT: Cell<bool> = const { Cell::new(false) };
840 }
841 static INSTALL: Once = Once::new();
842
843 pub(super) fn silently<R: Default>(f: impl FnOnce() -> R) -> R {
844 INSTALL.call_once(|| {
845 let previous = std::panic::take_hook();
846 std::panic::set_hook(Box::new(move |info| {
847 if !SILENT.with(|s| s.get()) {
848 previous(info);
849 }
850 }));
851 });
852 SILENT.with(|s| s.set(true));
853 let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_default();
854 SILENT.with(|s| s.set(false));
855 out
856 }
857}
858
859/// The interface a data source implements to be queried by Quarb.
860///
861/// An adapter maps its native structure onto the arbor model: a tree
862/// backbone whose edges carry *names*. The engine drives navigation
863/// purely through this trait, so the same query language runs over
864/// any adapter. Three methods are required (`root`, `children`,
865/// `name`); every other one has a default an adapter overrides
866/// only where its substrate has more to say. The methods fall into
867/// groups:
868///
869/// - **The tree**: `root`, `children`, `name`, `parent`,
870/// `child_host` (a transparent link lends its target's
871/// children), `non_tree_nodes` (joined by crosslinks alone),
872/// `has_node`.
873/// - **Naming and traits**: `answers_to` (per-node spelling
874/// aliases, consulted wherever a literal name is matched),
875/// `traits`, `has_trait`.
876/// - **Properties and metadata**: `property`, `properties`,
877/// `property_names`, `default_value`, `metadata`,
878/// `aliased_metadata`.
879/// - **References**, three protocols the engine consults in this
880/// order. *Materialized edges*: `links` / `backlinks` (the `->` /
881/// `<-` hops) with `link_property` and the `prefetch_links`
882/// batching hint. *Property resolution* (`::prop-->`, `-->`,
883/// `<--`): `resolve`, `ref_property`, `ref_label`,
884/// `resolve_needs_label`, `reverse_resolve`, `reverse_label`,
885/// `can_refer_to`, `reference_properties`,
886/// `walks_for_references`, `resolves_references`. *Cross-document*
887/// (a reference naming another document): `external_ref`,
888/// `resolve_fragment`, `document_by_ref`, `tree_identities`; the
889/// engine joins the identity against the tree the reference was
890/// written in and lands through the host's registered targets,
891/// then `document_by_ref`, then the identities, and records what
892/// it could not reach. The engine performs no I/O for any of it.
893/// - **Declared indexes** (a model's `index SCOPE::prop;`):
894/// `index_lookup`, `index_referrers`.
895/// - **Search fast paths**: [`select`](Self::select) — one plan
896/// per step (axis, name matcher, the conjuncts the engine can
897/// lower, a limit, an order) an adapter may answer from an index;
898/// its default folds `children_named`, `descendants_named` and
899/// `descendants_where`, which an adapter may still implement
900/// individually. A fast path changes cost, never the answer.
901/// - **Evaluation hints**: `quantifier_bound` (the depth the
902/// adapter's own structures need; a run may override it),
903/// `unit_scale`.
904/// - **Provenance**: `provenance`, `provenance_list`.
905///
906/// Pairs the engine relies on being observationally equal, so an
907/// override must keep them so: `children_named(n, x)` is
908/// `children(n)` filtered by `answers_to(_, x)` (except deliberate
909/// container-scoped aliasing, documented on the method);
910/// `has_trait(n, t)` is `t ∈ traits(n)`; `property_names(n)` covers
911/// the names of `properties(n)`, the `ref_property`, and the child
912/// names; `provenance_list(n)`'s first entry is `provenance(n)`;
913/// `select` answers exactly what the walk would find. The
914/// conformance suite ([`crate::conformance::check`]) tests these
915/// over a sample of an adapter's nodes.
916///
917/// Optional capabilities with a single implementor each — fine to
918/// leave at the default: `unit_scale` (kaiv's units),
919/// `descendants_where` (the corpus reading's positional index),
920/// `child_host` (filesystem symlinks), `aliased_metadata` (git's
921/// closed surface), `provenance_list` (kaiv's layered sources),
922/// `index_lookup` / `index_referrers` (a model's declared indexes).
923pub trait AstAdapter {
924 /// The root node — the initial navigation context.
925 fn root(&self) -> NodeId;
926
927 /// The tree children of `node`, in document order.
928 ///
929 /// Returns an empty vector for a leaf (or an unreadable node).
930 fn children(&self, node: NodeId) -> Vec<NodeId>;
931
932 /// The name of `node` — the label of its incoming tree edge.
933 ///
934 /// `None` when the adapter leaves a node unnamed (typically the
935 /// root; e.g. the filesystem root `/` carries no name).
936 fn name(&self, node: NodeId) -> Option<String>;
937
938 /// The parent of `node`, or `None` for the root.
939 fn parent(&self, _node: NodeId) -> Option<NodeId> {
940 None
941 }
942
943 /// The traits of `node` — its adapter-defined classifications,
944 /// used by `<trait>` navigation filters (e.g. a filesystem
945 /// adapter's `<dir>`, `<code>`, `<image>`).
946 fn traits(&self, _node: NodeId) -> Vec<String> {
947 Vec::new()
948 }
949
950 /// A named property of `node` — `::prop`. `None` if absent.
951 fn property(&self, _node: NodeId, _name: &str) -> Option<Value> {
952 None
953 }
954
955 /// The node's own properties, enumerated — name and value, in
956 /// the source's order — for readings that keep values off the
957 /// tree (the JSON property reading, where an object's scalar
958 /// fields are no nodes). The serializers (`| json`, `| xml`,
959 /// `| kaiv`) merge them with the children, a child winning a
960 /// shared name. The default is none: for a reading whose values
961 /// are all nodes, the children already say everything.
962 fn properties(&self, _node: NodeId) -> Vec<(String, Value)> {
963 Vec::new()
964 }
965
966 /// Whether a reverse resolution or a value match with no scope
967 /// may walk the whole arbor to find its referrers. An adapter
968 /// over a small, in-memory source opens to the walk; a store or
969 /// a service does not, and such a query is refused with a message
970 /// asking for a scope — an empty answer could not be told from an
971 /// absence of referrers. A declared reference (a foreign key, a
972 /// model `ref`) answers through [`reverse_resolve`](Self::reverse_resolve)
973 /// and needs no walk.
974 fn walks_for_references(&self) -> bool {
975 false
976 }
977
978 /// Whether a reference *declared* on `source` — a catalog key, a
979 /// model `ref`, a reference property the format defines — can
980 /// land in `target`'s container. A question of the schema, never
981 /// of the data; `None` where nothing is declared at the node.
982 /// The default: a node with a reference property of its own can
983 /// refer anywhere (an href may point at any element).
984 fn can_refer_to(&self, source: NodeId, _target: NodeId) -> Option<bool> {
985 self.ref_property(source).map(|_| true)
986 }
987
988 /// The reference properties declared on `source` that can land
989 /// in `target`'s container: a catalog's key columns into the
990 /// target's table, the fields of a model's `ref`s, the node's
991 /// own reference property. What `t<--` and `t<--(SCOPE)` read
992 /// when the query names no property; empty where none is
993 /// declared.
994 fn reference_properties(&self, source: NodeId, _target: NodeId) -> Vec<String> {
995 self.ref_property(source).into_iter().collect()
996 }
997
998 /// Whether resolving `property` of `node` needs a reference
999 /// label, because the property itself declares no target: a
1000 /// column with no key, or any property of a property graph,
1001 /// where a value names a node only under a node label. A
1002 /// label-less `::p-->` over such a property is refused with the
1003 /// remedy, since an empty answer would read as "points nowhere".
1004 fn resolve_needs_label(&self, _node: NodeId, _property: &str) -> bool {
1005 false
1006 }
1007
1008 /// The reference label under which a referrer's property is
1009 /// also resolved when a reverse arrow asks about `target` and
1010 /// the query writes no label. `None` (the default): only the
1011 /// label-less resolution counts. An adapter whose label names a
1012 /// container (a table, a collection, a node label) returns the
1013 /// one `target` stands in, so `/authors/1<--(/books/*::author_id)`
1014 /// reads `author_id` as a key into `authors` without the query
1015 /// saying so. The engine derives no label of its own.
1016 fn reverse_label(&self, _target: NodeId) -> Option<String> {
1017 None
1018 }
1019
1020 /// The trees of this arbor other trees refer to by name: each
1021 /// identity (a URL, a path, any string a reference may hold)
1022 /// with the node that stands for the tree. An identity ending
1023 /// in a separator is a base: every identity beneath it names
1024 /// the node reached beneath the base's node by the remainder's
1025 /// segments. Declared in a model (`tree IDENTITY: PATH;`) and
1026 /// automatic for a mount's own target; the engine reads nothing
1027 /// inside an identity beyond prefix and separator.
1028 fn tree_identities(&self) -> Vec<(String, NodeId)> {
1029 Vec::new()
1030 }
1031
1032 /// The names of the properties a node has — what "any
1033 /// property" means for `<--(SCOPE::*)`. By default its off-tree
1034 /// properties, its reference property, and its children's names
1035 /// (a row's columns, an object's keys are properties under the
1036 /// same names); an adapter whose properties are not children
1037 /// (an element's attributes) lists them.
1038 fn property_names(&self, node: NodeId) -> Vec<String> {
1039 let mut ps: Vec<String> = self.properties(node).into_iter().map(|(k, _)| k).collect();
1040 if let Some(r) = self.ref_property(node)
1041 && !ps.contains(&r)
1042 {
1043 ps.push(r);
1044 }
1045 for c in self.children(node) {
1046 if let Some(name) = self.name(c)
1047 && !ps.contains(&name)
1048 {
1049 ps.push(name);
1050 }
1051 }
1052 ps
1053 }
1054
1055 /// Whether this source resolves references at all. An adapter
1056 /// with no notion of a reference (a CSV file, a directory, a
1057 /// log stream) says no, and a reverse arrow over it is refused
1058 /// with the value match as the remedy, rather than answered
1059 /// with an empty set that would read as "nothing points here".
1060 fn resolves_references(&self) -> bool {
1061 true
1062 }
1063
1064 /// `index SCOPE::prop;` — the nodes of the declared scope whose
1065 /// property holds `value`, from an index the model keeps; `None`
1066 /// when no declaration covers the pair (the walk answers). An
1067 /// index changes cost, never the answer: the caller still tests
1068 /// each node it names.
1069 fn index_lookup(&self, _scope: &str, _property: &str, _value: &Value) -> Option<Vec<NodeId>> {
1070 None
1071 }
1072 /// The declared-scope nodes whose `property` resolves (under the
1073 /// relation `label`, when given) to `target`, from the same index;
1074 /// `None` when no declaration covers the pair.
1075 fn index_referrers(
1076 &self,
1077 _scope: &str,
1078 _property: &str,
1079 _label: Option<&str>,
1080 _target: NodeId,
1081 ) -> Option<Vec<NodeId>> {
1082 None
1083 }
1084
1085 /// The nodes of the arbor that are not in the tree — joined to
1086 /// it by crosslinks alone, with no parent and no path. The
1087 /// universal search `*//` reaches them after the tree, in this
1088 /// order; `:::is-non-tree` is true on them. The default is
1089 /// none: every shipped adapter puts every node in the tree.
1090 fn non_tree_nodes(&self) -> Vec<NodeId> {
1091 Vec::new()
1092 }
1093
1094 /// The node whose children an explicit child step from `node`
1095 /// reads. The default is `node` itself. An adapter whose tree
1096 /// holds transparent links (a filesystem's symlink to a
1097 /// directory) answers the link's target, so `/link/name` lands
1098 /// on the target's own child — the same node `/target/name`
1099 /// reaches. The descendant walk never consults this: it reads
1100 /// [`children`](Self::children) alone, so a linked subtree is
1101 /// walked once, under its own parent, and a cycle of links
1102 /// cannot trap it.
1103 fn child_host(&self, node: NodeId) -> NodeId {
1104 node
1105 }
1106
1107 /// The children of `node` whose edge name is exactly `name` —
1108 /// the engine's fast path for name-matcher child hops. The
1109 /// default filters [`children`](Self::children); an adapter
1110 /// whose containers cannot be enumerated (permission-scoped or
1111 /// unbounded remote trees) overrides this with a direct,
1112 /// name-addressed lookup. Must be observationally identical to
1113 /// the default wherever enumeration works. The adapter owns the
1114 /// name test: it may deliberately *alias* — resolve a name to a
1115 /// node whose edge name differs (git revision syntax landing on
1116 /// a hash-named commit) — and the engine will not re-filter.
1117 /// Container-scoped resolution like that stays child-axis only;
1118 /// per-node spelling aliases belong in
1119 /// [`answers_to`](Self::answers_to), which this default
1120 /// consults.
1121 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
1122 self.children(node)
1123 .into_iter()
1124 .filter(|&c| self.answers_to(c, name))
1125 .collect()
1126 }
1127
1128 /// Whether `node` answers to `name` as a literal spelling
1129 ///. The default is canonical equality. An adapter
1130 /// may declare per-node aliases — a social feed spelling a
1131 /// handle `@alice` while the stripped `alice` is the hop name —
1132 /// and the engine consults this wherever a *literal* name is
1133 /// matched, on every axis, so one override keeps `/@alice` and
1134 /// `//@alice` in agreement. `:::name` stays canonical (locators
1135 /// and reflection print it; an alias is a way in, never a way
1136 /// out), and name patterns (`~(...)`, `*`) test the canonical
1137 /// name only.
1138 fn answers_to(&self, node: NodeId, name: &str) -> bool {
1139 self.name(node).as_deref() == Some(name)
1140 }
1141
1142 /// Whether `node` bears `name` as a trait, as written in a
1143 /// `<...>` clause. The default is membership in
1144 /// [`traits`](Self::traits); an adapter may declare aliases the
1145 /// way [`answers_to`](Self::answers_to) does for names — a
1146 /// model file's `alias <chunk> <block>;` or
1147 /// `alias <s/^tag://i>;` — and the engine consults this
1148 /// wherever a written trait is matched. Reflection and
1149 /// serialization print the canonical traits only.
1150 fn has_trait(&self, node: NodeId, name: &str) -> bool {
1151 self.traits(node).iter().any(|t| t == name)
1152 }
1153
1154 /// The default projection of `node` — bare `::`, adapter-specific
1155 /// (a filesystem adapter returns file content).
1156 fn default_value(&self, _node: NodeId) -> Option<Value> {
1157 None
1158 }
1159
1160 /// Adapter-defined metadata — `::::key` (a filesystem adapter's
1161 /// `size`, `modified`, `permissions`, …). `None` if absent.
1162 fn metadata(&self, _node: NodeId, _key: &str) -> Option<Value> {
1163 None
1164 }
1165
1166 /// Metadata keys this adapter also answers at `::` (ruling
1167 /// #29), for the given node — per node, because a composite
1168 /// adapter (a multi-mount, a graft) answers for whichever
1169 /// document owns it. Only an adapter whose property surface is *closed* —
1170 /// fixed by the adapter, never grown by document content — may
1171 /// declare aliases: a git commit cannot sprout a `short` field,
1172 /// where a JSON object can sprout anything. Data always wins:
1173 /// the engine consults `property` first and falls through only
1174 /// when it answers `None` for that node, so an alias can never
1175 /// shadow a document. Core metadata (`:::`) is never aliased —
1176 /// `name`, `id` and `index` exist on every node, and aliasing
1177 /// them would change what `::name` means wherever a document
1178 /// happens to lack that field.
1179 fn aliased_metadata(&self, _node: NodeId) -> &'static [&'static str] {
1180 &[]
1181 }
1182
1183 /// Outgoing crosslinks from `node`, as `(label, target)` pairs,
1184 /// for `->` navigation (a filesystem adapter's symlinks).
1185 fn links(&self, _node: NodeId) -> Vec<(String, NodeId)> {
1186 Vec::new()
1187 }
1188
1189 /// Incoming crosslinks to `node`, as `(label, source)` pairs, for
1190 /// `<-` navigation. May be expensive (an adapter that does not
1191 /// precompute edges must search for referrers).
1192 fn backlinks(&self, _node: NodeId) -> Vec<(String, NodeId)> {
1193 Vec::new()
1194 }
1195
1196 /// Resolve a cross-reference: `::property-->hint` maps `node`'s
1197 /// `property` (a value that references another node) to its target,
1198 /// with an optional adapter-specific `hint` (a relation type such as
1199 /// an html `rel`, or a target container such as a table). A JSON
1200 /// adapter resolves a `$ref` JSON Pointer; `None` if unresolvable.
1201 fn resolve(&self, _node: NodeId, _property: &str, _hint: Option<&str>) -> Option<NodeId> {
1202 None
1203 }
1204
1205 /// The external reference `node`'s `property` holds, if any: the
1206 /// identity of a document *outside this arbor* and, where the
1207 /// reference says so, the location within it — both as the
1208 /// adapter reads them out of its own reference syntax (for html,
1209 /// the anchor's absolute URL and its fragment). The engine does
1210 /// not look inside either string: it asks for the document among
1211 /// the mounts (the host's registration, then
1212 /// [`document_by_ref`](Self::document_by_ref)), and hands the
1213 /// location to [`resolve_fragment`](Self::resolve_fragment) on
1214 /// the document it found. Consulted by `::property-->` when
1215 /// in-document resolution misses; an unmounted document lands
1216 /// among the run's unresolved external references for the
1217 /// host's acquisition loop. The engine itself never fetches.
1218 fn external_ref(
1219 &self,
1220 _node: NodeId,
1221 _property: &str,
1222 _hint: Option<&str>,
1223 ) -> Option<ExternalRef> {
1224 None
1225 }
1226
1227 /// The element `fragment` names inside the document `node`
1228 /// belongs to — html's `id` lookup. The landing rung of a
1229 /// fragment-carrying external reference, once its document is
1230 /// mounted.
1231 fn resolve_fragment(&self, _node: NodeId, _fragment: &str) -> Option<NodeId> {
1232 None
1233 }
1234
1235 /// The property carrying `node`'s own reference, for the bare
1236 /// arrow (`//a-->`): an html anchor's `href`, an iframe's
1237 /// `src`. `None` for a node that references nothing.
1238 fn ref_property(&self, _node: NodeId) -> Option<String> {
1239 None
1240 }
1241
1242 /// The relation name a resolution edge from `node` via
1243 /// `property` carries — html's `rel` attribute. `None` falls
1244 /// back to the property name as the edge label.
1245 fn ref_label(&self, _node: NodeId, _property: &str) -> Option<String> {
1246 None
1247 }
1248
1249 /// A property of the crosslink `source --label--> target` — the
1250 /// `$-::prop` read. Adapters whose edges carry data (a property
1251 /// graph's relationship properties) override this; `None` if the
1252 /// edge is bare or unknown. Where parallel edges share source,
1253 /// label, and target, the adapter answers for one of them,
1254 /// consistently.
1255 fn link_property(
1256 &self,
1257 _source: NodeId,
1258 _label: &str,
1259 _target: NodeId,
1260 _name: &str,
1261 ) -> Option<Value> {
1262 None
1263 }
1264
1265 /// The document this arbor holds under an external-reference
1266 /// identifier (a URL, fragment-stripped), when it can answer
1267 /// without the host's registration: the landing rung of `-->`
1268 /// after the mounted-document table misses. An adapter over a
1269 /// store of pages answers by URL lookup; the engine performs
1270 /// no IO either way.
1271 fn document_by_ref(&self, _id: &str) -> Option<NodeId> {
1272 None
1273 }
1274
1275 /// Every node whose `property` (or, when `None`, own reference
1276 /// property) resolves to `node`, in document order — the
1277 /// reverse index behind `<--`. `None` keeps the engine's walk
1278 /// of the whole arbor; `Some` is the adapter's complete answer.
1279 fn reverse_resolve(
1280 &self,
1281 _node: NodeId,
1282 _property: Option<&str>,
1283 _hint: Option<&str>,
1284 ) -> Option<Vec<NodeId>> {
1285 None
1286 }
1287
1288 /// The descendants of `node` that answer to `name`, each with
1289 /// its depth (children are depth 1), in document order — the
1290 /// fast path behind `//name` for an adapter whose substrate
1291 /// indexes names. `None` keeps the engine's walk; `Some` must
1292 /// be exactly what the walk would find.
1293 fn descendants_named(&self, _node: NodeId, _name: &str) -> Option<Vec<(NodeId, usize)>> {
1294 None
1295 }
1296
1297 /// The descendants of `node` named `name` whose property
1298 /// `property` equals `value`, each with its depth, in document
1299 /// order — the fast path behind `//name[::property = "value"]`
1300 /// for an adapter that indexes a property (the corpus reading's
1301 /// positional index answers `//token[::lower = "tom"]` from it).
1302 /// `None` keeps the engine's walk and its comparison; `Some`
1303 /// must be exactly what the walk would find under the standard
1304 /// equality, so an adapter answers only the values it can
1305 /// compare as the engine does (text against text).
1306 fn descendants_where(
1307 &self,
1308 _node: NodeId,
1309 _name: &str,
1310 _property: &str,
1311 _value: &Value,
1312 ) -> Option<Vec<(NodeId, usize)>> {
1313 None
1314 }
1315
1316 /// The search fast path: the nodes one step reaches from `from`
1317 /// under `plan`, answered from an index — `None` keeps the
1318 /// engine's walk (the default folds `children_named`,
1319 /// `descendants_named` and `descendants_where`:
1320 /// [`select_by_hooks`]). `Some` must be exactly what the walk
1321 /// would find for the conjuncts it marks applied, under the
1322 /// engine's standard equality; the engine tests the remaining
1323 /// conjuncts and every other predicate on the nodes returned. A
1324 /// child plan's answer is never re-filtered by name (the adapter
1325 /// owns the name test, as `children_named` does); a descendant
1326 /// plan's nodes still pass the nameless tests.
1327 fn select(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<Selection> {
1328 select_by_hooks(self, from, plan)
1329 }
1330 /// The dry-run form of [`select`](Self::select): which of the
1331 /// plan's conjuncts the adapter would apply, decided from its
1332 /// catalog alone (a column's existence and type, an index's
1333 /// presence), with no node in hand. `scope` is the literal path
1334 /// from the root to the step's base (`["invoices"]` for
1335 /// `/invoices/*[...]`), empty at the root. `None` when the
1336 /// adapter cannot say — the static plan then counts every
1337 /// conjunct as the engine's.
1338 fn select_plan(&self, _scope: &[&str], _plan: &StepPlan<'_>) -> Option<Vec<bool>> {
1339 None
1340 }
1341 /// What reaching one step's nodes would cost, said from the
1342 /// catalog with no node in hand (`scope` as for
1343 /// [`select_plan`](Self::select_plan)): a warehouse's dry run,
1344 /// a table's statistics, a file's size, an arena's count.
1345 /// `None` where the adapter has no estimator — the plan then
1346 /// reads "unknown" for the step.
1347 fn estimate(&self, _scope: &[&str], _plan: &StepPlan<'_>) -> Option<Estimate> {
1348 None
1349 }
1350 /// How many nodes one step reaches from `from` under `plan`,
1351 /// without materializing them — `@| count` right after the step
1352 /// asks before walking. `None` walks; `Some` must be exactly the
1353 /// walk's count for the conjuncts the plan carries (every one of
1354 /// them, or none: a count cannot be partially applied). SQL:
1355 /// `SELECT COUNT(*)`; a document index: the bucket's length.
1356 fn count(&self, _from: NodeId, _plan: &StepPlan<'_>) -> Option<usize> {
1357 None
1358 }
1359 /// A batching hint, the projection twin of `prefetch_links`: the
1360 /// engine is about to read the properties `names` of every node
1361 /// in `nodes` (the next command projects them). A remote adapter
1362 /// fetches the columns for the frontier in one round trip;
1363 /// semantics are unchanged either way.
1364 fn prefetch_properties(&self, _nodes: &[NodeId], _names: &[&str]) {}
1365 /// A batching hint: the engine is about to follow `dir`
1366 /// crosslinks from every node in `nodes`. An adapter backed by
1367 /// a store may fetch them in one statement; semantics are
1368 /// unchanged either way.
1369 fn prefetch_links(&self, _nodes: &[NodeId], _dir: LinkDir) {}
1370
1371 /// The quantifier bound N_max: the depth to which open-ended path
1372 /// quantifiers (`+`, `*`, `{m,}`) expand, and the ceiling of any
1373 /// explicit `{m,n}` (the effective upper bound is min(n, N_max)).
1374 /// An adapter whose natural structures run deep may raise it; a
1375 /// run may override it (`Run::quantifier_bound`, the CLI's
1376 /// `--quantifier-bound`). The shell gate and the invocation
1377 /// instant are the run's alone (`Run::allow_shell`, `Run::now`).
1378 fn quantifier_bound(&self) -> usize {
1379 32
1380 }
1381
1382 /// The data provenance of `node` — the `:::source` /
1383 /// `:::instant` / `:::dpid` / `:::provenance` core-metadata
1384 /// keys. Empty by default: an adapter answers only the
1385 /// components its substrate genuinely records (never the
1386 /// invocation clock); wrapper adapters fill missing components
1387 /// from what they know — the mount its target, a graft its
1388 /// outer leaf, a model its derivation — and forward the rest
1389 /// inward, so resolution is nearest-ancestor per component.
1390 fn provenance(&self, _node: NodeId) -> Provenance {
1391 Provenance::default()
1392 }
1393
1394 /// Whether `node` is an id this adapter minted — the guard
1395 /// behind the node-id anchor `((!N))`, which may name an id
1396 /// from an earlier run (ids are minted deterministically: the
1397 /// same source, the same ids). The default probes the id
1398 /// through `parent` and `name` and treats a panic as absence,
1399 /// so an adapter that indexes by id needs no override; one that
1400 /// can answer cheaply should.
1401 fn has_node(&self, node: NodeId) -> bool {
1402 if node == self.root() {
1403 return true;
1404 }
1405 // An adapter that indexes its arena panics on a foreign id;
1406 // the probe catches that. The panic hook is installed once
1407 // for the process and silenced per thread while probing —
1408 // never swapped, so other threads keep theirs.
1409 probe::silently(|| self.parent(node).is_some() || self.name(node).is_some())
1410 }
1411
1412 /// The data provenance of `node` as a list — every source the
1413 /// substrate records for the datum, and how many it elides.
1414 /// The default is the singular answer as a one-entry list; an
1415 /// adapter whose substrate records several (kaiv's `?a;b;+N`)
1416 /// overrides it, and a wrapper layers each entry.
1417 fn provenance_list(&self, node: NodeId) -> ProvenanceList {
1418 ProvenanceList::single(self.provenance(node))
1419 }
1420
1421 /// The scale of a unit expression — (factor, canonical SI-base
1422 /// expansion) — for the unital reading's criterion text (spec:
1423 /// The Quantital Fragment). The default answers from the
1424 /// engine's frozen built-in table; a unit-aware adapter (kaiv)
1425 /// overrides it to include the mounted document's own custom
1426 /// units, so `[::range < '50kellicam']` resolves through the
1427 /// document's `.!units` imports.
1428 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1429 crate::quantity::scale_expr(expr)
1430 }
1431}
1432
1433/// Forwards every [`AstAdapter`] method to a wrapped adapter — the
1434/// one definition behind the wrappers (the boxed carrier, the
1435/// decoding layer, the mount and model wrappers), so a method added to the
1436/// trait is forwarded everywhere at once instead of silently
1437/// falling back to the trait default in whichever wrapper the
1438/// hand-written list missed.
1439///
1440/// The first argument names the wrapped adapter: a field (`inner`,
1441/// `0`) reached as `self.FIELD`, or `*` for a `Box<dyn AstAdapter>`
1442/// forwarding through `**self`. The optional `except` list names the
1443/// methods the wrapper defines itself, right after the invocation:
1444///
1445/// ```ignore
1446/// impl<A: AstAdapter> AstAdapter for Bounded<'_, A> {
1447/// quarb::forward_ast_adapter!(inner; except quantifier_bound);
1448/// fn quantifier_bound(&self) -> usize {
1449/// self.bound
1450/// }
1451/// }
1452/// ```
1453///
1454/// The method table inside the macro is checked against the trait
1455/// by `forwarding::the_macro_lists_every_trait_method`, and each
1456/// shipped wrapper against a probe adapter by
1457/// `forwarding::every_wrapper_forwards_every_method`.
1458#[macro_export]
1459macro_rules! forward_ast_adapter {
1460 ($inner:tt $(; except $($except:ident),+ $(,)?)? $(;)?) => {
1461 $crate::forward_ast_adapter!(@each [$inner] [$($($except)+)?]
1462 // Every method of `AstAdapter`, in the trait's order; the
1463 // coverage test reads this list from the source.
1464 root children name parent traits property properties
1465 walks_for_references can_refer_to reference_properties
1466 resolve_needs_label reverse_label tree_identities
1467 property_names resolves_references index_lookup
1468 index_referrers non_tree_nodes child_host children_named
1469 answers_to has_trait default_value metadata
1470 aliased_metadata links backlinks resolve external_ref
1471 resolve_fragment ref_property ref_label link_property
1472 document_by_ref reverse_resolve descendants_named
1473 descendants_where select select_plan estimate count
1474 prefetch_properties
1475 prefetch_links quantifier_bound
1476 provenance has_node
1477 provenance_list unit_scale
1478 );
1479 };
1480 (@each [$inner:tt] $ex:tt $($m:ident)*) => {
1481 $( $crate::forward_ast_adapter!(@unless [$inner] $ex $m); )*
1482 };
1483
1484 // The excepted method heads the list: emit nothing. One arm per
1485 // method, since a macro cannot compare two captured identifiers.
1486 (@unless [$i:tt] [root $($r:ident)*] root) => {};
1487 (@unless [$i:tt] [children $($r:ident)*] children) => {};
1488 (@unless [$i:tt] [name $($r:ident)*] name) => {};
1489 (@unless [$i:tt] [parent $($r:ident)*] parent) => {};
1490 (@unless [$i:tt] [traits $($r:ident)*] traits) => {};
1491 (@unless [$i:tt] [property $($r:ident)*] property) => {};
1492 (@unless [$i:tt] [properties $($r:ident)*] properties) => {};
1493 (@unless [$i:tt] [walks_for_references $($r:ident)*] walks_for_references) => {};
1494 (@unless [$i:tt] [can_refer_to $($r:ident)*] can_refer_to) => {};
1495 (@unless [$i:tt] [reference_properties $($r:ident)*] reference_properties) => {};
1496 (@unless [$i:tt] [resolve_needs_label $($r:ident)*] resolve_needs_label) => {};
1497 (@unless [$i:tt] [reverse_label $($r:ident)*] reverse_label) => {};
1498 (@unless [$i:tt] [tree_identities $($r:ident)*] tree_identities) => {};
1499 (@unless [$i:tt] [property_names $($r:ident)*] property_names) => {};
1500 (@unless [$i:tt] [resolves_references $($r:ident)*] resolves_references) => {};
1501 (@unless [$i:tt] [index_lookup $($r:ident)*] index_lookup) => {};
1502 (@unless [$i:tt] [index_referrers $($r:ident)*] index_referrers) => {};
1503 (@unless [$i:tt] [non_tree_nodes $($r:ident)*] non_tree_nodes) => {};
1504 (@unless [$i:tt] [child_host $($r:ident)*] child_host) => {};
1505 (@unless [$i:tt] [children_named $($r:ident)*] children_named) => {};
1506 (@unless [$i:tt] [answers_to $($r:ident)*] answers_to) => {};
1507 (@unless [$i:tt] [has_trait $($r:ident)*] has_trait) => {};
1508 (@unless [$i:tt] [default_value $($r:ident)*] default_value) => {};
1509 (@unless [$i:tt] [metadata $($r:ident)*] metadata) => {};
1510 (@unless [$i:tt] [aliased_metadata $($r:ident)*] aliased_metadata) => {};
1511 (@unless [$i:tt] [links $($r:ident)*] links) => {};
1512 (@unless [$i:tt] [backlinks $($r:ident)*] backlinks) => {};
1513 (@unless [$i:tt] [resolve $($r:ident)*] resolve) => {};
1514 (@unless [$i:tt] [external_ref $($r:ident)*] external_ref) => {};
1515 (@unless [$i:tt] [resolve_fragment $($r:ident)*] resolve_fragment) => {};
1516 (@unless [$i:tt] [ref_property $($r:ident)*] ref_property) => {};
1517 (@unless [$i:tt] [ref_label $($r:ident)*] ref_label) => {};
1518 (@unless [$i:tt] [link_property $($r:ident)*] link_property) => {};
1519 (@unless [$i:tt] [document_by_ref $($r:ident)*] document_by_ref) => {};
1520 (@unless [$i:tt] [reverse_resolve $($r:ident)*] reverse_resolve) => {};
1521 (@unless [$i:tt] [descendants_named $($r:ident)*] descendants_named) => {};
1522 (@unless [$i:tt] [descendants_where $($r:ident)*] descendants_where) => {};
1523 (@unless [$i:tt] [select $($r:ident)*] select) => {};
1524 (@unless [$i:tt] [select_plan $($r:ident)*] select_plan) => {};
1525 (@unless [$i:tt] [estimate $($r:ident)*] estimate) => {};
1526 (@unless [$i:tt] [count $($r:ident)*] count) => {};
1527 (@unless [$i:tt] [prefetch_properties $($r:ident)*] prefetch_properties) => {};
1528 (@unless [$i:tt] [prefetch_links $($r:ident)*] prefetch_links) => {};
1529 (@unless [$i:tt] [quantifier_bound $($r:ident)*] quantifier_bound) => {};
1530 (@unless [$i:tt] [provenance $($r:ident)*] provenance) => {};
1531 (@unless [$i:tt] [has_node $($r:ident)*] has_node) => {};
1532 (@unless [$i:tt] [provenance_list $($r:ident)*] provenance_list) => {};
1533 (@unless [$i:tt] [unit_scale $($r:ident)*] unit_scale) => {};
1534 // Another method heads the list: look further down.
1535 (@unless [$i:tt] [$x:ident $($r:ident)*] $m:ident) => {
1536 $crate::forward_ast_adapter!(@unless [$i] [$($r)*] $m);
1537 };
1538 // Not excepted: forward.
1539 (@unless [$i:tt] [] $m:ident) => {
1540 $crate::forward_ast_adapter!(@fwd [$i] $m);
1541 };
1542
1543 // The wrapped adapter, as an expression on `self`.
1544 (@target $s:ident *) => { (**$s) };
1545 (@target $s:ident $f:tt) => { $s.$f };
1546
1547 (@fwd [$i:tt] root) => {
1548 fn root(&self) -> $crate::NodeId {
1549 $crate::forward_ast_adapter!(@target self $i).root()
1550 }
1551 };
1552 (@fwd [$i:tt] children) => {
1553 fn children(&self, node: $crate::NodeId) -> Vec<$crate::NodeId> {
1554 $crate::forward_ast_adapter!(@target self $i).children(node)
1555 }
1556 };
1557 (@fwd [$i:tt] name) => {
1558 fn name(&self, node: $crate::NodeId) -> Option<String> {
1559 $crate::forward_ast_adapter!(@target self $i).name(node)
1560 }
1561 };
1562 (@fwd [$i:tt] parent) => {
1563 fn parent(&self, node: $crate::NodeId) -> Option<$crate::NodeId> {
1564 $crate::forward_ast_adapter!(@target self $i).parent(node)
1565 }
1566 };
1567 (@fwd [$i:tt] traits) => {
1568 fn traits(&self, node: $crate::NodeId) -> Vec<String> {
1569 $crate::forward_ast_adapter!(@target self $i).traits(node)
1570 }
1571 };
1572 (@fwd [$i:tt] property) => {
1573 fn property(&self, node: $crate::NodeId, name: &str) -> Option<$crate::Value> {
1574 $crate::forward_ast_adapter!(@target self $i).property(node, name)
1575 }
1576 };
1577 (@fwd [$i:tt] properties) => {
1578 fn properties(&self, node: $crate::NodeId) -> Vec<(String, $crate::Value)> {
1579 $crate::forward_ast_adapter!(@target self $i).properties(node)
1580 }
1581 };
1582 (@fwd [$i:tt] walks_for_references) => {
1583 fn walks_for_references(&self) -> bool {
1584 $crate::forward_ast_adapter!(@target self $i).walks_for_references()
1585 }
1586 };
1587 (@fwd [$i:tt] can_refer_to) => {
1588 fn can_refer_to(&self, source: $crate::NodeId, target: $crate::NodeId) -> Option<bool> {
1589 $crate::forward_ast_adapter!(@target self $i).can_refer_to(source, target)
1590 }
1591 };
1592 (@fwd [$i:tt] reference_properties) => {
1593 fn reference_properties(
1594 &self,
1595 source: $crate::NodeId,
1596 target: $crate::NodeId,
1597 ) -> Vec<String> {
1598 $crate::forward_ast_adapter!(@target self $i).reference_properties(source, target)
1599 }
1600 };
1601 (@fwd [$i:tt] resolve_needs_label) => {
1602 fn resolve_needs_label(&self, node: $crate::NodeId, property: &str) -> bool {
1603 $crate::forward_ast_adapter!(@target self $i).resolve_needs_label(node, property)
1604 }
1605 };
1606 (@fwd [$i:tt] reverse_label) => {
1607 fn reverse_label(&self, target: $crate::NodeId) -> Option<String> {
1608 $crate::forward_ast_adapter!(@target self $i).reverse_label(target)
1609 }
1610 };
1611 (@fwd [$i:tt] tree_identities) => {
1612 fn tree_identities(&self) -> Vec<(String, $crate::NodeId)> {
1613 $crate::forward_ast_adapter!(@target self $i).tree_identities()
1614 }
1615 };
1616 (@fwd [$i:tt] property_names) => {
1617 fn property_names(&self, node: $crate::NodeId) -> Vec<String> {
1618 $crate::forward_ast_adapter!(@target self $i).property_names(node)
1619 }
1620 };
1621 (@fwd [$i:tt] resolves_references) => {
1622 fn resolves_references(&self) -> bool {
1623 $crate::forward_ast_adapter!(@target self $i).resolves_references()
1624 }
1625 };
1626 (@fwd [$i:tt] index_lookup) => {
1627 fn index_lookup(
1628 &self,
1629 scope: &str,
1630 property: &str,
1631 value: &$crate::Value,
1632 ) -> Option<Vec<$crate::NodeId>> {
1633 $crate::forward_ast_adapter!(@target self $i).index_lookup(scope, property, value)
1634 }
1635 };
1636 (@fwd [$i:tt] index_referrers) => {
1637 fn index_referrers(
1638 &self,
1639 scope: &str,
1640 property: &str,
1641 label: Option<&str>,
1642 target: $crate::NodeId,
1643 ) -> Option<Vec<$crate::NodeId>> {
1644 $crate::forward_ast_adapter!(@target self $i)
1645 .index_referrers(scope, property, label, target)
1646 }
1647 };
1648 (@fwd [$i:tt] non_tree_nodes) => {
1649 fn non_tree_nodes(&self) -> Vec<$crate::NodeId> {
1650 $crate::forward_ast_adapter!(@target self $i).non_tree_nodes()
1651 }
1652 };
1653 (@fwd [$i:tt] child_host) => {
1654 fn child_host(&self, node: $crate::NodeId) -> $crate::NodeId {
1655 $crate::forward_ast_adapter!(@target self $i).child_host(node)
1656 }
1657 };
1658 (@fwd [$i:tt] children_named) => {
1659 fn children_named(&self, node: $crate::NodeId, name: &str) -> Vec<$crate::NodeId> {
1660 $crate::forward_ast_adapter!(@target self $i).children_named(node, name)
1661 }
1662 };
1663 (@fwd [$i:tt] answers_to) => {
1664 fn answers_to(&self, node: $crate::NodeId, name: &str) -> bool {
1665 $crate::forward_ast_adapter!(@target self $i).answers_to(node, name)
1666 }
1667 };
1668 (@fwd [$i:tt] has_trait) => {
1669 fn has_trait(&self, node: $crate::NodeId, name: &str) -> bool {
1670 $crate::forward_ast_adapter!(@target self $i).has_trait(node, name)
1671 }
1672 };
1673 (@fwd [$i:tt] default_value) => {
1674 fn default_value(&self, node: $crate::NodeId) -> Option<$crate::Value> {
1675 $crate::forward_ast_adapter!(@target self $i).default_value(node)
1676 }
1677 };
1678 (@fwd [$i:tt] metadata) => {
1679 fn metadata(&self, node: $crate::NodeId, key: &str) -> Option<$crate::Value> {
1680 $crate::forward_ast_adapter!(@target self $i).metadata(node, key)
1681 }
1682 };
1683 (@fwd [$i:tt] aliased_metadata) => {
1684 fn aliased_metadata(&self, node: $crate::NodeId) -> &'static [&'static str] {
1685 $crate::forward_ast_adapter!(@target self $i).aliased_metadata(node)
1686 }
1687 };
1688 (@fwd [$i:tt] links) => {
1689 fn links(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
1690 $crate::forward_ast_adapter!(@target self $i).links(node)
1691 }
1692 };
1693 (@fwd [$i:tt] backlinks) => {
1694 fn backlinks(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
1695 $crate::forward_ast_adapter!(@target self $i).backlinks(node)
1696 }
1697 };
1698 (@fwd [$i:tt] resolve) => {
1699 fn resolve(
1700 &self,
1701 node: $crate::NodeId,
1702 property: &str,
1703 hint: Option<&str>,
1704 ) -> Option<$crate::NodeId> {
1705 $crate::forward_ast_adapter!(@target self $i).resolve(node, property, hint)
1706 }
1707 };
1708 (@fwd [$i:tt] external_ref) => {
1709 fn external_ref(
1710 &self,
1711 node: $crate::NodeId,
1712 property: &str,
1713 hint: Option<&str>,
1714 ) -> Option<$crate::ExternalRef> {
1715 $crate::forward_ast_adapter!(@target self $i).external_ref(node, property, hint)
1716 }
1717 };
1718 (@fwd [$i:tt] resolve_fragment) => {
1719 fn resolve_fragment(&self, node: $crate::NodeId, fragment: &str) -> Option<$crate::NodeId> {
1720 $crate::forward_ast_adapter!(@target self $i).resolve_fragment(node, fragment)
1721 }
1722 };
1723 (@fwd [$i:tt] ref_property) => {
1724 fn ref_property(&self, node: $crate::NodeId) -> Option<String> {
1725 $crate::forward_ast_adapter!(@target self $i).ref_property(node)
1726 }
1727 };
1728 (@fwd [$i:tt] ref_label) => {
1729 fn ref_label(&self, node: $crate::NodeId, property: &str) -> Option<String> {
1730 $crate::forward_ast_adapter!(@target self $i).ref_label(node, property)
1731 }
1732 };
1733 (@fwd [$i:tt] link_property) => {
1734 fn link_property(
1735 &self,
1736 source: $crate::NodeId,
1737 label: &str,
1738 target: $crate::NodeId,
1739 name: &str,
1740 ) -> Option<$crate::Value> {
1741 $crate::forward_ast_adapter!(@target self $i).link_property(source, label, target, name)
1742 }
1743 };
1744 (@fwd [$i:tt] document_by_ref) => {
1745 fn document_by_ref(&self, id: &str) -> Option<$crate::NodeId> {
1746 $crate::forward_ast_adapter!(@target self $i).document_by_ref(id)
1747 }
1748 };
1749 (@fwd [$i:tt] reverse_resolve) => {
1750 fn reverse_resolve(
1751 &self,
1752 node: $crate::NodeId,
1753 property: Option<&str>,
1754 hint: Option<&str>,
1755 ) -> Option<Vec<$crate::NodeId>> {
1756 $crate::forward_ast_adapter!(@target self $i).reverse_resolve(node, property, hint)
1757 }
1758 };
1759 (@fwd [$i:tt] descendants_named) => {
1760 fn descendants_named(
1761 &self,
1762 node: $crate::NodeId,
1763 name: &str,
1764 ) -> Option<Vec<($crate::NodeId, usize)>> {
1765 $crate::forward_ast_adapter!(@target self $i).descendants_named(node, name)
1766 }
1767 };
1768 (@fwd [$i:tt] descendants_where) => {
1769 fn descendants_where(
1770 &self,
1771 node: $crate::NodeId,
1772 name: &str,
1773 property: &str,
1774 value: &$crate::Value,
1775 ) -> Option<Vec<($crate::NodeId, usize)>> {
1776 $crate::forward_ast_adapter!(@target self $i)
1777 .descendants_where(node, name, property, value)
1778 }
1779 };
1780 (@fwd [$i:tt] select) => {
1781 fn select(
1782 &self,
1783 from: $crate::NodeId,
1784 plan: &$crate::adapter::StepPlan<'_>,
1785 ) -> Option<$crate::adapter::Selection> {
1786 $crate::forward_ast_adapter!(@target self $i).select(from, plan)
1787 }
1788 };
1789 (@fwd [$i:tt] select_plan) => {
1790 fn select_plan(
1791 &self,
1792 scope: &[&str],
1793 plan: &$crate::adapter::StepPlan<'_>,
1794 ) -> Option<Vec<bool>> {
1795 $crate::forward_ast_adapter!(@target self $i).select_plan(scope, plan)
1796 }
1797 };
1798 (@fwd [$i:tt] estimate) => {
1799 fn estimate(
1800 &self,
1801 scope: &[&str],
1802 plan: &$crate::adapter::StepPlan<'_>,
1803 ) -> Option<$crate::adapter::Estimate> {
1804 $crate::forward_ast_adapter!(@target self $i).estimate(scope, plan)
1805 }
1806 };
1807 (@fwd [$i:tt] count) => {
1808 fn count(&self, from: $crate::NodeId, plan: &$crate::adapter::StepPlan<'_>) -> Option<usize> {
1809 $crate::forward_ast_adapter!(@target self $i).count(from, plan)
1810 }
1811 };
1812 (@fwd [$i:tt] prefetch_properties) => {
1813 fn prefetch_properties(&self, nodes: &[$crate::NodeId], names: &[&str]) {
1814 $crate::forward_ast_adapter!(@target self $i).prefetch_properties(nodes, names)
1815 }
1816 };
1817 (@fwd [$i:tt] prefetch_links) => {
1818 fn prefetch_links(&self, nodes: &[$crate::NodeId], dir: $crate::LinkDir) {
1819 $crate::forward_ast_adapter!(@target self $i).prefetch_links(nodes, dir)
1820 }
1821 };
1822 (@fwd [$i:tt] quantifier_bound) => {
1823 fn quantifier_bound(&self) -> usize {
1824 $crate::forward_ast_adapter!(@target self $i).quantifier_bound()
1825 }
1826 };
1827 (@fwd [$i:tt] provenance) => {
1828 fn provenance(&self, node: $crate::NodeId) -> $crate::Provenance {
1829 $crate::forward_ast_adapter!(@target self $i).provenance(node)
1830 }
1831 };
1832 (@fwd [$i:tt] has_node) => {
1833 fn has_node(&self, node: $crate::NodeId) -> bool {
1834 $crate::forward_ast_adapter!(@target self $i).has_node(node)
1835 }
1836 };
1837 (@fwd [$i:tt] provenance_list) => {
1838 fn provenance_list(&self, node: $crate::NodeId) -> $crate::ProvenanceList {
1839 $crate::forward_ast_adapter!(@target self $i).provenance_list(node)
1840 }
1841 };
1842 (@fwd [$i:tt] unit_scale) => {
1843 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1844 $crate::forward_ast_adapter!(@target self $i).unit_scale(expr)
1845 }
1846 };
1847}
1848
1849impl AstAdapter for Box<dyn AstAdapter> {
1850 crate::forward_ast_adapter!(*);
1851}
1852
1853#[cfg(test)]
1854mod forwarding {
1855 use super::*;
1856
1857 /// The names of the `fn` items at `indent` within `block`.
1858 fn fn_names(block: &str, indent: &str) -> Vec<String> {
1859 block
1860 .lines()
1861 .filter_map(|l| l.strip_prefix(indent)?.strip_prefix("fn "))
1862 .map(|rest| {
1863 rest.split(|c: char| !(c.is_alphanumeric() || c == '_'))
1864 .next()
1865 .unwrap()
1866 .to_string()
1867 })
1868 .collect()
1869 }
1870
1871 /// The macro's method table is the trait's method list — a
1872 /// method added to `AstAdapter` without a forwarding arm would
1873 /// otherwise answer with the trait default inside every wrapper.
1874 #[test]
1875 fn the_macro_lists_every_trait_method() {
1876 let src = include_str!("adapter.rs");
1877 let trait_src = &src[src.find("pub trait AstAdapter {").unwrap()..];
1878 let trait_src = &trait_src[..trait_src.find("\n}\n").unwrap()];
1879 let in_trait = fn_names(trait_src, " ");
1880
1881 let table = &src[src
1882 .find("coverage test reads this list from the source.")
1883 .unwrap()..];
1884 let table = &table[table.find('\n').unwrap()..table.find(");").unwrap()];
1885 let listed: Vec<String> = table.split_whitespace().map(str::to_string).collect();
1886
1887 assert_eq!(
1888 listed, in_trait,
1889 "the forwarding table drifted from the trait"
1890 );
1891
1892 let macro_src = &src[src.find("macro_rules! forward_ast_adapter").unwrap()..];
1893 let macro_src = ¯o_src[..macro_src.find("\n}\n").unwrap()];
1894 for m in &in_trait {
1895 assert!(
1896 macro_src.contains(&format!(
1897 "(@unless [$i:tt] [{m} $($r:ident)*] {m}) => {{}};"
1898 )),
1899 "no except arm for {m}"
1900 );
1901 assert!(
1902 macro_src.contains(&format!("(@fwd [$i:tt] {m}) => {{")),
1903 "no forwarding arm for {m}"
1904 );
1905 }
1906 }
1907
1908 /// Answers every method distinctively, so a wrapper that
1909 /// forwards one method to another (or to the default) is caught.
1910 struct Probe;
1911
1912 fn n(k: u64) -> NodeId {
1913 NodeId(k)
1914 }
1915
1916 fn int(v: &Value) -> u64 {
1917 match v {
1918 Value::Int(i) => *i as u64,
1919 _ => 0,
1920 }
1921 }
1922
1923 impl AstAdapter for Probe {
1924 fn root(&self) -> NodeId {
1925 n(1)
1926 }
1927 fn children(&self, node: NodeId) -> Vec<NodeId> {
1928 vec![n(node.0 * 10)]
1929 }
1930 fn name(&self, node: NodeId) -> Option<String> {
1931 Some(format!("name{}", node.0))
1932 }
1933 fn parent(&self, node: NodeId) -> Option<NodeId> {
1934 Some(n(node.0 + 100))
1935 }
1936 fn traits(&self, node: NodeId) -> Vec<String> {
1937 vec![format!("trait{}", node.0)]
1938 }
1939 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
1940 Some(Value::Str(format!("prop:{}:{name}", node.0)))
1941 }
1942 fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
1943 vec![(format!("p{}", node.0), Value::Int(node.0 as i64))]
1944 }
1945 fn walks_for_references(&self) -> bool {
1946 true
1947 }
1948 fn can_refer_to(&self, source: NodeId, target: NodeId) -> Option<bool> {
1949 Some(source.0 < target.0)
1950 }
1951 fn reference_properties(&self, source: NodeId, target: NodeId) -> Vec<String> {
1952 vec![format!("rp{}-{}", source.0, target.0)]
1953 }
1954 fn resolve_needs_label(&self, node: NodeId, property: &str) -> bool {
1955 node.0 == 7 && property == "x"
1956 }
1957 fn reverse_label(&self, target: NodeId) -> Option<String> {
1958 Some(format!("rl{}", target.0))
1959 }
1960 fn tree_identities(&self) -> Vec<(String, NodeId)> {
1961 vec![("tree".into(), n(42))]
1962 }
1963 fn property_names(&self, node: NodeId) -> Vec<String> {
1964 vec![format!("pn{}", node.0)]
1965 }
1966 fn resolves_references(&self) -> bool {
1967 false
1968 }
1969 fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
1970 Some(vec![
1971 n((scope.len() + property.len()) as u64),
1972 n(int(value)),
1973 ])
1974 }
1975 fn index_referrers(
1976 &self,
1977 scope: &str,
1978 property: &str,
1979 label: Option<&str>,
1980 target: NodeId,
1981 ) -> Option<Vec<NodeId>> {
1982 Some(vec![n(
1983 (scope.len() + property.len() + label.map_or(0, str::len)) as u64 + target.0,
1984 )])
1985 }
1986 fn non_tree_nodes(&self) -> Vec<NodeId> {
1987 vec![n(99)]
1988 }
1989 fn child_host(&self, node: NodeId) -> NodeId {
1990 n(node.0 + 1000)
1991 }
1992 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
1993 vec![n(node.0 + name.len() as u64)]
1994 }
1995 fn answers_to(&self, node: NodeId, name: &str) -> bool {
1996 node.0 == 3 && name == "alias"
1997 }
1998 fn has_trait(&self, node: NodeId, name: &str) -> bool {
1999 node.0 == 4 && name == "talias"
2000 }
2001 fn default_value(&self, node: NodeId) -> Option<Value> {
2002 Some(Value::Int(-(node.0 as i64)))
2003 }
2004 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
2005 Some(Value::Str(format!("meta:{}:{key}", node.0)))
2006 }
2007 fn aliased_metadata(&self, node: NodeId) -> &'static [&'static str] {
2008 if node.0 == 5 { &["size"] } else { &["other"] }
2009 }
2010 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
2011 vec![("link".into(), n(node.0 + 2))]
2012 }
2013 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
2014 vec![("back".into(), n(node.0 + 3))]
2015 }
2016 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
2017 Some(n(node.0
2018 + property.len() as u64
2019 + hint.map_or(0, |h| h.len() as u64)))
2020 }
2021 fn external_ref(
2022 &self,
2023 node: NodeId,
2024 property: &str,
2025 hint: Option<&str>,
2026 ) -> Option<ExternalRef> {
2027 Some(ExternalRef {
2028 document: format!("doc{}:{property}", node.0),
2029 within: hint.map(str::to_string),
2030 })
2031 }
2032 fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
2033 Some(n(node.0 + fragment.len() as u64 + 7))
2034 }
2035 fn ref_property(&self, node: NodeId) -> Option<String> {
2036 Some(format!("href{}", node.0))
2037 }
2038 fn ref_label(&self, node: NodeId, property: &str) -> Option<String> {
2039 Some(format!("rel{}:{property}", node.0))
2040 }
2041 fn link_property(
2042 &self,
2043 source: NodeId,
2044 label: &str,
2045 target: NodeId,
2046 name: &str,
2047 ) -> Option<Value> {
2048 Some(Value::Str(format!(
2049 "{}-{label}-{}-{name}",
2050 source.0, target.0
2051 )))
2052 }
2053 fn document_by_ref(&self, id: &str) -> Option<NodeId> {
2054 Some(n(id.len() as u64 + 50))
2055 }
2056 fn reverse_resolve(
2057 &self,
2058 node: NodeId,
2059 property: Option<&str>,
2060 hint: Option<&str>,
2061 ) -> Option<Vec<NodeId>> {
2062 Some(vec![n(node.0
2063 + property.map_or(0, |p| p.len() as u64) * 2
2064 + hint.map_or(0, |h| h.len() as u64) * 3)])
2065 }
2066 fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
2067 Some(vec![(n(node.0 + 9), name.len())])
2068 }
2069 fn descendants_where(
2070 &self,
2071 node: NodeId,
2072 name: &str,
2073 property: &str,
2074 value: &Value,
2075 ) -> Option<Vec<(NodeId, usize)>> {
2076 Some(vec![(n(node.0 + int(value)), name.len() + property.len())])
2077 }
2078 fn select(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<Selection> {
2079 Some(Selection {
2080 nodes: vec![(n(from.0 + plan.conjuncts.len() as u64 + 1), 3)],
2081 applied: vec![true; plan.conjuncts.len()],
2082 })
2083 }
2084 fn select_plan(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Vec<bool>> {
2085 Some(vec![scope.len() == 1; plan.conjuncts.len()])
2086 }
2087 fn estimate(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Estimate> {
2088 Some(Estimate {
2089 nodes: (scope.len() as u64, plan.conjuncts.len() as u64 + 9),
2090 bytes: None,
2091 requests: Some(1),
2092 latency: Latency::Local,
2093 confidence: Confidence::Bound,
2094 })
2095 }
2096 fn count(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<usize> {
2097 Some(from.0 as usize + plan.conjuncts.len() + 40)
2098 }
2099 fn prefetch_properties(&self, nodes: &[NodeId], names: &[&str]) {
2100 PREFETCHED.with(|p| p.set(nodes.len() * 100 + names.len()));
2101 }
2102 fn prefetch_links(&self, nodes: &[NodeId], dir: LinkDir) {
2103 PREFETCHED.with(|p| p.set(nodes.len() * 10 + matches!(dir, LinkDir::In) as usize));
2104 }
2105 fn quantifier_bound(&self) -> usize {
2106 77
2107 }
2108 fn provenance(&self, node: NodeId) -> Provenance {
2109 Provenance {
2110 source: Some(format!("src{}", node.0)),
2111 ..Default::default()
2112 }
2113 }
2114 fn has_node(&self, node: NodeId) -> bool {
2115 node.0 == 8
2116 }
2117 fn provenance_list(&self, node: NodeId) -> ProvenanceList {
2118 ProvenanceList {
2119 entries: vec![self.provenance(node)],
2120 elided: node.0 as u32,
2121 }
2122 }
2123 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
2124 Some((expr.len() as f64, format!("scale:{expr}")))
2125 }
2126 }
2127
2128 thread_local! {
2129 static PREFETCHED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2130 }
2131
2132 /// Every method of `w` answers as `Probe` does, except those in
2133 /// `except`, which the wrapper defines itself.
2134 fn assert_forwards(w: &dyn AstAdapter, except: &[&str]) {
2135 let p = Probe;
2136 let v = Value::Int(6);
2137 macro_rules! same {
2138 ($m:ident $(, $a:expr)*) => {
2139 if !except.contains(&stringify!($m)) {
2140 assert_eq!(w.$m($($a),*), p.$m($($a),*), stringify!($m));
2141 }
2142 };
2143 }
2144 same!(root);
2145 same!(children, n(2));
2146 same!(name, n(2));
2147 same!(parent, n(2));
2148 same!(traits, n(2));
2149 same!(property, n(2), "k");
2150 same!(properties, n(2));
2151 same!(walks_for_references);
2152 same!(can_refer_to, n(2), n(3));
2153 same!(reference_properties, n(2), n(3));
2154 same!(resolve_needs_label, n(7), "x");
2155 same!(reverse_label, n(2));
2156 same!(tree_identities);
2157 same!(property_names, n(2));
2158 same!(resolves_references);
2159 same!(index_lookup, "s", "pp", &v);
2160 same!(index_referrers, "s", "pp", Some("lbl"), n(2));
2161 same!(non_tree_nodes);
2162 same!(child_host, n(2));
2163 same!(children_named, n(2), "abc");
2164 same!(answers_to, n(3), "alias");
2165 same!(has_trait, n(4), "talias");
2166 same!(default_value, n(2));
2167 same!(metadata, n(2), "key");
2168 same!(aliased_metadata, n(5));
2169 same!(links, n(2));
2170 same!(backlinks, n(2));
2171 same!(resolve, n(2), "pp", Some("h"));
2172 same!(external_ref, n(2), "pp", Some("frag"));
2173 same!(resolve_fragment, n(2), "frag");
2174 same!(ref_property, n(2));
2175 same!(ref_label, n(2), "pp");
2176 same!(link_property, n(2), "lbl", n(3), "nm");
2177 same!(document_by_ref, "id");
2178 same!(reverse_resolve, n(2), Some("pp"), Some("h"));
2179 same!(descendants_named, n(2), "abc");
2180 same!(descendants_where, n(2), "abc", "pp", &v);
2181 let plan = StepPlan {
2182 traits: Vec::new(),
2183 cap: Default::default(),
2184 axis: PlanAxis::Descendant,
2185 name: Some("abc"),
2186 conjuncts: vec![Conjunct::Equals("pp", &v)],
2187 limit: None,
2188 order: None,
2189 };
2190 if !except.contains(&"select") {
2191 assert_eq!(
2192 w.select(n(2), &plan).map(|s| s.nodes),
2193 p.select(n(2), &plan).map(|s| s.nodes),
2194 "select"
2195 );
2196 }
2197 same!(count, n(2), &plan);
2198 same!(select_plan, &["t"], &plan);
2199 same!(estimate, &["t"], &plan);
2200 if !except.contains(&"prefetch_properties") {
2201 PREFETCHED.with(|p| p.set(0));
2202 w.prefetch_properties(&[n(1), n(2), n(3)], &["a", "b"]);
2203 assert_eq!(PREFETCHED.with(|p| p.get()), 302, "prefetch_properties");
2204 }
2205 if !except.contains(&"prefetch_links") {
2206 PREFETCHED.with(|c| c.set(0));
2207 w.prefetch_links(&[n(1), n(2)], LinkDir::In);
2208 assert_eq!(PREFETCHED.with(|c| c.get()), 21, "prefetch_links");
2209 }
2210 same!(quantifier_bound);
2211 same!(provenance, n(2));
2212 same!(has_node, n(8));
2213 same!(has_node, n(9));
2214 same!(provenance_list, n(2));
2215 same!(unit_scale, "km");
2216 }
2217
2218 #[test]
2219 fn every_wrapper_forwards_every_method() {
2220 let p = Probe;
2221 let b: Box<dyn AstAdapter> = Box::new(Probe);
2222 assert_forwards(&b, &[]);
2223
2224 let s = crate::source::Sourced {
2225 inner: Probe,
2226 encoding: "utf-8".into(),
2227 replaced: 0,
2228 };
2229 assert_forwards(&s, &["metadata"]);
2230 assert_eq!(s.metadata(n(2), "key"), p.metadata(n(2), "key"));
2231 assert_eq!(
2232 s.metadata(n(1), "encoding"),
2233 Some(Value::Str("utf-8".into()))
2234 );
2235 }
2236}