Skip to main content

quarb_model/
lib.rs

1//! Model files: derived arbor structure over any Quarb source.
2//!
3//! A [`Model`] (parsed from a `--model` file, see [`parse_model`])
4//! declares *derived* containers, references, and edges over a
5//! *base* arbor — the top rung of the reference-provenance ladder,
6//! generalizing the SQLite views-and-`--refs` construction to any
7//! substrate (a CSV, a JSON export, a log stream).
8//!
9//! [`ModelAdapter`] wraps a base adapter and presents the base's own
10//! nodes unchanged, adding the derived containers as new root
11//! children in a reserved node-id band (bit 63). Derived structure
12//! materializes lazily: a `node` constructor runs its query over the
13//! *base* (never over `self`, so no recursion) on first touch, and
14//! the distinct string-keyed values are cached. Declared references
15//! and edges build forward/reverse value indexes on first use.
16
17mod parse;
18
19pub use parse::{
20    AliasDecl, AliasKind, AliasRule, EdgeDecl, Model, Mount, NodeDecl, RefDecl, RelDecl,
21    parse_model, parse_model_file, resolve_mount_target,
22};
23
24use quarb::{AstAdapter, NodeId, QueryResult, Value};
25use std::cell::OnceCell;
26use std::collections::HashMap;
27
28/// Derived nodes carry this tag bit; the base's own ids never set it
29/// (the mount layer's index rides bits 56–63 but never reaches 128,
30/// so bit 63 stays clear for realistic inputs). Below it: the
31/// container index (bits 44–62) and the value index (bits 0–43).
32const MODEL_TAG: u64 = 1 << 63;
33const CIDX_SHIFT: u64 = 44;
34const VAL_MASK: u64 = (1 << CIDX_SHIFT) - 1;
35
36/// One member of a container: the two jobs a `node` constructor
37/// does, kept apart because they answer differently to "what does
38/// this node contain?".
39#[derive(Clone)]
40enum Member {
41    /// A constructor yielding *values* elevates them: each distinct
42    /// scalar becomes a node whose default projection is that value.
43    Value(Value),
44    /// A constructor yielding *nodes* aliases them: an existing node
45    /// given a container and a role, creating nothing. Such a node
46    /// holds everything its source holds, because it *is* the
47    /// source under a role.
48    Node(NodeId),
49}
50
51/// A derived container's materialized member set. A container
52/// declared by several `node` statements holds several roles: each
53/// statement appends its members under its own role, so
54/// `/tables/class` and `/tables/schema` share one container.
55struct Container {
56    name: String,
57    /// The members, in first-appearance order across the statements
58    /// that fill the container (member index `i` is member `i`, and
59    /// its node carries slot `i+1`; slot `0` names the container
60    /// node itself).
61    members: Vec<Member>,
62    /// Per member: the role it plays — `ip` in `/ips/ip` — which
63    /// names the child *and* labels every hop that lands on it, and
64    /// the trait it carries, the role unless declared.
65    roles: Vec<(String, String)>,
66    /// member key string → member index, over the whole container;
67    /// on a collision across roles the earlier statement wins.
68    by_str: HashMap<String, usize>,
69    /// role → (member key string → member index).
70    by_role: HashMap<String, HashMap<String, usize>>,
71}
72
73impl Container {
74    /// The member in node slot `v` (slot 0 is the container itself).
75    fn member(&self, v: usize) -> Option<Member> {
76        if v == 0 {
77            return None;
78        }
79        self.members.get(v - 1).cloned()
80    }
81
82    /// The role of the member in slot `v` (`v >= 1`).
83    fn role_of(&self, v: usize) -> &str {
84        &self.roles[v - 1].0
85    }
86
87    /// The trait of the member in slot `v` (`v >= 1`).
88    fn trait_of(&self, v: usize) -> &str {
89        &self.roles[v - 1].1
90    }
91
92    /// The 1-based position of slot `v` among the members of its
93    /// own role, for locators.
94    fn ordinal_in_role(&self, v: usize) -> usize {
95        let role = &self.roles[v - 1].0;
96        self.roles[..v - 1]
97            .iter()
98            .filter(|(r, _)| r == role)
99            .count()
100            + 1
101    }
102
103    /// The member indexes of one role, in order.
104    fn of_role<'a>(&'a self, role: &'a str) -> impl Iterator<Item = usize> + 'a {
105        self.roles
106            .iter()
107            .enumerate()
108            .filter(move |(_, (r, _))| r == role)
109            .map(|(i, _)| i)
110    }
111}
112
113/// The reference and edge fabric, built together on first use: it
114/// needs the containers materialized and one pass over each scope.
115struct Fabric {
116    /// base node → the derived node aliasing it, and that
117    /// container's role. A hop that would land on the raw node lands
118    /// on the alias instead, so the role a model declared is the one
119    /// the label uses.
120    alias: HashMap<NodeId, (NodeId, String)>,
121    /// (base node, field) → derived value node it resolves to.
122    resolve: HashMap<(NodeId, String), NodeId>,
123    /// The reverse of `resolve` and of every further element of a
124    /// list-valued field: per (target, field), the source nodes —
125    /// each once, the alias when a `node` gave the source a role.
126    sources: HashMap<(NodeId, String), Vec<NodeId>>,
127    /// derived value node → base nodes pointing at it, with the
128    /// field label (the backlink of a declared ref).
129    ref_back: HashMap<NodeId, Vec<(String, NodeId)>>,
130    /// base node → its outgoing declared refs (label, target node).
131    ref_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
132    /// source node → the nodes a declared `rel` relates it to, and
133    /// the reverse. One declaration serves both directions: an edge
134    /// is one thing, and both ends can walk it.
135    rel_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
136    rel_back: HashMap<NodeId, Vec<(String, NodeId)>>,
137    /// derived value node → container-labeled neighbours (the pair
138    /// edges; parallel edges collapsed, undirected so stored both
139    /// ways).
140    edges: HashMap<NodeId, Vec<(String, NodeId)>>,
141}
142
143/// A base arbor enriched with a model's derived structure.
144pub struct ModelAdapter<A: AstAdapter> {
145    base: A,
146    model: Model,
147    containers: OnceCell<Vec<Container>>,
148    fabric: OnceCell<Fabric>,
149}
150
151impl<A: AstAdapter> ModelAdapter<A> {
152    pub fn new(base: A, model: Model) -> Self {
153        ModelAdapter {
154            base,
155            model,
156            containers: OnceCell::new(),
157            fabric: OnceCell::new(),
158        }
159    }
160
161    pub fn base(&self) -> &A {
162        &self.base
163    }
164
165    /// A human-readable locator, composing the base's own renderer
166    /// for base nodes: `/container/value` for derived nodes.
167    pub fn locator(&self, node: NodeId, base_locator: impl Fn(NodeId) -> String) -> String {
168        match self.decode(node) {
169            None => base_locator(node),
170            Some((c, 0)) => format!("/{}", self.containers()[c].name),
171            Some((c, v)) => {
172                // Children share a role name, so the locator carries
173                // a position within the role, as a CSV row's does.
174                let cont = &self.containers()[c];
175                format!(
176                    "/{}/{}[{}]",
177                    cont.name,
178                    cont.role_of(v),
179                    cont.ordinal_in_role(v)
180                )
181            }
182        }
183    }
184
185    fn seeded_defs(&self) -> quarb::Defs {
186        quarb::parse_defs(&self.model.defs_text).unwrap_or_default()
187    }
188
189    /// The derived containers, materialized on first touch by running
190    /// each constructor over the base. A later constructor sees the
191    /// containers declared before it (chaining), so this fills the
192    /// vector incrementally.
193    fn containers(&self) -> &[Container] {
194        self.containers.get_or_init(|| {
195            let defs = self.seeded_defs();
196            let mut built: Vec<Container> = Vec::new();
197            for decl in &self.model.nodes {
198                let members = self.members(&decl.query, &defs, &built);
199                // A second statement naming an existing container
200                // fills it further under its own role.
201                let idx = match built.iter().position(|c| c.name == decl.name) {
202                    Some(i) => i,
203                    None => {
204                        built.push(Container {
205                            name: decl.name.clone(),
206                            members: Vec::new(),
207                            roles: Vec::new(),
208                            by_str: HashMap::new(),
209                            by_role: HashMap::new(),
210                        });
211                        built.len() - 1
212                    }
213                };
214                // Keys: an elevated value by its rendering; an aliased
215                // node by what it projects, so a `ref` into the
216                // container resolves against the same thing `::`
217                // would show.
218                let keyed: Vec<(Member, Option<String>)> = members
219                    .into_iter()
220                    .map(|m| {
221                        let key = match &m {
222                            Member::Value(v) => Some(v.to_string()),
223                            Member::Node(n) => self.base_key(*n),
224                        };
225                        (m, key)
226                    })
227                    .collect();
228                let cont = &mut built[idx];
229                // a role declared twice adds only members it does not
230                // hold: an elevated value by its rendering, an aliased
231                // node by its identity (rows of a table share a name,
232                // so their key is no identity)
233                let held_nodes: std::collections::HashSet<NodeId> = cont
234                    .members
235                    .iter()
236                    .zip(&cont.roles)
237                    .filter(|(_, (r, _))| *r == decl.role)
238                    .filter_map(|(m, _)| match m {
239                        Member::Node(n) => Some(*n),
240                        Member::Value(_) => None,
241                    })
242                    .collect();
243                let by_role = cont.by_role.entry(decl.role.clone()).or_default();
244                for (m, key) in keyed {
245                    let held = match &m {
246                        Member::Node(n) => held_nodes.contains(n),
247                        Member::Value(_) => key.as_deref().is_some_and(|k| by_role.contains_key(k)),
248                    };
249                    if held {
250                        continue;
251                    }
252                    let i = cont.members.len();
253                    cont.members.push(m);
254                    cont.roles
255                        .push((decl.role.clone(), decl.trait_name.clone()));
256                    if let Some(k) = key {
257                        by_role.entry(k.clone()).or_insert(i);
258                        cont.by_str.entry(k).or_insert(i);
259                    }
260                }
261            }
262            built
263        })
264    }
265
266    /// Run one constructor query and collect its members, in
267    /// first-appearance order: distinct values if it projects,
268    /// aliased nodes if it navigates. `prior` is the containers
269    /// already built (so a constructor may navigate an earlier
270    /// derived container) — exposed by wrapping the base in a partial
271    /// [`ModelAdapter`] over those.
272    fn members(&self, query: &str, defs: &quarb::Defs, prior: &[Container]) -> Vec<Member> {
273        // A constructor over the base alone is the common case; one
274        // that reaches an earlier derived container runs against a
275        // scratch enrichment holding just those.
276        let result = if prior.is_empty() {
277            quarb::run_with_defs(query, defs, &self.base)
278        } else {
279            let scratch = PriorView {
280                base: &self.base,
281                prior,
282            };
283            quarb::run_with_defs(query, defs, &scratch)
284        };
285        match result {
286            Ok(QueryResult::Values(vs)) => {
287                let mut seen = std::collections::HashSet::new();
288                vs.into_iter()
289                    .filter(|v| seen.insert(v.to_string()))
290                    .map(Member::Value)
291                    .collect()
292            }
293            Ok(QueryResult::Nodes(ns)) => {
294                let mut seen = std::collections::HashSet::new();
295                ns.into_iter()
296                    .filter(|n| seen.insert(*n))
297                    .map(Member::Node)
298                    .collect()
299            }
300            Err(_) => Vec::new(),
301        }
302    }
303
304    /// What a node projects, as a string — the key an aliased member
305    /// is found by.
306    fn base_key(&self, node: NodeId) -> Option<String> {
307        self.base
308            .default_value(node)
309            .map(|v| v.to_string())
310            .or_else(|| self.base.name(node))
311    }
312
313    /// The base node an aliased member stands for, if it is one.
314    fn aliased(&self, node: NodeId) -> Option<NodeId> {
315        match self.decode(node) {
316            Some((c, v)) if v > 0 => match self.containers()[c].member(v)? {
317                Member::Node(n) => Some(n),
318                Member::Value(_) => None,
319            },
320            _ => None,
321        }
322    }
323
324    /// The role of a derived member node, the label a hop landing
325    /// on it carries.
326    fn role_at(&self, node: NodeId) -> Option<String> {
327        match self.decode(node) {
328            Some((c, v)) if v > 0 => Some(self.containers()[c].role_of(v).to_string()),
329            _ => None,
330        }
331    }
332
333    /// An elevated member's value, if the node is one.
334    fn elevated(&self, node: NodeId) -> Option<Value> {
335        match self.decode(node) {
336            Some((c, v)) if v > 0 => match self.containers()[c].member(v)? {
337                Member::Value(val) => Some(val),
338                Member::Node(_) => None,
339            },
340            _ => None,
341        }
342    }
343
344    fn container_node(c: usize) -> NodeId {
345        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
346    }
347
348    fn value_node(c: usize, v: usize) -> NodeId {
349        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (v as u64 + 1))
350    }
351
352    /// Decode a derived node into `(container, value-slot)` where
353    /// slot 0 is the container node and slot `v` is value `v-1`.
354    /// `None` for a base node.
355    fn decode(&self, node: NodeId) -> Option<(usize, usize)> {
356        if node.0 & MODEL_TAG == 0 {
357            return None;
358        }
359        let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
360        let v = (node.0 & VAL_MASK) as usize;
361        (c < self.containers().len()).then_some((c, v))
362    }
363
364    fn container_by_name(&self, name: &str) -> Option<usize> {
365        self.containers().iter().position(|c| c.name == name)
366    }
367
368    /// The reference and edge fabric, built on first use.
369    fn fabric(&self) -> &Fabric {
370        self.fabric.get_or_init(|| {
371            let defs = self.seeded_defs();
372            let mut f = Fabric {
373                alias: HashMap::new(),
374                resolve: HashMap::new(),
375                sources: HashMap::new(),
376                ref_back: HashMap::new(),
377                ref_fwd: HashMap::new(),
378                rel_fwd: HashMap::new(),
379                rel_back: HashMap::new(),
380                edges: HashMap::new(),
381            };
382            for (c, cont) in self.containers().iter().enumerate() {
383                for (i, m) in cont.members.iter().enumerate() {
384                    if let Member::Node(n) = m {
385                        f.alias
386                            .entry(*n)
387                            .or_insert((Self::value_node(c, i), cont.roles[i].0.clone()));
388                    }
389                }
390            }
391            // References: for each scoped base node, resolve its
392            // field value into the target container. What each
393            // (node, field) reaches is kept for the edges below.
394            let mut reached: HashMap<(NodeId, String), Vec<(String, NodeId)>> = HashMap::new();
395            for decl in &self.model.refs {
396                // The target's key index: a derived container's
397                // members (within the role the path names, when it
398                // names one), keyed by the explicit target property
399                // or the member key; or, when no container answers to
400                // the name, the nodes any path selects over the base
401                // and derived view (another mount's rows), keyed by
402                // the target property or their default projection.
403                // The first node with a key wins a collision.
404                let mut index: HashMap<String, NodeId> = HashMap::new();
405                let derived = self.container_by_name(&decl.container);
406                match derived {
407                    Some(container) => {
408                        let cont = &self.containers()[container];
409                        let slots: Vec<usize> = match decl.role.as_deref() {
410                            Some(role) => cont.of_role(role).map(|i| i + 1).collect(),
411                            None => (1..=cont.members.len()).collect(),
412                        };
413                        for v in slots {
414                            let n = Self::value_node(container, v - 1);
415                            let key = match decl.key_field.as_deref() {
416                                Some(f) => self.property(n, f).map(|k| k.to_string()),
417                                None => match cont.member(v) {
418                                    Some(Member::Value(val)) => Some(val.to_string()),
419                                    Some(Member::Node(b)) => self.base_key(b),
420                                    None => None,
421                                },
422                            };
423                            if let Some(k) = key {
424                                index.entry(k).or_insert(n);
425                            }
426                        }
427                    }
428                    None => {
429                        let view = PriorView {
430                            base: &self.base,
431                            prior: self.containers(),
432                        };
433                        for n in self.view_nodes(&decl.path, &defs) {
434                            let key = match decl.key_field.as_deref() {
435                                Some(f) => view.property(n, f).map(|k| k.to_string()),
436                                None => view
437                                    .default_value(n)
438                                    .map(|v| v.to_string())
439                                    .or_else(|| view.name(n)),
440                            };
441                            if let Some(k) = key {
442                                index.entry(k).or_insert(n);
443                            }
444                        }
445                    }
446                }
447                // A hop to a base target is labelled by the last
448                // named segment of the target path (`--кузнецова`);
449                // a derived target by the role of the node it lands on.
450                let path_label = scope_role(&decl.path);
451                // The source may be written through a role
452                // (`ref /library/book::author --> …`): the role nodes
453                // stand for the base rows beneath them.
454                let mut scoped = self.scope_nodes(&decl.scope, &defs);
455                if scoped.is_empty() {
456                    scoped = self
457                        .view_nodes(&decl.scope, &defs)
458                        .into_iter()
459                        .filter_map(|n| self.aliased(n))
460                        .collect();
461                }
462                for node in scoped {
463                    let Some(value) = self.base.property(node, &decl.field) else {
464                        continue;
465                    };
466                    if matches!(value, Value::Null) {
467                        continue;
468                    }
469                    // A list-valued property (a page's tags) refers
470                    // to one node per element: the first element is
471                    // what `-->` resolves to, every element gets its
472                    // hop and its backlink.
473                    let elements: Vec<Value> = match value {
474                        l @ Value::List(_) => l.into_items().unwrap_or_default(),
475                        other => vec![other],
476                    };
477                    let mut first = true;
478                    for value in elements {
479                        let Some(target) = index.get(&value.to_string()).copied() else {
480                            continue;
481                        };
482                        let fwd = self.role_at(target).unwrap_or_else(|| path_label.clone());
483                        // Backward the hop lands on the source node — on
484                        // the alias if a `node` gave it a role, else on
485                        // the raw node, named by the path that found it.
486                        let (back_node, back) = match f.alias.get(&node) {
487                            Some((alias, role)) => (*alias, role.clone()),
488                            None => (node, scope_role(&decl.scope)),
489                        };
490                        if first {
491                            f.resolve.insert((node, decl.field.clone()), target);
492                            first = false;
493                        }
494                        reached
495                            .entry((node, decl.field.clone()))
496                            .or_default()
497                            .push((fwd.clone(), target));
498                        let from = f.sources.entry((target, decl.field.clone())).or_default();
499                        if !from.contains(&back_node) {
500                            from.push(back_node);
501                        }
502                        f.ref_fwd.entry(node).or_default().push((fwd, target));
503                        f.ref_back
504                            .entry(target)
505                            .or_default()
506                            .push((back, back_node));
507                    }
508                }
509            }
510            // Relations: a condition evaluated per pair, with `$$`
511            // standing for the source node. Resolution is the special
512            // case where the condition is fixed (`the target whose
513            // identity is this value`) and so can go unwritten; a
514            // `rel` says its own, which is why it may hold for many
515            // targets and fork threads like any other hop.
516            for decl in &self.model.rels {
517                let fwd = scope_role(&decl.target);
518                let back = scope_role(&decl.source);
519                for source in self.view_nodes(&decl.source, &defs) {
520                    let cond = self.bind_driver(&decl.cond, source);
521                    let query = format!("{}{}", decl.target, cond);
522                    for target in self.view_nodes(&query, &defs) {
523                        if target == source {
524                            continue;
525                        }
526                        f.rel_fwd
527                            .entry(source)
528                            .or_default()
529                            .push((fwd.clone(), target));
530                        f.rel_back
531                            .entry(target)
532                            .or_default()
533                            .push((back.clone(), source));
534                    }
535                }
536            }
537            // Edges: per scoped node, connect what its two fields
538            // reach — the very nodes the refs resolved to, so an edge
539            // follows a ref by key property, into another mount, or
540            // with both ends in one role (a network of pairs) as it
541            // follows one into a value container. Each end is
542            // labelled by the role it lands in; parallel edges
543            // collapse.
544            for decl in &self.model.edges {
545                let mut seen = std::collections::HashSet::new();
546                for node in self.scope_nodes(&decl.scope, &defs) {
547                    let (Some(ends_a), Some(ends_b)) = (
548                        reached.get(&(node, decl.field_a.clone())),
549                        reached.get(&(node, decl.field_b.clone())),
550                    ) else {
551                        continue;
552                    };
553                    for (ra, na) in ends_a {
554                        for (rb, nb) in ends_b {
555                            if na != nb && seen.insert((*na, *nb)) {
556                                f.edges.entry(*na).or_default().push((rb.clone(), *nb));
557                                f.edges.entry(*nb).or_default().push((ra.clone(), *na));
558                            }
559                        }
560                    }
561                }
562            }
563            f
564        })
565    }
566
567    /// The base nodes selected by a scope path.
568    fn scope_nodes(&self, scope: &str, defs: &quarb::Defs) -> Vec<NodeId> {
569        match quarb::run_with_defs(scope, defs, &self.base) {
570            Ok(QueryResult::Nodes(ns)) => ns,
571            _ => Vec::new(),
572        }
573    }
574
575    /// The nodes a path selects over the base *and* the derived
576    /// containers — the view a relation's two ends navigate, since
577    /// either may be derived. It never consults the fabric, so
578    /// building the fabric cannot recurse into itself.
579    fn view_nodes(&self, path: &str, defs: &quarb::Defs) -> Vec<NodeId> {
580        let view = PriorView {
581            base: &self.base,
582            prior: self.containers(),
583        };
584        match quarb::run_with_defs(path, defs, &view) {
585            Ok(QueryResult::Nodes(ns)) => ns,
586            _ => Vec::new(),
587        }
588    }
589
590    /// Substitute the driver operand in a relation's condition:
591    /// `_::field` becomes that property of the source node, bare
592    /// `$$_` its default projection (`$$` is the heritage spelling). Text inside string literals is
593    /// left alone.
594    fn bind_driver(&self, cond: &str, source: NodeId) -> String {
595        let view = PriorView {
596            base: &self.base,
597            prior: self.containers(),
598        };
599        let b: Vec<char> = cond.chars().collect();
600        let mut out = String::new();
601        let mut i = 0;
602        while i < b.len() {
603            match b[i] {
604                q @ ('\'' | '"') => {
605                    out.push(q);
606                    i += 1;
607                    while i < b.len() && b[i] != q {
608                        if b[i] == '\\' && i + 1 < b.len() {
609                            out.push(b[i]);
610                            i += 1;
611                        }
612                        out.push(b[i]);
613                        i += 1;
614                    }
615                    if i < b.len() {
616                        out.push(b[i]);
617                        i += 1;
618                    }
619                }
620                // `_` — the served node (the relation's source); `$$_`
621                // and the bare `$$` are its heritage spellings.
622                c @ ('$' | '_')
623                    if (c == '$' && b.get(i + 1) == Some(&'$'))
624                        || (c == '_'
625                            && !(i > 0
626                                && (b[i - 1].is_alphanumeric()
627                                    || b[i - 1] == '_'
628                                    || b[i - 1] == '-'))
629                            && !b.get(i + 1).is_some_and(|c| {
630                                c.is_alphanumeric() || *c == '_' || *c == '-'
631                            })) =>
632                {
633                    if c == '$' {
634                        i += 2;
635                        if b.get(i) == Some(&'_')
636                            && !b
637                                .get(i + 1)
638                                .is_some_and(|c| c.is_alphanumeric() || *c == '_' || *c == '-')
639                        {
640                            i += 1;
641                        }
642                    } else {
643                        i += 1;
644                    }
645                    let value = if b.get(i) == Some(&':') && b.get(i + 1) == Some(&':') {
646                        i += 2;
647                        let start = i;
648                        while i < b.len() && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '-')
649                        {
650                            i += 1;
651                        }
652                        let field: String = b[start..i].iter().collect();
653                        view.property(source, &field)
654                    } else {
655                        view.default_value(source)
656                    };
657                    out.push_str(&literal(value.unwrap_or(Value::Null)));
658                }
659                c => {
660                    out.push(c);
661                    i += 1;
662                }
663            }
664        }
665        out
666    }
667}
668
669/// Render a value as query-source text, so a bound driver operand
670/// reads back as the literal it stands for.
671fn literal(v: Value) -> String {
672    match v {
673        Value::Int(n) => n.to_string(),
674        Value::Float(f) => f.to_string(),
675        Value::Bool(b) => b.to_string(),
676        Value::Null => "''".to_string(),
677        other => format!(
678            "'{}'",
679            other.to_string().replace('\\', "\\\\").replace('\'', "\\'")
680        ),
681    }
682}
683
684/// A read-only enrichment exposing just the already-built prior
685/// containers over the base — the view a chaining `node` constructor
686/// navigates. It never triggers further construction.
687struct PriorView<'a, A: AstAdapter> {
688    base: &'a A,
689    prior: &'a [Container],
690}
691
692impl<A: AstAdapter> AstAdapter for PriorView<'_, A> {
693    fn root(&self) -> NodeId {
694        self.base.root()
695    }
696    fn children(&self, node: NodeId) -> Vec<NodeId> {
697        if node.0 & MODEL_TAG != 0 {
698            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
699            let v = (node.0 & VAL_MASK) as usize;
700            if v == 0 && c < self.prior.len() {
701                return (0..self.prior[c].members.len())
702                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
703                    .collect();
704            }
705            return Vec::new();
706        }
707        let mut kids = self.base.children(node);
708        if node == self.base.root() {
709            for c in 0..self.prior.len() {
710                kids.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
711            }
712        }
713        kids
714    }
715    fn name(&self, node: NodeId) -> Option<String> {
716        if node.0 & MODEL_TAG != 0 {
717            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
718            let v = (node.0 & VAL_MASK) as usize;
719            let cont = self.prior.get(c)?;
720            // A derived child is named for its role, as in the full
721            // adapter — the value stays on `::`.
722            return Some(if v == 0 {
723                cont.name.clone()
724            } else {
725                cont.role_of(v).to_string()
726            });
727        }
728        self.base.name(node)
729    }
730    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
731        if node == self.base.root() {
732            let mut out = self.base.children_named(node, name);
733            if let Some(c) = self.prior.iter().position(|k| k.name == name) {
734                out.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
735            }
736            return out;
737        }
738        if node.0 & MODEL_TAG != 0 {
739            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
740            let v = (node.0 & VAL_MASK) as usize;
741            let Some(cont) = self.prior.get(c) else {
742                return Vec::new();
743            };
744            if v == 0 {
745                return cont
746                    .of_role(name)
747                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
748                    .collect();
749            }
750            return Vec::new();
751        }
752        self.base.children_named(node, name)
753    }
754    fn traits(&self, node: NodeId) -> Vec<String> {
755        if node.0 & MODEL_TAG != 0 {
756            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
757            let v = (node.0 & VAL_MASK) as usize;
758            return match self.prior.get(c) {
759                Some(cont) if v > 0 => vec![cont.trait_of(v).to_string()],
760                _ => Vec::new(),
761            };
762        }
763        self.base.traits(node)
764    }
765    fn parent(&self, node: NodeId) -> Option<NodeId> {
766        if node.0 & MODEL_TAG != 0 {
767            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
768            let v = (node.0 & VAL_MASK) as usize;
769            return Some(if v == 0 {
770                self.base.root()
771            } else {
772                NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
773            });
774        }
775        self.base.parent(node)
776    }
777    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
778        if node.0 & MODEL_TAG != 0 {
779            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
780            let v = (node.0 & VAL_MASK) as usize;
781            return match self.prior.get(c)?.member(v)? {
782                // elevated members have no named properties — the
783                // value lives on the bare projection only
784                Member::Value(_) => None,
785                Member::Node(n) => self.base.property(n, name),
786            };
787        }
788        self.base.property(node, name)
789    }
790    fn default_value(&self, node: NodeId) -> Option<Value> {
791        if node.0 & MODEL_TAG != 0 {
792            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
793            let v = (node.0 & VAL_MASK) as usize;
794            return match self.prior.get(c)?.member(v)? {
795                Member::Value(val) => Some(val),
796                Member::Node(n) => self.base.default_value(n),
797            };
798        }
799        self.base.default_value(node)
800    }
801    // Minimal on purpose: base nodes forward; prior-container nodes
802    // answer default (constructor queries never project
803    // `:::provenance` mid-derivation).
804    fn document_by_ref(&self, id: &str) -> Option<NodeId> {
805        self.base.document_by_ref(id)
806    }
807    fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
808        if node.0 & MODEL_TAG != 0 || node == self.base.root() {
809            return None;
810        }
811        self.base.descendants_named(node, name)
812    }
813    fn prefetch_links(&self, nodes: &[NodeId], dir: quarb::LinkDir) {
814        let base: Vec<NodeId> = nodes
815            .iter()
816            .copied()
817            .filter(|n| n.0 & MODEL_TAG == 0)
818            .collect();
819        if !base.is_empty() {
820            self.base.prefetch_links(&base, dir);
821        }
822    }
823    fn provenance(&self, node: NodeId) -> quarb::Provenance {
824        if node.0 & MODEL_TAG != 0 {
825            return quarb::Provenance::default();
826        }
827        self.base.provenance(node)
828    }
829    fn provenance_list(&self, node: NodeId) -> quarb::ProvenanceList {
830        if node.0 & MODEL_TAG != 0 {
831            return quarb::ProvenanceList::default();
832        }
833        self.base.provenance_list(node)
834    }
835}
836
837impl<A: AstAdapter> AstAdapter for ModelAdapter<A> {
838    fn root(&self) -> NodeId {
839        self.base.root()
840    }
841
842    fn children(&self, node: NodeId) -> Vec<NodeId> {
843        match self.decode(node) {
844            // A container node's children are its value nodes.
845            Some((c, 0)) => (0..self.containers()[c].members.len())
846                .map(|v| Self::value_node(c, v))
847                .collect(),
848            // An elevated node is a leaf; an aliased one has the
849            // children of the node it stands for.
850            Some(_) => match self.aliased(node) {
851                Some(base) => self.base.children(base),
852                None => Vec::new(),
853            },
854            // A base node: its own children, plus (at the root) the
855            // derived containers as new siblings.
856            None => {
857                let mut kids = self.base.children(node);
858                if node == self.base.root() {
859                    for c in 0..self.containers().len() {
860                        kids.push(Self::container_node(c));
861                    }
862                }
863                kids
864            }
865        }
866    }
867
868    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
869        // The root's fast path must see the derived containers too.
870        if node == self.base.root() {
871            let mut out = self.base.children_named(node, name);
872            if let Some(c) = self.container_by_name(name) {
873                out.push(Self::container_node(c));
874            }
875            return out;
876        }
877        match self.decode(node) {
878            // Every child answers to the role; none answers to its
879            // value (that is a predicate's job, not a name's).
880            Some((c, 0)) => self.containers()[c]
881                .of_role(name)
882                .map(|i| Self::value_node(c, i))
883                .collect(),
884            Some(_) => match self.aliased(node) {
885                Some(base) => self.base.children_named(base, name),
886                None => Vec::new(),
887            },
888            None => self.base.children_named(node, name),
889        }
890    }
891
892    fn name(&self, node: NodeId) -> Option<String> {
893        match self.decode(node) {
894            Some((c, 0)) => Some(self.containers()[c].name.clone()),
895            // A derived node is named for its role, not its value:
896            // a name says what a node *is* where you found it. The
897            // value stays in the value space, on `::`.
898            Some((c, v)) => Some(self.containers()[c].role_of(v).to_string()),
899            None => self.base.name(node),
900        }
901    }
902
903    fn parent(&self, node: NodeId) -> Option<NodeId> {
904        match self.decode(node) {
905            Some((_, 0)) => Some(self.base.root()),
906            Some((c, _)) => Some(Self::container_node(c)),
907            None => self.base.parent(node),
908        }
909    }
910
911    fn traits(&self, node: NodeId) -> Vec<String> {
912        match self.decode(node) {
913            // Each derived value node carries its container's trait,
914            // so mixed-type walk results self-describe (`[<ip>]`).
915            Some((c, v)) if v > 0 => {
916                let mut out = vec![self.containers()[c].trait_of(v).to_string()];
917                // An aliased node is the source wearing a role, so it
918                // keeps the traits the source already carried.
919                if let Some(base) = self.aliased(node) {
920                    out.extend(self.base.traits(base));
921                }
922                out
923            }
924            Some(_) => Vec::new(),
925            None => self.base.traits(node),
926        }
927    }
928
929    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
930        match self.decode(node) {
931            // An aliased node answers with the source's own
932            // properties. An elevated node has exactly one datum —
933            // its value, on the *bare* projection (`::`) — and no
934            // named properties: answering any name with the value
935            // would let `::cookie` on an ip node return the ip.
936            Some((_, v)) if v > 0 => match self.aliased(node) {
937                Some(base) => self.base.property(base, name),
938                None => None,
939            },
940            Some(_) => None,
941            None => self.base.property(node, name),
942        }
943    }
944
945    fn default_value(&self, node: NodeId) -> Option<Value> {
946        match self.decode(node) {
947            Some((_, v)) if v > 0 => match self.aliased(node) {
948                Some(base) => self.base.default_value(base),
949                None => self.elevated(node),
950            },
951            Some(_) => None,
952            None => self.base.default_value(node),
953        }
954    }
955
956    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
957        match self.decode(node) {
958            Some((c, 0)) if key == "n-rows" => {
959                Some(Value::Int(self.containers()[c].members.len() as i64))
960            }
961            Some(_) => None,
962            None => self.base.metadata(node, key),
963        }
964    }
965
966    /// Model nodes add their own annotations (`n-rows`); base nodes
967    /// keep the mounted adapter's, aliases included.
968    fn aliased_metadata(&self, node: NodeId) -> &'static [&'static str] {
969        self.base.aliased_metadata(node)
970    }
971
972    /// Data provenance. An aliased node *is* its base node under a
973    /// role, so it answers that node's provenance. An elevated node
974    /// is a new value the model made: it answers a derivation source
975    /// only (`model:/container`), never the components of any row it
976    /// was computed from — the same no-leak discipline `property`
977    /// applies to elevated values.
978    fn provenance(&self, node: NodeId) -> quarb::Provenance {
979        match self.decode(node) {
980            Some((c, _)) => match self.aliased(node) {
981                Some(base) => self.base.provenance(base),
982                None => quarb::Provenance {
983                    source: Some(format!("model:/{}", self.containers()[c].name)),
984                    ..Default::default()
985                },
986            },
987            None => self.base.provenance(node),
988        }
989    }
990    fn provenance_list(&self, node: NodeId) -> quarb::ProvenanceList {
991        match self.decode(node) {
992            Some((_, _)) => match self.aliased(node) {
993                Some(base) => self.base.provenance_list(base),
994                None => quarb::ProvenanceList::single(self.provenance(node)),
995            },
996            None => self.base.provenance_list(node),
997        }
998    }
999
1000    /// A written name reaches a node under its canonical name or
1001    /// any `alias` the model declares for names.
1002    fn answers_to(&self, node: NodeId, name: &str) -> bool {
1003        if self.decode(node).is_none() && self.base.answers_to(node, name) {
1004            return true;
1005        }
1006        let Some(canonical) = self.name(node) else {
1007            return canonical_none(name);
1008        };
1009        if canonical == name {
1010            return true;
1011        }
1012        let aliases = self
1013            .model
1014            .aliases
1015            .iter()
1016            .filter(|a| a.kind == AliasKind::Name);
1017        for a in aliases {
1018            if a.admits(name, &canonical) {
1019                return true;
1020            }
1021            // An exact alias rewrites the written name; a base node
1022            // then answers under its own rules (a kind name, a stem,
1023            // a title), not only under its canonical spelling.
1024            if self.decode(node).is_none()
1025                && let AliasRule::Exact { alias, original } = &a.rule
1026                && alias == name
1027                && self.base.answers_to(node, original)
1028            {
1029                return true;
1030            }
1031        }
1032        false
1033    }
1034
1035    /// A written trait reaches a node under a canonical trait or any
1036    /// `alias <...>` the model declares.
1037    fn has_trait(&self, node: NodeId, name: &str) -> bool {
1038        let traits = self.traits(node);
1039        if traits.iter().any(|t| t == name) {
1040            return true;
1041        }
1042        self.model
1043            .aliases
1044            .iter()
1045            .filter(|a| a.kind == AliasKind::Trait)
1046            .any(|a| traits.iter().any(|t| a.admits(name, t)))
1047    }
1048
1049    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
1050        // An aliased node resolves as the node it stands for.
1051        let under = self.aliased(node).unwrap_or(node);
1052        if let Some(&target) = self.fabric().resolve.get(&(under, property.to_string())) {
1053            return Some(target);
1054        }
1055        if self.decode(under).is_none() {
1056            return self.base.resolve(under, property, hint);
1057        }
1058        // A purely derived node (a class container, an elevated
1059        // value) has no base identity to resolve through — and base
1060        // adapters index by their own ids, so a tagged id must never
1061        // reach them.
1062        None
1063    }
1064
1065    fn external_ref(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<String> {
1066        // An aliased node references as the node it stands for; a
1067        // base id passes straight through, and a purely derived
1068        // node (tagged id) references nothing external.
1069        let under = self.aliased(node).unwrap_or(node);
1070        if self.decode(under).is_none() {
1071            return self.base.external_ref(under, property, hint);
1072        }
1073        None
1074    }
1075
1076    fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
1077        let under = self.aliased(node).unwrap_or(node);
1078        if self.decode(under).is_none() {
1079            return self.base.resolve_fragment(under, fragment);
1080        }
1081        None
1082    }
1083
1084    fn ref_property(&self, node: NodeId) -> Option<String> {
1085        let under = self.aliased(node).unwrap_or(node);
1086        if self.decode(under).is_none() {
1087            return self.base.ref_property(under);
1088        }
1089        None
1090    }
1091
1092    fn ref_label(&self, node: NodeId, property: &str) -> Option<String> {
1093        let under = self.aliased(node).unwrap_or(node);
1094        if self.decode(under).is_none() {
1095            return self.base.ref_label(under, property);
1096        }
1097        None
1098    }
1099
1100    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
1101        let f = self.fabric();
1102        match self.decode(node) {
1103            // A value node's crosslinks are its pair edges and any
1104            // relation declared from it.
1105            Some((_, v)) if v > 0 => {
1106                let mut out = f.edges.get(&node).cloned().unwrap_or_default();
1107                if let Some(rels) = f.rel_fwd.get(&node) {
1108                    out.extend(rels.iter().cloned());
1109                }
1110                if let Some(base) = self.aliased(node) {
1111                    out.extend(self.base.links(base));
1112                    if let Some(refs) = f.ref_fwd.get(&base) {
1113                        out.extend(refs.iter().cloned());
1114                    }
1115                }
1116                out
1117            }
1118            Some(_) => Vec::new(),
1119            // A base node: its own links plus any declared refs and
1120            // relations.
1121            None => {
1122                let mut out = self.base.links(node);
1123                if let Some(refs) = f.ref_fwd.get(&node) {
1124                    out.extend(refs.iter().cloned());
1125                }
1126                if let Some(rels) = f.rel_fwd.get(&node) {
1127                    out.extend(rels.iter().cloned());
1128                }
1129                out
1130            }
1131        }
1132    }
1133
1134    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
1135        let f = self.fabric();
1136        match self.decode(node) {
1137            // A value node: the base nodes whose declared ref points
1138            // here, its pair edges (undirected), and the far end of
1139            // any relation declared toward it.
1140            Some((_, v)) if v > 0 => {
1141                let mut out = f.ref_back.get(&node).cloned().unwrap_or_default();
1142                if let Some(e) = f.edges.get(&node) {
1143                    out.extend(e.iter().cloned());
1144                }
1145                if let Some(rels) = f.rel_back.get(&node) {
1146                    out.extend(rels.iter().cloned());
1147                }
1148                out
1149            }
1150            Some(_) => Vec::new(),
1151            // A base node: its own backlinks, the declared refs that
1152            // resolve to it (a ref may target any path, so another
1153            // mount's row is a target too), and the relations toward it.
1154            None => {
1155                let mut out = self.base.backlinks(node);
1156                if let Some(refs) = f.ref_back.get(&node) {
1157                    out.extend(refs.iter().cloned());
1158                }
1159                if let Some(rels) = f.rel_back.get(&node) {
1160                    out.extend(rels.iter().cloned());
1161                }
1162                out
1163            }
1164        }
1165    }
1166
1167    /// The document the base holds under a URL — base ids pass
1168    /// straight through this layer.
1169    fn document_by_ref(&self, id: &str) -> Option<NodeId> {
1170        self.base.document_by_ref(id)
1171    }
1172
1173    /// The base's reverse index answers only when the model adds
1174    /// no reference fabric of its own; otherwise the walk sees
1175    /// both.
1176    fn reverse_resolve(
1177        &self,
1178        node: NodeId,
1179        property: Option<&str>,
1180        hint: Option<&str>,
1181    ) -> Option<Vec<NodeId>> {
1182        // A field the model declares a ref for: the fabric knows its
1183        // sources, each once — the walk would meet a source twice,
1184        // as the base row and as the role node over it.
1185        if let Some(field) = property
1186            && self.model.refs.iter().any(|r| r.field == field)
1187        {
1188            let f = self.fabric();
1189            let target = f.alias.get(&node).map_or(node, |(alias, _)| *alias);
1190            let mut out = f
1191                .sources
1192                .get(&(target, field.to_string()))
1193                .cloned()
1194                .unwrap_or_default();
1195            if target != node
1196                && let Some(more) = f.sources.get(&(node, field.to_string()))
1197            {
1198                for n in more {
1199                    if !out.contains(n) {
1200                        out.push(*n);
1201                    }
1202                }
1203            }
1204            return Some(out);
1205        }
1206        if self.decode(node).is_some()
1207            || !self.model.refs.is_empty()
1208            || !self.model.rels.is_empty()
1209            || !self.model.edges.is_empty()
1210        {
1211            return None;
1212        }
1213        self.base.reverse_resolve(node, property, hint)
1214    }
1215
1216    /// The base's name index answers below any base node but the
1217    /// root (the model's containers hang there) and only when no
1218    /// name alias could widen the match.
1219    fn descendants_named(&self, node: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
1220        if self.decode(node).is_some()
1221            || node == self.base.root()
1222            || self.model.aliases.iter().any(|a| a.kind == AliasKind::Name)
1223        {
1224            return None;
1225        }
1226        self.base.descendants_named(node, name)
1227    }
1228
1229    fn prefetch_links(&self, nodes: &[NodeId], dir: quarb::LinkDir) {
1230        let base: Vec<NodeId> = nodes
1231            .iter()
1232            .copied()
1233            .filter(|n| self.decode(*n).is_none())
1234            .collect();
1235        if !base.is_empty() {
1236            self.base.prefetch_links(&base, dir);
1237        }
1238    }
1239
1240    fn quantifier_bound(&self) -> usize {
1241        self.base.quantifier_bound()
1242    }
1243    fn allow_shell(&self) -> bool {
1244        self.base.allow_shell()
1245    }
1246    fn invocation_instant(&self) -> Option<(i64, u32)> {
1247        self.base.invocation_instant()
1248    }
1249    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1250        self.base.unit_scale(expr)
1251    }
1252}
1253
1254/// An unnamed node answers to no written name.
1255fn canonical_none(_name: &str) -> bool {
1256    false
1257}
1258
1259/// The role of the nodes a scope path selects: the last *named*
1260/// segment of the path (`/posts/*` -> `posts`, `/row` -> `row`).
1261/// A base node's own name cannot serve — a relational row is named
1262/// by its primary key, which is an identity, not a role — so the
1263/// path that selected it supplies the word instead.
1264fn scope_role(scope: &str) -> String {
1265    scope
1266        .split('/')
1267        .filter(|seg| {
1268            !seg.is_empty()
1269                && !seg.starts_with('*')
1270                && !seg.starts_with('[')
1271                && !seg.chars().next().is_some_and(|c| c.is_ascii_digit())
1272        })
1273        .next_back()
1274        .unwrap_or("")
1275        .split(['[', ':'])
1276        .next()
1277        .unwrap_or("")
1278        .to_string()
1279}
1280
1281/// A borrowing adapter: lets a [`ModelAdapter`] enrich an adapter a
1282/// caller holds by reference (behind `dyn`) without taking
1283/// ownership — the shape both `qua`'s `run` funnel and `quai`'s
1284/// per-query dispatch need, since the concrete adapter is already
1285/// wrapped (now-binding, shell-gating) and only borrowed there.
1286pub struct Borrowed<'a>(pub &'a dyn AstAdapter);
1287
1288impl AstAdapter for Borrowed<'_> {
1289    fn root(&self) -> NodeId {
1290        self.0.root()
1291    }
1292    fn children(&self, n: NodeId) -> Vec<NodeId> {
1293        self.0.children(n)
1294    }
1295    fn name(&self, n: NodeId) -> Option<String> {
1296        self.0.name(n)
1297    }
1298
1299    /// Aliases (ruling #30) and trait aliases pass through: the
1300    /// wrapped adapter's word, not name equality on this layer.
1301    fn answers_to(&self, n: NodeId, name: &str) -> bool {
1302        self.0.answers_to(n, name)
1303    }
1304
1305    fn has_trait(&self, n: NodeId, name: &str) -> bool {
1306        self.0.has_trait(n, name)
1307    }
1308    fn parent(&self, n: NodeId) -> Option<NodeId> {
1309        self.0.parent(n)
1310    }
1311    fn traits(&self, n: NodeId) -> Vec<String> {
1312        self.0.traits(n)
1313    }
1314    fn children_named(&self, n: NodeId, name: &str) -> Vec<NodeId> {
1315        self.0.children_named(n, name)
1316    }
1317    fn property(&self, n: NodeId, name: &str) -> Option<Value> {
1318        self.0.property(n, name)
1319    }
1320    fn default_value(&self, n: NodeId) -> Option<Value> {
1321        self.0.default_value(n)
1322    }
1323    fn metadata(&self, n: NodeId, key: &str) -> Option<Value> {
1324        self.0.metadata(n, key)
1325    }
1326    fn links(&self, n: NodeId) -> Vec<(String, NodeId)> {
1327        self.0.links(n)
1328    }
1329    fn backlinks(&self, n: NodeId) -> Vec<(String, NodeId)> {
1330        self.0.backlinks(n)
1331    }
1332    fn resolve(&self, n: NodeId, p: &str, h: Option<&str>) -> Option<NodeId> {
1333        self.0.resolve(n, p, h)
1334    }
1335    fn external_ref(&self, n: NodeId, p: &str, h: Option<&str>) -> Option<String> {
1336        self.0.external_ref(n, p, h)
1337    }
1338    fn resolve_fragment(&self, n: NodeId, f: &str) -> Option<NodeId> {
1339        self.0.resolve_fragment(n, f)
1340    }
1341    fn ref_property(&self, n: NodeId) -> Option<String> {
1342        self.0.ref_property(n)
1343    }
1344    fn ref_label(&self, n: NodeId, p: &str) -> Option<String> {
1345        self.0.ref_label(n, p)
1346    }
1347    fn link_property(&self, s: NodeId, l: &str, t: NodeId, name: &str) -> Option<Value> {
1348        self.0.link_property(s, l, t, name)
1349    }
1350    fn document_by_ref(&self, id: &str) -> Option<NodeId> {
1351        self.0.document_by_ref(id)
1352    }
1353    fn reverse_resolve(&self, n: NodeId, p: Option<&str>, h: Option<&str>) -> Option<Vec<NodeId>> {
1354        self.0.reverse_resolve(n, p, h)
1355    }
1356    fn descendants_named(&self, n: NodeId, name: &str) -> Option<Vec<(NodeId, usize)>> {
1357        self.0.descendants_named(n, name)
1358    }
1359    fn prefetch_links(&self, nodes: &[NodeId], dir: quarb::LinkDir) {
1360        self.0.prefetch_links(nodes, dir)
1361    }
1362    fn quantifier_bound(&self) -> usize {
1363        self.0.quantifier_bound()
1364    }
1365    fn allow_shell(&self) -> bool {
1366        self.0.allow_shell()
1367    }
1368    fn invocation_instant(&self) -> Option<(i64, u32)> {
1369        self.0.invocation_instant()
1370    }
1371    fn provenance(&self, n: NodeId) -> quarb::Provenance {
1372        self.0.provenance(n)
1373    }
1374    fn provenance_list(&self, n: NodeId) -> quarb::ProvenanceList {
1375        self.0.provenance_list(n)
1376    }
1377    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
1378        self.0.unit_scale(expr)
1379    }
1380}