sql_cli/data/row_expanders/
mod.rs1use crate::data::datatable::DataValue;
14use anyhow::Result;
15
16#[derive(Debug, Clone)]
18pub struct ExpansionResult {
19 pub values: Vec<DataValue>,
21}
22
23impl ExpansionResult {
24 pub fn new(values: Vec<DataValue>) -> Self {
26 Self { values }
27 }
28
29 pub fn row_count(&self) -> usize {
31 self.values.len()
32 }
33}
34
35pub trait RowExpander: Send + Sync {
40 fn name(&self) -> &str;
42
43 fn description(&self) -> &str;
45
46 fn expand(&self, value: &DataValue, args: &[DataValue]) -> Result<ExpansionResult>;
55}
56
57pub struct RowExpanderRegistry {
59 expanders: std::collections::HashMap<String, Box<dyn RowExpander>>,
60}
61
62impl RowExpanderRegistry {
63 pub fn new() -> Self {
65 let mut registry = Self {
66 expanders: std::collections::HashMap::new(),
67 };
68
69 registry.register(Box::new(unnest::UnnestExpander));
71
72 registry
73 }
74
75 pub fn register(&mut self, expander: Box<dyn RowExpander>) {
77 self.expanders
78 .insert(expander.name().to_uppercase(), expander);
79 }
80
81 pub fn get(&self, name: &str) -> Option<&dyn RowExpander> {
83 self.expanders.get(&name.to_uppercase()).map(|e| e.as_ref())
84 }
85
86 pub fn contains(&self, name: &str) -> bool {
88 self.expanders.contains_key(&name.to_uppercase())
89 }
90
91 pub fn list(&self) -> Vec<&str> {
93 self.expanders.keys().map(|s| s.as_str()).collect()
94 }
95}
96
97impl Default for RowExpanderRegistry {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103pub mod unnest {
105 use super::*;
106
107 pub struct UnnestExpander;
108
109 impl RowExpander for UnnestExpander {
110 fn name(&self) -> &str {
111 "UNNEST"
112 }
113
114 fn description(&self) -> &str {
115 "Split a delimited string into multiple rows"
116 }
117
118 fn expand(&self, value: &DataValue, args: &[DataValue]) -> Result<ExpansionResult> {
119 let delimiter = match args.first() {
121 Some(DataValue::String(s)) => s.as_str(),
122 Some(_) => {
123 return Err(anyhow::anyhow!(
124 "UNNEST delimiter must be a string, got {:?}",
125 args.first()
126 ))
127 }
128 None => return Err(anyhow::anyhow!("UNNEST requires a delimiter argument")),
129 };
130
131 let text = match value {
133 DataValue::String(s) => s.clone(),
134 DataValue::Null => {
135 return Ok(ExpansionResult::new(vec![DataValue::Null]));
137 }
138 other => other.to_string(),
139 };
140
141 let parts: Vec<DataValue> = if delimiter.is_empty() {
143 text.chars()
145 .map(|ch| DataValue::String(ch.to_string()))
146 .collect()
147 } else {
148 text.split(delimiter)
150 .filter(|s| !s.is_empty())
151 .map(|s| DataValue::String(s.to_string()))
152 .collect()
153 };
154
155 if parts.is_empty() {
157 Ok(ExpansionResult::new(vec![DataValue::Null]))
158 } else {
159 Ok(ExpansionResult::new(parts))
160 }
161 }
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_unnest_basic() {
171 let expander = unnest::UnnestExpander;
172 let value = DataValue::String("A|B|C".to_string());
173 let delimiter = DataValue::String("|".to_string());
174
175 let result = expander.expand(&value, &[delimiter]).unwrap();
176 assert_eq!(result.row_count(), 3);
177 assert_eq!(result.values[0], DataValue::String("A".to_string()));
178 assert_eq!(result.values[1], DataValue::String("B".to_string()));
179 assert_eq!(result.values[2], DataValue::String("C".to_string()));
180 }
181
182 #[test]
183 fn test_unnest_null() {
184 let expander = unnest::UnnestExpander;
185 let value = DataValue::Null;
186 let delimiter = DataValue::String("|".to_string());
187
188 let result = expander.expand(&value, &[delimiter]).unwrap();
189 assert_eq!(result.row_count(), 1);
190 assert_eq!(result.values[0], DataValue::Null);
191 }
192
193 #[test]
194 fn test_registry() {
195 let registry = RowExpanderRegistry::new();
196 assert!(registry.contains("UNNEST"));
197 assert!(registry.contains("unnest"));
198
199 let expander = registry.get("UNNEST").unwrap();
200 assert_eq!(expander.name(), "UNNEST");
201 }
202}