Skip to main content

aegis_client/
result.rs

1//! Aegis Client Query Results
2//!
3//! Types for query result handling.
4//!
5//! @version 0.1.0
6//! @author AutomataNexus Development Team
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11// =============================================================================
12// Value
13// =============================================================================
14
15/// A database value.
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub enum Value {
18    Null,
19    Bool(bool),
20    Int(i64),
21    Float(f64),
22    String(String),
23    Bytes(Vec<u8>),
24    Array(Vec<Value>),
25    Object(HashMap<String, Value>),
26    Timestamp(i64),
27}
28
29impl Value {
30    /// Check if the value is null.
31    pub fn is_null(&self) -> bool {
32        matches!(self, Self::Null)
33    }
34
35    /// Try to get as bool.
36    pub fn as_bool(&self) -> Option<bool> {
37        match self {
38            Self::Bool(b) => Some(*b),
39            _ => None,
40        }
41    }
42
43    /// Try to get as i64.
44    pub fn as_int(&self) -> Option<i64> {
45        match self {
46            Self::Int(i) => Some(*i),
47            Self::Float(f) => Some(*f as i64),
48            _ => None,
49        }
50    }
51
52    /// Try to get as f64.
53    pub fn as_float(&self) -> Option<f64> {
54        match self {
55            Self::Float(f) => Some(*f),
56            Self::Int(i) => Some(*i as f64),
57            _ => None,
58        }
59    }
60
61    /// Try to get as string.
62    pub fn as_str(&self) -> Option<&str> {
63        match self {
64            Self::String(s) => Some(s),
65            _ => None,
66        }
67    }
68
69    /// Try to get as bytes.
70    pub fn as_bytes(&self) -> Option<&[u8]> {
71        match self {
72            Self::Bytes(b) => Some(b),
73            _ => None,
74        }
75    }
76
77    /// Try to get as array.
78    pub fn as_array(&self) -> Option<&[Value]> {
79        match self {
80            Self::Array(a) => Some(a),
81            _ => None,
82        }
83    }
84
85    /// Try to get as object.
86    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
87        match self {
88            Self::Object(o) => Some(o),
89            _ => None,
90        }
91    }
92}
93
94impl From<bool> for Value {
95    fn from(v: bool) -> Self {
96        Self::Bool(v)
97    }
98}
99
100impl From<i32> for Value {
101    fn from(v: i32) -> Self {
102        Self::Int(v as i64)
103    }
104}
105
106impl From<i64> for Value {
107    fn from(v: i64) -> Self {
108        Self::Int(v)
109    }
110}
111
112impl From<f64> for Value {
113    fn from(v: f64) -> Self {
114        Self::Float(v)
115    }
116}
117
118impl From<String> for Value {
119    fn from(v: String) -> Self {
120        Self::String(v)
121    }
122}
123
124impl From<&str> for Value {
125    fn from(v: &str) -> Self {
126        Self::String(v.to_string())
127    }
128}
129
130impl From<Vec<u8>> for Value {
131    fn from(v: Vec<u8>) -> Self {
132        Self::Bytes(v)
133    }
134}
135
136// =============================================================================
137// Column
138// =============================================================================
139
140/// Metadata about a result column.
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct Column {
143    pub name: String,
144    pub data_type: DataType,
145    pub nullable: bool,
146}
147
148impl Column {
149    pub fn new(name: impl Into<String>, data_type: DataType) -> Self {
150        Self {
151            name: name.into(),
152            data_type,
153            nullable: true,
154        }
155    }
156}
157
158// =============================================================================
159// Data Type
160// =============================================================================
161
162/// SQL data types.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164pub enum DataType {
165    Boolean,
166    Integer,
167    BigInt,
168    Float,
169    Double,
170    Text,
171    Varchar,
172    Blob,
173    Timestamp,
174    Date,
175    Time,
176    Json,
177    Array,
178    Unknown,
179}
180
181// =============================================================================
182// Row
183// =============================================================================
184
185/// A row in a query result.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct Row {
188    columns: Vec<String>,
189    values: Vec<Value>,
190}
191
192impl Row {
193    /// Create a new row.
194    pub fn new(columns: Vec<String>, values: Vec<Value>) -> Self {
195        Self { columns, values }
196    }
197
198    /// Get a value by column index.
199    pub fn get(&self, index: usize) -> Option<&Value> {
200        self.values.get(index)
201    }
202
203    /// Get a value by column name.
204    pub fn get_by_name(&self, name: &str) -> Option<&Value> {
205        let index = self.columns.iter().position(|c| c == name)?;
206        self.values.get(index)
207    }
208
209    /// Get the number of columns.
210    pub fn len(&self) -> usize {
211        self.values.len()
212    }
213
214    /// Check if the row is empty.
215    pub fn is_empty(&self) -> bool {
216        self.values.is_empty()
217    }
218
219    /// Get column names.
220    pub fn columns(&self) -> &[String] {
221        &self.columns
222    }
223
224    /// Get all values.
225    pub fn values(&self) -> &[Value] {
226        &self.values
227    }
228
229    /// Try to get a bool value.
230    pub fn get_bool(&self, index: usize) -> Option<bool> {
231        self.get(index).and_then(|v| v.as_bool())
232    }
233
234    /// Try to get an int value.
235    pub fn get_int(&self, index: usize) -> Option<i64> {
236        self.get(index).and_then(|v| v.as_int())
237    }
238
239    /// Try to get a float value.
240    pub fn get_float(&self, index: usize) -> Option<f64> {
241        self.get(index).and_then(|v| v.as_float())
242    }
243
244    /// Try to get a string value.
245    pub fn get_str(&self, index: usize) -> Option<&str> {
246        self.get(index).and_then(|v| v.as_str())
247    }
248
249    /// Convert to a HashMap.
250    pub fn to_map(&self) -> HashMap<String, Value> {
251        self.columns
252            .iter()
253            .zip(self.values.iter())
254            .map(|(k, v)| (k.clone(), v.clone()))
255            .collect()
256    }
257}
258
259impl IntoIterator for Row {
260    type Item = Value;
261    type IntoIter = std::vec::IntoIter<Value>;
262
263    fn into_iter(self) -> Self::IntoIter {
264        self.values.into_iter()
265    }
266}
267
268// =============================================================================
269// Query Result
270// =============================================================================
271
272/// Result of a query execution.
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct QueryResult {
275    columns: Vec<Column>,
276    rows: Vec<Row>,
277    rows_affected: u64,
278    execution_time_ms: u64,
279}
280
281impl QueryResult {
282    /// Create a new query result.
283    pub fn new(columns: Vec<Column>, rows: Vec<Row>) -> Self {
284        Self {
285            columns,
286            rows,
287            rows_affected: 0,
288            execution_time_ms: 0,
289        }
290    }
291
292    /// Create an empty result.
293    pub fn empty() -> Self {
294        Self {
295            columns: Vec::new(),
296            rows: Vec::new(),
297            rows_affected: 0,
298            execution_time_ms: 0,
299        }
300    }
301
302    /// Create a result for a write operation.
303    pub fn affected(rows_affected: u64) -> Self {
304        Self {
305            columns: Vec::new(),
306            rows: Vec::new(),
307            rows_affected,
308            execution_time_ms: 0,
309        }
310    }
311
312    /// Set execution time.
313    pub fn with_execution_time(mut self, ms: u64) -> Self {
314        self.execution_time_ms = ms;
315        self
316    }
317
318    /// Get column metadata.
319    pub fn columns(&self) -> &[Column] {
320        &self.columns
321    }
322
323    /// Get all rows.
324    pub fn rows(&self) -> &[Row] {
325        &self.rows
326    }
327
328    /// Get the number of rows.
329    pub fn row_count(&self) -> usize {
330        self.rows.len()
331    }
332
333    /// Get rows affected (for INSERT, UPDATE, DELETE).
334    pub fn rows_affected(&self) -> u64 {
335        self.rows_affected
336    }
337
338    /// Get execution time in milliseconds.
339    pub fn execution_time_ms(&self) -> u64 {
340        self.execution_time_ms
341    }
342
343    /// Check if the result is empty.
344    pub fn is_empty(&self) -> bool {
345        self.rows.is_empty()
346    }
347
348    /// Get the first row.
349    pub fn first(&self) -> Option<&Row> {
350        self.rows.first()
351    }
352
353    /// Get the first value of the first row.
354    pub fn scalar(&self) -> Option<&Value> {
355        self.first().and_then(|r| r.get(0))
356    }
357
358    /// Iterate over rows.
359    pub fn iter(&self) -> impl Iterator<Item = &Row> {
360        self.rows.iter()
361    }
362}
363
364impl IntoIterator for QueryResult {
365    type Item = Row;
366    type IntoIter = std::vec::IntoIter<Row>;
367
368    fn into_iter(self) -> Self::IntoIter {
369        self.rows.into_iter()
370    }
371}
372
373// =============================================================================
374// Tests
375// =============================================================================
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_value_types() {
383        let v = Value::Int(42);
384        assert_eq!(v.as_int(), Some(42));
385        assert_eq!(v.as_float(), Some(42.0));
386
387        let v = Value::String("hello".to_string());
388        assert_eq!(v.as_str(), Some("hello"));
389
390        let v = Value::Null;
391        assert!(v.is_null());
392    }
393
394    #[test]
395    fn test_value_from() {
396        let v: Value = 42i32.into();
397        assert_eq!(v, Value::Int(42));
398
399        let v: Value = "hello".into();
400        assert_eq!(v, Value::String("hello".to_string()));
401
402        let v: Value = true.into();
403        assert_eq!(v, Value::Bool(true));
404    }
405
406    #[test]
407    fn test_row() {
408        let columns = vec!["id".to_string(), "name".to_string()];
409        let values = vec![Value::Int(1), Value::String("Alice".to_string())];
410        let row = Row::new(columns, values);
411
412        assert_eq!(row.len(), 2);
413        assert_eq!(row.get_int(0), Some(1));
414        assert_eq!(row.get_str(1), Some("Alice"));
415        assert_eq!(
416            row.get_by_name("name"),
417            Some(&Value::String("Alice".to_string()))
418        );
419    }
420
421    #[test]
422    fn test_row_to_map() {
423        let columns = vec!["a".to_string(), "b".to_string()];
424        let values = vec![Value::Int(1), Value::Int(2)];
425        let row = Row::new(columns, values);
426
427        let map = row.to_map();
428        assert_eq!(map.get("a"), Some(&Value::Int(1)));
429        assert_eq!(map.get("b"), Some(&Value::Int(2)));
430    }
431
432    #[test]
433    fn test_query_result() {
434        let columns = vec![Column::new("id", DataType::Integer)];
435        let rows = vec![
436            Row::new(vec!["id".to_string()], vec![Value::Int(1)]),
437            Row::new(vec!["id".to_string()], vec![Value::Int(2)]),
438        ];
439
440        let result = QueryResult::new(columns, rows);
441        assert_eq!(result.row_count(), 2);
442        assert_eq!(result.scalar(), Some(&Value::Int(1)));
443    }
444
445    #[test]
446    fn test_query_result_affected() {
447        let result = QueryResult::affected(5);
448        assert_eq!(result.rows_affected(), 5);
449        assert!(result.is_empty());
450    }
451}