fv_plan/row.rs
1//! Ordered pipeline rows. Column order is significant (it becomes the output schema), so a row is an
2//! ordered list of (name, value) — not a sorted map. Values are `fv_value::Value`.
3
4use fv_value::{Scope, Value};
5
6/// One pipeline row, preserving column order (unlike a sorted map).
7#[derive(Debug, Clone, Default, PartialEq)]
8pub struct Row(pub Vec<(String, Value)>);
9
10impl Row {
11 pub fn new() -> Self {
12 Row(Vec::new())
13 }
14
15 pub fn get(&self, key: &str) -> Value {
16 self.0
17 .iter()
18 .find(|(k, _)| k == key)
19 .map(|(_, v)| v.clone())
20 .unwrap_or(Value::Null)
21 }
22
23 pub fn contains(&self, key: &str) -> bool {
24 self.0.iter().any(|(k, _)| k == key)
25 }
26
27 /// Set a key: overwrite in place if present (preserving position), else append.
28 pub fn set(&mut self, key: &str, value: Value) {
29 if let Some(slot) = self.0.iter_mut().find(|(k, _)| k == key) {
30 slot.1 = value;
31 } else {
32 self.0.push((key.to_string(), value));
33 }
34 }
35
36 pub fn columns(&self) -> Vec<String> {
37 self.0.iter().map(|(k, _)| k.clone()).collect()
38 }
39
40 /// A lookup scope for the value dialect (order irrelevant for evaluation). Prefer
41 /// [`lookup`](Self::lookup) on the hot path — it evaluates directly against the row with no map
42 /// build (T3).
43 pub fn scope(&self) -> Scope {
44 self.0.iter().cloned().collect()
45 }
46
47 /// The row as a zero-copy [`fv_value::Lookup`] — evaluate an expression against it without
48 /// building a `Scope` per row (the T0-measured 211ns/row saved on filter/applyExpression).
49 pub fn lookup(&self) -> &[(String, Value)] {
50 &self.0
51 }
52}