json_eval_rs/parse_schema/
common.rs

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