1use std::cmp::Ordering;
4use std::collections::HashMap;
5
6use regex::Regex;
7
8use crate::errors::MdqlError;
9use crate::model::{Row, Value};
10use crate::query_parser::*;
11use crate::schema::Schema;
12
13pub fn execute_query(
14 query: &SelectQuery,
15 rows: &[Row],
16 _schema: &Schema,
17) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
18 if let Some(ref sub) = query.subquery {
19 let (sub_rows, _sub_cols) = execute_inner(sub, rows, None)?;
20 return execute_inner(query, &sub_rows, None);
21 }
22 execute_inner(query, rows, None)
23}
24
25#[allow(dead_code)]
26pub(crate) fn execute_query_indexed(
27 query: &SelectQuery,
28 rows: &[Row],
29 schema: &Schema,
30 index: Option<&crate::index::TableIndex>,
31 searcher: Option<&crate::search::TableSearcher>,
32) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
33 let fts_results = if let (Some(ref wc), Some(searcher)) = (&query.where_clause, searcher) {
35 collect_fts_results(wc, schema, searcher)
36 } else {
37 HashMap::new()
38 };
39
40 execute_with_fts(query, rows, index, &fts_results)
41}
42
43#[allow(dead_code)]
44fn collect_fts_results(
45 clause: &WhereClause,
46 schema: &Schema,
47 searcher: &crate::search::TableSearcher,
48) -> HashMap<(String, String), std::collections::HashSet<String>> {
49 let mut results = HashMap::new();
50 collect_fts_results_inner(clause, schema, searcher, &mut results);
51 results
52}
53
54#[allow(dead_code)]
55fn collect_fts_results_inner(
56 clause: &WhereClause,
57 schema: &Schema,
58 searcher: &crate::search::TableSearcher,
59 results: &mut HashMap<(String, String), std::collections::HashSet<String>>,
60) {
61 match clause {
62 WhereClause::Comparison(cmp) => {
63 if (cmp.op == "LIKE" || cmp.op == "NOT LIKE") && schema.sections.contains_key(&cmp.column) {
64 if let Some(SqlValue::String(pattern)) = &cmp.value {
65 let search_term = pattern.replace('%', " ").replace('_', " ").trim().to_string();
67 if !search_term.is_empty() {
68 if let Ok(paths) = searcher.search(&search_term, Some(&cmp.column)) {
69 let key = (cmp.column.clone(), pattern.clone());
70 results.insert(key, paths.into_iter().collect());
71 }
72 }
73 }
74 }
75 }
76 WhereClause::BoolOp(bop) => {
77 collect_fts_results_inner(&bop.left, schema, searcher, results);
78 collect_fts_results_inner(&bop.right, schema, searcher, results);
79 }
80 }
81}
82
83type FtsResults = HashMap<(String, String), std::collections::HashSet<String>>;
84
85fn execute_with_fts(
86 query: &SelectQuery,
87 rows: &[Row],
88 index: Option<&crate::index::TableIndex>,
89 fts: &FtsResults,
90) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
91 let mut all_columns: Vec<String> = Vec::new();
93 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
94 for r in rows {
95 for k in r.keys() {
96 if seen.insert(k.clone()) {
97 all_columns.push(k.clone());
98 }
99 }
100 }
101
102 let has_aggregates = match &query.columns {
104 ColumnList::Named(exprs) => exprs.iter().any(|e| e.is_aggregate()),
105 _ => false,
106 };
107
108 let columns: Vec<String> = match &query.columns {
110 ColumnList::All => all_columns,
111 ColumnList::Named(exprs) => exprs.iter().map(|e| e.output_name()).collect(),
112 };
113
114 let filtered: Vec<Row> = if let Some(ref wc) = query.where_clause {
116 let candidate_paths = index.and_then(|idx| try_index_filter(wc, idx));
117 if let Some(paths) = candidate_paths {
118 rows.iter()
119 .filter(|r| {
120 r.get("path")
121 .and_then(|v| v.as_str())
122 .map_or(false, |p| paths.contains(p))
123 })
124 .filter(|r| evaluate_with_fts(wc, r, fts))
125 .cloned()
126 .collect()
127 } else {
128 rows.iter()
129 .filter(|r| evaluate_with_fts(wc, r, fts))
130 .cloned()
131 .collect()
132 }
133 } else {
134 rows.to_vec()
135 };
136
137 let mut result = if has_aggregates || query.group_by.is_some() {
139 let exprs = match &query.columns {
140 ColumnList::Named(exprs) => exprs.clone(),
141 _ => return Err(MdqlError::QueryExecution(
142 "SELECT * with GROUP BY is not supported".into(),
143 )),
144 };
145 let group_keys = query.group_by.as_deref().unwrap_or(&[]);
146 aggregate_rows(&filtered, &exprs, group_keys)?
147 } else {
148 filtered
149 };
150
151 if let Some(ref having) = query.having {
153 result.retain(|row| evaluate(having, row));
154 }
155
156 if let Some(ref order_by) = query.order_by {
158 let resolved = resolve_order_aliases(order_by, &query.columns);
159 sort_rows(&mut result, &resolved);
160 }
161
162 if let Some(limit) = query.limit {
164 result.truncate(limit as usize);
165 }
166
167 if !matches!(query.columns, ColumnList::All) {
169 let named_exprs = match &query.columns {
170 ColumnList::Named(exprs) => exprs,
171 _ => unreachable!(),
172 };
173
174 let has_expr_cols = named_exprs.iter().any(|e| matches!(e, SelectExpr::Expr { .. }));
178 let already_aggregated = has_aggregates || query.group_by.is_some();
179 if has_expr_cols && !already_aggregated {
180 for row in &mut result {
181 for expr in named_exprs {
182 if let SelectExpr::Expr { expr: e, alias } = expr {
183 let name = alias.clone().unwrap_or_else(|| e.display_name());
184 let val = evaluate_expr(e, row);
185 row.insert(name, val);
186 }
187 }
188 }
189 }
190
191 let col_set: std::collections::HashSet<&str> =
192 columns.iter().map(|s| s.as_str()).collect();
193 for row in &mut result {
194 row.retain(|k, _| col_set.contains(k.as_str()));
195 }
196 }
197
198 Ok((result, columns))
199}
200
201fn aggregate_rows(
202 rows: &[Row],
203 exprs: &[SelectExpr],
204 group_keys: &[String],
205) -> crate::errors::Result<Vec<Row>> {
206 let mut groups: Vec<(Vec<Value>, Vec<&Row>)> = Vec::new();
208 let mut key_index: HashMap<Vec<String>, usize> = HashMap::new();
209
210 if group_keys.is_empty() {
211 let all_refs: Vec<&Row> = rows.iter().collect();
213 groups.push((vec![], all_refs));
214 } else {
215 for row in rows {
216 let key: Vec<String> = group_keys
217 .iter()
218 .map(|k| {
219 row.get(k)
220 .map(|v| v.to_display_string())
221 .unwrap_or_default()
222 })
223 .collect();
224 let key_vals: Vec<Value> = group_keys
225 .iter()
226 .map(|k| row.get(k).cloned().unwrap_or(Value::Null))
227 .collect();
228 if let Some(&idx) = key_index.get(&key) {
229 groups[idx].1.push(row);
230 } else {
231 let idx = groups.len();
232 key_index.insert(key, idx);
233 groups.push((key_vals, vec![row]));
234 }
235 }
236 }
237
238 let mut result = Vec::new();
240 for (key_vals, group_rows) in &groups {
241 let mut out = Row::new();
242
243 for (i, k) in group_keys.iter().enumerate() {
245 out.insert(k.clone(), key_vals[i].clone());
246 }
247
248 for expr in exprs {
250 match expr {
251 SelectExpr::Column(name) => {
252 if !out.contains_key(name) {
254 if let Some(first) = group_rows.first() {
255 out.insert(
256 name.clone(),
257 first.get(name).cloned().unwrap_or(Value::Null),
258 );
259 }
260 }
261 }
262 SelectExpr::Aggregate { func, arg, arg_expr, alias } => {
263 let out_name = alias
264 .clone()
265 .unwrap_or_else(|| expr.output_name());
266 let val = compute_aggregate(func, arg, arg_expr.as_ref(), group_rows);
267 out.insert(out_name, val);
268 }
269 SelectExpr::Expr { expr: e, alias } => {
270 let out_name = alias.clone().unwrap_or_else(|| e.display_name());
271 if e.contains_aggregate() {
272 let val = evaluate_agg_expr(e, group_rows);
273 out.insert(out_name, val);
274 } else if let Some(first) = group_rows.first() {
275 let val = evaluate_expr(e, first);
276 out.insert(out_name, val);
277 }
278 }
279 }
280 }
281
282 result.push(out);
283 }
284
285 Ok(result)
286}
287
288fn resolve_agg_value<'a>(arg: &str, arg_expr: Option<&Expr>, row: &'a Row) -> Value {
291 if let Some(expr) = arg_expr {
292 evaluate_expr(expr, row)
293 } else {
294 row.get(arg).cloned().unwrap_or(Value::Null)
295 }
296}
297
298fn compute_aggregate(func: &AggFunc, arg: &str, arg_expr: Option<&Expr>, rows: &[&Row]) -> Value {
299 match func {
300 AggFunc::Count => {
301 if arg == "*" && arg_expr.is_none() {
302 Value::Int(rows.len() as i64)
303 } else {
304 let count = rows
305 .iter()
306 .filter(|r| {
307 let v = resolve_agg_value(arg, arg_expr, r);
308 !v.is_null()
309 })
310 .count();
311 Value::Int(count as i64)
312 }
313 }
314 AggFunc::Sum => {
315 let mut total = 0.0f64;
316 let mut has_any = false;
317 for r in rows {
318 let v = resolve_agg_value(arg, arg_expr, r);
319 match v {
320 Value::Int(n) => { total += n as f64; has_any = true; }
321 Value::Float(f) => { total += f; has_any = true; }
322 _ => {}
323 }
324 }
325 if has_any { Value::Float(total) } else { Value::Null }
326 }
327 AggFunc::Avg => {
328 let mut total = 0.0f64;
329 let mut count = 0usize;
330 for r in rows {
331 let v = resolve_agg_value(arg, arg_expr, r);
332 match v {
333 Value::Int(n) => { total += n as f64; count += 1; }
334 Value::Float(f) => { total += f; count += 1; }
335 _ => {}
336 }
337 }
338 if count > 0 { Value::Float(total / count as f64) } else { Value::Null }
339 }
340 AggFunc::Min => {
341 let mut min_val: Option<Value> = None;
342 for r in rows {
343 let v = resolve_agg_value(arg, arg_expr, r);
344 if v.is_null() { continue; }
345 min_val = Some(match min_val {
346 None => v,
347 Some(ref current) => {
348 if v.partial_cmp(current) == Some(std::cmp::Ordering::Less) {
349 v
350 } else {
351 current.clone()
352 }
353 }
354 });
355 }
356 min_val.unwrap_or(Value::Null)
357 }
358 AggFunc::Max => {
359 let mut max_val: Option<Value> = None;
360 for r in rows {
361 let v = resolve_agg_value(arg, arg_expr, r);
362 if v.is_null() { continue; }
363 max_val = Some(match max_val {
364 None => v,
365 Some(ref current) => {
366 if v.partial_cmp(current) == Some(std::cmp::Ordering::Greater) {
367 v
368 } else {
369 current.clone()
370 }
371 }
372 });
373 }
374 max_val.unwrap_or(Value::Null)
375 }
376 }
377}
378
379fn evaluate_with_fts(clause: &WhereClause, row: &Row, fts: &FtsResults) -> bool {
380 match clause {
381 WhereClause::BoolOp(bop) => {
382 let left = evaluate_with_fts(&bop.left, row, fts);
383 match bop.op.as_str() {
384 "AND" => left && evaluate_with_fts(&bop.right, row, fts),
385 "OR" => left || evaluate_with_fts(&bop.right, row, fts),
386 _ => false,
387 }
388 }
389 WhereClause::Comparison(cmp) => {
390 if cmp.op == "LIKE" || cmp.op == "NOT LIKE" {
392 if let Some(SqlValue::String(pattern)) = &cmp.value {
393 let key = (cmp.column.clone(), pattern.clone());
394 if let Some(matching_paths) = fts.get(&key) {
395 let row_path = row.get("path").and_then(|v| v.as_str()).unwrap_or("");
396 let matched = matching_paths.contains(row_path);
397 return if cmp.op == "LIKE" { matched } else { !matched };
398 }
399 }
400 }
401 evaluate_comparison(cmp, row)
402 }
403 }
404}
405
406pub use crate::query_join::execute_join_query;
407
408pub(crate) fn execute_inner(
409 query: &SelectQuery,
410 rows: &[Row],
411 index: Option<&crate::index::TableIndex>,
412) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
413 let empty_fts = HashMap::new();
414 execute_with_fts(query, rows, index, &empty_fts)
415}
416
417pub fn evaluate(clause: &WhereClause, row: &Row) -> bool {
418 match clause {
419 WhereClause::BoolOp(bop) => {
420 let left = evaluate(&bop.left, row);
421 match bop.op.as_str() {
422 "AND" => left && evaluate(&bop.right, row),
423 "OR" => left || evaluate(&bop.right, row),
424 _ => false,
425 }
426 }
427 WhereClause::Comparison(cmp) => evaluate_comparison(cmp, row),
428 }
429}
430
431pub fn evaluate_expr(expr: &Expr, row: &Row) -> Value {
433 match expr {
434 Expr::Literal(SqlValue::Int(n)) => Value::Int(*n),
435 Expr::Literal(SqlValue::Float(f)) => Value::Float(*f),
436 Expr::Literal(SqlValue::String(s)) => Value::String(s.clone()),
437 Expr::Literal(SqlValue::Null) => Value::Null,
438 Expr::Literal(SqlValue::List(_)) => Value::Null,
439 Expr::Column(name) => {
440 if let Some(val) = row.get(name) {
441 return val.clone();
442 }
443 for (i, _) in name.match_indices('.') {
445 let dict_col = &name[..i];
446 let dict_key = &name[i + 1..];
447 if let Some(Value::Dict(map)) = row.get(dict_col) {
448 return map.get(dict_key).cloned().unwrap_or(Value::Null);
449 }
450 }
451 Value::Null
452 }
453 Expr::UnaryMinus(inner) => {
454 match evaluate_expr(inner, row) {
455 Value::Int(n) => Value::Int(-n),
456 Value::Float(f) => Value::Float(-f),
457 Value::Null => Value::Null,
458 _ => Value::Null, }
460 }
461 Expr::BinaryOp { left, op, right } => {
462 let lv = evaluate_expr(left, row);
463 let rv = evaluate_expr(right, row);
464
465 if lv.is_null() || rv.is_null() {
467 return Value::Null;
468 }
469
470 match (&lv, &rv) {
472 (Value::Int(a), Value::Int(b)) => {
473 match op {
474 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
475 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
476 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
477 ArithOp::Div => {
478 if *b == 0 { Value::Null } else { Value::Int(a / b) }
479 }
480 ArithOp::Mod => {
481 if *b == 0 { Value::Null } else { Value::Int(a % b) }
482 }
483 }
484 }
485 _ => {
486 let a = match &lv {
488 Value::Int(n) => *n as f64,
489 Value::Float(f) => *f,
490 _ => return Value::Null,
491 };
492 let b = match &rv {
493 Value::Int(n) => *n as f64,
494 Value::Float(f) => *f,
495 _ => return Value::Null,
496 };
497 match op {
498 ArithOp::Add => Value::Float(a + b),
499 ArithOp::Sub => Value::Float(a - b),
500 ArithOp::Mul => Value::Float(a * b),
501 ArithOp::Div => {
502 if b == 0.0 { Value::Null } else { Value::Float(a / b) }
503 }
504 ArithOp::Mod => {
505 if b == 0.0 { Value::Null } else { Value::Float(a % b) }
506 }
507 }
508 }
509 }
510 }
511 Expr::Case { whens, else_expr } => {
512 for (condition, result) in whens {
513 if evaluate(condition, row) {
514 return evaluate_expr(result, row);
515 }
516 }
517 match else_expr {
518 Some(e) => evaluate_expr(e, row),
519 None => Value::Null,
520 }
521 }
522 Expr::CurrentDate => {
523 Value::Date(chrono::Local::now().naive_local().date())
524 }
525 Expr::CurrentTimestamp => {
526 Value::DateTime(chrono::Local::now().naive_local())
527 }
528 Expr::DateAdd { date, days } => {
529 let date_val = evaluate_expr(date, row);
530 let days_val = evaluate_expr(days, row);
531 let n = match &days_val {
532 Value::Int(n) => *n,
533 Value::Float(f) => *f as i64,
534 _ => return Value::Null,
535 };
536 let duration = chrono::Duration::days(n);
537 match date_val {
538 Value::Date(d) => {
539 match d.checked_add_signed(duration) {
540 Some(result) => Value::Date(result),
541 None => Value::Null,
542 }
543 }
544 Value::DateTime(dt) => {
545 match dt.checked_add_signed(duration) {
546 Some(result) => Value::DateTime(result),
547 None => Value::Null,
548 }
549 }
550 _ => Value::Null,
551 }
552 }
553 Expr::DateDiff { left, right } => {
554 let lv = evaluate_expr(left, row);
555 let rv = evaluate_expr(right, row);
556 let left_date = match &lv {
557 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
558 Value::DateTime(dt) => *dt,
559 _ => return Value::Null,
560 };
561 let right_date = match &rv {
562 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
563 Value::DateTime(dt) => *dt,
564 _ => return Value::Null,
565 };
566 Value::Int((left_date - right_date).num_days())
567 }
568 Expr::Aggregate { func, arg, .. } => {
569 let func_name = match func {
571 AggFunc::Count => "COUNT",
572 AggFunc::Sum => "SUM",
573 AggFunc::Avg => "AVG",
574 AggFunc::Min => "MIN",
575 AggFunc::Max => "MAX",
576 };
577 let col = format!("{}({})", func_name, arg);
578 row.get(&col).cloned().unwrap_or(Value::Null)
579 }
580 }
581}
582
583fn evaluate_agg_expr(expr: &Expr, group_rows: &[&Row]) -> Value {
584 match expr {
585 Expr::Aggregate { func, arg, arg_expr } => {
586 compute_aggregate(func, arg, arg_expr.as_deref(), group_rows)
587 }
588 Expr::BinaryOp { left, op, right } => {
589 let lv = evaluate_agg_expr(left, group_rows);
590 let rv = evaluate_agg_expr(right, group_rows);
591 apply_arith_op(op, &lv, &rv)
592 }
593 Expr::UnaryMinus(inner) => {
594 match evaluate_agg_expr(inner, group_rows) {
595 Value::Int(n) => Value::Int(-n),
596 Value::Float(f) => Value::Float(-f),
597 _ => Value::Null,
598 }
599 }
600 other => {
601 if let Some(first) = group_rows.first() {
602 evaluate_expr(other, first)
603 } else {
604 Value::Null
605 }
606 }
607 }
608}
609
610fn apply_arith_op(op: &ArithOp, lv: &Value, rv: &Value) -> Value {
611 if lv.is_null() || rv.is_null() {
612 return Value::Null;
613 }
614 match (lv, rv) {
615 (Value::Int(a), Value::Int(b)) => match op {
616 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
617 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
618 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
619 ArithOp::Div => if *b == 0 { Value::Null } else { Value::Int(a / b) },
620 ArithOp::Mod => if *b == 0 { Value::Null } else { Value::Int(a % b) },
621 },
622 _ => {
623 let a = match lv {
624 Value::Int(n) => *n as f64,
625 Value::Float(f) => *f,
626 _ => return Value::Null,
627 };
628 let b = match rv {
629 Value::Int(n) => *n as f64,
630 Value::Float(f) => *f,
631 _ => return Value::Null,
632 };
633 match op {
634 ArithOp::Add => Value::Float(a + b),
635 ArithOp::Sub => Value::Float(a - b),
636 ArithOp::Mul => Value::Float(a * b),
637 ArithOp::Div => if b == 0.0 { Value::Null } else { Value::Float(a / b) },
638 ArithOp::Mod => if b == 0.0 { Value::Null } else { Value::Float(a % b) },
639 }
640 }
641 }
642}
643
644fn evaluate_comparison(cmp: &Comparison, row: &Row) -> bool {
645 if let (Some(left_expr), Some(right_expr)) = (&cmp.left_expr, &cmp.right_expr) {
647 if ["=", "!=", "<", ">", "<=", ">="].contains(&cmp.op.as_str()) {
648 let left_val = evaluate_expr(left_expr, row);
649 let right_val = evaluate_expr(right_expr, row);
650
651 if left_val.is_null() || right_val.is_null() {
653 return false;
654 }
655
656 let ord = compare_model_values(&left_val, &right_val);
658
659 return match cmp.op.as_str() {
660 "=" => ord == Some(Ordering::Equal),
661 "!=" => ord != Some(Ordering::Equal),
662 "<" => ord == Some(Ordering::Less),
663 ">" => ord == Some(Ordering::Greater),
664 "<=" => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
665 ">=" => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
666 _ => false,
667 };
668 }
669 }
670
671 let actual = row.get(&cmp.column);
673
674 if cmp.op == "IS NULL" {
675 return actual.map_or(true, |v| v.is_null());
676 }
677 if cmp.op == "IS NOT NULL" {
678 return actual.map_or(false, |v| !v.is_null());
679 }
680
681 let actual = match actual {
682 Some(v) if !v.is_null() => v,
683 _ => return false,
684 };
685
686 let expected = match &cmp.value {
687 Some(v) => v,
688 None => return false,
689 };
690
691 match cmp.op.as_str() {
692 "=" => eq_match(actual, expected),
693 "!=" => !eq_match(actual, expected),
694 "<" => compare_values(actual, expected) == Some(Ordering::Less),
695 ">" => compare_values(actual, expected) == Some(Ordering::Greater),
696 "<=" => matches!(compare_values(actual, expected), Some(Ordering::Less | Ordering::Equal)),
697 ">=" => matches!(compare_values(actual, expected), Some(Ordering::Greater | Ordering::Equal)),
698 "LIKE" => like_match(actual, expected),
699 "NOT LIKE" => !like_match(actual, expected),
700 "IN" => {
701 if let SqlValue::List(items) = expected {
702 items.iter().any(|v| eq_match(actual, v))
703 } else {
704 eq_match(actual, expected)
705 }
706 }
707 _ => false,
708 }
709}
710
711fn compare_model_values(a: &Value, b: &Value) -> Option<Ordering> {
713 match (a, b) {
714 (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y),
715 (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)),
716 _ => a.partial_cmp(b),
717 }
718}
719
720fn coerce_sql_to_value(sql_val: &SqlValue, target: &Value) -> Value {
721 match sql_val {
722 SqlValue::Null => Value::Null,
723 SqlValue::String(s) => {
724 match target {
725 Value::Int(_) => s.parse::<i64>().map(Value::Int).unwrap_or(Value::String(s.clone())),
726 Value::Float(_) => s.parse::<f64>().map(Value::Float).unwrap_or(Value::String(s.clone())),
727 Value::Date(_) => {
728 chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
729 .map(Value::Date)
730 .unwrap_or(Value::String(s.clone()))
731 }
732 Value::DateTime(_) => {
733 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
734 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f"))
735 .map(Value::DateTime)
736 .unwrap_or(Value::String(s.clone()))
737 }
738 _ => Value::String(s.clone()),
739 }
740 }
741 SqlValue::Int(n) => {
742 match target {
743 Value::Float(_) => Value::Float(*n as f64),
744 _ => Value::Int(*n),
745 }
746 }
747 SqlValue::Float(f) => Value::Float(*f),
748 SqlValue::List(_) => Value::Null, }
750}
751
752fn eq_match(actual: &Value, expected: &SqlValue) -> bool {
753 if let Value::List(items) = actual {
755 if let SqlValue::String(s) = expected {
756 return items.contains(s);
757 }
758 }
759
760 let coerced = coerce_sql_to_value(expected, actual);
761 actual == &coerced
762}
763
764fn like_match(actual: &Value, pattern: &SqlValue) -> bool {
765 let pattern_str = match pattern {
766 SqlValue::String(s) => s,
767 _ => return false,
768 };
769
770 let mut regex_str = String::from("(?is)^");
772 for ch in pattern_str.chars() {
773 match ch {
774 '%' => regex_str.push_str(".*"),
775 '_' => regex_str.push('.'),
776 c => {
777 if regex::escape(&c.to_string()) != c.to_string() {
778 regex_str.push_str(®ex::escape(&c.to_string()));
779 } else {
780 regex_str.push(c);
781 }
782 }
783 }
784 }
785 regex_str.push('$');
786
787 let re = match Regex::new(®ex_str) {
788 Ok(r) => r,
789 Err(_) => return false,
790 };
791
792 match actual {
793 Value::List(items) => items.iter().any(|item| re.is_match(item)),
794 _ => re.is_match(&actual.to_display_string()),
795 }
796}
797
798fn compare_values(actual: &Value, expected: &SqlValue) -> Option<Ordering> {
799 let coerced = coerce_sql_to_value(expected, actual);
800 actual.partial_cmp(&coerced).map(|o| o)
801}
802
803fn sql_value_to_index_value(sv: &SqlValue) -> Value {
805 match sv {
806 SqlValue::String(s) => {
807 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
809 return Value::DateTime(dt);
810 }
811 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
812 return Value::DateTime(dt);
813 }
814 if let Ok(d) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
816 return Value::Date(d);
817 }
818 Value::String(s.clone())
819 }
820 SqlValue::Int(n) => Value::Int(*n),
821 SqlValue::Float(f) => Value::Float(*f),
822 SqlValue::Null => Value::Null,
823 SqlValue::List(_) => Value::Null,
824 }
825}
826
827fn try_index_filter(
831 clause: &WhereClause,
832 index: &crate::index::TableIndex,
833) -> Option<std::collections::HashSet<String>> {
834 match clause {
835 WhereClause::Comparison(cmp) => {
836 if !index.has_index(&cmp.column) {
837 return None;
838 }
839 match cmp.op.as_str() {
840 "=" => {
841 let val = sql_value_to_index_value(cmp.value.as_ref()?);
842 let paths = index.lookup_eq(&cmp.column, &val);
843 Some(paths.into_iter().map(|s| s.to_string()).collect())
844 }
845 "<" => {
846 let val = sql_value_to_index_value(cmp.value.as_ref()?);
847 let range_paths = index.lookup_range(&cmp.column, None, Some(&val));
850 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
851 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
852 }
853 ">" => {
854 let val = sql_value_to_index_value(cmp.value.as_ref()?);
855 let range_paths = index.lookup_range(&cmp.column, Some(&val), None);
856 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
857 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
858 }
859 "<=" => {
860 let val = sql_value_to_index_value(cmp.value.as_ref()?);
861 let paths = index.lookup_range(&cmp.column, None, Some(&val));
862 Some(paths.into_iter().map(|s| s.to_string()).collect())
863 }
864 ">=" => {
865 let val = sql_value_to_index_value(cmp.value.as_ref()?);
866 let paths = index.lookup_range(&cmp.column, Some(&val), None);
867 Some(paths.into_iter().map(|s| s.to_string()).collect())
868 }
869 "IN" => {
870 if let Some(SqlValue::List(items)) = &cmp.value {
871 let vals: Vec<Value> = items.iter().map(sql_value_to_index_value).collect();
872 let paths = index.lookup_in(&cmp.column, &vals);
873 Some(paths.into_iter().map(|s| s.to_string()).collect())
874 } else {
875 None
876 }
877 }
878 _ => None, }
880 }
881 WhereClause::BoolOp(bop) => {
882 let left = try_index_filter(&bop.left, index);
883 let right = try_index_filter(&bop.right, index);
884 match bop.op.as_str() {
885 "AND" => {
886 match (left, right) {
887 (Some(l), Some(r)) => Some(l.intersection(&r).cloned().collect()),
888 (Some(l), None) => Some(l), (None, Some(r)) => Some(r),
890 (None, None) => None,
891 }
892 }
893 "OR" => {
894 match (left, right) {
895 (Some(l), Some(r)) => Some(l.union(&r).cloned().collect()),
896 _ => None, }
898 }
899 _ => None,
900 }
901 }
902 }
903}
904
905fn resolve_order_aliases(specs: &[OrderSpec], columns: &ColumnList) -> Vec<OrderSpec> {
908 let named = match columns {
909 ColumnList::Named(exprs) => exprs,
910 _ => return specs.to_vec(),
911 };
912
913 let alias_map: HashMap<String, &Expr> = named
915 .iter()
916 .filter_map(|se| match se {
917 SelectExpr::Expr { expr, alias: Some(a) } => Some((a.clone(), expr)),
918 _ => None,
919 })
920 .collect();
921
922 specs
923 .iter()
924 .map(|spec| {
925 if let Some(expr) = alias_map.get(&spec.column) {
927 OrderSpec {
928 column: spec.column.clone(),
929 expr: Some((*expr).clone()),
930 descending: spec.descending,
931 }
932 } else {
933 spec.clone()
934 }
935 })
936 .collect()
937}
938
939fn sort_rows(rows: &mut Vec<Row>, specs: &[OrderSpec]) {
940 rows.sort_by(|a, b| {
941 for spec in specs {
942 let (va, vb) = if let Some(ref expr) = spec.expr {
943 (evaluate_expr(expr, a), evaluate_expr(expr, b))
944 } else {
945 (
946 a.get(&spec.column).cloned().unwrap_or(Value::Null),
947 b.get(&spec.column).cloned().unwrap_or(Value::Null),
948 )
949 };
950
951 let ordering = match (&va, &vb) {
953 (Value::Null, Value::Null) => Ordering::Equal,
954 (Value::Null, _) => Ordering::Greater,
955 (_, Value::Null) => Ordering::Less,
956 (a_val, b_val) => {
957 compare_model_values(a_val, b_val).unwrap_or(Ordering::Equal)
958 }
959 };
960
961 let ordering = if spec.descending {
962 ordering.reverse()
963 } else {
964 ordering
965 };
966
967 if ordering != Ordering::Equal {
968 return ordering;
969 }
970 }
971 Ordering::Equal
972 });
973}
974
975pub fn sql_value_to_value(sql_val: &SqlValue) -> Value {
977 match sql_val {
978 SqlValue::Null => Value::Null,
979 SqlValue::String(s) => Value::String(s.clone()),
980 SqlValue::Int(n) => Value::Int(*n),
981 SqlValue::Float(f) => Value::Float(*f),
982 SqlValue::List(items) => {
983 let strings: Vec<String> = items
984 .iter()
985 .filter_map(|v| match v {
986 SqlValue::String(s) => Some(s.clone()),
987 _ => None,
988 })
989 .collect();
990 Value::List(strings)
991 }
992 }
993}
994
995#[cfg(test)]
996mod tests {
997 use super::*;
998
999 fn make_rows() -> Vec<Row> {
1000 vec![
1001 Row::from([
1002 ("path".into(), Value::String("a.md".into())),
1003 ("title".into(), Value::String("Alpha".into())),
1004 ("count".into(), Value::Int(10)),
1005 ]),
1006 Row::from([
1007 ("path".into(), Value::String("b.md".into())),
1008 ("title".into(), Value::String("Beta".into())),
1009 ("count".into(), Value::Int(5)),
1010 ]),
1011 Row::from([
1012 ("path".into(), Value::String("c.md".into())),
1013 ("title".into(), Value::String("Gamma".into())),
1014 ("count".into(), Value::Int(20)),
1015 ]),
1016 ]
1017 }
1018
1019 #[test]
1020 fn test_select_all() {
1021 let q = SelectQuery {
1022 columns: ColumnList::All,
1023 table: "test".into(),
1024 table_alias: None,
1025 subquery: None,
1026 joins: vec![],
1027 where_clause: None,
1028 group_by: None,
1029 having: None,
1030 order_by: None,
1031 limit: None,
1032 };
1033 let (rows, _cols) = execute_inner(&q, &make_rows(), None).unwrap();
1034 assert_eq!(rows.len(), 3);
1035 }
1036
1037 #[test]
1038 fn test_where_gt() {
1039 let q = SelectQuery {
1040 columns: ColumnList::All,
1041 table: "test".into(),
1042 table_alias: None,
1043 subquery: None,
1044 joins: vec![],
1045 where_clause: Some(WhereClause::Comparison(Comparison {
1046 column: "count".into(),
1047 op: ">".into(),
1048 value: Some(SqlValue::Int(5)),
1049 left_expr: Some(Expr::Column("count".into())),
1050 right_expr: Some(Expr::Literal(SqlValue::Int(5))),
1051 })),
1052 group_by: None,
1053 having: None,
1054 order_by: None,
1055 limit: None,
1056 };
1057 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1058 assert_eq!(rows.len(), 2);
1059 }
1060
1061 #[test]
1062 fn test_order_by_desc() {
1063 let q = SelectQuery {
1064 columns: ColumnList::All,
1065 table: "test".into(),
1066 table_alias: None,
1067 subquery: None,
1068 joins: vec![],
1069 where_clause: None,
1070 group_by: None,
1071 having: None,
1072 order_by: Some(vec![OrderSpec {
1073 column: "count".into(),
1074 expr: Some(Expr::Column("count".into())),
1075 descending: true,
1076 }]),
1077 limit: None,
1078 };
1079 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1080 assert_eq!(rows[0]["count"], Value::Int(20));
1081 assert_eq!(rows[2]["count"], Value::Int(5));
1082 }
1083
1084 #[test]
1085 fn test_limit() {
1086 let q = SelectQuery {
1087 columns: ColumnList::All,
1088 table: "test".into(),
1089 table_alias: None,
1090 subquery: None,
1091 joins: vec![],
1092 where_clause: None,
1093 group_by: None,
1094 having: None,
1095 order_by: None,
1096 limit: Some(2),
1097 };
1098 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1099 assert_eq!(rows.len(), 2);
1100 }
1101
1102 #[test]
1103 fn test_like() {
1104 let q = SelectQuery {
1105 columns: ColumnList::All,
1106 table: "test".into(),
1107 table_alias: None,
1108 subquery: None,
1109 joins: vec![],
1110 where_clause: Some(WhereClause::Comparison(Comparison {
1111 column: "title".into(),
1112 op: "LIKE".into(),
1113 value: Some(SqlValue::String("%lph%".into())),
1114 left_expr: Some(Expr::Column("title".into())),
1115 right_expr: None,
1116 })),
1117 group_by: None,
1118 having: None,
1119 order_by: None,
1120 limit: None,
1121 };
1122 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1123 assert_eq!(rows.len(), 1);
1124 assert_eq!(rows[0]["title"], Value::String("Alpha".into()));
1125 }
1126
1127 #[test]
1128 fn test_is_null() {
1129 let mut rows = make_rows();
1130 rows[1].insert("optional".into(), Value::Null);
1131
1132 let q = SelectQuery {
1133 columns: ColumnList::All,
1134 table: "test".into(),
1135 table_alias: None,
1136 subquery: None,
1137 joins: vec![],
1138 where_clause: Some(WhereClause::Comparison(Comparison {
1139 column: "optional".into(),
1140 op: "IS NULL".into(),
1141 value: None,
1142 left_expr: Some(Expr::Column("optional".into())),
1143 right_expr: None,
1144 })),
1145 group_by: None,
1146 having: None,
1147 order_by: None,
1148 limit: None,
1149 };
1150 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1151 assert_eq!(result.len(), 3);
1153 }
1154
1155 #[test]
1158 fn test_evaluate_expr_literal() {
1159 let row = Row::new();
1160 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Int(42)), &row), Value::Int(42));
1161 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Float(3.14)), &row), Value::Float(3.14));
1162 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Null), &row), Value::Null);
1163 }
1164
1165 #[test]
1166 fn test_evaluate_expr_column() {
1167 let row = Row::from([("x".into(), Value::Int(10))]);
1168 assert_eq!(evaluate_expr(&Expr::Column("x".into()), &row), Value::Int(10));
1169 assert_eq!(evaluate_expr(&Expr::Column("missing".into()), &row), Value::Null);
1170 }
1171
1172 #[test]
1173 fn test_evaluate_expr_int_arithmetic() {
1174 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(3))]);
1175 let add = Expr::BinaryOp {
1176 left: Box::new(Expr::Column("a".into())),
1177 op: ArithOp::Add,
1178 right: Box::new(Expr::Column("b".into())),
1179 };
1180 assert_eq!(evaluate_expr(&add, &row), Value::Int(13));
1181
1182 let sub = Expr::BinaryOp {
1183 left: Box::new(Expr::Column("a".into())),
1184 op: ArithOp::Sub,
1185 right: Box::new(Expr::Column("b".into())),
1186 };
1187 assert_eq!(evaluate_expr(&sub, &row), Value::Int(7));
1188
1189 let mul = Expr::BinaryOp {
1190 left: Box::new(Expr::Column("a".into())),
1191 op: ArithOp::Mul,
1192 right: Box::new(Expr::Column("b".into())),
1193 };
1194 assert_eq!(evaluate_expr(&mul, &row), Value::Int(30));
1195
1196 let div = Expr::BinaryOp {
1197 left: Box::new(Expr::Column("a".into())),
1198 op: ArithOp::Div,
1199 right: Box::new(Expr::Column("b".into())),
1200 };
1201 assert_eq!(evaluate_expr(&div, &row), Value::Int(3)); let modulo = Expr::BinaryOp {
1204 left: Box::new(Expr::Column("a".into())),
1205 op: ArithOp::Mod,
1206 right: Box::new(Expr::Column("b".into())),
1207 };
1208 assert_eq!(evaluate_expr(&modulo, &row), Value::Int(1));
1209 }
1210
1211 #[test]
1212 fn test_evaluate_expr_float_coercion() {
1213 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Float(3.0))]);
1214 let add = Expr::BinaryOp {
1215 left: Box::new(Expr::Column("a".into())),
1216 op: ArithOp::Add,
1217 right: Box::new(Expr::Column("b".into())),
1218 };
1219 assert_eq!(evaluate_expr(&add, &row), Value::Float(13.0));
1220 }
1221
1222 #[test]
1223 fn test_evaluate_expr_null_propagation() {
1224 let row = Row::from([("a".into(), Value::Int(10))]);
1225 let add = Expr::BinaryOp {
1226 left: Box::new(Expr::Column("a".into())),
1227 op: ArithOp::Add,
1228 right: Box::new(Expr::Column("missing".into())),
1229 };
1230 assert_eq!(evaluate_expr(&add, &row), Value::Null);
1231 }
1232
1233 #[test]
1234 fn test_evaluate_expr_div_by_zero() {
1235 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(0))]);
1236 let div = Expr::BinaryOp {
1237 left: Box::new(Expr::Column("a".into())),
1238 op: ArithOp::Div,
1239 right: Box::new(Expr::Column("b".into())),
1240 };
1241 assert_eq!(evaluate_expr(&div, &row), Value::Null);
1242 }
1243
1244 #[test]
1245 fn test_evaluate_expr_unary_minus() {
1246 let row = Row::from([("x".into(), Value::Int(5))]);
1247 let neg = Expr::UnaryMinus(Box::new(Expr::Column("x".into())));
1248 assert_eq!(evaluate_expr(&neg, &row), Value::Int(-5));
1249 }
1250
1251 #[test]
1252 fn test_select_with_expression() {
1253 let stmt = crate::query_parser::parse_query(
1255 "SELECT count * 2 AS doubled FROM test"
1256 ).unwrap();
1257 if let crate::query_parser::Statement::Select(q) = stmt {
1258 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1259 assert_eq!(cols, vec!["doubled"]);
1260 assert_eq!(rows.len(), 3);
1261 let values: Vec<Value> = rows.iter().map(|r| r["doubled"].clone()).collect();
1263 assert!(values.contains(&Value::Int(20)));
1264 assert!(values.contains(&Value::Int(10)));
1265 assert!(values.contains(&Value::Int(40)));
1266 } else {
1267 panic!("Expected Select");
1268 }
1269 }
1270
1271 #[test]
1272 fn test_where_with_expression() {
1273 let stmt = crate::query_parser::parse_query(
1275 "SELECT * FROM test WHERE count * 2 > 15"
1276 ).unwrap();
1277 if let crate::query_parser::Statement::Select(q) = stmt {
1278 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1279 assert_eq!(rows.len(), 2);
1281 } else {
1282 panic!("Expected Select");
1283 }
1284 }
1285
1286 #[test]
1287 fn test_order_by_expression() {
1288 let stmt = crate::query_parser::parse_query(
1290 "SELECT title, count FROM test ORDER BY count * -1 ASC"
1291 ).unwrap();
1292 if let crate::query_parser::Statement::Select(q) = stmt {
1293 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1294 assert_eq!(rows[0]["count"], Value::Int(20));
1296 assert_eq!(rows[1]["count"], Value::Int(10));
1297 assert_eq!(rows[2]["count"], Value::Int(5));
1298 } else {
1299 panic!("Expected Select");
1300 }
1301 }
1302
1303 #[test]
1306 fn test_case_when_eval_basic() {
1307 let row = Row::from([("status".into(), Value::String("ACTIVE".into()))]);
1308 let expr = Expr::Case {
1309 whens: vec![(
1310 WhereClause::Comparison(Comparison {
1311 column: "status".into(),
1312 op: "=".into(),
1313 value: Some(SqlValue::String("ACTIVE".into())),
1314 left_expr: Some(Expr::Column("status".into())),
1315 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1316 }),
1317 Box::new(Expr::Literal(SqlValue::Int(1))),
1318 )],
1319 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1320 };
1321 assert_eq!(evaluate_expr(&expr, &row), Value::Int(1));
1322 }
1323
1324 #[test]
1325 fn test_case_when_eval_else() {
1326 let row = Row::from([("status".into(), Value::String("KILLED".into()))]);
1327 let expr = Expr::Case {
1328 whens: vec![(
1329 WhereClause::Comparison(Comparison {
1330 column: "status".into(),
1331 op: "=".into(),
1332 value: Some(SqlValue::String("ACTIVE".into())),
1333 left_expr: Some(Expr::Column("status".into())),
1334 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1335 }),
1336 Box::new(Expr::Literal(SqlValue::Int(1))),
1337 )],
1338 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1339 };
1340 assert_eq!(evaluate_expr(&expr, &row), Value::Int(0));
1341 }
1342
1343 #[test]
1344 fn test_case_when_eval_no_else_null() {
1345 let row = Row::from([("x".into(), Value::Int(99))]);
1346 let expr = Expr::Case {
1347 whens: vec![(
1348 WhereClause::Comparison(Comparison {
1349 column: "x".into(),
1350 op: "=".into(),
1351 value: Some(SqlValue::Int(1)),
1352 left_expr: Some(Expr::Column("x".into())),
1353 right_expr: Some(Expr::Literal(SqlValue::Int(1))),
1354 }),
1355 Box::new(Expr::Literal(SqlValue::String("one".into()))),
1356 )],
1357 else_expr: None,
1358 };
1359 assert_eq!(evaluate_expr(&expr, &row), Value::Null);
1360 }
1361
1362 #[test]
1363 fn test_case_when_in_aggregate_query() {
1364 let stmt = crate::query_parser::parse_query(
1367 "SELECT SUM(CASE WHEN count > 5 THEN count ELSE 0 END) AS total FROM test"
1368 ).unwrap();
1369 if let crate::query_parser::Statement::Select(q) = stmt {
1370 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1371 assert_eq!(cols, vec!["total"]);
1372 assert_eq!(rows.len(), 1);
1373 assert_eq!(rows[0]["total"], Value::Float(30.0));
1374 } else {
1375 panic!("Expected Select");
1376 }
1377 }
1378
1379 #[test]
1380 fn test_case_when_with_unary_minus_in_aggregate() {
1381 let stmt = crate::query_parser::parse_query(
1384 "SELECT SUM(CASE WHEN title = 'Alpha' THEN count ELSE -count END) AS net FROM test"
1385 ).unwrap();
1386 if let crate::query_parser::Statement::Select(q) = stmt {
1387 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1388 assert_eq!(rows.len(), 1);
1389 assert_eq!(rows[0]["net"], Value::Float(-15.0));
1390 } else {
1391 panic!("Expected Select");
1392 }
1393 }
1394
1395 #[test]
1396 fn test_dateadd_with_dict_in_group_by() {
1397 use indexmap::IndexMap;
1399 let mut params = IndexMap::new();
1400 params.insert("exit_days".to_string(), Value::Int(21));
1401
1402 let rows = vec![
1403 Row::from([
1404 ("o.token".into(), Value::String("BTC".into())),
1405 ("o.event_date".into(), Value::Date(
1406 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1407 )),
1408 ("o.size".into(), Value::Int(100)),
1409 ("s.params".into(), Value::Dict(params.clone())),
1410 ]),
1411 Row::from([
1412 ("o.token".into(), Value::String("BTC".into())),
1413 ("o.event_date".into(), Value::Date(
1414 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1415 )),
1416 ("o.size".into(), Value::Int(50)),
1417 ("s.params".into(), Value::Dict(params.clone())),
1418 ]),
1419 ];
1420
1421 let q = SelectQuery {
1422 columns: ColumnList::Named(vec![
1423 SelectExpr::Column("o.token".into()),
1424 SelectExpr::Column("o.event_date".into()),
1425 SelectExpr::Expr {
1426 expr: Expr::DateAdd {
1427 date: Box::new(Expr::Column("o.event_date".into())),
1428 days: Box::new(Expr::Column("s.params.exit_days".into())),
1429 },
1430 alias: Some("exit_date".into()),
1431 },
1432 SelectExpr::Aggregate {
1433 func: AggFunc::Sum,
1434 arg: "o.size".into(),
1435 arg_expr: Some(Expr::Column("o.size".into())),
1436 alias: Some("total".into()),
1437 },
1438 ]),
1439 table: "orders".into(),
1440 table_alias: None,
1441 subquery: None,
1442 joins: vec![],
1443 where_clause: None,
1444 group_by: Some(vec!["o.token".into(), "o.event_date".into()]),
1445 having: None,
1446 order_by: None,
1447 limit: None,
1448 };
1449
1450 let (rows, cols) = execute_inner(&q, &rows, None).unwrap();
1451 assert_eq!(rows.len(), 1);
1452 assert!(cols.contains(&"exit_date".to_string()));
1453 assert_eq!(rows[0]["total"], Value::Float(150.0));
1454 assert_eq!(
1456 rows[0]["exit_date"],
1457 Value::Date(chrono::NaiveDate::from_ymd_opt(2026, 1, 22).unwrap())
1458 );
1459 }
1460
1461 #[test]
1462 fn test_aggregate_arithmetic() {
1463 let stmt = crate::query_parser::parse_query(
1467 "SELECT SUM(count) / COUNT(*) AS avg_count FROM test"
1468 ).unwrap();
1469 if let crate::query_parser::Statement::Select(q) = stmt {
1470 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1471 assert_eq!(cols, vec!["avg_count"]);
1472 assert_eq!(rows.len(), 1);
1473 match &rows[0]["avg_count"] {
1474 Value::Float(f) => assert!((f - 11.666666666666666).abs() < 0.001),
1475 other => panic!("Expected Float, got {:?}", other),
1476 }
1477 } else {
1478 panic!("Expected Select");
1479 }
1480 }
1481
1482 #[test]
1483 fn test_aggregate_subtraction_with_group_by() {
1484 let rows = vec![
1485 {
1486 let mut r = Row::new();
1487 r.insert("token".into(), Value::String("BTC".into()));
1488 r.insert("side".into(), Value::String("BUY".into()));
1489 r.insert("size".into(), Value::Float(100.0));
1490 r
1491 },
1492 {
1493 let mut r = Row::new();
1494 r.insert("token".into(), Value::String("BTC".into()));
1495 r.insert("side".into(), Value::String("SELL".into()));
1496 r.insert("size".into(), Value::Float(60.0));
1497 r
1498 },
1499 ];
1500 let stmt = crate::query_parser::parse_query(
1501 "SELECT token, SUM(CASE WHEN side = 'BUY' THEN size ELSE 0 END) - SUM(CASE WHEN side = 'SELL' THEN size ELSE 0 END) AS net FROM test GROUP BY token"
1502 ).unwrap();
1503 if let crate::query_parser::Statement::Select(q) = stmt {
1504 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1505 assert_eq!(result.len(), 1);
1506 assert_eq!(result[0]["net"], Value::Float(40.0));
1507 } else {
1508 panic!("Expected Select");
1509 }
1510 }
1511
1512 #[test]
1515 fn test_aggregate_subtraction_no_group() {
1516 let stmt = crate::query_parser::parse_query(
1518 "SELECT SUM(count) - COUNT(*) as diff FROM test"
1519 ).unwrap();
1520 if let crate::query_parser::Statement::Select(q) = stmt {
1521 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1522 assert_eq!(cols, vec!["diff"]);
1523 assert_eq!(rows.len(), 1);
1524 assert_eq!(rows[0]["diff"], Value::Float(32.0));
1525 } else {
1526 panic!("Expected Select");
1527 }
1528 }
1529
1530 #[test]
1533 fn test_aggregate_division_with_group_by() {
1534 let rows = vec![
1535 {
1536 let mut r = Row::new();
1537 r.insert("category".into(), Value::String("A".into()));
1538 r.insert("count".into(), Value::Int(10));
1539 r
1540 },
1541 {
1542 let mut r = Row::new();
1543 r.insert("category".into(), Value::String("A".into()));
1544 r.insert("count".into(), Value::Int(20));
1545 r
1546 },
1547 {
1548 let mut r = Row::new();
1549 r.insert("category".into(), Value::String("B".into()));
1550 r.insert("count".into(), Value::Int(6));
1551 r
1552 },
1553 ];
1554 let stmt = crate::query_parser::parse_query(
1557 "SELECT category, SUM(count) / COUNT(*) as ratio FROM test GROUP BY category"
1558 ).unwrap();
1559 if let crate::query_parser::Statement::Select(q) = stmt {
1560 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
1561 assert!(cols.contains(&"ratio".to_string()));
1562 assert_eq!(result.len(), 2);
1563 let group_a = result.iter().find(|r| r["category"] == Value::String("A".into())).unwrap();
1565 let group_b = result.iter().find(|r| r["category"] == Value::String("B".into())).unwrap();
1566 match &group_a["ratio"] {
1567 Value::Float(f) => assert!((f - 15.0).abs() < 0.001),
1568 other => panic!("Expected Float for group A ratio, got {:?}", other),
1569 }
1570 match &group_b["ratio"] {
1571 Value::Float(f) => assert!((f - 6.0).abs() < 0.001),
1572 other => panic!("Expected Float for group B ratio, got {:?}", other),
1573 }
1574 } else {
1575 panic!("Expected Select");
1576 }
1577 }
1578}