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::{parse_model, resolve_mount_target, EdgeDecl, Model, Mount, NodeDecl, RefDecl};
20
21use quarb::{AstAdapter, NodeId, QueryResult, Value};
22use std::cell::OnceCell;
23use std::collections::HashMap;
24
25/// Derived nodes carry this tag bit; the base's own ids never set it
26/// (the mount layer's index rides bits 56–63 but never reaches 128,
27/// so bit 63 stays clear for realistic inputs). Below it: the
28/// container index (bits 44–62) and the value index (bits 0–43).
29const MODEL_TAG: u64 = 1 << 63;
30const CIDX_SHIFT: u64 = 44;
31const VAL_MASK: u64 = (1 << CIDX_SHIFT) - 1;
32
33/// A derived container's materialized value set.
34struct Container {
35    name: String,
36    /// The distinct values, in first-appearance order (value index
37    /// `v` is `values[v]`, and its node carries value index `v+1`;
38    /// `0` names the container node itself).
39    values: Vec<Value>,
40    /// value string → value index (0-based into `values`).
41    by_str: HashMap<String, usize>,
42}
43
44/// The reference and edge fabric, built together on first use: it
45/// needs the containers materialized and one pass over each scope.
46struct Fabric {
47    /// (base node, field) → derived value node it resolves to.
48    resolve: HashMap<(NodeId, String), NodeId>,
49    /// derived value node → base nodes pointing at it, with the
50    /// field label (the backlink of a declared ref).
51    ref_back: HashMap<NodeId, Vec<(String, NodeId)>>,
52    /// base node → its outgoing declared refs (label, target node).
53    ref_fwd: HashMap<NodeId, Vec<(String, NodeId)>>,
54    /// derived value node → container-labeled neighbours (the pair
55    /// edges; parallel edges collapsed, undirected so stored both
56    /// ways).
57    edges: HashMap<NodeId, Vec<(String, NodeId)>>,
58}
59
60/// A base arbor enriched with a model's derived structure.
61pub struct ModelAdapter<A: AstAdapter> {
62    base: A,
63    model: Model,
64    containers: OnceCell<Vec<Container>>,
65    fabric: OnceCell<Fabric>,
66}
67
68impl<A: AstAdapter> ModelAdapter<A> {
69    pub fn new(base: A, model: Model) -> Self {
70        ModelAdapter {
71            base,
72            model,
73            containers: OnceCell::new(),
74            fabric: OnceCell::new(),
75        }
76    }
77
78    pub fn base(&self) -> &A {
79        &self.base
80    }
81
82    /// A human-readable locator, composing the base's own renderer
83    /// for base nodes: `/container/value` for derived nodes.
84    pub fn locator(&self, node: NodeId, base_locator: impl Fn(NodeId) -> String) -> String {
85        match self.decode(node) {
86            None => base_locator(node),
87            Some((c, 0)) => format!("/{}", self.containers()[c].name),
88            Some((c, v)) => {
89                let cont = &self.containers()[c];
90                format!("/{}/{}", cont.name, cont.values[v - 1])
91            }
92        }
93    }
94
95    fn seeded_defs(&self) -> quarb::Defs {
96        quarb::parse_defs(&self.model.defs_text).unwrap_or_default()
97    }
98
99    /// The derived containers, materialized on first touch by running
100    /// each constructor over the base. A later constructor sees the
101    /// containers declared before it (chaining), so this fills the
102    /// vector incrementally.
103    fn containers(&self) -> &[Container] {
104        self.containers.get_or_init(|| {
105            let defs = self.seeded_defs();
106            let mut built: Vec<Container> = Vec::new();
107            for decl in &self.model.nodes {
108                let values = self.distinct_values(&decl.query, &defs, &built);
109                let by_str = values
110                    .iter()
111                    .enumerate()
112                    .map(|(i, v)| (v.to_string(), i))
113                    .collect();
114                built.push(Container {
115                    name: decl.name.clone(),
116                    values,
117                    by_str,
118                });
119            }
120            built
121        })
122    }
123
124    /// Run one constructor query and collect its distinct values, in
125    /// first-appearance order, string-keyed. `prior` is the
126    /// containers already built (so a constructor may navigate an
127    /// earlier derived container) — exposed by wrapping the base in a
128    /// partial [`ModelAdapter`] over those.
129    fn distinct_values(
130        &self,
131        query: &str,
132        defs: &quarb::Defs,
133        prior: &[Container],
134    ) -> Vec<Value> {
135        let mut seen = std::collections::HashSet::new();
136        let mut out = Vec::new();
137        let mut push = |vs: Vec<Value>| {
138            for v in vs {
139                if seen.insert(v.to_string()) {
140                    out.push(v);
141                }
142            }
143        };
144        // A constructor over the base alone is the common case; one
145        // that reaches an earlier derived container runs against a
146        // scratch enrichment holding just those.
147        let result = if prior.is_empty() {
148            quarb::run_with_defs(query, defs, &self.base)
149        } else {
150            let scratch = PriorView {
151                base: &self.base,
152                prior,
153            };
154            quarb::run_with_defs(query, defs, &scratch)
155        };
156        match result {
157            Ok(QueryResult::Values(vs)) => push(vs),
158            Ok(QueryResult::Nodes(ns)) => {
159                // A node result contributes each node's name as a
160                // value (a bare `/container/*` constructor).
161                let named = ns
162                    .into_iter()
163                    .filter_map(|n| self.base.name(n).map(Value::Str))
164                    .collect();
165                push(named)
166            }
167            Err(_) => {}
168        }
169        out
170    }
171
172    fn container_node(c: usize) -> NodeId {
173        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT)
174    }
175
176    fn value_node(c: usize, v: usize) -> NodeId {
177        NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (v as u64 + 1))
178    }
179
180    /// Decode a derived node into `(container, value-slot)` where
181    /// slot 0 is the container node and slot `v` is value `v-1`.
182    /// `None` for a base node.
183    fn decode(&self, node: NodeId) -> Option<(usize, usize)> {
184        if node.0 & MODEL_TAG == 0 {
185            return None;
186        }
187        let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
188        let v = (node.0 & VAL_MASK) as usize;
189        (c < self.containers().len()).then_some((c, v))
190    }
191
192    fn container_by_name(&self, name: &str) -> Option<usize> {
193        self.containers().iter().position(|c| c.name == name)
194    }
195
196    /// The value node in `container` whose string equals `value`.
197    fn find_value(&self, container: usize, value: &Value) -> Option<NodeId> {
198        let idx = *self.containers()[container].by_str.get(&value.to_string())?;
199        Some(Self::value_node(container, idx))
200    }
201
202    /// The reference and edge fabric, built on first use.
203    fn fabric(&self) -> &Fabric {
204        self.fabric.get_or_init(|| {
205            let defs = self.seeded_defs();
206            let mut f = Fabric {
207                resolve: HashMap::new(),
208                ref_back: HashMap::new(),
209                ref_fwd: HashMap::new(),
210                edges: HashMap::new(),
211            };
212            // References: for each scoped base node, resolve its
213            // field value into the target container.
214            for decl in &self.model.refs {
215                let Some(container) = self.container_by_name(&decl.container) else {
216                    continue;
217                };
218                for node in self.scope_nodes(&decl.scope, &defs) {
219                    let Some(value) = self.base.property(node, &decl.field) else {
220                        continue;
221                    };
222                    if matches!(value, Value::Null) {
223                        continue;
224                    }
225                    let Some(target) = self.find_value(container, &value) else {
226                        continue;
227                    };
228                    f.resolve.insert((node, decl.field.clone()), target);
229                    f.ref_fwd
230                        .entry(node)
231                        .or_default()
232                        .push((decl.field.clone(), target));
233                    f.ref_back
234                        .entry(target)
235                        .or_default()
236                        .push((decl.field.clone(), node));
237                }
238            }
239            // Edges: read the two fields per scoped node, connect the
240            // two derived value nodes, labelled by the container each
241            // reaches; collapse parallel edges.
242            for decl in &self.model.edges {
243                let ca = self.field_container(&decl.field_a);
244                let cb = self.field_container(&decl.field_b);
245                let (Some((ca, la)), Some((cb, lb))) = (ca, cb) else {
246                    continue;
247                };
248                let mut seen = std::collections::HashSet::new();
249                for node in self.scope_nodes(&decl.scope, &defs) {
250                    let (Some(va), Some(vb)) = (
251                        self.base.property(node, &decl.field_a),
252                        self.base.property(node, &decl.field_b),
253                    ) else {
254                        continue;
255                    };
256                    if matches!(va, Value::Null) || matches!(vb, Value::Null) {
257                        continue;
258                    }
259                    let (Some(na), Some(nb)) =
260                        (self.find_value(ca, &va), self.find_value(cb, &vb))
261                    else {
262                        continue;
263                    };
264                    if seen.insert((na, nb)) {
265                        f.edges.entry(na).or_default().push((lb.clone(), nb));
266                        f.edges.entry(nb).or_default().push((la.clone(), na));
267                    }
268                }
269            }
270            f
271        })
272    }
273
274    /// The container a `ref`-declared field points into, and the
275    /// container's name (the edge label reaching it).
276    fn field_container(&self, field: &str) -> Option<(usize, String)> {
277        let decl = self.model.refs.iter().find(|r| r.field == field)?;
278        let c = self.container_by_name(&decl.container)?;
279        Some((c, decl.container.clone()))
280    }
281
282    /// The base nodes selected by a scope path.
283    fn scope_nodes(&self, scope: &str, defs: &quarb::Defs) -> Vec<NodeId> {
284        match quarb::run_with_defs(scope, defs, &self.base) {
285            Ok(QueryResult::Nodes(ns)) => ns,
286            _ => Vec::new(),
287        }
288    }
289}
290
291/// A read-only enrichment exposing just the already-built prior
292/// containers over the base — the view a chaining `node` constructor
293/// navigates. It never triggers further construction.
294struct PriorView<'a, A: AstAdapter> {
295    base: &'a A,
296    prior: &'a [Container],
297}
298
299impl<A: AstAdapter> AstAdapter for PriorView<'_, A> {
300    fn root(&self) -> NodeId {
301        self.base.root()
302    }
303    fn children(&self, node: NodeId) -> Vec<NodeId> {
304        if node.0 & MODEL_TAG != 0 {
305            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
306            let v = (node.0 & VAL_MASK) as usize;
307            if v == 0 && c < self.prior.len() {
308                return (0..self.prior[c].values.len())
309                    .map(|i| NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT | (i as u64 + 1)))
310                    .collect();
311            }
312            return Vec::new();
313        }
314        let mut kids = self.base.children(node);
315        if node == self.base.root() {
316            for c in 0..self.prior.len() {
317                kids.push(NodeId(MODEL_TAG | (c as u64) << CIDX_SHIFT));
318            }
319        }
320        kids
321    }
322    fn name(&self, node: NodeId) -> Option<String> {
323        if node.0 & MODEL_TAG != 0 {
324            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
325            let v = (node.0 & VAL_MASK) as usize;
326            let cont = self.prior.get(c)?;
327            return Some(if v == 0 {
328                cont.name.clone()
329            } else {
330                cont.values[v - 1].to_string()
331            });
332        }
333        self.base.name(node)
334    }
335    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
336        if node.0 & MODEL_TAG != 0 {
337            return None;
338        }
339        self.base.property(node, name)
340    }
341    fn default_value(&self, node: NodeId) -> Option<Value> {
342        if node.0 & MODEL_TAG != 0 {
343            let c = ((node.0 & !MODEL_TAG) >> CIDX_SHIFT) as usize;
344            let v = (node.0 & VAL_MASK) as usize;
345            return (v > 0).then(|| self.prior.get(c).map(|k| k.values[v - 1].clone()))?;
346        }
347        self.base.default_value(node)
348    }
349}
350
351impl<A: AstAdapter> AstAdapter for ModelAdapter<A> {
352    fn root(&self) -> NodeId {
353        self.base.root()
354    }
355
356    fn children(&self, node: NodeId) -> Vec<NodeId> {
357        match self.decode(node) {
358            // A container node's children are its value nodes.
359            Some((c, 0)) => (0..self.containers()[c].values.len())
360                .map(|v| Self::value_node(c, v))
361                .collect(),
362            // A value node is a leaf.
363            Some(_) => Vec::new(),
364            // A base node: its own children, plus (at the root) the
365            // derived containers as new siblings.
366            None => {
367                let mut kids = self.base.children(node);
368                if node == self.base.root() {
369                    for c in 0..self.containers().len() {
370                        kids.push(Self::container_node(c));
371                    }
372                }
373                kids
374            }
375        }
376    }
377
378    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
379        // The root's fast path must see the derived containers too.
380        if node == self.base.root() {
381            let mut out = self.base.children_named(node, name);
382            if let Some(c) = self.container_by_name(name) {
383                out.push(Self::container_node(c));
384            }
385            return out;
386        }
387        match self.decode(node) {
388            Some((c, 0)) => {
389                let cont = &self.containers()[c];
390                cont.by_str
391                    .get(name)
392                    .map(|&v| vec![Self::value_node(c, v)])
393                    .unwrap_or_default()
394            }
395            Some(_) => Vec::new(),
396            None => self.base.children_named(node, name),
397        }
398    }
399
400    fn name(&self, node: NodeId) -> Option<String> {
401        match self.decode(node) {
402            Some((c, 0)) => Some(self.containers()[c].name.clone()),
403            Some((c, v)) => Some(self.containers()[c].values[v - 1].to_string()),
404            None => self.base.name(node),
405        }
406    }
407
408    fn parent(&self, node: NodeId) -> Option<NodeId> {
409        match self.decode(node) {
410            Some((_, 0)) => Some(self.base.root()),
411            Some((c, _)) => Some(Self::container_node(c)),
412            None => self.base.parent(node),
413        }
414    }
415
416    fn traits(&self, node: NodeId) -> Vec<String> {
417        match self.decode(node) {
418            // Each derived value node carries its container's trait,
419            // so mixed-type walk results self-describe (`[<ip>]`).
420            Some((c, v)) if v > 0 => vec![singular(&self.containers()[c].name)],
421            Some(_) => Vec::new(),
422            None => self.base.traits(node),
423        }
424    }
425
426    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
427        match self.decode(node) {
428            // A value node's own value answers `::id` (and any
429            // name), so a derived node prints like a row with one
430            // column.
431            Some((c, v)) if v > 0 => Some(self.containers()[c].values[v - 1].clone()),
432            Some(_) => None,
433            None => self.base.property(node, name),
434        }
435    }
436
437    fn default_value(&self, node: NodeId) -> Option<Value> {
438        match self.decode(node) {
439            Some((c, v)) if v > 0 => Some(self.containers()[c].values[v - 1].clone()),
440            Some(_) => None,
441            None => self.base.default_value(node),
442        }
443    }
444
445    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
446        match self.decode(node) {
447            Some((c, 0)) if key == "n-rows" => {
448                Some(Value::Int(self.containers()[c].values.len() as i64))
449            }
450            Some(_) => None,
451            None => self.base.metadata(node, key),
452        }
453    }
454
455    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
456        if self.decode(node).is_none() {
457            if let Some(&target) = self.fabric().resolve.get(&(node, property.to_string())) {
458                return Some(target);
459            }
460        }
461        self.base.resolve(node, property, hint)
462    }
463
464    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
465        match self.decode(node) {
466            // A value node's crosslinks are its pair edges.
467            Some((_, v)) if v > 0 => {
468                self.fabric().edges.get(&node).cloned().unwrap_or_default()
469            }
470            Some(_) => Vec::new(),
471            // A base node: its own links plus any declared refs.
472            None => {
473                let mut out = self.base.links(node);
474                if let Some(refs) = self.fabric().ref_fwd.get(&node) {
475                    out.extend(refs.iter().cloned());
476                }
477                out
478            }
479        }
480    }
481
482    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
483        match self.decode(node) {
484            // A value node: the base nodes whose declared ref points
485            // here, plus its pair edges (undirected).
486            Some((_, v)) if v > 0 => {
487                let mut out = self.fabric().ref_back.get(&node).cloned().unwrap_or_default();
488                if let Some(e) = self.fabric().edges.get(&node) {
489                    out.extend(e.iter().cloned());
490                }
491                out
492            }
493            Some(_) => Vec::new(),
494            None => self.base.backlinks(node),
495        }
496    }
497
498    fn quantifier_bound(&self) -> usize {
499        self.base.quantifier_bound()
500    }
501    fn allow_shell(&self) -> bool {
502        self.base.allow_shell()
503    }
504    fn invocation_instant(&self) -> Option<(i64, u32)> {
505        self.base.invocation_instant()
506    }
507    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
508        self.base.unit_scale(expr)
509    }
510}
511
512/// A container name's singular trait form: `ips` → `ip`, `cookies` →
513/// `cookie` (drop a trailing `s`); other names stand as-is.
514fn singular(name: &str) -> String {
515    name.strip_suffix('s').unwrap_or(name).to_string()
516}
517
518/// A borrowing adapter: lets a [`ModelAdapter`] enrich an adapter a
519/// caller holds by reference (behind `dyn`) without taking
520/// ownership — the shape both `qua`'s `run` funnel and `quai`'s
521/// per-query dispatch need, since the concrete adapter is already
522/// wrapped (now-binding, shell-gating) and only borrowed there.
523pub struct Borrowed<'a>(pub &'a dyn AstAdapter);
524
525impl AstAdapter for Borrowed<'_> {
526    fn root(&self) -> NodeId {
527        self.0.root()
528    }
529    fn children(&self, n: NodeId) -> Vec<NodeId> {
530        self.0.children(n)
531    }
532    fn name(&self, n: NodeId) -> Option<String> {
533        self.0.name(n)
534    }
535    fn parent(&self, n: NodeId) -> Option<NodeId> {
536        self.0.parent(n)
537    }
538    fn traits(&self, n: NodeId) -> Vec<String> {
539        self.0.traits(n)
540    }
541    fn children_named(&self, n: NodeId, name: &str) -> Vec<NodeId> {
542        self.0.children_named(n, name)
543    }
544    fn property(&self, n: NodeId, name: &str) -> Option<Value> {
545        self.0.property(n, name)
546    }
547    fn default_value(&self, n: NodeId) -> Option<Value> {
548        self.0.default_value(n)
549    }
550    fn metadata(&self, n: NodeId, key: &str) -> Option<Value> {
551        self.0.metadata(n, key)
552    }
553    fn links(&self, n: NodeId) -> Vec<(String, NodeId)> {
554        self.0.links(n)
555    }
556    fn backlinks(&self, n: NodeId) -> Vec<(String, NodeId)> {
557        self.0.backlinks(n)
558    }
559    fn resolve(&self, n: NodeId, p: &str, h: Option<&str>) -> Option<NodeId> {
560        self.0.resolve(n, p, h)
561    }
562    fn link_property(&self, s: NodeId, l: &str, t: NodeId, name: &str) -> Option<Value> {
563        self.0.link_property(s, l, t, name)
564    }
565    fn quantifier_bound(&self) -> usize {
566        self.0.quantifier_bound()
567    }
568    fn allow_shell(&self) -> bool {
569        self.0.allow_shell()
570    }
571    fn invocation_instant(&self) -> Option<(i64, u32)> {
572        self.0.invocation_instant()
573    }
574    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
575        self.0.unit_scale(expr)
576    }
577}