Skip to main content

sqlcx_core/
param_naming.rs

1use std::collections::{HashMap, HashSet};
2
3pub struct RawParam {
4    pub index: u32,
5    pub column: Option<String>,
6    pub r#override: Option<String>,
7}
8
9pub fn resolve_param_names(params: &[RawParam]) -> Vec<String> {
10    // Pass 1: count column frequency for params without an override
11    let mut freq: HashMap<&str, u32> = HashMap::new();
12    for p in params {
13        if p.r#override.is_none()
14            && let Some(col) = &p.column
15        {
16            *freq.entry(col.as_str()).or_insert(0) += 1;
17        }
18    }
19
20    // Pass 2: assign names, then dedup any collisions
21    let mut counters: HashMap<&str, u32> = HashMap::new();
22    let mut seen: HashSet<String> = HashSet::new();
23    let mut result: Vec<String> = Vec::with_capacity(params.len());
24
25    for p in params {
26        let mut name = if let Some(ov) = &p.r#override {
27            ov.clone()
28        } else if let Some(col) = &p.column {
29            if freq.get(col.as_str()).copied().unwrap_or(0) > 1 {
30                let n = counters.entry(col.as_str()).or_insert(0);
31                *n += 1;
32                format!("{}_{}", col, n)
33            } else {
34                col.clone()
35            }
36        } else {
37            format!("param_{}", p.index)
38        };
39
40        // Dedup: resolve any remaining collisions
41        let base = name.clone();
42        let mut suffix = 1u32;
43        while seen.contains(&name) {
44            name = format!("{}_{}", base, suffix);
45            suffix += 1;
46        }
47
48        seen.insert(name.clone());
49        result.push(name);
50    }
51
52    result
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn simple_column_name() {
61        let params = vec![RawParam {
62            index: 1,
63            column: Some("id".to_string()),
64            r#override: None,
65        }];
66        assert_eq!(resolve_param_names(&params), vec!["id"]);
67    }
68
69    #[test]
70    fn collision_adds_suffix() {
71        let params = vec![
72            RawParam {
73                index: 1,
74                column: Some("created_at".to_string()),
75                r#override: None,
76            },
77            RawParam {
78                index: 2,
79                column: Some("created_at".to_string()),
80                r#override: None,
81            },
82        ];
83        assert_eq!(
84            resolve_param_names(&params),
85            vec!["created_at_1", "created_at_2"]
86        );
87    }
88
89    #[test]
90    fn null_column_falls_back() {
91        let params = vec![RawParam {
92            index: 1,
93            column: None,
94            r#override: None,
95        }];
96        assert_eq!(resolve_param_names(&params), vec!["param_1"]);
97    }
98
99    #[test]
100    fn override_takes_precedence() {
101        let params = vec![RawParam {
102            index: 1,
103            column: Some("created_at".to_string()),
104            r#override: Some("start_date".to_string()),
105        }];
106        assert_eq!(resolve_param_names(&params), vec!["start_date"]);
107    }
108
109    #[test]
110    fn dedup_override_vs_inferred() {
111        let params = vec![
112            RawParam {
113                index: 1,
114                column: Some("id".to_string()),
115                r#override: Some("id".to_string()),
116            },
117            RawParam {
118                index: 2,
119                column: Some("id".to_string()),
120                r#override: None,
121            },
122        ];
123        let result = resolve_param_names(&params);
124        assert_eq!(result[0], "id");
125        assert_eq!(result[1], "id_1");
126    }
127}