featherdb_query/executor/
types.rs1use featherdb_catalog::Catalog;
4use featherdb_core::{Error, Result, TransactionId, Value};
5use featherdb_mvcc::{Snapshot, TransactionManager};
6use featherdb_storage::BufferPool;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
12pub struct Row {
13 pub values: Vec<Value>,
14 pub columns: Arc<Vec<String>>,
15}
16
17impl Row {
18 pub fn new(values: Vec<Value>, columns: Vec<String>) -> Self {
20 Row {
21 values,
22 columns: Arc::new(columns),
23 }
24 }
25
26 pub fn with_shared_columns(values: Vec<Value>, columns: Arc<Vec<String>>) -> Self {
29 Row { values, columns }
30 }
31
32 pub fn get(&self, name: &str) -> Result<&Value> {
34 let suffix = format!(".{}", name);
35 let idx = self
36 .columns
37 .iter()
38 .position(|c| {
39 c.eq_ignore_ascii_case(name)
40 || c.len() > suffix.len()
41 && c[c.len() - suffix.len()..].eq_ignore_ascii_case(&suffix)
42 })
43 .ok_or_else(|| Error::ColumnNotFound {
44 column: name.to_string(),
45 table: String::new(),
46 suggestion: None,
47 })?;
48 Ok(&self.values[idx])
49 }
50
51 pub fn get_index(&self, idx: usize) -> Option<&Value> {
53 self.values.get(idx)
54 }
55
56 pub fn len(&self) -> usize {
58 self.values.len()
59 }
60
61 pub fn is_empty(&self) -> bool {
63 self.values.is_empty()
64 }
65}
66
67pub struct ExecutionContext<'a> {
69 pub catalog: &'a Catalog,
70 pub buffer_pool: &'a Arc<BufferPool>,
71 pub snapshot: &'a Snapshot,
72 pub txn_manager: &'a TransactionManager,
73 pub txn_id: TransactionId,
76 pub cte_context: HashMap<String, Vec<Row>>,
78 pub subquery_plans: HashMap<usize, crate::planner::LogicalPlan>,
80 pub metrics: Option<&'a Arc<crate::metrics::QueryMetrics>>,
82 pub api_key_id: Option<&'a str>,
84 pub auth_enabled: bool,
86}
87
88pub enum QueryResult {
90 Rows(Vec<Row>),
92 Affected(usize),
94 SchemaChanged,
96 RowsAffected(usize),
98}
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_row_get() {
105 let row = Row::new(
106 vec![Value::Integer(1), Value::Text("Alice".into())],
107 vec!["id".into(), "name".into()],
108 );
109
110 assert_eq!(row.get("id").unwrap(), &Value::Integer(1));
111 assert_eq!(row.get("name").unwrap(), &Value::Text("Alice".into()));
112 }
113
114 #[test]
115 fn test_build_col_map() {
116 use crate::executor::filter::build_col_map;
117
118 let columns = vec!["users.id".into(), "users.name".into()];
119 let map = build_col_map(&columns);
120
121 assert_eq!(map.get("users.id"), Some(&0));
122 assert_eq!(map.get("id"), Some(&0));
123 assert_eq!(map.get("name"), Some(&1));
124 }
125
126 #[test]
127 fn test_semi_join_exists() {
128 let left_rows = vec![
133 Row::new(
134 vec![Value::Integer(1), Value::Text("Alice".into())],
135 vec!["users.id".into(), "users.name".into()],
136 ),
137 Row::new(
138 vec![Value::Integer(2), Value::Text("Bob".into())],
139 vec!["users.id".into(), "users.name".into()],
140 ),
141 Row::new(
142 vec![Value::Integer(3), Value::Text("Charlie".into())],
143 vec!["users.id".into(), "users.name".into()],
144 ),
145 ];
146
147 let right_rows = vec![
148 Row::new(
149 vec![Value::Integer(1), Value::Integer(1)],
150 vec!["orders.id".into(), "orders.user_id".into()],
151 ),
152 Row::new(
153 vec![Value::Integer(2), Value::Integer(3)],
154 vec!["orders.id".into(), "orders.user_id".into()],
155 ),
156 ];
157
158 assert_eq!(left_rows.len(), 3);
160 assert_eq!(right_rows.len(), 2);
161
162 println!("Semi-join test data prepared - would return 2 matching rows");
165 }
166
167 #[test]
168 fn test_anti_join_not_in() {
169 let left_rows = vec![
173 Row::new(
174 vec![Value::Integer(1), Value::Text("Alice".into())],
175 vec!["users.id".into(), "users.name".into()],
176 ),
177 Row::new(
178 vec![Value::Integer(2), Value::Text("Bob".into())],
179 vec!["users.id".into(), "users.name".into()],
180 ),
181 Row::new(
182 vec![Value::Null, Value::Text("Unknown".into())],
183 vec!["users.id".into(), "users.name".into()],
184 ),
185 ];
186
187 let _right_rows = vec![Row::new(
188 vec![Value::Integer(1)],
189 vec!["orders.user_id".into()],
190 )];
191
192 assert_eq!(left_rows.len(), 3);
197 println!("Anti-join test data prepared - would return 1 row (Bob)");
198 }
199
200 #[test]
201 fn test_execution_context_with_metrics() {
202 use crate::metrics::QueryMetrics;
203 use std::sync::Arc;
204
205 let metrics = Arc::new(QueryMetrics::new());
207
208 let snapshot = metrics.snapshot();
210 assert_eq!(snapshot.queries_executed, 0);
211 assert_eq!(snapshot.rows_scanned, 0);
212 assert_eq!(snapshot.rows_returned, 0);
213
214 metrics.increment_queries();
216 metrics.record_parse_time(100);
217 metrics.record_plan_time(200);
218 metrics.record_exec_time(500);
219 metrics.record_rows_scanned(50);
220 metrics.record_rows_returned(10);
221
222 let snapshot2 = metrics.snapshot();
224 assert_eq!(snapshot2.queries_executed, 1);
225 assert_eq!(snapshot2.total_parse_time_us, 100);
226 assert_eq!(snapshot2.total_plan_time_us, 200);
227 assert_eq!(snapshot2.total_exec_time_us, 500);
228 assert_eq!(snapshot2.rows_scanned, 50);
229 assert_eq!(snapshot2.rows_returned, 10);
230
231 assert_eq!(snapshot2.avg_parse_time_us(), 100.0);
233 assert_eq!(snapshot2.avg_plan_time_us(), 200.0);
234 assert_eq!(snapshot2.avg_exec_time_us(), 500.0);
235 assert_eq!(snapshot2.avg_rows_scanned(), 50.0);
236 assert_eq!(snapshot2.avg_rows_returned(), 10.0);
237 assert_eq!(snapshot2.selectivity_ratio(), 0.2); }
243
244 #[test]
245 fn test_execute_with_timing() {
246 use crate::metrics::QueryMetrics;
247 use std::sync::Arc;
248
249 let _plan = crate::planner::LogicalPlan::EmptyRelation;
251
252 let metrics = Arc::new(QueryMetrics::new());
259
260 let start = std::time::Instant::now();
262 std::thread::sleep(std::time::Duration::from_micros(100));
264 let elapsed = start.elapsed().as_micros() as u64;
265
266 metrics.record_exec_time(elapsed);
267
268 let snapshot = metrics.snapshot();
269 assert!(
270 snapshot.total_exec_time_us >= 100,
271 "Should record at least 100 microseconds"
272 );
273 }
274}