Skip to main content

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 = &macro_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}