Skip to main content

quarb/graph/
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                // the comparison the predicate makes, so a listing
387                // prices what the engine would answer: a bare literal
388                // beside a size reads in the size's written unit
389                use crate::syntax::ast::CmpOp;
390                let op = match self {
391                    Cmp::Eq => CmpOp::Eq,
392                    Cmp::Ne => CmpOp::Ne,
393                    Cmp::Lt => CmpOp::Lt,
394                    Cmp::Le => CmpOp::Le,
395                    Cmp::Gt => CmpOp::Gt,
396                    Cmp::Ge => CmpOp::Ge,
397                    Cmp::Contains => unreachable!(),
398                };
399                let _ = (Equal, Less, Greater);
400                crate::stdlib::compare::compare(
401                    value,
402                    op,
403                    literal,
404                    &crate::value::quantity::scale_expr,
405                )
406            }
407        }
408    }
409}
410
411/// What an adapter answers to a [`StepPlan`]: the nodes (each with
412/// its depth from the step's base; children are depth 1) in
413/// document order, and which conjuncts the adapter already applied
414/// (the engine runs the rest).
415#[derive(Debug, Clone, Default)]
416pub struct Selection {
417    pub nodes: Vec<(NodeId, usize)>,
418    pub applied: Vec<bool>,
419}
420
421/// What reaching one step's nodes would cost, as an adapter can
422/// say before running it ([`AstAdapter::estimate`]): intervals, a
423/// request count, a latency class and how sure the adapter is. A
424/// host composes these along a query ([`crate::graph::plan::Plan`]) and
425/// refuses before running when a budget is exceeded.
426#[derive(Debug, Clone, PartialEq)]
427pub struct Estimate {
428    /// The nodes the step yields, from the least to the most.
429    pub nodes: (u64, u64),
430    /// The bytes read to yield them, when the adapter can say (a
431    /// warehouse's dry run, a file's size).
432    pub bytes: Option<(u64, u64)>,
433    /// Round trips to a remote, when any.
434    pub requests: Option<u32>,
435    pub latency: Latency,
436    pub confidence: Confidence,
437}
438
439/// Where a step's cost is paid.
440#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
441pub enum Latency {
442    /// The nodes are in memory.
443    Memory,
444    /// A local file or store.
445    Local,
446    /// A network round trip.
447    Remote,
448}
449
450/// How much an [`Estimate`] is worth.
451#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
452pub enum Confidence {
453    /// A guess from statistics or defaults.
454    Guess,
455    /// An upper bound (a clustered table's dry run, a partition's
456    /// size): the actual may come in lower.
457    Bound,
458    /// Exact: the catalog or the store knows.
459    Exact,
460}
461
462/// What a source knows about itself before it is read: how many
463/// entries it holds (files under a directory, members of an
464/// archive, rows of a table, documents on a shelf) and how many
465/// bytes they come to — from a listing, a central directory, a
466/// `stat`, a store's catalog, never from the contents. A latent
467/// mount answers the plan from it; an [`Unopened`] source is only
468/// it; an opened arena answers from its nodes instead.
469#[derive(Debug, Clone, PartialEq)]
470pub struct Catalog {
471    /// The entries, from the least to the most. A listing that
472    /// stopped early reports `(counted, u64::MAX)`: unbounded.
473    pub entries: (u64, u64),
474    /// Their bytes, when the listing says.
475    pub bytes: Option<(u64, u64)>,
476    pub latency: Latency,
477    pub confidence: Confidence,
478    /// Which of the asked conjuncts the listing applied before
479    /// counting (empty: none; a catalog that applies filters sets
480    /// one flag per conjunct asked).
481    pub applied: Vec<bool>,
482    /// Whether the asked trait filters were all applied.
483    pub traits_applied: bool,
484}
485
486impl Catalog {
487    /// A catalog that applied nothing.
488    pub fn plain(
489        entries: (u64, u64),
490        bytes: Option<(u64, u64)>,
491        latency: Latency,
492        confidence: Confidence,
493    ) -> Catalog {
494        Catalog {
495            entries,
496            bytes,
497            latency,
498            confidence,
499            applied: Vec::new(),
500            traits_applied: false,
501        }
502    }
503}
504
505/// What a catalog is asked for: the literal path under the source,
506/// where counting may stop, and the step's filters a listing may
507/// apply before counting (it says which in [`Catalog::applied`]).
508pub struct Ask<'a> {
509    pub scope: &'a [&'a str],
510    pub cap: Cap,
511    pub conjuncts: &'a [Conjunct<'a>],
512    pub traits: &'a [&'a str],
513}
514
515impl<'a> Ask<'a> {
516    /// A bare ask: the path and the cap, no filters.
517    pub fn at(scope: &'a [&'a str], cap: Cap) -> Ask<'a> {
518        Ask {
519            scope,
520            cap,
521            conjuncts: &[],
522            traits: &[],
523        }
524    }
525}
526
527/// A source known only by its catalog: a root with nothing under
528/// it, answering the plan's estimate for a step from the root out
529/// of the catalog. A front end plans and budgets a query against
530/// it before paying to open the source — a directory of a hundred
531/// thousand files, a tarball of books — and opens only within
532/// budget.
533pub struct Unopened(pub CatalogLookup);
534
535/// A catalog by literal path from a source's root: `&[]` is the
536/// whole source, `&["shelf", "emma.txt"]` one entry under it (a
537/// folder's own listing, one member's header), `None` where the
538/// path names nothing the catalog can see. The cap says where
539/// counting may stop.
540pub type CatalogLookup = Box<dyn Fn(&Ask<'_>) -> Option<Catalog>>;
541
542impl Unopened {
543    /// A source with one catalog, for its root alone.
544    pub fn fixed(c: Catalog) -> Unopened {
545        Unopened(Box::new(move |ask| ask.scope.is_empty().then(|| c.clone())))
546    }
547}
548
549/// The bare root (spec: the expression head): the source a
550/// calculator query runs against — one node, no name, no children,
551/// no properties. `qua '= expr'` without an input and `qc` run on
552/// it, so the two agree by construction.
553pub struct Bare;
554
555impl AstAdapter for Bare {
556    fn root(&self) -> NodeId {
557        NodeId(0)
558    }
559    fn children(&self, _: NodeId) -> Vec<NodeId> {
560        Vec::new()
561    }
562    fn name(&self, _: NodeId) -> Option<String> {
563        None
564    }
565}
566
567impl AstAdapter for Unopened {
568    fn root(&self) -> NodeId {
569        NodeId(0)
570    }
571    fn children(&self, _: NodeId) -> Vec<NodeId> {
572        Vec::new()
573    }
574    fn name(&self, _: NodeId) -> Option<String> {
575        None
576    }
577    fn estimate(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Estimate> {
578        Estimate::at_catalog(&*self.0, scope, plan)
579    }
580}
581
582impl Estimate {
583    /// The estimate a catalog gives a step from the source's root.
584    /// The entries are files or members, the step yields nodes,
585    /// and the two meet only as bounds: a child step reaches at most
586    /// the entries (`/*`, or a named one, a subset), an unfiltered
587    /// descendant step reaches at least every entry (`//*`: each is
588    /// a node, with what the reading finds under it), anything else
589    /// is open. The bytes are the source's: what opening it reads.
590    pub fn from_catalog(c: &Catalog, plan: &StepPlan<'_>) -> Estimate {
591        // Filters the listing applied make the count a bound on the
592        // matches: nothing under an entry answers a listing's
593        // property, so a filtered descendant step is bounded too.
594        let filtered = !plan.conjuncts.is_empty() || !plan.traits.is_empty();
595        let applied = filtered
596            && c.applied.len() == plan.conjuncts.len()
597            && c.applied.iter().all(|&b| b)
598            && (plan.traits.is_empty() || c.traits_applied);
599        let rows = match (plan.axis, plan.name, filtered) {
600            (PlanAxis::Child, _, _) => (0, c.entries.1),
601            (PlanAxis::Descendant, None, false) => (c.entries.0, u64::MAX),
602            (PlanAxis::Descendant, None, true) if applied => (0, c.entries.1),
603            _ => (0, u64::MAX),
604        };
605        Estimate {
606            nodes: rows,
607            bytes: c.bytes,
608            requests: Some(0),
609            latency: c.latency,
610            confidence: c.confidence.min(Confidence::Bound),
611        }
612    }
613
614    /// The estimate a catalogued source gives a step at a literal
615    /// `scope`, narrowing to the path the plan records: a bare hop
616    /// by name is one entry when the catalog finds it (and costs
617    /// nothing; the step that reads under it is priced by that
618    /// entry's own catalog) and may read the whole when it does not
619    /// (a node inside a document); a reading step at a path the
620    /// catalog resolves is priced by it, and one the catalog cannot
621    /// place is a bounded unknown under the source. `None` when the
622    /// source itself has no catalog.
623    pub fn at_catalog(
624        lookup: &dyn Fn(&Ask<'_>) -> Option<Catalog>,
625        scope: &[&str],
626        plan: &StepPlan<'_>,
627    ) -> Option<Estimate> {
628        let whole = |scope: &[&str]| lookup(&Ask::at(scope, plan.cap));
629        let bare =
630            plan.conjuncts.is_empty() && plan.traits.is_empty() && plan.axis == PlanAxis::Child;
631        if bare && let Some(name) = plan.name {
632            let mut sub: Vec<&str> = scope.to_vec();
633            sub.push(name);
634            return Some(match whole(&sub) {
635                Some(c) => Estimate {
636                    nodes: (1, 1),
637                    bytes: None,
638                    requests: Some(0),
639                    latency: c.latency,
640                    confidence: Confidence::Exact,
641                },
642                // Not an entry the catalog sees (a node inside a
643                // document): reaching it from the root may read the
644                // whole; deeper, the whole was charged already.
645                None => {
646                    let whole = whole(&[])?;
647                    if scope.is_empty() {
648                        Estimate::from_catalog(&whole, plan)
649                    } else {
650                        Estimate::beneath_catalog(&whole)
651                    }
652                }
653            });
654        }
655        // A reading step asks with its filters: a listing that can
656        // apply them counts the matches alone.
657        let ask = Ask {
658            scope,
659            cap: plan.cap,
660            conjuncts: &plan.conjuncts,
661            traits: &plan.traits,
662        };
663        Some(match lookup(&ask) {
664            Some(c) => Estimate::from_catalog(&c, plan),
665            None => Estimate::beneath_catalog(&whole(&[])?),
666        })
667    }
668
669    /// The estimate a catalog gives a step deeper than the root:
670    /// nothing is known of the nodes, and the bytes were counted at
671    /// the root (`None` here, so the branch keeps them).
672    pub fn beneath_catalog(c: &Catalog) -> Estimate {
673        Estimate {
674            nodes: (0, u64::MAX),
675            bytes: None,
676            requests: Some(0),
677            latency: c.latency,
678            confidence: Confidence::Bound,
679        }
680    }
681
682    /// `self` followed by `next` along one branch: the nodes are
683    /// the next step's, the bytes and requests add, the latency is
684    /// the slower, the confidence the weaker.
685    pub fn then(&self, next: &Estimate) -> Estimate {
686        Estimate {
687            nodes: next.nodes,
688            bytes: match (self.bytes, next.bytes) {
689                (Some(a), Some(b)) => Some((a.0.saturating_add(b.0), a.1.saturating_add(b.1))),
690                (a, None) => a,
691                (None, b) => b,
692            },
693            requests: match (self.requests, next.requests) {
694                (Some(a), Some(b)) => Some(a + b),
695                (a, None) => a,
696                (None, b) => b,
697            },
698            latency: self.latency.max(next.latency),
699            confidence: self.confidence.min(next.confidence),
700        }
701    }
702
703    /// `self` beside `other` (two branches of one query): nodes,
704    /// bytes and requests add.
705    pub fn beside(&self, other: &Estimate) -> Estimate {
706        Estimate {
707            nodes: (
708                self.nodes.0.saturating_add(other.nodes.0),
709                self.nodes.1.saturating_add(other.nodes.1),
710            ),
711            ..self.then(other)
712        }
713    }
714}
715
716/// Nodes an arena walk may touch while estimating before it stops
717/// counting: past this many the count is a lower bound.
718pub const ARENA_ESTIMATE_BUDGET: u64 = 2_000_000;
719
720/// An estimate for an adapter whose arbor is in memory (a parsed
721/// document, a walked directory, an archive's member table, a
722/// shelf of documents): the plan's base found by the literal
723/// `scope` from the root (one hit per name, else unknown), then the
724/// step's nodes counted — the children answering to the name, or
725/// the descendants — with `bytes_of` summing what the adapter knows
726/// per node. Exact; a bound under a conjunct or past the walk's
727/// budget. Not the trait's default: for a lazy or remote adapter a
728/// walk is a read.
729pub fn arena_estimate<A: AstAdapter + ?Sized>(
730    adapter: &A,
731    scope: &[&str],
732    plan: &StepPlan<'_>,
733    latency: Latency,
734    bytes_of: Option<&dyn Fn(NodeId) -> Option<u64>>,
735) -> Option<Estimate> {
736    let mut at = adapter.root();
737    for name in scope {
738        let mut hits = adapter.children_named(at, name).into_iter();
739        at = hits.next()?;
740        if hits.next().is_some() {
741            return None;
742        }
743    }
744    let matches = |c: NodeId| plan.name.is_none_or(|n| adapter.answers_to(c, n));
745    let mut n: u64 = 0;
746    let mut bytes: u64 = 0;
747    let mut cut = false;
748    // Counts one node; `true` once the cap is passed: stop.
749    let mut count = |c: NodeId| {
750        if matches(c) {
751            n += 1;
752            if let Some(f) = bytes_of {
753                bytes += f(c).unwrap_or(0);
754            }
755        }
756        plan.cap.past(n, bytes)
757    };
758    match plan.axis {
759        PlanAxis::Child => {
760            for c in adapter.children(at) {
761                if count(c) {
762                    cut = true;
763                    break;
764                }
765            }
766        }
767        PlanAxis::Descendant => {
768            let mut visited: u64 = 0;
769            let mut stack = adapter.children(at);
770            while let Some(c) = stack.pop() {
771                visited += 1;
772                if visited > ARENA_ESTIMATE_BUDGET || count(c) {
773                    cut = true;
774                    break;
775                }
776                stack.extend(adapter.children(c));
777            }
778        }
779    }
780    let filtered = !plan.conjuncts.is_empty();
781    Some(Estimate {
782        nodes: if cut {
783            (n, u64::MAX)
784        } else if filtered {
785            (0, n)
786        } else {
787            (n, n)
788        },
789        bytes: bytes_of.map(|_| if cut { (bytes, u64::MAX) } else { (0, bytes) }),
790        requests: Some(0),
791        latency,
792        confidence: if cut || filtered {
793            Confidence::Bound
794        } else {
795            Confidence::Exact
796        },
797    })
798}
799
800/// [`AstAdapter::select`]'s default, through the individual hooks:
801/// a child hop by `children_named`; a descendant search by
802/// `descendants_where` when the plan leads with an equality the
803/// adapter indexes, else `descendants_named`. A wrapper that
804/// translates node ids forwards `select` to its inner adapter and
805/// falls back to this over its own hooks.
806pub fn select_by_hooks<A: AstAdapter + ?Sized>(
807    adapter: &A,
808    from: NodeId,
809    plan: &StepPlan<'_>,
810) -> Option<Selection> {
811    let name = plan.name?;
812    match plan.axis {
813        PlanAxis::Child => Some(Selection {
814            nodes: adapter
815                .children_named(from, name)
816                .into_iter()
817                .map(|n| (n, 1))
818                .collect(),
819            applied: vec![false; plan.conjuncts.len()],
820        }),
821        PlanAxis::Descendant => {
822            if let Some(Conjunct::Equals(prop, value)) = plan.conjuncts.first()
823                && let Some(nodes) = adapter.descendants_where(from, name, prop, value)
824            {
825                let mut applied = vec![false; plan.conjuncts.len()];
826                applied[0] = true;
827                return Some(Selection { nodes, applied });
828            }
829            let nodes = adapter.descendants_named(from, name)?;
830            Some(Selection {
831                nodes,
832                applied: vec![false; plan.conjuncts.len()],
833            })
834        }
835    }
836}
837
838/// The probe behind [`AstAdapter::has_node`]'s default: run a
839/// closure that may panic on a foreign node id, answering `false`
840/// when it does, without the panic reaching stderr. The process's
841/// hook is wrapped once (the first probe installs the wrapper,
842/// which defers to whatever hook was there) and a thread-local
843/// flag silences it only on the probing thread.
844mod probe {
845    use std::cell::Cell;
846    use std::sync::Once;
847
848    thread_local! {
849        static SILENT: Cell<bool> = const { Cell::new(false) };
850    }
851    static INSTALL: Once = Once::new();
852
853    pub(super) fn silently<R: Default>(f: impl FnOnce() -> R) -> R {
854        INSTALL.call_once(|| {
855            let previous = std::panic::take_hook();
856            std::panic::set_hook(Box::new(move |info| {
857                if !SILENT.with(|s| s.get()) {
858                    previous(info);
859                }
860            }));
861        });
862        SILENT.with(|s| s.set(true));
863        let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_default();
864        SILENT.with(|s| s.set(false));
865        out
866    }
867}
868
869/// The kind word of a value held by a node (spec: Traits): the one
870/// structural word an adapter leads its traits with when the node
871/// is a field holding that value — what JSON's adapter mints for
872/// its scalars and containers, so every store's fields answer
873/// `:kind` alike.
874pub fn value_kind(v: &crate::Value) -> &'static str {
875    use crate::Value;
876    match v {
877        Value::Record(_) => "map",
878        Value::List(_) => "array",
879        Value::Str(_) => "string",
880        Value::Int(_) | Value::Float(_) => "number",
881        Value::Bool(_) => "boolean",
882        Value::Null => "null",
883        Value::Instant { .. } => "instant",
884        Value::Quantity { base, .. } if base == "s" => "duration",
885        Value::Period { .. } => "period",
886        Value::Quantity { .. } => "quantity",
887        Value::Table(_) => "table",
888        Value::Foreign(_) => "opaque",
889        Value::Node(_) => "node",
890        Value::Projection { .. } => "projector",
891        Value::Quarq(_) => "quarq",
892    }
893}
894
895/// The interface a data source implements to be queried by Quarb.
896///
897/// An adapter maps its native structure onto the arbor model: a tree
898/// backbone whose edges carry *names*. The engine drives navigation
899/// purely through this trait, so the same query language runs over
900/// any adapter. Three methods are required (`root`, `children`,
901/// `name`); every other one has a default an adapter overrides
902/// only where its substrate has more to say. The methods fall into
903/// groups:
904///
905/// - **The tree**: `root`, `children`, `name`, `parent`,
906///   `child_host` (a transparent link lends its target's
907///   children), `non_tree_nodes` (joined by crosslinks alone),
908///   `has_node`.
909/// - **Naming and traits**: `answers_to` (per-node spelling
910///   aliases, consulted wherever a literal name is matched),
911///   `traits`, `has_trait`.
912/// - **Properties and metadata**: `property`, `properties`,
913///   `property_names`, `default_value`, `metadata`,
914///   `aliased_metadata`.
915/// - **References**, three protocols the engine consults in this
916///   order. *Materialized edges*: `links` / `backlinks` (the `->` /
917///   `<-` hops) with `link_property` and the `prefetch_links`
918///   batching hint. *Property resolution* (`::prop-->`, `-->`,
919///   `<--`): `resolve`, `ref_property`, `ref_label`,
920///   `resolve_needs_label`, `reverse_resolve`, `reverse_label`,
921///   `can_refer_to`, `reference_properties`,
922///   `walks_for_references`, `resolves_references`. *Cross-document*
923///   (a reference naming another document): `external_ref`,
924///   `resolve_fragment`, `document_by_ref`, `tree_identities`; the
925///   engine joins the identity against the tree the reference was
926///   written in and lands through the host's registered targets,
927///   then `document_by_ref`, then the identities, and records what
928///   it could not reach. The engine performs no I/O for any of it.
929/// - **Declared indexes** (a model's `index SCOPE::prop;`):
930///   `index_lookup`, `index_referrers`.
931/// - **Search fast paths**: [`select`](Self::select) — one plan
932///   per step (axis, name matcher, the conjuncts the engine can
933///   lower, a limit, an order) an adapter may answer from an index;
934///   its default folds `children_named`, `descendants_named` and
935///   `descendants_where`, which an adapter may still implement
936///   individually. A fast path changes cost, never the answer.
937/// - **Evaluation hints**: `quantifier_bound` (the depth the
938///   adapter's own structures need; a run may override it),
939///   `unit_scale`.
940/// - **Provenance**: `provenance`, `provenance_list`.
941///
942/// Pairs the engine relies on being observationally equal, so an
943/// override must keep them so: `children_named(n, x)` is
944/// `children(n)` filtered by `answers_to(_, x)` (except deliberate
945/// container-scoped aliasing, documented on the method);
946/// `has_trait(n, t)` is `t ∈ traits(n)`; `property_names(n)` covers
947/// the names of `properties(n)`, the `ref_property`, and the child
948/// names; `provenance_list(n)`'s first entry is `provenance(n)`;
949/// `select` answers exactly what the walk would find. The
950/// conformance suite ([`crate::graph::conformance::check`]) tests these
951/// over a sample of an adapter's nodes.
952///
953/// Optional capabilities with a single implementor each — fine to
954/// leave at the default: `unit_scale` (kaiv's units),
955/// `descendants_where` (the corpus reading's positional index),
956/// `child_host` (filesystem symlinks), `aliased_metadata` (git's
957/// closed surface), `provenance_list` (kaiv's layered sources),
958/// `index_lookup` / `index_referrers` (a model's declared indexes).
959pub trait AstAdapter {
960    /// The root node — the initial navigation context.
961    fn root(&self) -> NodeId;
962
963    /// The tree children of `node`, in document order.
964    ///
965    /// Returns an empty vector for a leaf (or an unreadable node).
966    fn children(&self, node: NodeId) -> Vec<NodeId>;
967
968    /// The name of `node` — the label of its incoming tree edge.
969    ///
970    /// `None` when the adapter leaves a node unnamed (typically the
971    /// root; e.g. the filesystem root `/` carries no name).
972    fn name(&self, node: NodeId) -> Option<String>;
973
974    /// The parent of `node`, or `None` for the root.
975    fn parent(&self, _node: NodeId) -> Option<NodeId> {
976        None
977    }
978
979    /// The traits of `node` — its adapter-defined classifications,
980    /// used by `<trait>` navigation filters (e.g. a filesystem
981    /// adapter's `<dir>`, `<code>`, `<image>`).
982    fn traits(&self, _node: NodeId) -> Vec<String> {
983        Vec::new()
984    }
985
986    /// A named property of `node` — `::prop`. `None` if absent.
987    fn property(&self, _node: NodeId, _name: &str) -> Option<Value> {
988        None
989    }
990
991    /// The node's own properties, enumerated — name and value, in
992    /// the source's order — for readings that keep values off the
993    /// tree (the JSON property reading, where an object's scalar
994    /// fields are no nodes). The serializers (`| json`, `| xml`,
995    /// `| kaiv`) merge them with the children, a child winning a
996    /// shared name. The default is none: for a reading whose values
997    /// are all nodes, the children already say everything.
998    fn properties(&self, _node: NodeId) -> Vec<(String, Value)> {
999        Vec::new()
1000    }
1001
1002    /// Whether a reverse resolution or a value match with no scope
1003    /// may walk the whole arbor to find its referrers. An adapter
1004    /// over a small, in-memory source opens to the walk; a store or
1005    /// a service does not, and such a query is refused with a message
1006    /// asking for a scope — an empty answer could not be told from an
1007    /// absence of referrers. A declared reference (a foreign key, a
1008    /// model `ref`) answers through [`reverse_resolve`](Self::reverse_resolve)
1009    /// and needs no walk.
1010    fn walks_for_references(&self) -> bool {
1011        false
1012    }
1013
1014    /// Whether a reference *declared* on `source` — a catalog key, a
1015    /// model `ref`, a reference property the format defines — can
1016    /// land in `target`'s container. A question of the schema, never
1017    /// of the data; `None` where nothing is declared at the node.
1018    /// The default: a node with a reference property of its own can
1019    /// refer anywhere (an href may point at any element).
1020    fn can_refer_to(&self, source: NodeId, _target: NodeId) -> Option<bool> {
1021        self.ref_property(source).map(|_| true)
1022    }
1023
1024    /// The reference properties declared on `source` that can land
1025    /// in `target`'s container: a catalog's key columns into the
1026    /// target's table, the fields of a model's `ref`s, the node's
1027    /// own reference property. What `t<--` and `t<--(SCOPE)` read
1028    /// when the query names no property; empty where none is
1029    /// declared.
1030    fn reference_properties(&self, source: NodeId, _target: NodeId) -> Vec<String> {
1031        self.ref_property(source).into_iter().collect()
1032    }
1033
1034    /// Whether resolving `property` of `node` needs a reference
1035    /// label, because the property itself declares no target: a
1036    /// column with no key, or any property of a property graph,
1037    /// where a value names a node only under a node label. A
1038    /// label-less `::p-->` over such a property is refused with the
1039    /// remedy, since an empty answer would read as "points nowhere".
1040    fn resolve_needs_label(&self, _node: NodeId, _property: &str) -> bool {
1041        false
1042    }
1043
1044    /// The reference label under which a referrer's property is
1045    /// also resolved when a reverse arrow asks about `target` and
1046    /// the query writes no label. `None` (the default): only the
1047    /// label-less resolution counts. An adapter whose label names a
1048    /// container (a table, a collection, a node label) returns the
1049    /// one `target` stands in, so `/authors/1<--(/books/*::author_id)`
1050    /// reads `author_id` as a key into `authors` without the query
1051    /// saying so. The engine derives no label of its own.
1052    fn reverse_label(&self, _target: NodeId) -> Option<String> {
1053        None
1054    }
1055
1056    /// The trees of this arbor other trees refer to by name: each
1057    /// identity (a URL, a path, any string a reference may hold)
1058    /// with the node that stands for the tree. An identity ending
1059    /// in a separator is a base: every identity beneath it names
1060    /// the node reached beneath the base's node by the remainder's
1061    /// segments. Declared in a model (`tree IDENTITY: PATH;`) and
1062    /// automatic for a mount's own target; the engine reads nothing
1063    /// inside an identity beyond prefix and separator.
1064    fn tree_identities(&self) -> Vec<(String, NodeId)> {
1065        Vec::new()
1066    }
1067
1068    /// The names of the properties a node has — what "any
1069    /// property" means for `<--(SCOPE::*)`. By default its off-tree
1070    /// properties, its reference property, and its children's names
1071    /// (a row's columns, an object's keys are properties under the
1072    /// same names); an adapter whose properties are not children
1073    /// (an element's attributes) lists them.
1074    fn property_names(&self, node: NodeId) -> Vec<String> {
1075        let mut ps: Vec<String> = self.properties(node).into_iter().map(|(k, _)| k).collect();
1076        if let Some(r) = self.ref_property(node)
1077            && !ps.contains(&r)
1078        {
1079            ps.push(r);
1080        }
1081        for c in self.children(node) {
1082            if let Some(name) = self.name(c)
1083                && !ps.contains(&name)
1084            {
1085                ps.push(name);
1086            }
1087        }
1088        ps
1089    }
1090
1091    /// Whether this source resolves references at all. An adapter
1092    /// with no notion of a reference (a CSV file, a directory, a
1093    /// log stream) says no, and a reverse arrow over it is refused
1094    /// with the value match as the remedy, rather than answered
1095    /// with an empty set that would read as "nothing points here".
1096    fn resolves_references(&self) -> bool {
1097        true
1098    }
1099
1100    /// `index SCOPE::prop;` — the nodes of the declared scope whose
1101    /// property holds `value`, from an index the model keeps; `None`
1102    /// when no declaration covers the pair (the walk answers). An
1103    /// index changes cost, never the answer: the caller still tests
1104    /// each node it names.
1105    fn index_lookup(&self, _scope: &str, _property: &str, _value: &Value) -> Option<Vec<NodeId>> {
1106        None
1107    }
1108    /// The declared-scope nodes whose `property` resolves (under the
1109    /// relation `label`, when given) to `target`, from the same index;
1110    /// `None` when no declaration covers the pair.
1111    fn index_referrers(
1112        &self,
1113        _scope: &str,
1114        _property: &str,
1115        _label: Option<&str>,
1116        _target: NodeId,
1117    ) -> Option<Vec<NodeId>> {
1118        None
1119    }
1120
1121    /// The nodes of the arbor that are not in the tree — joined to
1122    /// it by crosslinks alone, with no parent and no path. The
1123    /// universal search `*//` reaches them after the tree, in this
1124    /// order; `:is-non-tree` is true on them. The default is
1125    /// none: every shipped adapter puts every node in the tree.
1126    fn non_tree_nodes(&self) -> Vec<NodeId> {
1127        Vec::new()
1128    }
1129
1130    /// The node whose children an explicit child step from `node`
1131    /// reads. The default is `node` itself. An adapter whose tree
1132    /// holds transparent links (a filesystem's symlink to a
1133    /// directory) answers the link's target, so `/link/name` lands
1134    /// on the target's own child — the same node `/target/name`
1135    /// reaches. The descendant walk never consults this: it reads
1136    /// [`children`](Self::children) alone, so a linked subtree is
1137    /// walked once, under its own parent, and a cycle of links
1138    /// cannot trap it.
1139    fn child_host(&self, node: NodeId) -> NodeId {
1140        node
1141    }
1142
1143    /// The children of `node` whose edge name is exactly `name` —
1144    /// the engine's fast path for name-matcher child hops. The
1145    /// default filters [`children`](Self::children); an adapter
1146    /// whose containers cannot be enumerated (permission-scoped or
1147    /// unbounded remote trees) overrides this with a direct,
1148    /// name-addressed lookup. Must be observationally identical to
1149    /// the default wherever enumeration works. The adapter owns the
1150    /// name test: it may deliberately *alias* — resolve a name to a
1151    /// node whose edge name differs (git revision syntax landing on
1152    /// a hash-named commit) — and the engine will not re-filter.
1153    /// Container-scoped resolution like that stays child-axis only;
1154    /// per-node spelling aliases belong in
1155    /// [`answers_to`](Self::answers_to), which this default
1156    /// consults.
1157    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
1158        self.children(node)
1159            .into_iter()
1160            .filter(|&c| self.answers_to(c, name))
1161            .collect()
1162    }
1163
1164    /// Whether `node` answers to `name` as a literal spelling
1165    ///. The default is canonical equality. An adapter
1166    /// may declare per-node aliases — a social feed spelling a
1167    /// handle `@alice` while the stripped `alice` is the hop name —
1168    /// and the engine consults this wherever a *literal* name is
1169    /// matched, on every axis, so one override keeps `/@alice` and
1170    /// `//@alice` in agreement. `:name` stays canonical (locators
1171    /// and reflection print it; an alias is a way in, never a way
1172    /// out), and name patterns (`~(...)`, `*`) test the canonical
1173    /// name only.
1174    fn answers_to(&self, node: NodeId, name: &str) -> bool {
1175        self.name(node).as_deref() == Some(name)
1176    }
1177
1178    /// Whether `node` bears `name` as a trait, as written in a
1179    /// `<...>` clause. The default is membership in
1180    /// [`traits`](Self::traits); an adapter may declare aliases the
1181    /// way [`answers_to`](Self::answers_to) does for names — a
1182    /// model file's `alias <chunk> <block>;` or
1183    /// `alias <s/^tag://i>;` — and the engine consults this
1184    /// wherever a written trait is matched. Reflection and
1185    /// serialization print the canonical traits only.
1186    fn has_trait(&self, node: NodeId, name: &str) -> bool {
1187        self.traits(node).iter().any(|t| t == name)
1188    }
1189
1190    /// The default projection of `node` — bare `::`, adapter-specific
1191    /// (a filesystem adapter returns file content).
1192    fn default_value(&self, _node: NodeId) -> Option<Value> {
1193        None
1194    }
1195
1196    /// Adapter-defined metadata — `:::key` (a filesystem adapter's
1197    /// `size`, `modified`, `permissions`, …). `None` if absent.
1198    fn metadata(&self, _node: NodeId, _key: &str) -> Option<Value> {
1199        None
1200    }
1201
1202    /// The `:::` keys this node answers (spec: the `:meta` core
1203    /// field, draft 164): the adapter's catalogue filtered by what
1204    /// the node answers. A composite adapter delegates to the
1205    /// adapter that owns the node.
1206    fn metadata_keys(&self, node: NodeId) -> Vec<String> {
1207        self.metadata_catalogue()
1208            .iter()
1209            .filter(|k| self.metadata(node, k).is_some())
1210            .map(|k| k.to_string())
1211            .collect()
1212    }
1213
1214    /// Every `:::` key this adapter can answer, for `metadata_keys`;
1215    /// an adapter with none declared lists none.
1216    fn metadata_catalogue(&self) -> &'static [&'static str] {
1217        &[]
1218    }
1219
1220    /// Metadata keys this adapter also answers at `::` (ruling
1221    /// #29), for the given node — per node, because a composite
1222    /// adapter (a multi-mount, a graft) answers for whichever
1223    /// document owns it. Only an adapter whose property surface is *closed* —
1224    /// fixed by the adapter, never grown by document content — may
1225    /// declare aliases: a git commit cannot sprout a `short` field,
1226    /// where a JSON object can sprout anything. Data always wins:
1227    /// the engine consults `property` first and falls through only
1228    /// when it answers `None` for that node, so an alias can never
1229    /// shadow a document. A core field (`:`) is never aliased —
1230    /// `name`, `id` and `index` exist on every node, and aliasing
1231    /// them would change what `::name` means wherever a document
1232    /// happens to lack that field.
1233    fn aliased_metadata(&self, _node: NodeId) -> &'static [&'static str] {
1234        &[]
1235    }
1236
1237    /// Outgoing crosslinks from `node`, as `(label, target)` pairs,
1238    /// for `->` navigation (a filesystem adapter's symlinks).
1239    fn links(&self, _node: NodeId) -> Vec<(String, NodeId)> {
1240        Vec::new()
1241    }
1242
1243    /// Incoming crosslinks to `node`, as `(label, source)` pairs, for
1244    /// `<-` navigation. May be expensive (an adapter that does not
1245    /// precompute edges must search for referrers).
1246    fn backlinks(&self, _node: NodeId) -> Vec<(String, NodeId)> {
1247        Vec::new()
1248    }
1249
1250    /// `links` with each edge's identity: the node that IS the edge,
1251    /// for an adapter whose edges are rows (a model's `edge … as
1252    /// flight` over a flights table: the row is the edge, its cells
1253    /// the edge's properties), `None` for an edge that is nothing but
1254    /// a link. Two rows on one leg are two edges — each its own
1255    /// thread, each its own `$-` (spec: Edge rows). The default reads
1256    /// `links` with no identity.
1257    fn link_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
1258        self.links(node)
1259            .into_iter()
1260            .map(|(l, t)| (l, t, None))
1261            .collect()
1262    }
1263
1264    /// The edges of the graph as a table (spec: The graph as a
1265    /// table, The graph library): what `edges` collects and the
1266    /// graph library reads, from `node` outward. By default the link
1267    /// rows; a level whose hop lands inside the target — the web's
1268    /// fragment link, which `links` lands on the section — reports
1269    /// the edge to the target node itself, so that the induced
1270    /// subgraph of the pages carries it and the stored rank's graph
1271    /// is the one the library ranks.
1272    fn edge_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
1273        self.link_rows(node)
1274    }
1275
1276    /// `backlinks` with each edge's identity, as `link_rows`.
1277    fn backlink_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
1278        self.backlinks(node)
1279            .into_iter()
1280            .map(|(l, s)| (l, s, None))
1281            .collect()
1282    }
1283
1284    /// Whether the link from `source` under `label` is a reference
1285    /// an edge declaration consumed: a bare wildcard hop (`->*`,
1286    /// `<-*`, `--*`, the crosslink part of `~`) skips it, the label
1287    /// named (`<-origin`) still walks it (spec: Model Files, Edge
1288    /// rows). A model's `edge /flights/*: ::origin -- ::dest`
1289    /// consumes the two references its fields resolve through, so a
1290    /// wildcard from an airport reaches the carrier edges and not the
1291    /// flight rows; a reference no edge consumed stays visible to
1292    /// wildcards. The default consumes nothing.
1293    fn link_consumed(&self, _source: NodeId, _label: &str) -> bool {
1294        false
1295    }
1296
1297    /// Resolve a cross-reference: `::property-->hint` maps `node`'s
1298    /// `property` (a value that references another node) to its target,
1299    /// with an optional adapter-specific `hint` (a relation type such as
1300    /// an html `rel`, or a target container such as a table). A JSON
1301    /// adapter resolves a `$ref` JSON Pointer; `None` if unresolvable.
1302    fn resolve(&self, _node: NodeId, _property: &str, _hint: Option<&str>) -> Option<NodeId> {
1303        None
1304    }
1305
1306    /// The external reference `node`'s `property` holds, if any: the
1307    /// identity of a document *outside this arbor* and, where the
1308    /// reference says so, the location within it — both as the
1309    /// adapter reads them out of its own reference syntax (for html,
1310    /// the anchor's absolute URL and its fragment). The engine does
1311    /// not look inside either string: it asks for the document among
1312    /// the mounts (the host's registration, then
1313    /// [`document_by_ref`](Self::document_by_ref)), and hands the
1314    /// location to [`resolve_fragment`](Self::resolve_fragment) on
1315    /// the document it found. Consulted by `::property-->` when
1316    /// in-document resolution misses; an unmounted document lands
1317    /// among the run's unresolved external references for the
1318    /// host's acquisition loop. The engine itself never fetches.
1319    fn external_ref(
1320        &self,
1321        _node: NodeId,
1322        _property: &str,
1323        _hint: Option<&str>,
1324    ) -> Option<ExternalRef> {
1325        None
1326    }
1327
1328    /// The element `fragment` names inside the document `node`
1329    /// belongs to — html's `id` lookup. The landing rung of a
1330    /// fragment-carrying external reference, once its document is
1331    /// mounted.
1332    fn resolve_fragment(&self, _node: NodeId, _fragment: &str) -> Option<NodeId> {
1333        None
1334    }
1335
1336    /// The property carrying `node`'s own reference, for the bare
1337    /// arrow (`//a-->`): an html anchor's `href`, an iframe's
1338    /// `src`. `None` for a node that references nothing.
1339    fn ref_property(&self, _node: NodeId) -> Option<String> {
1340        None
1341    }
1342
1343    /// The relation name a resolution edge from `node` via
1344    /// `property` carries — html's `rel` attribute. `None` falls
1345    /// back to the property name as the edge label.
1346    fn ref_label(&self, _node: NodeId, _property: &str) -> Option<String> {
1347        None
1348    }
1349
1350    /// A property of the crosslink `source --label--> target` — the
1351    /// `$-::prop` read. Adapters whose edges carry data (a property
1352    /// graph's relationship properties) override this; `None` if the
1353    /// edge is bare or unknown. Where parallel edges share source,
1354    /// label, and target, the adapter answers for one of them,
1355    /// consistently.
1356    fn link_property(
1357        &self,
1358        _source: NodeId,
1359        _label: &str,
1360        _target: NodeId,
1361        _name: &str,
1362    ) -> Option<Value> {
1363        None
1364    }
1365
1366    /// The document this arbor holds under an external-reference
1367    /// identifier (a URL, fragment-stripped), when it can answer
1368    /// without the host's registration: the landing rung of `-->`
1369    /// after the mounted-document table misses. An adapter over a
1370    /// store of pages answers by URL lookup; the engine performs
1371    /// no IO either way.
1372    fn document_by_ref(&self, _id: &str) -> Option<NodeId> {
1373        None
1374    }
1375
1376    /// Every node whose `property` (or, when `None`, own reference
1377    /// property) resolves to `node`, in document order — the
1378    /// reverse index behind `<--`. `None` keeps the engine's walk
1379    /// of the whole arbor; `Some` is the adapter's complete answer.
1380    fn reverse_resolve(
1381        &self,
1382        _node: NodeId,
1383        _property: Option<&str>,
1384        _hint: Option<&str>,
1385    ) -> Option<Vec<NodeId>> {
1386        None
1387    }
1388
1389    /// The descendants of `node` that answer to `name`, each with
1390    /// its depth (children are depth 1), in document order — the
1391    /// fast path behind `//name` for an adapter whose substrate
1392    /// indexes names. `None` keeps the engine's walk; `Some` must
1393    /// be exactly what the walk would find.
1394    fn descendants_named(&self, _node: NodeId, _name: &str) -> Option<Vec<(NodeId, usize)>> {
1395        None
1396    }
1397
1398    /// The descendants of `node` named `name` whose property
1399    /// `property` equals `value`, each with its depth, in document
1400    /// order — the fast path behind `//name[::property = "value"]`
1401    /// for an adapter that indexes a property (the corpus reading's
1402    /// positional index answers `//token[::lower = "tom"]` from it).
1403    /// `None` keeps the engine's walk and its comparison; `Some`
1404    /// must be exactly what the walk would find under the standard
1405    /// equality, so an adapter answers only the values it can
1406    /// compare as the engine does (text against text).
1407    fn descendants_where(
1408        &self,
1409        _node: NodeId,
1410        _name: &str,
1411        _property: &str,
1412        _value: &Value,
1413    ) -> Option<Vec<(NodeId, usize)>> {
1414        None
1415    }
1416
1417    /// Whether a deep walk looking for `name` (None for any name)
1418    /// with `traits` may still find a match below `node`; a cost
1419    /// hint only, never a reading. An adapter whose subtrees are
1420    /// typed answers false where nothing sought can stand beneath
1421    /// (a web page's subtree is its document: no page, dir, redirect,
1422    /// tag or category below it), and the walk skips the subtree.
1423    /// True by default.
1424    fn may_hold_below(&self, _node: NodeId, _name: Option<&str>, _traits: &[&str]) -> bool {
1425        true
1426    }
1427
1428    /// The search fast path: the nodes one step reaches from `from`
1429    /// under `plan`, answered from an index — `None` keeps the
1430    /// engine's walk (the default folds `children_named`,
1431    /// `descendants_named` and `descendants_where`:
1432    /// [`select_by_hooks`]). `Some` must be exactly what the walk
1433    /// would find for the conjuncts it marks applied, under the
1434    /// engine's standard equality; the engine tests the remaining
1435    /// conjuncts and every other predicate on the nodes returned. A
1436    /// child plan's answer is never re-filtered by name (the adapter
1437    /// owns the name test, as `children_named` does); a descendant
1438    /// plan's nodes still pass the nameless tests.
1439    fn select(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<Selection> {
1440        select_by_hooks(self, from, plan)
1441    }
1442    /// The dry-run form of [`select`](Self::select): which of the
1443    /// plan's conjuncts the adapter would apply, decided from its
1444    /// catalog alone (a column's existence and type, an index's
1445    /// presence), with no node in hand. `scope` is the literal path
1446    /// from the root to the step's base (`["invoices"]` for
1447    /// `/invoices/*[...]`), empty at the root. `None` when the
1448    /// adapter cannot say — the static plan then counts every
1449    /// conjunct as the engine's.
1450    fn select_plan(&self, _scope: &[&str], _plan: &StepPlan<'_>) -> Option<Vec<bool>> {
1451        None
1452    }
1453    /// What reaching one step's nodes would cost, said from the
1454    /// catalog with no node in hand (`scope` as for
1455    /// [`select_plan`](Self::select_plan)): a warehouse's dry run,
1456    /// a table's statistics, a file's size, an arena's count.
1457    /// `None` where the adapter has no estimator — the plan then
1458    /// reads "unknown" for the step.
1459    fn estimate(&self, _scope: &[&str], _plan: &StepPlan<'_>) -> Option<Estimate> {
1460        None
1461    }
1462    /// How many nodes one step reaches from `from` under `plan`,
1463    /// without materializing them — `@| count` right after the step
1464    /// asks before walking. `None` walks; `Some` must be exactly the
1465    /// walk's count for the conjuncts the plan carries (every one of
1466    /// them, or none: a count cannot be partially applied). SQL:
1467    /// `SELECT COUNT(*)`; a document index: the bucket's length.
1468    fn count(&self, _from: NodeId, _plan: &StepPlan<'_>) -> Option<usize> {
1469        None
1470    }
1471    /// A batching hint, the projection twin of `prefetch_links`: the
1472    /// engine is about to read the properties `names` of every node
1473    /// in `nodes` (the next command projects them). A remote adapter
1474    /// fetches the columns for the frontier in one round trip;
1475    /// semantics are unchanged either way.
1476    fn prefetch_properties(&self, _nodes: &[NodeId], _names: &[&str]) {}
1477    /// A batching hint: the engine is about to follow `dir`
1478    /// crosslinks from every node in `nodes`. An adapter backed by
1479    /// a store may fetch them in one statement; semantics are
1480    /// unchanged either way.
1481    fn prefetch_links(&self, _nodes: &[NodeId], _dir: LinkDir) {}
1482    /// Drop any pushed-down listing filter: from here on every
1483    /// table's listing is the whole table. A model's fabric indexes
1484    /// a ref's target container by listing it, and a listing the
1485    /// planner narrowed to the query's rows would drop every edge
1486    /// whose far end the predicate excluded; the engine re-applies
1487    /// the pushed predicates, so a whole listing changes no answer.
1488    /// A no-op for an adapter that never filters a listing.
1489    fn whole_listings(&self) {}
1490
1491    /// The names of the properties an edge carries — the weights a
1492    /// budget quantifier may sum (spec: The budget quantifier) and
1493    /// the columns `@| edges` lists. Empty by default: an adapter
1494    /// whose edges carry properties ([`AstAdapter::link_property`])
1495    /// names them here, so the engine can pick the one of the
1496    /// budget's dimension.
1497    fn link_property_names(&self, _source: NodeId, _label: &str, _target: NodeId) -> Vec<String> {
1498        Vec::new()
1499    }
1500
1501    /// The promise that no walk over the link `label` returns to a
1502    /// node (spec: Model Files, Acyclic links): the declaration, or
1503    /// the source's own word, that makes it — for the refusal that
1504    /// names it when the data breaks it. `None` for a link that
1505    /// makes no promise, every link by default. Under the promise the
1506    /// exact forms of a quantified walk expand by node and round
1507    /// instead of by history.
1508    fn acyclic_link(&self, _label: &str) -> Option<String> {
1509        None
1510    }
1511
1512    /// The quantifier bound N_max: the depth to which open-ended path
1513    /// quantifiers (`+`, `*`, `{m,}`) expand, and the ceiling of any
1514    /// explicit `{m,n}` (the effective upper bound is min(n, N_max)).
1515    /// An adapter whose natural structures run deep may raise it; a
1516    /// run may override it (`Run::quantifier_bound`, the CLI's
1517    /// `--quantifier-bound`). The shell gate and the invocation
1518    /// instant are the run's alone (`Run::allow_shell`, `Run::now`).
1519    fn quantifier_bound(&self) -> usize {
1520        32
1521    }
1522
1523    /// The frontier bound: the most expansion paths one round of a
1524    /// quantified walk may hold in flight. A set walk (an open
1525    /// quantifier with a floor of at most one, under all matches or
1526    /// the proximal reach, no push or mark) reaches each node once
1527    /// and never nears it; an exact walk keeps one acyclic walk per
1528    /// history, and over data with many routes to a node that count
1529    /// explodes — the bound refuses the walk by name instead of
1530    /// running for hours. A run
1531    /// may override it (`Run::frontier_bound`, the CLI's
1532    /// `--frontier-bound`).
1533    fn frontier_bound(&self) -> usize {
1534        100_000
1535    }
1536
1537    /// Whether `property(node, name)` answered None because the caller
1538    /// may not read the property, rather than the node lacking it
1539    /// (spec: omitted data). Asked only after an absent read. The
1540    /// property stays absent; the engine warns once per query, and
1541    /// refuses where the read would key a filter, a sort, a group or
1542    /// an aggregate. False by default.
1543    fn property_withheld(&self, _node: NodeId, _name: &str) -> bool {
1544        false
1545    }
1546
1547    /// The properties the caller may not read anywhere in the source
1548    /// (spec: omitted data): never pushed into the store's query as a
1549    /// conjunct or an index lookup. None by default.
1550    fn withheld_properties(&self) -> Vec<String> {
1551        Vec::new()
1552    }
1553
1554    /// Whether the source dropped whole nodes for the caller in this
1555    /// run (spec: omitted data): a gated table or branch that is
1556    /// simply not there. Asked once after the run; true raises the
1557    /// omission warning. False by default.
1558    fn nodes_withheld(&self) -> bool {
1559        false
1560    }
1561
1562    /// The graph bound (spec: The graph library, Other bounds): the
1563    /// most cost units a library command may compute over, a unit
1564    /// being a node or an edge of the induced subgraph (their product
1565    /// for betweenness); reaching it refuses before the command
1566    /// computes. Ten million unless the adapter says otherwise.
1567    fn graph_bound(&self) -> usize {
1568        10_000_000
1569    }
1570
1571    /// The graphs the source maintains measures over (spec: Graph
1572    /// Declarations): a model's `graph` statements, or a level's own
1573    /// stored measures (the web level's rank). None by default.
1574    fn graph_declarations(&self) -> Vec<crate::graph::edges::GraphDeclaration> {
1575        Vec::new()
1576    }
1577
1578    /// A declared measure's values for `nodes`, the declared scope
1579    /// entire in the engine's order, once the engine has found the
1580    /// command's hop, options and context to be the declared ones:
1581    /// one value per node, or one table for a table-answering
1582    /// command. `None` declines, and the engine computes.
1583    fn graph_measure(
1584        &self,
1585        _scope: &str,
1586        _hop: &str,
1587        _measure: &str,
1588        _nodes: &[NodeId],
1589    ) -> Option<Vec<Value>> {
1590        None
1591    }
1592
1593    /// The values the engine computed for a declared measure the
1594    /// source declined, offered for the source to keep and answer
1595    /// next time; nothing by default.
1596    fn store_graph_measure(
1597        &self,
1598        _scope: &str,
1599        _hop: &str,
1600        _measure: &str,
1601        _nodes: &[NodeId],
1602        _values: &[Value],
1603    ) {
1604    }
1605
1606    /// The data provenance of `node` — the `:source` /
1607    /// `:instant` / `:dpid` / `:provenance` core-metadata
1608    /// keys. Empty by default: an adapter answers only the
1609    /// components its substrate genuinely records (never the
1610    /// invocation clock); wrapper adapters fill missing components
1611    /// from what they know — the mount its target, a graft its
1612    /// outer leaf, a model its derivation — and forward the rest
1613    /// inward, so resolution is nearest-ancestor per component.
1614    fn provenance(&self, _node: NodeId) -> Provenance {
1615        Provenance::default()
1616    }
1617
1618    /// Whether `node` is an id this adapter minted — the guard
1619    /// behind the node-id anchor `((!N))`, which may name an id
1620    /// from an earlier run (ids are minted deterministically: the
1621    /// same source, the same ids). The default probes the id
1622    /// through `parent` and `name` and treats a panic as absence,
1623    /// so an adapter that indexes by id needs no override; one that
1624    /// can answer cheaply should.
1625    fn has_node(&self, node: NodeId) -> bool {
1626        if node == self.root() {
1627            return true;
1628        }
1629        // An adapter that indexes its arena panics on a foreign id;
1630        // the probe catches that. The panic hook is installed once
1631        // for the process and silenced per thread while probing —
1632        // never swapped, so other threads keep theirs.
1633        probe::silently(|| self.parent(node).is_some() || self.name(node).is_some())
1634    }
1635
1636    /// The data provenance of `node` as a list — every source the
1637    /// substrate records for the datum, and how many it elides.
1638    /// The default is the singular answer as a one-entry list; an
1639    /// adapter whose substrate records several (kaiv's `?a;b;+N`)
1640    /// overrides it, and a wrapper layers each entry.
1641    fn provenance_list(&self, node: NodeId) -> ProvenanceList {
1642        ProvenanceList::single(self.provenance(node))
1643    }
1644
1645    /// The scale of a unit expression — (factor, canonical SI-base
1646    /// expansion) — for the unital reading's criterion text (spec:
1647    /// The Quantital Fragment). The default answers from the
1648    /// engine's frozen built-in table; a unit-aware adapter (kaiv)
1649    /// overrides it to include the mounted document's own custom
1650    /// units, so `[::range < '50kellicam']` resolves through the
1651    /// document's `.!units` imports.
1652    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1653        crate::value::quantity::scale_expr(expr)
1654    }
1655
1656    /// The unit names the mounted source adds to the built-in
1657    /// table, every spelling (a model's declarations, a kaiv
1658    /// document's own units); empty by default. The parser judges
1659    /// the operand of `convert(U)` and `unit(U)` against the table
1660    /// and this list (spec: The conversion): an unknown name there
1661    /// is refused, where a value's unknown unit stays opaque.
1662    fn unit_names(&self) -> Vec<String> {
1663        Vec::new()
1664    }
1665
1666    /// The notation the substrate declares for a leaf's text (spec:
1667    /// Grafting): a column typed json or jsonb answers `json`, an
1668    /// HTTP response its content type's notation. The composition
1669    /// layer grafts such a leaf as declared; a leaf nothing declares
1670    /// (not this, not its name, not the mount, not the query) is a
1671    /// leaf. `None` by default.
1672    fn declared_notation(&self, _node: NodeId) -> Option<String> {
1673        None
1674    }
1675
1676    /// Graft a text on demand (spec: Grafting, `graft(NOTATION)`):
1677    /// parse `text` as the notation named, or as what it declares
1678    /// about itself when none is, and answer the parsed document's
1679    /// root, a free root with no parent in the arbor. `None` when
1680    /// this adapter has no composing layer to graft with; `Some(Err)`
1681    /// names what did not parse or what the operand did not name.
1682    fn graft_text(&self, _text: &str, _notation: Option<&str>) -> Option<Result<NodeId, String>> {
1683        None
1684    }
1685
1686    /// Whether a composing layer stands in this adapter, so that
1687    /// `graft_text` answers and a host need not add one (the host
1688    /// wraps every other source in a graft-only layer at the mount).
1689    fn grafts_on_demand(&self) -> bool {
1690        false
1691    }
1692}
1693
1694/// Forwards every [`AstAdapter`] method to a wrapped adapter — the
1695/// one definition behind the wrappers (the boxed carrier, the
1696/// decoding layer, the mount and model wrappers), so a method added to the
1697/// trait is forwarded everywhere at once instead of silently
1698/// falling back to the trait default in whichever wrapper the
1699/// hand-written list missed.
1700///
1701/// The first argument names the wrapped adapter: a field (`inner`,
1702/// `0`) reached as `self.FIELD`, or `*` for a `Box<dyn AstAdapter>`
1703/// forwarding through `**self`. The optional `except` list names the
1704/// methods the wrapper defines itself, right after the invocation:
1705///
1706/// ```ignore
1707/// impl<A: AstAdapter> AstAdapter for Bounded<'_, A> {
1708///     quarb::forward_ast_adapter!(inner; except quantifier_bound);
1709///     fn quantifier_bound(&self) -> usize {
1710///         self.bound
1711///     }
1712/// }
1713/// ```
1714///
1715/// The method table inside the macro is checked against the trait
1716/// by `forwarding::the_macro_lists_every_trait_method`, and each
1717/// shipped wrapper against a probe adapter by
1718/// `forwarding::every_wrapper_forwards_every_method`.
1719#[macro_export]
1720macro_rules! forward_ast_adapter {
1721    ($inner:tt $(; except $($except:ident),+ $(,)?)? $(;)?) => {
1722        $crate::forward_ast_adapter!(@each [$inner] [$($($except)+)?]
1723            // Every method of `AstAdapter`, in the trait's order; the
1724            // coverage test reads this list from the source.
1725            root children name parent traits property properties
1726            walks_for_references can_refer_to reference_properties
1727            resolve_needs_label reverse_label tree_identities
1728            property_names resolves_references index_lookup
1729            index_referrers non_tree_nodes child_host children_named
1730            answers_to has_trait default_value metadata
1731            metadata_keys metadata_catalogue
1732            aliased_metadata links backlinks link_rows edge_rows backlink_rows link_consumed resolve external_ref
1733            resolve_fragment ref_property ref_label link_property
1734            document_by_ref reverse_resolve descendants_named
1735            descendants_where may_hold_below select select_plan estimate count
1736            prefetch_properties
1737            prefetch_links whole_listings link_property_names acyclic_link quantifier_bound frontier_bound
1738            property_withheld withheld_properties nodes_withheld
1739            graph_bound graph_declarations graph_measure store_graph_measure
1740            provenance has_node
1741            provenance_list unit_scale unit_names declared_notation graft_text grafts_on_demand
1742        );
1743    };
1744    (@each [$inner:tt] $ex:tt $($m:ident)*) => {
1745        $( $crate::forward_ast_adapter!(@unless [$inner] $ex $m); )*
1746    };
1747
1748    // The excepted method heads the list: emit nothing. One arm per
1749    // method, since a macro cannot compare two captured identifiers.
1750    (@unless [$i:tt] [root $($r:ident)*] root) => {};
1751    (@unless [$i:tt] [children $($r:ident)*] children) => {};
1752    (@unless [$i:tt] [name $($r:ident)*] name) => {};
1753    (@unless [$i:tt] [parent $($r:ident)*] parent) => {};
1754    (@unless [$i:tt] [traits $($r:ident)*] traits) => {};
1755    (@unless [$i:tt] [property $($r:ident)*] property) => {};
1756    (@unless [$i:tt] [properties $($r:ident)*] properties) => {};
1757    (@unless [$i:tt] [walks_for_references $($r:ident)*] walks_for_references) => {};
1758    (@unless [$i:tt] [can_refer_to $($r:ident)*] can_refer_to) => {};
1759    (@unless [$i:tt] [reference_properties $($r:ident)*] reference_properties) => {};
1760    (@unless [$i:tt] [resolve_needs_label $($r:ident)*] resolve_needs_label) => {};
1761    (@unless [$i:tt] [reverse_label $($r:ident)*] reverse_label) => {};
1762    (@unless [$i:tt] [tree_identities $($r:ident)*] tree_identities) => {};
1763    (@unless [$i:tt] [property_names $($r:ident)*] property_names) => {};
1764    (@unless [$i:tt] [resolves_references $($r:ident)*] resolves_references) => {};
1765    (@unless [$i:tt] [index_lookup $($r:ident)*] index_lookup) => {};
1766    (@unless [$i:tt] [index_referrers $($r:ident)*] index_referrers) => {};
1767    (@unless [$i:tt] [non_tree_nodes $($r:ident)*] non_tree_nodes) => {};
1768    (@unless [$i:tt] [child_host $($r:ident)*] child_host) => {};
1769    (@unless [$i:tt] [children_named $($r:ident)*] children_named) => {};
1770    (@unless [$i:tt] [answers_to $($r:ident)*] answers_to) => {};
1771    (@unless [$i:tt] [has_trait $($r:ident)*] has_trait) => {};
1772    (@unless [$i:tt] [default_value $($r:ident)*] default_value) => {};
1773    (@unless [$i:tt] [metadata $($r:ident)*] metadata) => {};
1774    (@unless [$i:tt] [metadata_keys $($r:ident)*] metadata_keys) => {};
1775    (@unless [$i:tt] [metadata_catalogue $($r:ident)*] metadata_catalogue) => {};
1776    (@unless [$i:tt] [aliased_metadata $($r:ident)*] aliased_metadata) => {};
1777    (@unless [$i:tt] [links $($r:ident)*] links) => {};
1778    (@unless [$i:tt] [backlinks $($r:ident)*] backlinks) => {};
1779    (@unless [$i:tt] [link_rows $($r:ident)*] link_rows) => {};
1780    (@unless [$i:tt] [edge_rows $($r:ident)*] edge_rows) => {};
1781    (@unless [$i:tt] [graph_bound $($r:ident)*] graph_bound) => {};
1782    (@unless [$i:tt] [may_hold_below $($r:ident)*] may_hold_below) => {};
1783    (@unless [$i:tt] [property_withheld $($r:ident)*] property_withheld) => {};
1784    (@unless [$i:tt] [withheld_properties $($r:ident)*] withheld_properties) => {};
1785    (@unless [$i:tt] [nodes_withheld $($r:ident)*] nodes_withheld) => {};
1786    (@unless [$i:tt] [graph_declarations $($r:ident)*] graph_declarations) => {};
1787    (@unless [$i:tt] [graph_measure $($r:ident)*] graph_measure) => {};
1788    (@unless [$i:tt] [store_graph_measure $($r:ident)*] store_graph_measure) => {};
1789    (@unless [$i:tt] [backlink_rows $($r:ident)*] backlink_rows) => {};
1790    (@unless [$i:tt] [link_consumed $($r:ident)*] link_consumed) => {};
1791    (@unless [$i:tt] [resolve $($r:ident)*] resolve) => {};
1792    (@unless [$i:tt] [external_ref $($r:ident)*] external_ref) => {};
1793    (@unless [$i:tt] [resolve_fragment $($r:ident)*] resolve_fragment) => {};
1794    (@unless [$i:tt] [ref_property $($r:ident)*] ref_property) => {};
1795    (@unless [$i:tt] [ref_label $($r:ident)*] ref_label) => {};
1796    (@unless [$i:tt] [link_property $($r:ident)*] link_property) => {};
1797    (@unless [$i:tt] [document_by_ref $($r:ident)*] document_by_ref) => {};
1798    (@unless [$i:tt] [reverse_resolve $($r:ident)*] reverse_resolve) => {};
1799    (@unless [$i:tt] [descendants_named $($r:ident)*] descendants_named) => {};
1800    (@unless [$i:tt] [descendants_where $($r:ident)*] descendants_where) => {};
1801    (@unless [$i:tt] [select $($r:ident)*] select) => {};
1802    (@unless [$i:tt] [select_plan $($r:ident)*] select_plan) => {};
1803    (@unless [$i:tt] [estimate $($r:ident)*] estimate) => {};
1804    (@unless [$i:tt] [count $($r:ident)*] count) => {};
1805    (@unless [$i:tt] [prefetch_properties $($r:ident)*] prefetch_properties) => {};
1806    (@unless [$i:tt] [prefetch_links $($r:ident)*] prefetch_links) => {};
1807    (@unless [$i:tt] [whole_listings $($r:ident)*] whole_listings) => {};
1808    (@unless [$i:tt] [quantifier_bound $($r:ident)*] quantifier_bound) => {};
1809    (@unless [$i:tt] [frontier_bound $($r:ident)*] frontier_bound) => {};
1810    (@unless [$i:tt] [link_property_names $($r:ident)*] link_property_names) => {};
1811    (@unless [$i:tt] [acyclic_link $($r:ident)*] acyclic_link) => {};
1812    (@unless [$i:tt] [provenance $($r:ident)*] provenance) => {};
1813    (@unless [$i:tt] [has_node $($r:ident)*] has_node) => {};
1814    (@unless [$i:tt] [provenance_list $($r:ident)*] provenance_list) => {};
1815    (@unless [$i:tt] [unit_scale $($r:ident)*] unit_scale) => {};
1816    (@unless [$i:tt] [unit_names $($r:ident)*] unit_names) => {};
1817    (@unless [$i:tt] [declared_notation $($r:ident)*] declared_notation) => {};
1818    (@unless [$i:tt] [graft_text $($r:ident)*] graft_text) => {};
1819    (@unless [$i:tt] [grafts_on_demand $($r:ident)*] grafts_on_demand) => {};
1820    // Another method heads the list: look further down.
1821    (@unless [$i:tt] [$x:ident $($r:ident)*] $m:ident) => {
1822        $crate::forward_ast_adapter!(@unless [$i] [$($r)*] $m);
1823    };
1824    // Not excepted: forward.
1825    (@unless [$i:tt] [] $m:ident) => {
1826        $crate::forward_ast_adapter!(@fwd [$i] $m);
1827    };
1828
1829    // The wrapped adapter, as an expression on `self`.
1830    (@target $s:ident *) => { (**$s) };
1831    (@target $s:ident $f:tt) => { $s.$f };
1832
1833    (@fwd [$i:tt] root) => {
1834        fn root(&self) -> $crate::NodeId {
1835            $crate::forward_ast_adapter!(@target self $i).root()
1836        }
1837    };
1838    (@fwd [$i:tt] children) => {
1839        fn children(&self, node: $crate::NodeId) -> Vec<$crate::NodeId> {
1840            $crate::forward_ast_adapter!(@target self $i).children(node)
1841        }
1842    };
1843    (@fwd [$i:tt] name) => {
1844        fn name(&self, node: $crate::NodeId) -> Option<String> {
1845            $crate::forward_ast_adapter!(@target self $i).name(node)
1846        }
1847    };
1848    (@fwd [$i:tt] parent) => {
1849        fn parent(&self, node: $crate::NodeId) -> Option<$crate::NodeId> {
1850            $crate::forward_ast_adapter!(@target self $i).parent(node)
1851        }
1852    };
1853    (@fwd [$i:tt] traits) => {
1854        fn traits(&self, node: $crate::NodeId) -> Vec<String> {
1855            $crate::forward_ast_adapter!(@target self $i).traits(node)
1856        }
1857    };
1858    (@fwd [$i:tt] property) => {
1859        fn property(&self, node: $crate::NodeId, name: &str) -> Option<$crate::Value> {
1860            $crate::forward_ast_adapter!(@target self $i).property(node, name)
1861        }
1862    };
1863    (@fwd [$i:tt] properties) => {
1864        fn properties(&self, node: $crate::NodeId) -> Vec<(String, $crate::Value)> {
1865            $crate::forward_ast_adapter!(@target self $i).properties(node)
1866        }
1867    };
1868    (@fwd [$i:tt] walks_for_references) => {
1869        fn walks_for_references(&self) -> bool {
1870            $crate::forward_ast_adapter!(@target self $i).walks_for_references()
1871        }
1872    };
1873    (@fwd [$i:tt] can_refer_to) => {
1874        fn can_refer_to(&self, source: $crate::NodeId, target: $crate::NodeId) -> Option<bool> {
1875            $crate::forward_ast_adapter!(@target self $i).can_refer_to(source, target)
1876        }
1877    };
1878    (@fwd [$i:tt] reference_properties) => {
1879        fn reference_properties(
1880            &self,
1881            source: $crate::NodeId,
1882            target: $crate::NodeId,
1883        ) -> Vec<String> {
1884            $crate::forward_ast_adapter!(@target self $i).reference_properties(source, target)
1885        }
1886    };
1887    (@fwd [$i:tt] resolve_needs_label) => {
1888        fn resolve_needs_label(&self, node: $crate::NodeId, property: &str) -> bool {
1889            $crate::forward_ast_adapter!(@target self $i).resolve_needs_label(node, property)
1890        }
1891    };
1892    (@fwd [$i:tt] reverse_label) => {
1893        fn reverse_label(&self, target: $crate::NodeId) -> Option<String> {
1894            $crate::forward_ast_adapter!(@target self $i).reverse_label(target)
1895        }
1896    };
1897    (@fwd [$i:tt] tree_identities) => {
1898        fn tree_identities(&self) -> Vec<(String, $crate::NodeId)> {
1899            $crate::forward_ast_adapter!(@target self $i).tree_identities()
1900        }
1901    };
1902    (@fwd [$i:tt] property_names) => {
1903        fn property_names(&self, node: $crate::NodeId) -> Vec<String> {
1904            $crate::forward_ast_adapter!(@target self $i).property_names(node)
1905        }
1906    };
1907    (@fwd [$i:tt] resolves_references) => {
1908        fn resolves_references(&self) -> bool {
1909            $crate::forward_ast_adapter!(@target self $i).resolves_references()
1910        }
1911    };
1912    (@fwd [$i:tt] index_lookup) => {
1913        fn index_lookup(
1914            &self,
1915            scope: &str,
1916            property: &str,
1917            value: &$crate::Value,
1918        ) -> Option<Vec<$crate::NodeId>> {
1919            $crate::forward_ast_adapter!(@target self $i).index_lookup(scope, property, value)
1920        }
1921    };
1922    (@fwd [$i:tt] index_referrers) => {
1923        fn index_referrers(
1924            &self,
1925            scope: &str,
1926            property: &str,
1927            label: Option<&str>,
1928            target: $crate::NodeId,
1929        ) -> Option<Vec<$crate::NodeId>> {
1930            $crate::forward_ast_adapter!(@target self $i)
1931                .index_referrers(scope, property, label, target)
1932        }
1933    };
1934    (@fwd [$i:tt] non_tree_nodes) => {
1935        fn non_tree_nodes(&self) -> Vec<$crate::NodeId> {
1936            $crate::forward_ast_adapter!(@target self $i).non_tree_nodes()
1937        }
1938    };
1939    (@fwd [$i:tt] child_host) => {
1940        fn child_host(&self, node: $crate::NodeId) -> $crate::NodeId {
1941            $crate::forward_ast_adapter!(@target self $i).child_host(node)
1942        }
1943    };
1944    (@fwd [$i:tt] children_named) => {
1945        fn children_named(&self, node: $crate::NodeId, name: &str) -> Vec<$crate::NodeId> {
1946            $crate::forward_ast_adapter!(@target self $i).children_named(node, name)
1947        }
1948    };
1949    (@fwd [$i:tt] answers_to) => {
1950        fn answers_to(&self, node: $crate::NodeId, name: &str) -> bool {
1951            $crate::forward_ast_adapter!(@target self $i).answers_to(node, name)
1952        }
1953    };
1954    (@fwd [$i:tt] has_trait) => {
1955        fn has_trait(&self, node: $crate::NodeId, name: &str) -> bool {
1956            $crate::forward_ast_adapter!(@target self $i).has_trait(node, name)
1957        }
1958    };
1959    (@fwd [$i:tt] default_value) => {
1960        fn default_value(&self, node: $crate::NodeId) -> Option<$crate::Value> {
1961            $crate::forward_ast_adapter!(@target self $i).default_value(node)
1962        }
1963    };
1964    (@fwd [$i:tt] metadata) => {
1965        fn metadata(&self, node: $crate::NodeId, key: &str) -> Option<$crate::Value> {
1966            $crate::forward_ast_adapter!(@target self $i).metadata(node, key)
1967        }
1968    };
1969    (@fwd [$i:tt] metadata_keys) => {
1970        fn metadata_keys(&self, node: $crate::NodeId) -> Vec<String> {
1971            $crate::forward_ast_adapter!(@target self $i).metadata_keys(node)
1972        }
1973    };
1974    (@fwd [$i:tt] metadata_catalogue) => {
1975        fn metadata_catalogue(&self) -> &'static [&'static str] {
1976            $crate::forward_ast_adapter!(@target self $i).metadata_catalogue()
1977        }
1978    };
1979    (@fwd [$i:tt] aliased_metadata) => {
1980        fn aliased_metadata(&self, node: $crate::NodeId) -> &'static [&'static str] {
1981            $crate::forward_ast_adapter!(@target self $i).aliased_metadata(node)
1982        }
1983    };
1984    (@fwd [$i:tt] links) => {
1985        fn links(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
1986            $crate::forward_ast_adapter!(@target self $i).links(node)
1987        }
1988    };
1989    (@fwd [$i:tt] backlinks) => {
1990        fn backlinks(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId)> {
1991            $crate::forward_ast_adapter!(@target self $i).backlinks(node)
1992        }
1993    };
1994    (@fwd [$i:tt] link_rows) => {
1995        fn link_rows(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId, Option<$crate::NodeId>)> {
1996            $crate::forward_ast_adapter!(@target self $i).link_rows(node)
1997        }
1998    };
1999    (@fwd [$i:tt] edge_rows) => {
2000        fn edge_rows(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId, Option<$crate::NodeId>)> {
2001            $crate::forward_ast_adapter!(@target self $i).edge_rows(node)
2002        }
2003    };
2004    (@fwd [$i:tt] graph_bound) => {
2005        fn graph_bound(&self) -> usize {
2006            $crate::forward_ast_adapter!(@target self $i).graph_bound()
2007        }
2008    };
2009    (@fwd [$i:tt] may_hold_below) => {
2010        fn may_hold_below(&self, node: $crate::NodeId, name: Option<&str>, traits: &[&str]) -> bool {
2011            $crate::forward_ast_adapter!(@target self $i).may_hold_below(node, name, traits)
2012        }
2013    };
2014    (@fwd [$i:tt] property_withheld) => {
2015        fn property_withheld(&self, node: $crate::NodeId, name: &str) -> bool {
2016            $crate::forward_ast_adapter!(@target self $i).property_withheld(node, name)
2017        }
2018    };
2019    (@fwd [$i:tt] withheld_properties) => {
2020        fn withheld_properties(&self) -> Vec<String> {
2021            $crate::forward_ast_adapter!(@target self $i).withheld_properties()
2022        }
2023    };
2024    (@fwd [$i:tt] nodes_withheld) => {
2025        fn nodes_withheld(&self) -> bool {
2026            $crate::forward_ast_adapter!(@target self $i).nodes_withheld()
2027        }
2028    };
2029    (@fwd [$i:tt] graph_declarations) => {
2030        fn graph_declarations(&self) -> Vec<$crate::GraphDeclaration> {
2031            $crate::forward_ast_adapter!(@target self $i).graph_declarations()
2032        }
2033    };
2034    (@fwd [$i:tt] graph_measure) => {
2035        fn graph_measure(&self, scope: &str, hop: &str, measure: &str, nodes: &[$crate::NodeId]) -> Option<Vec<$crate::Value>> {
2036            $crate::forward_ast_adapter!(@target self $i).graph_measure(scope, hop, measure, nodes)
2037        }
2038    };
2039    (@fwd [$i:tt] store_graph_measure) => {
2040        fn store_graph_measure(&self, scope: &str, hop: &str, measure: &str, nodes: &[$crate::NodeId], values: &[$crate::Value]) {
2041            $crate::forward_ast_adapter!(@target self $i).store_graph_measure(scope, hop, measure, nodes, values)
2042        }
2043    };
2044    (@fwd [$i:tt] backlink_rows) => {
2045        fn backlink_rows(&self, node: $crate::NodeId) -> Vec<(String, $crate::NodeId, Option<$crate::NodeId>)> {
2046            $crate::forward_ast_adapter!(@target self $i).backlink_rows(node)
2047        }
2048    };
2049    (@fwd [$i:tt] link_consumed) => {
2050        fn link_consumed(&self, source: $crate::NodeId, label: &str) -> bool {
2051            $crate::forward_ast_adapter!(@target self $i).link_consumed(source, label)
2052        }
2053    };
2054    (@fwd [$i:tt] resolve) => {
2055        fn resolve(
2056            &self,
2057            node: $crate::NodeId,
2058            property: &str,
2059            hint: Option<&str>,
2060        ) -> Option<$crate::NodeId> {
2061            $crate::forward_ast_adapter!(@target self $i).resolve(node, property, hint)
2062        }
2063    };
2064    (@fwd [$i:tt] external_ref) => {
2065        fn external_ref(
2066            &self,
2067            node: $crate::NodeId,
2068            property: &str,
2069            hint: Option<&str>,
2070        ) -> Option<$crate::ExternalRef> {
2071            $crate::forward_ast_adapter!(@target self $i).external_ref(node, property, hint)
2072        }
2073    };
2074    (@fwd [$i:tt] resolve_fragment) => {
2075        fn resolve_fragment(&self, node: $crate::NodeId, fragment: &str) -> Option<$crate::NodeId> {
2076            $crate::forward_ast_adapter!(@target self $i).resolve_fragment(node, fragment)
2077        }
2078    };
2079    (@fwd [$i:tt] ref_property) => {
2080        fn ref_property(&self, node: $crate::NodeId) -> Option<String> {
2081            $crate::forward_ast_adapter!(@target self $i).ref_property(node)
2082        }
2083    };
2084    (@fwd [$i:tt] ref_label) => {
2085        fn ref_label(&self, node: $crate::NodeId, property: &str) -> Option<String> {
2086            $crate::forward_ast_adapter!(@target self $i).ref_label(node, property)
2087        }
2088    };
2089    (@fwd [$i:tt] link_property) => {
2090        fn link_property(
2091            &self,
2092            source: $crate::NodeId,
2093            label: &str,
2094            target: $crate::NodeId,
2095            name: &str,
2096        ) -> Option<$crate::Value> {
2097            $crate::forward_ast_adapter!(@target self $i).link_property(source, label, target, name)
2098        }
2099    };
2100    (@fwd [$i:tt] document_by_ref) => {
2101        fn document_by_ref(&self, id: &str) -> Option<$crate::NodeId> {
2102            $crate::forward_ast_adapter!(@target self $i).document_by_ref(id)
2103        }
2104    };
2105    (@fwd [$i:tt] reverse_resolve) => {
2106        fn reverse_resolve(
2107            &self,
2108            node: $crate::NodeId,
2109            property: Option<&str>,
2110            hint: Option<&str>,
2111        ) -> Option<Vec<$crate::NodeId>> {
2112            $crate::forward_ast_adapter!(@target self $i).reverse_resolve(node, property, hint)
2113        }
2114    };
2115    (@fwd [$i:tt] descendants_named) => {
2116        fn descendants_named(
2117            &self,
2118            node: $crate::NodeId,
2119            name: &str,
2120        ) -> Option<Vec<($crate::NodeId, usize)>> {
2121            $crate::forward_ast_adapter!(@target self $i).descendants_named(node, name)
2122        }
2123    };
2124    (@fwd [$i:tt] descendants_where) => {
2125        fn descendants_where(
2126            &self,
2127            node: $crate::NodeId,
2128            name: &str,
2129            property: &str,
2130            value: &$crate::Value,
2131        ) -> Option<Vec<($crate::NodeId, usize)>> {
2132            $crate::forward_ast_adapter!(@target self $i)
2133                .descendants_where(node, name, property, value)
2134        }
2135    };
2136    (@fwd [$i:tt] select) => {
2137        fn select(
2138            &self,
2139            from: $crate::NodeId,
2140            plan: &$crate::adapter::StepPlan<'_>,
2141        ) -> Option<$crate::adapter::Selection> {
2142            $crate::forward_ast_adapter!(@target self $i).select(from, plan)
2143        }
2144    };
2145    (@fwd [$i:tt] select_plan) => {
2146        fn select_plan(
2147            &self,
2148            scope: &[&str],
2149            plan: &$crate::adapter::StepPlan<'_>,
2150        ) -> Option<Vec<bool>> {
2151            $crate::forward_ast_adapter!(@target self $i).select_plan(scope, plan)
2152        }
2153    };
2154    (@fwd [$i:tt] estimate) => {
2155        fn estimate(
2156            &self,
2157            scope: &[&str],
2158            plan: &$crate::adapter::StepPlan<'_>,
2159        ) -> Option<$crate::adapter::Estimate> {
2160            $crate::forward_ast_adapter!(@target self $i).estimate(scope, plan)
2161        }
2162    };
2163    (@fwd [$i:tt] count) => {
2164        fn count(&self, from: $crate::NodeId, plan: &$crate::adapter::StepPlan<'_>) -> Option<usize> {
2165            $crate::forward_ast_adapter!(@target self $i).count(from, plan)
2166        }
2167    };
2168    (@fwd [$i:tt] prefetch_properties) => {
2169        fn prefetch_properties(&self, nodes: &[$crate::NodeId], names: &[&str]) {
2170            $crate::forward_ast_adapter!(@target self $i).prefetch_properties(nodes, names)
2171        }
2172    };
2173    (@fwd [$i:tt] prefetch_links) => {
2174        fn prefetch_links(&self, nodes: &[$crate::NodeId], dir: $crate::LinkDir) {
2175            $crate::forward_ast_adapter!(@target self $i).prefetch_links(nodes, dir)
2176        }
2177    };
2178    (@fwd [$i:tt] whole_listings) => {
2179        fn whole_listings(&self) {
2180            $crate::forward_ast_adapter!(@target self $i).whole_listings()
2181        }
2182    };
2183    (@fwd [$i:tt] quantifier_bound) => {
2184        fn quantifier_bound(&self) -> usize {
2185            $crate::forward_ast_adapter!(@target self $i).quantifier_bound()
2186        }
2187    };
2188    (@fwd [$i:tt] frontier_bound) => {
2189        fn frontier_bound(&self) -> usize {
2190            $crate::forward_ast_adapter!(@target self $i).frontier_bound()
2191        }
2192    };
2193    (@fwd [$i:tt] link_property_names) => {
2194        fn link_property_names(
2195            &self,
2196            source: $crate::NodeId,
2197            label: &str,
2198            target: $crate::NodeId,
2199        ) -> Vec<String> {
2200            $crate::forward_ast_adapter!(@target self $i).link_property_names(source, label, target)
2201        }
2202    };
2203    (@fwd [$i:tt] acyclic_link) => {
2204        fn acyclic_link(&self, label: &str) -> Option<String> {
2205            $crate::forward_ast_adapter!(@target self $i).acyclic_link(label)
2206        }
2207    };
2208    (@fwd [$i:tt] provenance) => {
2209        fn provenance(&self, node: $crate::NodeId) -> $crate::Provenance {
2210            $crate::forward_ast_adapter!(@target self $i).provenance(node)
2211        }
2212    };
2213    (@fwd [$i:tt] has_node) => {
2214        fn has_node(&self, node: $crate::NodeId) -> bool {
2215            $crate::forward_ast_adapter!(@target self $i).has_node(node)
2216        }
2217    };
2218    (@fwd [$i:tt] provenance_list) => {
2219        fn provenance_list(&self, node: $crate::NodeId) -> $crate::ProvenanceList {
2220            $crate::forward_ast_adapter!(@target self $i).provenance_list(node)
2221        }
2222    };
2223    (@fwd [$i:tt] declared_notation) => {
2224        fn declared_notation(&self, node: $crate::NodeId) -> Option<String> {
2225            $crate::forward_ast_adapter!(@target self $i).declared_notation(node)
2226        }
2227    };
2228    (@fwd [$i:tt] graft_text) => {
2229        fn graft_text(
2230            &self,
2231            text: &str,
2232            notation: Option<&str>,
2233        ) -> Option<Result<$crate::NodeId, String>> {
2234            $crate::forward_ast_adapter!(@target self $i).graft_text(text, notation)
2235        }
2236    };
2237    (@fwd [$i:tt] grafts_on_demand) => {
2238        fn grafts_on_demand(&self) -> bool {
2239            $crate::forward_ast_adapter!(@target self $i).grafts_on_demand()
2240        }
2241    };
2242    (@fwd [$i:tt] unit_scale) => {
2243        fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
2244            $crate::forward_ast_adapter!(@target self $i).unit_scale(expr)
2245        }
2246    };
2247    (@fwd [$i:tt] unit_names) => {
2248        fn unit_names(&self) -> Vec<String> {
2249            $crate::forward_ast_adapter!(@target self $i).unit_names()
2250        }
2251    };
2252}
2253
2254impl AstAdapter for Box<dyn AstAdapter> {
2255    crate::forward_ast_adapter!(*);
2256}
2257
2258/// The label a reference declared by `property` gives its edge:
2259/// the property's name without a trailing `_id` or camel-case `Id`
2260/// (`manager_id` → `manager`, `authorId` → `author`; spec: A table
2261/// with foreign keys). A name that is only the suffix (`id`, `_id`)
2262/// or ends otherwise labels the edge as written.
2263pub fn reference_label(property: &str) -> &str {
2264    for suffix in ["_id", "Id"] {
2265        if let Some(stem) = property.strip_suffix(suffix)
2266            && !stem.is_empty()
2267        {
2268            return stem;
2269        }
2270    }
2271    property
2272}
2273
2274/// The labels of one scope's declared references, in declaration
2275/// order: where two would share a name, the later keeps its full
2276/// property name.
2277pub fn reference_labels<'a>(properties: impl IntoIterator<Item = &'a str>) -> Vec<String> {
2278    let mut out: Vec<String> = Vec::new();
2279    for p in properties {
2280        let label = reference_label(p);
2281        if out.iter().any(|o| o == label) {
2282            out.push(p.to_string());
2283        } else {
2284            out.push(label.to_string());
2285        }
2286    }
2287    out
2288}
2289
2290#[cfg(test)]
2291mod forwarding {
2292    use super::*;
2293
2294    /// The names of the `fn` items at `indent` within `block`.
2295    fn fn_names(block: &str, indent: &str) -> Vec<String> {
2296        block
2297            .lines()
2298            .filter_map(|l| l.strip_prefix(indent)?.strip_prefix("fn "))
2299            .map(|rest| {
2300                rest.split(|c: char| !(c.is_alphanumeric() || c == '_'))
2301                    .next()
2302                    .unwrap()
2303                    .to_string()
2304            })
2305            .collect()
2306    }
2307
2308    /// The macro's method table is the trait's method list — a
2309    /// method added to `AstAdapter` without a forwarding arm would
2310    /// otherwise answer with the trait default inside every wrapper.
2311    #[test]
2312    fn the_macro_lists_every_trait_method() {
2313        let src = include_str!("adapter.rs");
2314        let trait_src = &src[src.find("pub trait AstAdapter {").unwrap()..];
2315        let trait_src = &trait_src[..trait_src.find("\n}\n").unwrap()];
2316        let in_trait = fn_names(trait_src, "    ");
2317
2318        let table = &src[src
2319            .find("coverage test reads this list from the source.")
2320            .unwrap()..];
2321        let table = &table[table.find('\n').unwrap()..table.find(");").unwrap()];
2322        let listed: Vec<String> = table.split_whitespace().map(str::to_string).collect();
2323
2324        assert_eq!(
2325            listed, in_trait,
2326            "the forwarding table drifted from the trait"
2327        );
2328
2329        let macro_src = &src[src.find("macro_rules! forward_ast_adapter").unwrap()..];
2330        let macro_src = &macro_src[..macro_src.find("\n}\n").unwrap()];
2331        for m in &in_trait {
2332            assert!(
2333                macro_src.contains(&format!(
2334                    "(@unless [$i:tt] [{m} $($r:ident)*] {m}) => {{}};"
2335                )),
2336                "no except arm for {m}"
2337            );
2338            assert!(
2339                macro_src.contains(&format!("(@fwd [$i:tt] {m}) => {{")),
2340                "no forwarding arm for {m}"
2341            );
2342        }
2343    }
2344
2345    /// Answers every method distinctively, so a wrapper that
2346    /// forwards one method to another (or to the default) is caught.
2347    struct Probe;
2348
2349    fn n(k: u64) -> NodeId {
2350        NodeId(k)
2351    }
2352
2353    fn int(v: &Value) -> u64 {
2354        match v {
2355            Value::Int(i) => *i as u64,
2356            _ => 0,
2357        }
2358    }
2359
2360    impl AstAdapter for Probe {
2361        fn root(&self) -> NodeId {
2362            n(1)
2363        }
2364        fn children(&self, node: NodeId) -> Vec<NodeId> {
2365            vec![n(node.0 * 10)]
2366        }
2367        fn name(&self, node: NodeId) -> Option<String> {
2368            Some(format!("name{}", node.0))
2369        }
2370        fn parent(&self, node: NodeId) -> Option<NodeId> {
2371            Some(n(node.0 + 100))
2372        }
2373        fn traits(&self, node: NodeId) -> Vec<String> {
2374            vec![format!("trait{}", node.0)]
2375        }
2376        fn property(&self, node: NodeId, name: &str) -> Option<Value> {
2377            Some(Value::Str(format!("prop:{}:{name}", node.0)))
2378        }
2379        fn properties(&self, node: NodeId) -> Vec<(String, Value)> {
2380            vec![(format!("p{}", node.0), Value::Int(node.0 as i64))]
2381        }
2382        fn walks_for_references(&self) -> bool {
2383            true
2384        }
2385        fn can_refer_to(&self, source: NodeId, target: NodeId) -> Option<bool> {
2386            Some(source.0 < target.0)
2387        }
2388        fn reference_properties(&self, source: NodeId, target: NodeId) -> Vec<String> {
2389            vec![format!("rp{}-{}", source.0, target.0)]
2390        }
2391        fn resolve_needs_label(&self, node: NodeId, property: &str) -> bool {
2392            node.0 == 7 && property == "x"
2393        }
2394        fn reverse_label(&self, target: NodeId) -> Option<String> {
2395            Some(format!("rl{}", target.0))
2396        }
2397        fn tree_identities(&self) -> Vec<(String, NodeId)> {
2398            vec![("tree".into(), n(42))]
2399        }
2400        fn property_names(&self, node: NodeId) -> Vec<String> {
2401            vec![format!("pn{}", node.0)]
2402        }
2403        fn resolves_references(&self) -> bool {
2404            false
2405        }
2406        fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
2407            Some(vec![
2408                n((scope.len() + property.len()) as u64),
2409                n(int(value)),
2410            ])
2411        }
2412        fn index_referrers(
2413            &self,
2414            scope: &str,
2415            property: &str,
2416            label: Option<&str>,
2417            target: NodeId,
2418        ) -> Option<Vec<NodeId>> {
2419            Some(vec![n(
2420                (scope.len() + property.len() + label.map_or(0, str::len)) as u64 + target.0,
2421            )])
2422        }
2423        fn non_tree_nodes(&self) -> Vec<NodeId> {
2424            vec![n(99)]
2425        }
2426        fn child_host(&self, node: NodeId) -> NodeId {
2427            n(node.0 + 1000)
2428        }
2429        fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
2430            vec![n(node.0 + name.len() as u64)]
2431        }
2432        fn answers_to(&self, node: NodeId, name: &str) -> bool {
2433            node.0 == 3 && name == "alias"
2434        }
2435        fn has_trait(&self, node: NodeId, name: &str) -> bool {
2436            node.0 == 4 && name == "talias"
2437        }
2438        fn default_value(&self, node: NodeId) -> Option<Value> {
2439            Some(Value::Int(-(node.0 as i64)))
2440        }
2441        fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
2442            Some(Value::Str(format!("meta:{}:{key}", node.0)))
2443        }
2444        fn metadata_keys(&self, node: NodeId) -> Vec<String> {
2445            vec![format!("k{}", node.0)]
2446        }
2447        fn metadata_catalogue(&self) -> &'static [&'static str] {
2448            &["k"]
2449        }
2450        fn aliased_metadata(&self, node: NodeId) -> &'static [&'static str] {
2451            if node.0 == 5 { &["size"] } else { &["other"] }
2452        }
2453        fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
2454            vec![("link".into(), n(node.0 + 2))]
2455        }
2456        fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
2457            vec![("back".into(), n(node.0 + 3))]
2458        }
2459        fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
2460            Some(n(node.0
2461                + property.len() as u64
2462                + hint.map_or(0, |h| h.len() as u64)))
2463        }
2464        fn external_ref(
2465            &self,
2466            node: NodeId,
2467            property: &str,
2468            hint: Option<&str>,
2469        ) -> Option<ExternalRef> {
2470            Some(ExternalRef {
2471                document: format!("doc{}:{property}", node.0),
2472                within: hint.map(str::to_string),
2473            })
2474        }
2475        fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
2476            Some(n(node.0 + fragment.len() as u64 + 7))
2477        }
2478        fn ref_property(&self, node: NodeId) -> Option<String> {
2479            Some(format!("href{}", node.0))
2480        }
2481        fn ref_label(&self, node: NodeId, property: &str) -> Option<String> {
2482            Some(format!("rel{}:{property}", node.0))
2483        }
2484        fn link_property(
2485            &self,
2486            source: NodeId,
2487            label: &str,
2488            target: NodeId,
2489            name: &str,
2490        ) -> Option<Value> {
2491            Some(Value::Str(format!(
2492                "{}-{label}-{}-{name}",
2493                source.0, target.0
2494            )))
2495        }
2496        fn document_by_ref(&self, id: &str) -> Option<NodeId> {
2497            Some(n(id.len() as u64 + 50))
2498        }
2499        fn reverse_resolve(
2500            &self,
2501            node: NodeId,
2502            property: Option<&str>,
2503            hint: Option<&str>,
2504        ) -> Option<Vec<NodeId>> {
2505            Some(vec![n(node.0
2506                + property.map_or(0, |p| p.len() as u64) * 2
2507                + hint.map_or(0, |h| h.len() as u64) * 3)])
2508        }
2509        fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
2510            Some(vec![(n(node.0 + 9), name.len())])
2511        }
2512        fn descendants_where(
2513            &self,
2514            node: NodeId,
2515            name: &str,
2516            property: &str,
2517            value: &Value,
2518        ) -> Option<Vec<(NodeId, usize)>> {
2519            Some(vec![(n(node.0 + int(value)), name.len() + property.len())])
2520        }
2521        fn select(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<Selection> {
2522            Some(Selection {
2523                nodes: vec![(n(from.0 + plan.conjuncts.len() as u64 + 1), 3)],
2524                applied: vec![true; plan.conjuncts.len()],
2525            })
2526        }
2527        fn select_plan(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Vec<bool>> {
2528            Some(vec![scope.len() == 1; plan.conjuncts.len()])
2529        }
2530        fn estimate(&self, scope: &[&str], plan: &StepPlan<'_>) -> Option<Estimate> {
2531            Some(Estimate {
2532                nodes: (scope.len() as u64, plan.conjuncts.len() as u64 + 9),
2533                bytes: None,
2534                requests: Some(1),
2535                latency: Latency::Local,
2536                confidence: Confidence::Bound,
2537            })
2538        }
2539        fn count(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<usize> {
2540            Some(from.0 as usize + plan.conjuncts.len() + 40)
2541        }
2542        fn prefetch_properties(&self, nodes: &[NodeId], names: &[&str]) {
2543            PREFETCHED.with(|p| p.set(nodes.len() * 100 + names.len()));
2544        }
2545        fn prefetch_links(&self, nodes: &[NodeId], dir: LinkDir) {
2546            PREFETCHED.with(|p| p.set(nodes.len() * 10 + matches!(dir, LinkDir::In) as usize));
2547        }
2548        fn quantifier_bound(&self) -> usize {
2549            77
2550        }
2551        fn frontier_bound(&self) -> usize {
2552            777
2553        }
2554        fn link_property_names(&self, source: NodeId, _label: &str, target: NodeId) -> Vec<String> {
2555            vec![format!("w{}_{}", source.0, target.0)]
2556        }
2557        fn provenance(&self, node: NodeId) -> Provenance {
2558            Provenance {
2559                source: Some(format!("src{}", node.0)),
2560                ..Default::default()
2561            }
2562        }
2563        fn has_node(&self, node: NodeId) -> bool {
2564            node.0 == 8
2565        }
2566        fn provenance_list(&self, node: NodeId) -> ProvenanceList {
2567            ProvenanceList {
2568                entries: vec![self.provenance(node)],
2569                elided: node.0 as u32,
2570            }
2571        }
2572        fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
2573            Some((expr.len() as f64, format!("scale:{expr}")))
2574        }
2575        fn unit_names(&self) -> Vec<String> {
2576            vec!["probe".to_string()]
2577        }
2578    }
2579
2580    thread_local! {
2581        static PREFETCHED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2582    }
2583
2584    /// Every method of `w` answers as `Probe` does, except those in
2585    /// `except`, which the wrapper defines itself.
2586    fn assert_forwards(w: &dyn AstAdapter, except: &[&str]) {
2587        let p = Probe;
2588        let v = Value::Int(6);
2589        macro_rules! same {
2590            ($m:ident $(, $a:expr)*) => {
2591                if !except.contains(&stringify!($m)) {
2592                    assert_eq!(w.$m($($a),*), p.$m($($a),*), stringify!($m));
2593                }
2594            };
2595        }
2596        same!(root);
2597        same!(children, n(2));
2598        same!(name, n(2));
2599        same!(parent, n(2));
2600        same!(traits, n(2));
2601        same!(property, n(2), "k");
2602        same!(properties, n(2));
2603        same!(walks_for_references);
2604        same!(can_refer_to, n(2), n(3));
2605        same!(reference_properties, n(2), n(3));
2606        same!(resolve_needs_label, n(7), "x");
2607        same!(reverse_label, n(2));
2608        same!(tree_identities);
2609        same!(property_names, n(2));
2610        same!(resolves_references);
2611        same!(index_lookup, "s", "pp", &v);
2612        same!(index_referrers, "s", "pp", Some("lbl"), n(2));
2613        same!(non_tree_nodes);
2614        same!(child_host, n(2));
2615        same!(children_named, n(2), "abc");
2616        same!(answers_to, n(3), "alias");
2617        same!(has_trait, n(4), "talias");
2618        same!(default_value, n(2));
2619        same!(metadata, n(2), "key");
2620        same!(metadata_keys, n(2));
2621        same!(metadata_catalogue);
2622        same!(aliased_metadata, n(5));
2623        same!(links, n(2));
2624        same!(backlinks, n(2));
2625        same!(resolve, n(2), "pp", Some("h"));
2626        same!(external_ref, n(2), "pp", Some("frag"));
2627        same!(resolve_fragment, n(2), "frag");
2628        same!(ref_property, n(2));
2629        same!(ref_label, n(2), "pp");
2630        same!(link_property, n(2), "lbl", n(3), "nm");
2631        same!(document_by_ref, "id");
2632        same!(reverse_resolve, n(2), Some("pp"), Some("h"));
2633        same!(descendants_named, n(2), "abc");
2634        same!(descendants_where, n(2), "abc", "pp", &v);
2635        let plan = StepPlan {
2636            traits: Vec::new(),
2637            cap: Default::default(),
2638            axis: PlanAxis::Descendant,
2639            name: Some("abc"),
2640            conjuncts: vec![Conjunct::Equals("pp", &v)],
2641            limit: None,
2642            order: None,
2643        };
2644        if !except.contains(&"select") {
2645            assert_eq!(
2646                w.select(n(2), &plan).map(|s| s.nodes),
2647                p.select(n(2), &plan).map(|s| s.nodes),
2648                "select"
2649            );
2650        }
2651        same!(count, n(2), &plan);
2652        same!(select_plan, &["t"], &plan);
2653        same!(estimate, &["t"], &plan);
2654        if !except.contains(&"prefetch_properties") {
2655            PREFETCHED.with(|p| p.set(0));
2656            w.prefetch_properties(&[n(1), n(2), n(3)], &["a", "b"]);
2657            assert_eq!(PREFETCHED.with(|p| p.get()), 302, "prefetch_properties");
2658        }
2659        if !except.contains(&"prefetch_links") {
2660            PREFETCHED.with(|c| c.set(0));
2661            w.prefetch_links(&[n(1), n(2)], LinkDir::In);
2662            assert_eq!(PREFETCHED.with(|c| c.get()), 21, "prefetch_links");
2663        }
2664        same!(quantifier_bound);
2665        same!(frontier_bound);
2666        same!(link_property_names, n(1), "e", n(2));
2667        same!(provenance, n(2));
2668        same!(has_node, n(8));
2669        same!(has_node, n(9));
2670        same!(provenance_list, n(2));
2671        same!(unit_scale, "km");
2672        same!(unit_names);
2673    }
2674
2675    #[test]
2676    fn every_wrapper_forwards_every_method() {
2677        let p = Probe;
2678        let b: Box<dyn AstAdapter> = Box::new(Probe);
2679        assert_forwards(&b, &[]);
2680
2681        let s = crate::graph::source::Sourced {
2682            inner: Probe,
2683            encoding: "utf-8".into(),
2684            replaced: 0,
2685        };
2686        assert_forwards(&s, &["metadata"]);
2687        assert_eq!(s.metadata(n(2), "key"), p.metadata(n(2), "key"));
2688        assert_eq!(
2689            s.metadata(n(1), "encoding"),
2690            Some(Value::Str("utf-8".into()))
2691        );
2692    }
2693}