alopex_sql/executor/
result.rs

1//! Result types for the Executor module.
2//!
3//! This module defines the output types for SQL execution:
4//! - [`ExecutionResult`]: Top-level execution result
5//! - [`QueryResult`]: SELECT query results with column info
6//! - [`Row`]: Internal row representation with row_id
7
8use crate::planner::ResolvedType;
9use crate::storage::SqlValue;
10
11/// Result of executing a SQL statement.
12#[derive(Debug, Clone, PartialEq)]
13pub enum ExecutionResult {
14    /// DDL operation success (CREATE/DROP TABLE/INDEX).
15    Success,
16
17    /// DML operation success with affected row count.
18    RowsAffected(u64),
19
20    /// Query result with columns and rows.
21    Query(QueryResult),
22}
23
24/// Result of a SELECT query.
25#[derive(Debug, Clone, PartialEq)]
26pub struct QueryResult {
27    /// Column information for the result set.
28    pub columns: Vec<ColumnInfo>,
29
30    /// Result rows as vectors of SqlValue.
31    pub rows: Vec<Vec<SqlValue>>,
32}
33
34impl QueryResult {
35    /// Create a new query result with column info and rows.
36    pub fn new(columns: Vec<ColumnInfo>, rows: Vec<Vec<SqlValue>>) -> Self {
37        Self { columns, rows }
38    }
39
40    /// Create an empty query result with column info.
41    pub fn empty(columns: Vec<ColumnInfo>) -> Self {
42        Self {
43            columns,
44            rows: Vec::new(),
45        }
46    }
47
48    /// Returns the number of rows in the result.
49    pub fn row_count(&self) -> usize {
50        self.rows.len()
51    }
52
53    /// Returns the number of columns in the result.
54    pub fn column_count(&self) -> usize {
55        self.columns.len()
56    }
57
58    /// Returns true if the result is empty.
59    pub fn is_empty(&self) -> bool {
60        self.rows.is_empty()
61    }
62}
63
64/// Column information for query results.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ColumnInfo {
67    /// Column name (or alias if specified).
68    pub name: String,
69
70    /// Column data type.
71    pub data_type: ResolvedType,
72}
73
74impl ColumnInfo {
75    /// Create a new column info.
76    pub fn new(name: impl Into<String>, data_type: ResolvedType) -> Self {
77        Self {
78            name: name.into(),
79            data_type,
80        }
81    }
82}
83
84/// Internal row representation with row_id for DML operations.
85#[derive(Debug, Clone, PartialEq)]
86pub struct Row {
87    /// Row identifier (unique within table).
88    pub row_id: u64,
89
90    /// Column values.
91    pub values: Vec<SqlValue>,
92}
93
94impl Row {
95    /// Create a new row with row_id and values.
96    pub fn new(row_id: u64, values: Vec<SqlValue>) -> Self {
97        Self { row_id, values }
98    }
99
100    /// Get a column value by index.
101    pub fn get(&self, index: usize) -> Option<&SqlValue> {
102        self.values.get(index)
103    }
104
105    /// Returns the number of columns in the row.
106    pub fn len(&self) -> usize {
107        self.values.len()
108    }
109
110    /// Returns true if the row has no columns.
111    pub fn is_empty(&self) -> bool {
112        self.values.is_empty()
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_execution_result_success() {
122        let result = ExecutionResult::Success;
123        assert!(matches!(result, ExecutionResult::Success));
124    }
125
126    #[test]
127    fn test_execution_result_rows_affected() {
128        let result = ExecutionResult::RowsAffected(5);
129        if let ExecutionResult::RowsAffected(count) = result {
130            assert_eq!(count, 5);
131        } else {
132            panic!("Expected RowsAffected variant");
133        }
134    }
135
136    #[test]
137    fn test_query_result_new() {
138        let columns = vec![
139            ColumnInfo::new("id", ResolvedType::Integer),
140            ColumnInfo::new("name", ResolvedType::Text),
141        ];
142        let rows = vec![
143            vec![SqlValue::Integer(1), SqlValue::Text("Alice".into())],
144            vec![SqlValue::Integer(2), SqlValue::Text("Bob".into())],
145        ];
146        let result = QueryResult::new(columns, rows);
147
148        assert_eq!(result.row_count(), 2);
149        assert_eq!(result.column_count(), 2);
150        assert!(!result.is_empty());
151    }
152
153    #[test]
154    fn test_query_result_empty() {
155        let columns = vec![ColumnInfo::new("id", ResolvedType::Integer)];
156        let result = QueryResult::empty(columns);
157
158        assert_eq!(result.row_count(), 0);
159        assert_eq!(result.column_count(), 1);
160        assert!(result.is_empty());
161    }
162
163    #[test]
164    fn test_row_new() {
165        let row = Row::new(
166            42,
167            vec![SqlValue::Integer(1), SqlValue::Text("test".into())],
168        );
169
170        assert_eq!(row.row_id, 42);
171        assert_eq!(row.len(), 2);
172        assert!(!row.is_empty());
173        assert_eq!(row.get(0), Some(&SqlValue::Integer(1)));
174        assert_eq!(row.get(1), Some(&SqlValue::Text("test".into())));
175        assert_eq!(row.get(2), None);
176    }
177
178    #[test]
179    fn test_column_info_new() {
180        let info = ColumnInfo::new("age", ResolvedType::Integer);
181        assert_eq!(info.name, "age");
182        assert_eq!(info.data_type, ResolvedType::Integer);
183    }
184}