xdl_database/
recordset.rs

1//! Recordset - represents query results
2
3use serde_json::Value as JsonValue;
4use std::collections::HashMap;
5use xdl_core::{XdlError, XdlResult, XdlValue};
6
7/// Column information
8#[derive(Debug, Clone)]
9pub struct ColumnInfo {
10    pub name: String,
11    pub data_type: String,
12    pub ordinal: usize,
13}
14
15/// Recordset containing query results
16#[derive(Debug, Clone)]
17pub struct Recordset {
18    columns: Vec<ColumnInfo>,
19    rows: Vec<Vec<JsonValue>>,
20    current_row: usize,
21}
22
23impl Recordset {
24    /// Create a new recordset
25    pub fn new(columns: Vec<ColumnInfo>, rows: Vec<Vec<JsonValue>>) -> Self {
26        Self {
27            columns,
28            rows,
29            current_row: 0,
30        }
31    }
32
33    /// Create an empty recordset
34    pub fn empty() -> Self {
35        Self {
36            columns: Vec::new(),
37            rows: Vec::new(),
38            current_row: 0,
39        }
40    }
41
42    /// Get the number of rows
43    pub fn row_count(&self) -> usize {
44        self.rows.len()
45    }
46
47    /// Get the number of columns
48    pub fn column_count(&self) -> usize {
49        self.columns.len()
50    }
51
52    /// Get column names
53    pub fn column_names(&self) -> Vec<String> {
54        self.columns.iter().map(|c| c.name.clone()).collect()
55    }
56
57    /// Get column information
58    pub fn columns(&self) -> &[ColumnInfo] {
59        &self.columns
60    }
61
62    /// Move to the next row
63    #[allow(clippy::should_implement_trait)]
64    pub fn next(&mut self) -> bool {
65        if self.current_row < self.rows.len() {
66            self.current_row += 1;
67            true
68        } else {
69            false
70        }
71    }
72
73    /// Reset to the first row
74    pub fn reset(&mut self) {
75        self.current_row = 0;
76    }
77
78    /// Get current row data as a HashMap
79    pub fn current_row(&self) -> Option<HashMap<String, JsonValue>> {
80        if self.current_row < self.rows.len() {
81            let row = &self.rows[self.current_row];
82            let mut map = HashMap::new();
83
84            for (i, value) in row.iter().enumerate() {
85                if let Some(col) = self.columns.get(i) {
86                    map.insert(col.name.clone(), value.clone());
87                }
88            }
89
90            Some(map)
91        } else {
92            None
93        }
94    }
95
96    /// Get all data as XdlValue (structure array)
97    pub fn get_data(&self) -> XdlResult<XdlValue> {
98        // Convert recordset to XDL-compatible structure
99        // For now, we'll create a nested array where each row is an array of values
100
101        if self.rows.is_empty() {
102            return Ok(XdlValue::Undefined);
103        }
104
105        // Create array of row arrays
106        let mut row_arrays = Vec::new();
107
108        for row in &self.rows {
109            let mut row_values = Vec::new();
110
111            for cell in row {
112                let xdl_val = json_to_xdl(cell)?;
113                row_values.push(xdl_val);
114            }
115
116            row_arrays.push(XdlValue::NestedArray(row_values));
117        }
118
119        Ok(XdlValue::NestedArray(row_arrays))
120    }
121
122    /// Get data as a column-major structure (like IDL structures)
123    pub fn get_data_structured(&self) -> XdlResult<HashMap<String, Vec<XdlValue>>> {
124        let mut result = HashMap::new();
125
126        // Initialize column vectors
127        for col in &self.columns {
128            result.insert(col.name.clone(), Vec::new());
129        }
130
131        // Fill column vectors
132        for row in &self.rows {
133            for (i, cell) in row.iter().enumerate() {
134                if let Some(col) = self.columns.get(i) {
135                    let xdl_val = json_to_xdl(cell)?;
136                    if let Some(col_vec) = result.get_mut(&col.name) {
137                        col_vec.push(xdl_val);
138                    }
139                }
140            }
141        }
142
143        Ok(result)
144    }
145
146    /// Get a specific column as an array
147    pub fn get_column(&self, column_name: &str) -> XdlResult<Vec<XdlValue>> {
148        let col_index = self
149            .columns
150            .iter()
151            .position(|c| c.name == column_name)
152            .ok_or_else(|| XdlError::RuntimeError(format!("Column not found: {}", column_name)))?;
153
154        let mut values = Vec::new();
155        for row in &self.rows {
156            if let Some(cell) = row.get(col_index) {
157                values.push(json_to_xdl(cell)?);
158            }
159        }
160
161        Ok(values)
162    }
163
164    /// Get row at specific index
165    pub fn get_row(&self, index: usize) -> Option<&Vec<JsonValue>> {
166        self.rows.get(index)
167    }
168
169    /// Check if recordset is empty
170    pub fn is_empty(&self) -> bool {
171        self.rows.is_empty()
172    }
173}
174
175/// Convert JSON value to XdlValue
176fn json_to_xdl(value: &JsonValue) -> XdlResult<XdlValue> {
177    match value {
178        JsonValue::Null => Ok(XdlValue::Undefined),
179        JsonValue::Bool(b) => Ok(XdlValue::Long(if *b { 1 } else { 0 })),
180        JsonValue::Number(n) => {
181            if let Some(i) = n.as_i64() {
182                if i >= i32::MIN as i64 && i <= i32::MAX as i64 {
183                    Ok(XdlValue::Long(i as i32))
184                } else {
185                    Ok(XdlValue::Long64(i))
186                }
187            } else if let Some(f) = n.as_f64() {
188                Ok(XdlValue::Double(f))
189            } else {
190                Ok(XdlValue::Undefined)
191            }
192        }
193        JsonValue::String(s) => Ok(XdlValue::String(s.clone())),
194        JsonValue::Array(arr) => {
195            let values: Result<Vec<_>, _> = arr.iter().map(json_to_xdl).collect();
196            Ok(XdlValue::NestedArray(values?))
197        }
198        JsonValue::Object(_) => {
199            // For objects, convert to string representation
200            Ok(XdlValue::String(value.to_string()))
201        }
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_empty_recordset() {
211        let rs = Recordset::empty();
212        assert_eq!(rs.row_count(), 0);
213        assert_eq!(rs.column_count(), 0);
214        assert!(rs.is_empty());
215    }
216
217    #[test]
218    fn test_recordset_with_data() {
219        let columns = vec![
220            ColumnInfo {
221                name: "id".to_string(),
222                data_type: "integer".to_string(),
223                ordinal: 0,
224            },
225            ColumnInfo {
226                name: "name".to_string(),
227                data_type: "text".to_string(),
228                ordinal: 1,
229            },
230        ];
231
232        let rows = vec![
233            vec![JsonValue::from(1), JsonValue::from("Alice")],
234            vec![JsonValue::from(2), JsonValue::from("Bob")],
235        ];
236
237        let rs = Recordset::new(columns, rows);
238
239        assert_eq!(rs.row_count(), 2);
240        assert_eq!(rs.column_count(), 2);
241        assert!(!rs.is_empty());
242        assert_eq!(rs.column_names(), vec!["id", "name"]);
243    }
244}