json_eval_rs/parse_schema/
common.rs

1use crate::jsoneval::path_utils;
2use crate::jsoneval::table_metadata::ColumnMetadata;
3/// Shared utilities for schema parsing (used by both legacy and parsed implementations)
4use indexmap::IndexSet;
5use serde_json::Value;
6
7/// Collect $ref dependencies from a JSON value recursively
8pub fn collect_refs(value: &Value, refs: &mut IndexSet<String>) {
9    match value {
10        Value::Object(map) => {
11            if let Some(path) = map.get("$ref").and_then(Value::as_str) {
12                refs.insert(path_utils::normalize_to_json_pointer(path));
13            }
14            if let Some(path) = map.get("ref").and_then(Value::as_str) {
15                refs.insert(path_utils::normalize_to_json_pointer(path));
16            }
17            if let Some(var_val) = map.get("var") {
18                match var_val {
19                    Value::String(s) => {
20                        refs.insert(s.clone());
21                    }
22                    Value::Array(arr) => {
23                        if let Some(path) = arr.get(0).and_then(Value::as_str) {
24                            refs.insert(path.to_string());
25                        }
26                    }
27                    _ => {}
28                }
29            }
30            for val in map.values() {
31                collect_refs(val, refs);
32            }
33        }
34        Value::Array(arr) => {
35            for val in arr {
36                collect_refs(val, refs);
37            }
38        }
39        _ => {}
40    }
41}
42
43/// Compute forward/normal column partitions with transitive closure
44///
45/// This function identifies which columns have forward references (dependencies on later columns)
46/// and separates them from normal columns for proper evaluation order.
47pub fn compute_column_partitions(columns: &[ColumnMetadata]) -> (Vec<usize>, Vec<usize>) {
48    use std::collections::HashSet;
49
50    // Build set of all forward-referencing column names (direct + transitive)
51    let mut fwd_cols = HashSet::new();
52    for col in columns {
53        if col.has_forward_ref {
54            fwd_cols.insert(col.name.as_ref());
55        }
56    }
57
58    // Transitive closure: any column that depends on forward columns is also forward
59    loop {
60        let mut changed = false;
61        for col in columns {
62            if !fwd_cols.contains(col.name.as_ref()) {
63                // Check if this column depends on any forward column
64                for dep in col.dependencies.iter() {
65                    // Strip $ prefix from dependency name for comparison
66                    let dep_name = dep.trim_start_matches('$');
67                    if fwd_cols.contains(dep_name) {
68                        fwd_cols.insert(col.name.as_ref());
69                        changed = true;
70                        break;
71                    }
72                }
73            }
74        }
75        // Stop when no more changes
76        if !changed {
77            break;
78        }
79    }
80
81    // Separate into forward and normal indices
82    let mut forward_indices = Vec::new();
83    let mut normal_indices = Vec::new();
84
85    for (idx, col) in columns.iter().enumerate() {
86        if fwd_cols.contains(col.name.as_ref()) {
87            forward_indices.push(idx);
88        } else {
89            normal_indices.push(idx);
90        }
91    }
92
93    (forward_indices, normal_indices)
94}