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 = ¯o_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}