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    parse_model, resolve_mount_target, EdgeDecl, Model, Mount, NodeDecl, RefDecl, RelDecl,
21};
22
23use quarb::{AstAdapter, NodeId, QueryResult, Value};
24use std::cell::OnceCell;
25use std::collections::HashMap;
26
27/// Derived nodes carry this tag bit; the base's own ids never set it
28/// (the mount layer's index rides bits 56–63 but never reaches 128,
29/// so bit 63 stays clear for realistic inputs). Below it: the
30/// container index (bits 44–62) and the value index (bits 0–43).
31const MODEL_TAG: u64 = 1 << 63;
32const CIDX_SHIFT: u64 = 44;
33const VAL_MASK: u64 = (1 << CIDX_SHIFT) - 1;
34
35/// What a container's children are: the two jobs a `node`
36/// constructor does, kept apart because they answer differently to
37/// "what does this node contain?".
38enum Members {
39    /// A constructor yielding *values* elevates them: each distinct
40    /// scalar becomes a node whose default projection is that value.
41    Values(Vec<Value>),
42    /// A constructor yielding *nodes* aliases them: an existing node
43    /// set given a container and a role, creating nothing. Such a
44    /// node holds everything its source holds, because it *is* the
45    /// source under a role.
46    Nodes(Vec<NodeId>),
47}
48
49impl Members {
50    fn len(&self) -> usize {
51        match self {
52            Members::Values(v) => v.len(),
53            Members::Nodes(n) => n.len(),
54        }
55    }
56}
57
58/// A derived container's materialized member set.
59struct Container {
60    name: String,
61    /// The role each child plays: `ip` in `/ips/ip`. It names the
62    /// children *and* labels every hop that lands on one.
63    role: String,
64    /// The trait each child carries; the role unless declared.
65    trait_name: String,
66    /// The members, in first-appearance order (member index `v` is
67    /// member `v`, and its node carries slot `v+1`; slot `0` names
68    /// the container node itself).
69    members: Members,
70    /// member key string → member index (0-based).
71    by_str: HashMap<String, usize>,
72}
73
74/// One member of a container, addressed by node slot.
75enum Member {
76    Value(Value),
77    Node(NodeId),
78}
79
80impl Container {
81    /// The member in node slot `v` (slot 0 is the container itself).
82    fn member(&self, v: usize) -> Option<Member> {
83        if v == 0 {
84            return None;
85        }
86        match &self.members {
87            Members::Values(vs) => vs.get(v - 1).cloned().map(Member::Value),
88            Members::Nodes(ns) => ns.get(v - 1).copied().map(Member::Node),
89        }
90    }
91}
92
93/// The reference and edge fabric, built together on first use: it
94/// needs the containers materialized and one pass over each scope.
95struct Fabric {
96    /// base node → the derived node aliasing it, and that
97    /// container's role. A hop that would land on the raw node lands
98    /// on the alias instead, so the role a model declared is the one
99    /// the label uses.
100    alias: HashMap<NodeId, (NodeId, String)>,
101    /// (base node, field) → derived value node it resolves to.
102    resolve: HashMap<(NodeId, String), NodeId>,
103    /// derived value node → base nodes pointing at it, with the
104    /// field label (the backlink of a declared ref).
105    ref_back: HashMap<NodeId, Vec<(String, NodeId)>>,
106    /// base node → its outgoing declared refs (label, target node).
107    ref_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
108    /// source node → the nodes a declared `rel` relates it to, and
109    /// the reverse. One declaration serves both directions: an edge
110    /// is one thing, and both ends can walk it.
111    rel_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
112    rel_back: HashMap<NodeId, Vec<(String, NodeId)>>,
113    /// derived value node → container-labeled neighbours (the pair
114    /// edges; parallel edges collapsed, undirected so stored both
115    /// ways).
116    edges: HashMap<NodeId, Vec<(String, NodeId)>>,
117}
118
119/// A base arbor enriched with a model's derived structure.
120pub struct ModelAdapter<A: AstAdapter> {
121    base: A,
122    model: Model,
123    containers: OnceCell<Vec<Container>>,
124    fabric: OnceCell<Fabric>,
125}
126
127impl<A: AstAdapter> ModelAdapter<A> {
128    pub fn new(base: A, model: Model) -> Self {
129        ModelAdapter {
130            base,
131            model,
132            containers: OnceCell::new(),
133            fabric: OnceCell::new(),
134        }
135    }
136
137    pub fn base(&self) -> &A {
138        &self.base
139    }
140
141    /// A human-readable locator, composing the base's own renderer
142    /// for base nodes: `/container/value` for derived nodes.
143    pub fn locator(&self, node: NodeId, base_locator: impl Fn(NodeId) -> String) -> String {
144        match self.decode(node) {
145            None => base_locator(node),
146            Some((c, 0)) => format!("/{}", self.containers()[c].name),
147            Some((c, v)) => {
148                // Children share a role name, so the locator carries
149                // a position, as a CSV row's does.
150                let cont = &self.containers()[c];
151                format!("/{}/{}[{}]", cont.name, cont.role, v)
152            }
153        }
154    }
155
156    fn seeded_defs(&self) -> quarb::Defs {
157        quarb::parse_defs(&self.model.defs_text).unwrap_or_default()
158    }
159
160    /// The derived containers, materialized on first touch by running
161    /// each constructor over the base. A later constructor sees the
162    /// containers declared before it (chaining), so this fills the
163    /// vector incrementally.
164    fn containers(&self) -> &[Container] {
165        self.containers.get_or_init(|| {
166            let defs = self.seeded_defs();
167            let mut built: Vec<Container> = Vec::new();
168            for decl in &self.model.nodes {
169                let members = self.members(&decl.query, &defs, &built);
170                let by_str = match &members {
171                    Members::Values(vs) => vs
172                        .iter()
173                        .enumerate()
174                        .map(|(i, v)| (v.to_string(), i))
175                        .collect(),
176                    // An aliased node is keyed by what it projects,
177                    // so a `ref` into the container resolves against
178                    // the same thing `::` would show.
179                    Members::Nodes(ns) => ns
180                        .iter()
181                        .enumerate()
182                        .filter_map(|(i, n)| self.base_key(*n).map(|k| (k, i)))
183                        .collect(),
184                };
185                built.push(Container {
186                    name: decl.name.clone(),
187                    role: decl.role.clone(),
188                    trait_name: decl.trait_name.clone(),
189                    members,
190                    by_str,
191                });
192            }
193            built
194        })
195    }
196
197    /// Run one constructor query and collect its members, in
198    /// first-appearance order: distinct values if it projects,
199    /// aliased nodes if it navigates. `prior` is the containers
200    /// already built (so a constructor may navigate an earlier
201    /// derived container) — exposed by wrapping the base in a partial
202    /// [`ModelAdapter`] over those.
203    fn members(&self, query: &str, defs: &quarb::Defs, prior: &[Container]) -> Members {
204        // A constructor over the base alone is the common case; one
205        // that reaches an earlier derived container runs against a
206        // scratch enrichment holding just those.
207        let result = if prior.is_empty() {
208            quarb::run_with_defs(query, defs, &self.base)
209        } else {
210            let scratch = PriorView {
211                base: &self.base,
212                prior,
213            };
214            quarb::run_with_defs(query, defs, &scratch)
215        };
216        match result {
217            Ok(QueryResult::Values(vs)) => {
218                let mut seen = std::collections::HashSet::new();
219                Members::Values(vs.into_iter().filter(|v| seen.insert(v.to_string())).collect())
220            }
221            Ok(QueryResult::Nodes(ns)) => {
222                let mut seen = std::collections::HashSet::new();
223                Members::Nodes(ns.into_iter().filter(|n| seen.insert(*n)).collect())
224            }
225            Err(_) => Members::Values(Vec::new()),
226        }
227    }
228
229    /// What a node projects, as a string — the key an aliased member
230    /// is found by.
231    fn base_key(&self, node: NodeId) -> Option<String> {
232        self.base
233            .default_value(node)
234            .map(|v| v.to_string())
235            .or_else(|| self.base.name(node))
236    }
237
238    /// The base node an aliased member stands for, if it is one.
239    fn aliased(&self, node: NodeId) -> Option<NodeId> {
240        match self.decode(node) {
241            Some((c, v)) if v > 0 => match &self.containers()[c].members {
242                Members::Nodes(ns) => ns.get(v - 1).copied(),
243                Members::Values(_) => None,
244            },
245            _ => None,
246        }
247    }
248
249    /// An elevated member's value, if the node is one.
250    fn elevated(&self, node: NodeId) -> Option<Value> {
251        match self.decode(node) {
252            Some((c, v)) if v > 0 => match &self.containers()[c].members {
253                Members::Values(vs) => vs.get(v - 1).cloned(),
254                Members::Nodes(_) => None,
255            },
256            _ => None,
257        }
258    }
259
260    fn container_node(c: usize) -> NodeId {
261        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
262    }
263
264    fn value_node(c: usize, v: usize) -> NodeId {
265        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (v as u64 + 1))
266    }
267
268    /// Decode a derived node into `(container, value-slot)` where
269    /// slot 0 is the container node and slot `v` is value `v-1`.
270    /// `None` for a base node.
271    fn decode(&self, node: NodeId) -> Option<(usize, usize)> {
272        if node.0 & MODEL_TAG == 0 {
273            return None;
274        }
275        let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
276        let v = (node.0 & VAL_MASK) as usize;
277        (c < self.containers().len()).then_some((c, v))
278    }
279
280    fn container_by_name(&self, name: &str) -> Option<usize> {
281        self.containers().iter().position(|c| c.name == name)
282    }
283
284    /// The value node in `container` whose string equals `value`.
285    fn find_value(&self, container: usize, value: &Value) -> Option<NodeId> {
286        let idx = *self.containers()[container].by_str.get(&value.to_string())?;
287        Some(Self::value_node(container, idx))
288    }
289
290    /// The reference and edge fabric, built on first use.
291    fn fabric(&self) -> &Fabric {
292        self.fabric.get_or_init(|| {
293            let defs = self.seeded_defs();
294            let mut f = Fabric {
295                alias: HashMap::new(),
296                resolve: HashMap::new(),
297                ref_back: HashMap::new(),
298                ref_fwd: HashMap::new(),
299                rel_fwd: HashMap::new(),
300                rel_back: HashMap::new(),
301                edges: HashMap::new(),
302            };
303            for (c, cont) in self.containers().iter().enumerate() {
304                if let Members::Nodes(ns) = &cont.members {
305                    for (i, n) in ns.iter().enumerate() {
306                        f.alias
307                            .entry(*n)
308                            .or_insert((Self::value_node(c, i), cont.role.clone()));
309                    }
310                }
311            }
312            // References: for each scoped base node, resolve its
313            // field value into the target container.
314            for decl in &self.model.refs {
315                let Some(container) = self.container_by_name(&decl.container) else {
316                    continue;
317                };
318                // The explicit form names the target property the
319                // value matches (`[::id = $]`); the short form uses
320                // the member key (the default projection).
321                let by_key: Option<HashMap<String, NodeId>> =
322                    decl.key_field.as_deref().map(|f| {
323                        (1..=self.containers()[container].members.len())
324                            .filter_map(|v| {
325                                let n = Self::value_node(container, v - 1);
326                                self.property(n, f).map(|k| (k.to_string(), n))
327                            })
328                            .collect()
329                    });
330                for node in self.scope_nodes(&decl.scope, &defs) {
331                    let Some(value) = self.base.property(node, &decl.field) else {
332                        continue;
333                    };
334                    if matches!(value, Value::Null) {
335                        continue;
336                    }
337                    let target = match &by_key {
338                        Some(idx) => idx.get(&value.to_string()).copied(),
339                        None => self.find_value(container, &value),
340                    };
341                    let Some(target) = target else {
342                        continue;
343                    };
344                    // A hop is labelled by the role of the node it
345                    // lands on — never by the property it came
346                    // from. Forward, that is the target container's
347                    // role; backward, it is the base node's own
348                    // name. (`--ip` from an ip node therefore names
349                    // no relation at all, which is the point: it
350                    // would land on a row, not an ip.)
351                    let fwd = self.containers()[container].role.clone();
352                    // Backward the hop lands on the source node — on
353                    // the alias if a `node` gave it a role, else on
354                    // the raw node, named by the path that found it.
355                    let (back_node, back) = match f.alias.get(&node) {
356                        Some((alias, role)) => (*alias, role.clone()),
357                        None => (node, scope_role(&decl.scope)),
358                    };
359                    f.resolve.insert((node, decl.field.clone()), target);
360                    f.ref_fwd.entry(node).or_default().push((fwd, target));
361                    f.ref_back.entry(target).or_default().push((back, back_node));
362                }
363            }
364            // Relations: a condition evaluated per pair, with `$$`
365            // standing for the source node. Resolution is the special
366            // case where the condition is fixed (`the target whose
367            // identity is this value`) and so can go unwritten; a
368            // `rel` says its own, which is why it may hold for many
369            // targets and fork threads like any other hop.
370            for decl in &self.model.rels {
371                let fwd = scope_role(&decl.target);
372                let back = scope_role(&decl.source);
373                for source in self.view_nodes(&decl.source, &defs) {
374                    let cond = self.bind_driver(&decl.cond, source);
375                    let query = format!("{}{}", decl.target, cond);
376                    for target in self.view_nodes(&query, &defs) {
377                        if target == source {
378                            continue;
379                        }
380                        f.rel_fwd
381                            .entry(source)
382                            .or_default()
383                            .push((fwd.clone(), target));
384                        f.rel_back
385                            .entry(target)
386                            .or_default()
387                            .push((back.clone(), source));
388                    }
389                }
390            }
391            // Edges: read the two fields per scoped node, connect the
392            // two derived value nodes, labelled by the container each
393            // reaches; collapse parallel edges.
394            for decl in &self.model.edges {
395                let ca = self.field_container(&decl.field_a);
396                let cb = self.field_container(&decl.field_b);
397                let (Some((ca, la)), Some((cb, lb))) = (ca, cb) else {
398                    continue;
399                };
400                let mut seen = std::collections::HashSet::new();
401                for node in self.scope_nodes(&decl.scope, &defs) {
402                    let (Some(va), Some(vb)) = (
403                        self.base.property(node, &decl.field_a),
404                        self.base.property(node, &decl.field_b),
405                    ) else {
406                        continue;
407                    };
408                    if matches!(va, Value::Null) || matches!(vb, Value::Null) {
409                        continue;
410                    }
411                    let (Some(na), Some(nb)) =
412                        (self.find_value(ca, &va), self.find_value(cb, &vb))
413                    else {
414                        continue;
415                    };
416                    if seen.insert((na, nb)) {
417                        f.edges.entry(na).or_default().push((lb.clone(), nb));
418                        f.edges.entry(nb).or_default().push((la.clone(), na));
419                    }
420                }
421            }
422            f
423        })
424    }
425
426    /// The container a `ref`-declared field points into, and the
427    /// container's name (the edge label reaching it).
428    /// The container a `ref`-declared field points into, with the
429    /// role a hop landing there is labelled by.
430    fn field_container(&self, field: &str) -> Option<(usize, String)> {
431        let decl = self.model.refs.iter().find(|r| r.field == field)?;
432        let c = self.container_by_name(&decl.container)?;
433        Some((c, self.containers()[c].role.clone()))
434    }
435
436    /// The base nodes selected by a scope path.
437    fn scope_nodes(&self, scope: &str, defs: &quarb::Defs) -> Vec<NodeId> {
438        match quarb::run_with_defs(scope, defs, &self.base) {
439            Ok(QueryResult::Nodes(ns)) => ns,
440            _ => Vec::new(),
441        }
442    }
443
444    /// The nodes a path selects over the base *and* the derived
445    /// containers — the view a relation's two ends navigate, since
446    /// either may be derived. It never consults the fabric, so
447    /// building the fabric cannot recurse into itself.
448    fn view_nodes(&self, path: &str, defs: &quarb::Defs) -> Vec<NodeId> {
449        let view = PriorView {
450            base: &self.base,
451            prior: self.containers(),
452        };
453        match quarb::run_with_defs(path, defs, &view) {
454            Ok(QueryResult::Nodes(ns)) => ns,
455            _ => Vec::new(),
456        }
457    }
458
459    /// Substitute the driver operand in a relation's condition:
460    /// `$$::field` becomes that property of the source node, bare
461    /// `$$` its default projection. Text inside string literals is
462    /// left alone.
463    fn bind_driver(&self, cond: &str, source: NodeId) -> String {
464        let view = PriorView {
465            base: &self.base,
466            prior: self.containers(),
467        };
468        let b: Vec<char> = cond.chars().collect();
469        let mut out = String::new();
470        let mut i = 0;
471        while i < b.len() {
472            match b[i] {
473                q @ ('\'' | '"') => {
474                    out.push(q);
475                    i += 1;
476                    while i < b.len() && b[i] != q {
477                        if b[i] == '\\' && i + 1 < b.len() {
478                            out.push(b[i]);
479                            i += 1;
480                        }
481                        out.push(b[i]);
482                        i += 1;
483                    }
484                    if i < b.len() {
485                        out.push(b[i]);
486                        i += 1;
487                    }
488                }
489                '$' if b.get(i + 1) == Some(&'$') => {
490                    i += 2;
491                    let value = if b.get(i) == Some(&':') && b.get(i + 1) == Some(&':') {
492                        i += 2;
493                        let start = i;
494                        while i < b.len()
495                            && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '-')
496                        {
497                            i += 1;
498                        }
499                        let field: String = b[start..i].iter().collect();
500                        view.property(source, &field)
501                    } else {
502                        view.default_value(source)
503                    };
504                    out.push_str(&literal(value.unwrap_or(Value::Null)));
505                }
506                c => {
507                    out.push(c);
508                    i += 1;
509                }
510            }
511        }
512        out
513    }
514}
515
516/// Render a value as query-source text, so a bound driver operand
517/// reads back as the literal it stands for.
518fn literal(v: Value) -> String {
519    match v {
520        Value::Int(n) => n.to_string(),
521        Value::Float(f) => f.to_string(),
522        Value::Bool(b) => b.to_string(),
523        Value::Null => "''".to_string(),
524        other => format!("'{}'", other.to_string().replace('\\', "\\\\").replace('\'', "\\'")),
525    }
526}
527
528/// A read-only enrichment exposing just the already-built prior
529/// containers over the base — the view a chaining `node` constructor
530/// navigates. It never triggers further construction.
531struct PriorView<'a, A: AstAdapter> {
532    base: &'a A,
533    prior: &'a [Container],
534}
535
536impl<A: AstAdapter> AstAdapter for PriorView<'_, A> {
537    fn root(&self) -> NodeId {
538        self.base.root()
539    }
540    fn children(&self, node: NodeId) -> Vec<NodeId> {
541        if node.0 & MODEL_TAG != 0 {
542            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
543            let v = (node.0 & VAL_MASK) as usize;
544            if v == 0 && c < self.prior.len() {
545                return (0..self.prior[c].members.len())
546                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
547                    .collect();
548            }
549            return Vec::new();
550        }
551        let mut kids = self.base.children(node);
552        if node == self.base.root() {
553            for c in 0..self.prior.len() {
554                kids.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
555            }
556        }
557        kids
558    }
559    fn name(&self, node: NodeId) -> Option<String> {
560        if node.0 & MODEL_TAG != 0 {
561            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
562            let v = (node.0 & VAL_MASK) as usize;
563            let cont = self.prior.get(c)?;
564            // A derived child is named for its role, as in the full
565            // adapter — the value stays on `::`.
566            return Some(if v == 0 {
567                cont.name.clone()
568            } else {
569                cont.role.clone()
570            });
571        }
572        self.base.name(node)
573    }
574    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
575        if node == self.base.root() {
576            let mut out = self.base.children_named(node, name);
577            if let Some(c) = self.prior.iter().position(|k| k.name == name) {
578                out.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
579            }
580            return out;
581        }
582        if node.0 & MODEL_TAG != 0 {
583            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
584            let v = (node.0 & VAL_MASK) as usize;
585            let Some(cont) = self.prior.get(c) else {
586                return Vec::new();
587            };
588            if v == 0 && name == cont.role {
589                return (0..cont.members.len())
590                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
591                    .collect();
592            }
593            return Vec::new();
594        }
595        self.base.children_named(node, name)
596    }
597    fn traits(&self, node: NodeId) -> Vec<String> {
598        if node.0 & MODEL_TAG != 0 {
599            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
600            let v = (node.0 & VAL_MASK) as usize;
601            return match self.prior.get(c) {
602                Some(cont) if v > 0 => vec![cont.trait_name.clone()],
603                _ => Vec::new(),
604            };
605        }
606        self.base.traits(node)
607    }
608    fn parent(&self, node: NodeId) -> Option<NodeId> {
609        if node.0 & MODEL_TAG != 0 {
610            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
611            let v = (node.0 & VAL_MASK) as usize;
612            return Some(if v == 0 {
613                self.base.root()
614            } else {
615                NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
616            });
617        }
618        self.base.parent(node)
619    }
620    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
621        if node.0 & MODEL_TAG != 0 {
622            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
623            let v = (node.0 & VAL_MASK) as usize;
624            return match self.prior.get(c)?.member(v)? {
625                // elevated members have no named properties — the
626                // value lives on the bare projection only
627                Member::Value(_) => None,
628                Member::Node(n) => self.base.property(n, name),
629            };
630        }
631        self.base.property(node, name)
632    }
633    fn default_value(&self, node: NodeId) -> Option<Value> {
634        if node.0 & MODEL_TAG != 0 {
635            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
636            let v = (node.0 & VAL_MASK) as usize;
637            return match self.prior.get(c)?.member(v)? {
638                Member::Value(val) => Some(val),
639                Member::Node(n) => self.base.default_value(n),
640            };
641        }
642        self.base.default_value(node)
643    }
644}
645
646impl<A: AstAdapter> AstAdapter for ModelAdapter<A> {
647    fn root(&self) -> NodeId {
648        self.base.root()
649    }
650
651    fn children(&self, node: NodeId) -> Vec<NodeId> {
652        match self.decode(node) {
653            // A container node's children are its value nodes.
654            Some((c, 0)) => (0..self.containers()[c].members.len())
655                .map(|v| Self::value_node(c, v))
656                .collect(),
657            // An elevated node is a leaf; an aliased one has the
658            // children of the node it stands for.
659            Some(_) => match self.aliased(node) {
660                Some(base) => self.base.children(base),
661                None => Vec::new(),
662            },
663            // A base node: its own children, plus (at the root) the
664            // derived containers as new siblings.
665            None => {
666                let mut kids = self.base.children(node);
667                if node == self.base.root() {
668                    for c in 0..self.containers().len() {
669                        kids.push(Self::container_node(c));
670                    }
671                }
672                kids
673            }
674        }
675    }
676
677    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
678        // The root's fast path must see the derived containers too.
679        if node == self.base.root() {
680            let mut out = self.base.children_named(node, name);
681            if let Some(c) = self.container_by_name(name) {
682                out.push(Self::container_node(c));
683            }
684            return out;
685        }
686        match self.decode(node) {
687            // Every child answers to the role; none answers to its
688            // value (that is a predicate's job, not a name's).
689            Some((c, 0)) => {
690                let cont = &self.containers()[c];
691                if name == cont.role {
692                    (0..cont.members.len()).map(|v| Self::value_node(c, v)).collect()
693                } else {
694                    Vec::new()
695                }
696            }
697            Some(_) => match self.aliased(node) {
698                Some(base) => self.base.children_named(base, name),
699                None => Vec::new(),
700            },
701            None => self.base.children_named(node, name),
702        }
703    }
704
705    fn name(&self, node: NodeId) -> Option<String> {
706        match self.decode(node) {
707            Some((c, 0)) => Some(self.containers()[c].name.clone()),
708            // A derived node is named for its role, not its value:
709            // a name says what a node *is* where you found it. The
710            // value stays in the value space, on `::`.
711            Some((c, _)) => Some(self.containers()[c].role.clone()),
712            None => self.base.name(node),
713        }
714    }
715
716    fn parent(&self, node: NodeId) -> Option<NodeId> {
717        match self.decode(node) {
718            Some((_, 0)) => Some(self.base.root()),
719            Some((c, _)) => Some(Self::container_node(c)),
720            None => self.base.parent(node),
721        }
722    }
723
724    fn traits(&self, node: NodeId) -> Vec<String> {
725        match self.decode(node) {
726            // Each derived value node carries its container's trait,
727            // so mixed-type walk results self-describe (`[<ip>]`).
728            Some((c, v)) if v > 0 => {
729                let mut out = vec![self.containers()[c].trait_name.clone()];
730                // An aliased node is the source wearing a role, so it
731                // keeps the traits the source already carried.
732                if let Some(base) = self.aliased(node) {
733                    out.extend(self.base.traits(base));
734                }
735                out
736            }
737            Some(_) => Vec::new(),
738            None => self.base.traits(node),
739        }
740    }
741
742    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
743        match self.decode(node) {
744            // An aliased node answers with the source's own
745            // properties. An elevated node has exactly one datum —
746            // its value, on the *bare* projection (`::`) — and no
747            // named properties: answering any name with the value
748            // would let `::cookie` on an ip node return the ip.
749            Some((_, v)) if v > 0 => match self.aliased(node) {
750                Some(base) => self.base.property(base, name),
751                None => None,
752            },
753            Some(_) => None,
754            None => self.base.property(node, name),
755        }
756    }
757
758    fn default_value(&self, node: NodeId) -> Option<Value> {
759        match self.decode(node) {
760            Some((_, v)) if v > 0 => match self.aliased(node) {
761                Some(base) => self.base.default_value(base),
762                None => self.elevated(node),
763            },
764            Some(_) => None,
765            None => self.base.default_value(node),
766        }
767    }
768
769    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
770        match self.decode(node) {
771            Some((c, 0)) if key == "n-rows" => {
772                Some(Value::Int(self.containers()[c].members.len() as i64))
773            }
774            Some(_) => None,
775            None => self.base.metadata(node, key),
776        }
777    }
778
779    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
780        // An aliased node resolves as the node it stands for.
781        let under = self.aliased(node).unwrap_or(node);
782        if self.decode(under).is_none() {
783            if let Some(&target) = self.fabric().resolve.get(&(under, property.to_string())) {
784                return Some(target);
785            }
786        }
787        self.base.resolve(under, property, hint)
788    }
789
790    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
791        let f = self.fabric();
792        match self.decode(node) {
793            // A value node's crosslinks are its pair edges and any
794            // relation declared from it.
795            Some((_, v)) if v > 0 => {
796                let mut out = f.edges.get(&node).cloned().unwrap_or_default();
797                if let Some(rels) = f.rel_fwd.get(&node) {
798                    out.extend(rels.iter().cloned());
799                }
800                if let Some(base) = self.aliased(node) {
801                    out.extend(self.base.links(base));
802                    if let Some(refs) = f.ref_fwd.get(&base) {
803                        out.extend(refs.iter().cloned());
804                    }
805                }
806                out
807            }
808            Some(_) => Vec::new(),
809            // A base node: its own links plus any declared refs and
810            // relations.
811            None => {
812                let mut out = self.base.links(node);
813                if let Some(refs) = f.ref_fwd.get(&node) {
814                    out.extend(refs.iter().cloned());
815                }
816                if let Some(rels) = f.rel_fwd.get(&node) {
817                    out.extend(rels.iter().cloned());
818                }
819                out
820            }
821        }
822    }
823
824    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
825        let f = self.fabric();
826        match self.decode(node) {
827            // A value node: the base nodes whose declared ref points
828            // here, its pair edges (undirected), and the far end of
829            // any relation declared toward it.
830            Some((_, v)) if v > 0 => {
831                let mut out = f.ref_back.get(&node).cloned().unwrap_or_default();
832                if let Some(e) = f.edges.get(&node) {
833                    out.extend(e.iter().cloned());
834                }
835                if let Some(rels) = f.rel_back.get(&node) {
836                    out.extend(rels.iter().cloned());
837                }
838                out
839            }
840            Some(_) => Vec::new(),
841            None => {
842                let mut out = self.base.backlinks(node);
843                if let Some(rels) = f.rel_back.get(&node) {
844                    out.extend(rels.iter().cloned());
845                }
846                out
847            }
848        }
849    }
850
851    fn quantifier_bound(&self) -> usize {
852        self.base.quantifier_bound()
853    }
854    fn allow_shell(&self) -> bool {
855        self.base.allow_shell()
856    }
857    fn invocation_instant(&self) -> Option<(i64, u32)> {
858        self.base.invocation_instant()
859    }
860    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
861        self.base.unit_scale(expr)
862    }
863}
864
865/// The role of the nodes a scope path selects: the last *named*
866/// segment of the path (`/posts/*` -> `posts`, `/row` -> `row`).
867/// A base node's own name cannot serve — a relational row is named
868/// by its primary key, which is an identity, not a role — so the
869/// path that selected it supplies the word instead.
870fn scope_role(scope: &str) -> String {
871    scope
872        .split('/')
873        .filter(|seg| {
874            !seg.is_empty()
875                && !seg.starts_with('*')
876                && !seg.starts_with('[')
877                && !seg.chars().next().is_some_and(|c| c.is_ascii_digit())
878        })
879        .next_back()
880        .unwrap_or("")
881        .split(['[', ':'])
882        .next()
883        .unwrap_or("")
884        .to_string()
885}
886
887/// A borrowing adapter: lets a [`ModelAdapter`] enrich an adapter a
888/// caller holds by reference (behind `dyn`) without taking
889/// ownership — the shape both `qua`'s `run` funnel and `quai`'s
890/// per-query dispatch need, since the concrete adapter is already
891/// wrapped (now-binding, shell-gating) and only borrowed there.
892pub struct Borrowed<'a>(pub &'a dyn AstAdapter);
893
894impl AstAdapter for Borrowed<'_> {
895    fn root(&self) -> NodeId {
896        self.0.root()
897    }
898    fn children(&self, n: NodeId) -> Vec<NodeId> {
899        self.0.children(n)
900    }
901    fn name(&self, n: NodeId) -> Option<String> {
902        self.0.name(n)
903    }
904    fn parent(&self, n: NodeId) -> Option<NodeId> {
905        self.0.parent(n)
906    }
907    fn traits(&self, n: NodeId) -> Vec<String> {
908        self.0.traits(n)
909    }
910    fn children_named(&self, n: NodeId, name: &str) -> Vec<NodeId> {
911        self.0.children_named(n, name)
912    }
913    fn property(&self, n: NodeId, name: &str) -> Option<Value> {
914        self.0.property(n, name)
915    }
916    fn default_value(&self, n: NodeId) -> Option<Value> {
917        self.0.default_value(n)
918    }
919    fn metadata(&self, n: NodeId, key: &str) -> Option<Value> {
920        self.0.metadata(n, key)
921    }
922    fn links(&self, n: NodeId) -> Vec<(String, NodeId)> {
923        self.0.links(n)
924    }
925    fn backlinks(&self, n: NodeId) -> Vec<(String, NodeId)> {
926        self.0.backlinks(n)
927    }
928    fn resolve(&self, n: NodeId, p: &str, h: Option<&str>) -> Option<NodeId> {
929        self.0.resolve(n, p, h)
930    }
931    fn link_property(&self, s: NodeId, l: &str, t: NodeId, name: &str) -> Option<Value> {
932        self.0.link_property(s, l, t, name)
933    }
934    fn quantifier_bound(&self) -> usize {
935        self.0.quantifier_bound()
936    }
937    fn allow_shell(&self) -> bool {
938        self.0.allow_shell()
939    }
940    fn invocation_instant(&self) -> Option<(i64, u32)> {
941        self.0.invocation_instant()
942    }
943    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
944        self.0.unit_scale(expr)
945    }
946}