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 == CmpOp::Like || cmp.op == CmpOp::NotLike) && 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 {
384 BoolOpKind::And => left && evaluate_with_fts(&bop.right, row, fts),
385 BoolOpKind::Or => left || evaluate_with_fts(&bop.right, row, fts),
386 }
387 }
388 WhereClause::Comparison(cmp) => {
389 if cmp.op == CmpOp::Like || cmp.op == CmpOp::NotLike {
391 if let Some(SqlValue::String(pattern)) = &cmp.value {
392 let key = (cmp.column.clone(), pattern.clone());
393 if let Some(matching_paths) = fts.get(&key) {
394 let row_path = row.get("path").and_then(|v| v.as_str()).unwrap_or("");
395 let matched = matching_paths.contains(row_path);
396 return if cmp.op == CmpOp::Like { matched } else { !matched };
397 }
398 }
399 }
400 evaluate_comparison(cmp, row)
401 }
402 }
403}
404
405pub use crate::query_join::execute_join_query;
406
407pub(crate) fn execute_inner(
408 query: &SelectQuery,
409 rows: &[Row],
410 index: Option<&crate::index::TableIndex>,
411) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
412 let empty_fts = HashMap::new();
413 execute_with_fts(query, rows, index, &empty_fts)
414}
415
416pub fn evaluate(clause: &WhereClause, row: &Row) -> bool {
417 match clause {
418 WhereClause::BoolOp(bop) => {
419 let left = evaluate(&bop.left, row);
420 match bop.op {
421 BoolOpKind::And => left && evaluate(&bop.right, row),
422 BoolOpKind::Or => left || evaluate(&bop.right, row),
423 }
424 }
425 WhereClause::Comparison(cmp) => evaluate_comparison(cmp, row),
426 }
427}
428
429pub(crate) fn evaluate_expr(expr: &Expr, row: &Row) -> Value {
431 match expr {
432 Expr::Literal(SqlValue::Int(n)) => Value::Int(*n),
433 Expr::Literal(SqlValue::Float(f)) => Value::Float(*f),
434 Expr::Literal(SqlValue::String(s)) => Value::String(s.clone()),
435 Expr::Literal(SqlValue::Null) => Value::Null,
436 Expr::Literal(SqlValue::List(_)) => Value::Null,
437 Expr::Column(name) => {
438 if let Some(val) = row.get(name) {
439 return val.clone();
440 }
441 for (i, _) in name.match_indices('.') {
443 let dict_col = &name[..i];
444 let dict_key = &name[i + 1..];
445 if let Some(Value::Dict(map)) = row.get(dict_col) {
446 return map.get(dict_key).cloned().unwrap_or(Value::Null);
447 }
448 }
449 Value::Null
450 }
451 Expr::UnaryMinus(inner) => {
452 match evaluate_expr(inner, row) {
453 Value::Int(n) => Value::Int(-n),
454 Value::Float(f) => Value::Float(-f),
455 Value::Null => Value::Null,
456 _ => Value::Null, }
458 }
459 Expr::BinaryOp { left, op, right } => {
460 let lv = evaluate_expr(left, row);
461 let rv = evaluate_expr(right, row);
462
463 if lv.is_null() || rv.is_null() {
465 return Value::Null;
466 }
467
468 match (&lv, &rv) {
470 (Value::Int(a), Value::Int(b)) => {
471 match op {
472 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
473 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
474 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
475 ArithOp::Div => {
476 if *b == 0 { Value::Null } else { Value::Int(a / b) }
477 }
478 ArithOp::Mod => {
479 if *b == 0 { Value::Null } else { Value::Int(a % b) }
480 }
481 }
482 }
483 _ => {
484 let a = match &lv {
486 Value::Int(n) => *n as f64,
487 Value::Float(f) => *f,
488 _ => return Value::Null,
489 };
490 let b = match &rv {
491 Value::Int(n) => *n as f64,
492 Value::Float(f) => *f,
493 _ => return Value::Null,
494 };
495 match op {
496 ArithOp::Add => Value::Float(a + b),
497 ArithOp::Sub => Value::Float(a - b),
498 ArithOp::Mul => Value::Float(a * b),
499 ArithOp::Div => {
500 if b == 0.0 { Value::Null } else { Value::Float(a / b) }
501 }
502 ArithOp::Mod => {
503 if b == 0.0 { Value::Null } else { Value::Float(a % b) }
504 }
505 }
506 }
507 }
508 }
509 Expr::Case { whens, else_expr } => {
510 for (condition, result) in whens {
511 if evaluate(condition, row) {
512 return evaluate_expr(result, row);
513 }
514 }
515 match else_expr {
516 Some(e) => evaluate_expr(e, row),
517 None => Value::Null,
518 }
519 }
520 Expr::CurrentDate => {
521 Value::Date(chrono::Local::now().naive_local().date())
522 }
523 Expr::CurrentTimestamp => {
524 Value::DateTime(chrono::Local::now().naive_local())
525 }
526 Expr::DateAdd { date, days } => {
527 let date_val = evaluate_expr(date, row);
528 let days_val = evaluate_expr(days, row);
529 let n = match &days_val {
530 Value::Int(n) => *n,
531 Value::Float(f) => *f as i64,
532 _ => return Value::Null,
533 };
534 let duration = chrono::Duration::days(n);
535 match date_val {
536 Value::Date(d) => {
537 match d.checked_add_signed(duration) {
538 Some(result) => Value::Date(result),
539 None => Value::Null,
540 }
541 }
542 Value::DateTime(dt) => {
543 match dt.checked_add_signed(duration) {
544 Some(result) => Value::DateTime(result),
545 None => Value::Null,
546 }
547 }
548 _ => Value::Null,
549 }
550 }
551 Expr::DateDiff { left, right } => {
552 let lv = evaluate_expr(left, row);
553 let rv = evaluate_expr(right, row);
554 let left_date = match &lv {
555 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
556 Value::DateTime(dt) => *dt,
557 _ => return Value::Null,
558 };
559 let right_date = match &rv {
560 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
561 Value::DateTime(dt) => *dt,
562 _ => return Value::Null,
563 };
564 Value::Int((left_date - right_date).num_days())
565 }
566 Expr::Aggregate { func, arg, .. } => {
567 let func_name = match func {
569 AggFunc::Count => "COUNT",
570 AggFunc::Sum => "SUM",
571 AggFunc::Avg => "AVG",
572 AggFunc::Min => "MIN",
573 AggFunc::Max => "MAX",
574 };
575 let col = format!("{}({})", func_name, arg);
576 row.get(&col).cloned().unwrap_or(Value::Null)
577 }
578 }
579}
580
581fn evaluate_agg_expr(expr: &Expr, group_rows: &[&Row]) -> Value {
582 match expr {
583 Expr::Aggregate { func, arg, arg_expr } => {
584 compute_aggregate(func, arg, arg_expr.as_deref(), group_rows)
585 }
586 Expr::BinaryOp { left, op, right } => {
587 let lv = evaluate_agg_expr(left, group_rows);
588 let rv = evaluate_agg_expr(right, group_rows);
589 apply_arith_op(op, &lv, &rv)
590 }
591 Expr::UnaryMinus(inner) => {
592 match evaluate_agg_expr(inner, group_rows) {
593 Value::Int(n) => Value::Int(-n),
594 Value::Float(f) => Value::Float(-f),
595 _ => Value::Null,
596 }
597 }
598 other => {
599 if let Some(first) = group_rows.first() {
600 evaluate_expr(other, first)
601 } else {
602 Value::Null
603 }
604 }
605 }
606}
607
608fn apply_arith_op(op: &ArithOp, lv: &Value, rv: &Value) -> Value {
609 if lv.is_null() || rv.is_null() {
610 return Value::Null;
611 }
612 match (lv, rv) {
613 (Value::Int(a), Value::Int(b)) => match op {
614 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
615 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
616 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
617 ArithOp::Div => if *b == 0 { Value::Null } else { Value::Int(a / b) },
618 ArithOp::Mod => if *b == 0 { Value::Null } else { Value::Int(a % b) },
619 },
620 _ => {
621 let a = match lv {
622 Value::Int(n) => *n as f64,
623 Value::Float(f) => *f,
624 _ => return Value::Null,
625 };
626 let b = match rv {
627 Value::Int(n) => *n as f64,
628 Value::Float(f) => *f,
629 _ => return Value::Null,
630 };
631 match op {
632 ArithOp::Add => Value::Float(a + b),
633 ArithOp::Sub => Value::Float(a - b),
634 ArithOp::Mul => Value::Float(a * b),
635 ArithOp::Div => if b == 0.0 { Value::Null } else { Value::Float(a / b) },
636 ArithOp::Mod => if b == 0.0 { Value::Null } else { Value::Float(a % b) },
637 }
638 }
639 }
640}
641
642fn evaluate_comparison(cmp: &Comparison, row: &Row) -> bool {
643 if let (Some(left_expr), Some(right_expr)) = (&cmp.left_expr, &cmp.right_expr) {
645 if matches!(cmp.op, CmpOp::Eq | CmpOp::Ne | CmpOp::Lt | CmpOp::Gt | CmpOp::Le | CmpOp::Ge) {
646 let left_val = evaluate_expr(left_expr, row);
647 let right_val = evaluate_expr(right_expr, row);
648
649 if left_val.is_null() || right_val.is_null() {
651 return false;
652 }
653
654 let ord = compare_model_values(&left_val, &right_val);
656
657 return match cmp.op {
658 CmpOp::Eq => ord == Some(Ordering::Equal),
659 CmpOp::Ne => ord != Some(Ordering::Equal),
660 CmpOp::Lt => ord == Some(Ordering::Less),
661 CmpOp::Gt => ord == Some(Ordering::Greater),
662 CmpOp::Le => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
663 CmpOp::Ge => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
664 _ => false,
665 };
666 }
667 }
668
669 let actual = row.get(&cmp.column);
671
672 if cmp.op == CmpOp::IsNull {
673 return actual.map_or(true, |v| v.is_null());
674 }
675 if cmp.op == CmpOp::IsNotNull {
676 return actual.map_or(false, |v| !v.is_null());
677 }
678
679 let actual = match actual {
680 Some(v) if !v.is_null() => v,
681 _ => return false,
682 };
683
684 let expected = match &cmp.value {
685 Some(v) => v,
686 None => return false,
687 };
688
689 match cmp.op {
690 CmpOp::Eq => eq_match(actual, expected),
691 CmpOp::Ne => !eq_match(actual, expected),
692 CmpOp::Lt => compare_values(actual, expected) == Some(Ordering::Less),
693 CmpOp::Gt => compare_values(actual, expected) == Some(Ordering::Greater),
694 CmpOp::Le => matches!(compare_values(actual, expected), Some(Ordering::Less | Ordering::Equal)),
695 CmpOp::Ge => matches!(compare_values(actual, expected), Some(Ordering::Greater | Ordering::Equal)),
696 CmpOp::Like => like_match(actual, expected),
697 CmpOp::NotLike => !like_match(actual, expected),
698 CmpOp::In => {
699 if let SqlValue::List(items) = expected {
700 items.iter().any(|v| eq_match(actual, v))
701 } else {
702 eq_match(actual, expected)
703 }
704 }
705 CmpOp::IsNull | CmpOp::IsNotNull => unreachable!(),
706 }
707}
708
709fn compare_model_values(a: &Value, b: &Value) -> Option<Ordering> {
711 match (a, b) {
712 (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y),
713 (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)),
714 _ => a.partial_cmp(b),
715 }
716}
717
718fn coerce_sql_to_value(sql_val: &SqlValue, target: &Value) -> Value {
719 match sql_val {
720 SqlValue::Null => Value::Null,
721 SqlValue::String(s) => {
722 match target {
723 Value::Int(_) => s.parse::<i64>().map(Value::Int).unwrap_or(Value::String(s.clone())),
724 Value::Float(_) => s.parse::<f64>().map(Value::Float).unwrap_or(Value::String(s.clone())),
725 Value::Date(_) => {
726 chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
727 .map(Value::Date)
728 .unwrap_or(Value::String(s.clone()))
729 }
730 Value::DateTime(_) => {
731 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
732 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f"))
733 .map(Value::DateTime)
734 .unwrap_or(Value::String(s.clone()))
735 }
736 _ => Value::String(s.clone()),
737 }
738 }
739 SqlValue::Int(n) => {
740 match target {
741 Value::Float(_) => Value::Float(*n as f64),
742 _ => Value::Int(*n),
743 }
744 }
745 SqlValue::Float(f) => Value::Float(*f),
746 SqlValue::List(_) => Value::Null, }
748}
749
750fn eq_match(actual: &Value, expected: &SqlValue) -> bool {
751 if let Value::List(items) = actual {
753 if let SqlValue::String(s) = expected {
754 return items.contains(s);
755 }
756 }
757
758 let coerced = coerce_sql_to_value(expected, actual);
759 actual == &coerced
760}
761
762fn like_match(actual: &Value, pattern: &SqlValue) -> bool {
763 let pattern_str = match pattern {
764 SqlValue::String(s) => s,
765 _ => return false,
766 };
767
768 let mut regex_str = String::from("(?is)^");
770 for ch in pattern_str.chars() {
771 match ch {
772 '%' => regex_str.push_str(".*"),
773 '_' => regex_str.push('.'),
774 c => {
775 if regex::escape(&c.to_string()) != c.to_string() {
776 regex_str.push_str(®ex::escape(&c.to_string()));
777 } else {
778 regex_str.push(c);
779 }
780 }
781 }
782 }
783 regex_str.push('$');
784
785 let re = match Regex::new(®ex_str) {
786 Ok(r) => r,
787 Err(_) => return false,
788 };
789
790 match actual {
791 Value::List(items) => items.iter().any(|item| re.is_match(item)),
792 _ => re.is_match(&actual.to_display_string()),
793 }
794}
795
796fn compare_values(actual: &Value, expected: &SqlValue) -> Option<Ordering> {
797 let coerced = coerce_sql_to_value(expected, actual);
798 actual.partial_cmp(&coerced)
799}
800
801fn sql_value_to_index_value(sv: &SqlValue) -> Value {
803 match sv {
804 SqlValue::String(s) => {
805 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
807 return Value::DateTime(dt);
808 }
809 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
810 return Value::DateTime(dt);
811 }
812 if let Ok(d) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
814 return Value::Date(d);
815 }
816 Value::String(s.clone())
817 }
818 SqlValue::Int(n) => Value::Int(*n),
819 SqlValue::Float(f) => Value::Float(*f),
820 SqlValue::Null => Value::Null,
821 SqlValue::List(_) => Value::Null,
822 }
823}
824
825fn try_index_filter(
829 clause: &WhereClause,
830 index: &crate::index::TableIndex,
831) -> Option<std::collections::HashSet<String>> {
832 match clause {
833 WhereClause::Comparison(cmp) => {
834 if !index.has_index(&cmp.column) {
835 return None;
836 }
837 match cmp.op {
838 CmpOp::Eq => {
839 let val = sql_value_to_index_value(cmp.value.as_ref()?);
840 let paths = index.lookup_eq(&cmp.column, &val);
841 Some(paths.into_iter().map(|s| s.to_string()).collect())
842 }
843 CmpOp::Lt => {
844 let val = sql_value_to_index_value(cmp.value.as_ref()?);
845 let range_paths = index.lookup_range(&cmp.column, None, Some(&val));
848 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
849 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
850 }
851 CmpOp::Gt => {
852 let val = sql_value_to_index_value(cmp.value.as_ref()?);
853 let range_paths = index.lookup_range(&cmp.column, Some(&val), None);
854 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
855 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
856 }
857 CmpOp::Le => {
858 let val = sql_value_to_index_value(cmp.value.as_ref()?);
859 let paths = index.lookup_range(&cmp.column, None, Some(&val));
860 Some(paths.into_iter().map(|s| s.to_string()).collect())
861 }
862 CmpOp::Ge => {
863 let val = sql_value_to_index_value(cmp.value.as_ref()?);
864 let paths = index.lookup_range(&cmp.column, Some(&val), None);
865 Some(paths.into_iter().map(|s| s.to_string()).collect())
866 }
867 CmpOp::In => {
868 if let Some(SqlValue::List(items)) = &cmp.value {
869 let vals: Vec<Value> = items.iter().map(sql_value_to_index_value).collect();
870 let paths = index.lookup_in(&cmp.column, &vals);
871 Some(paths.into_iter().map(|s| s.to_string()).collect())
872 } else {
873 None
874 }
875 }
876 _ => None, }
878 }
879 WhereClause::BoolOp(bop) => {
880 let left = try_index_filter(&bop.left, index);
881 let right = try_index_filter(&bop.right, index);
882 match bop.op {
883 BoolOpKind::And => {
884 match (left, right) {
885 (Some(l), Some(r)) => Some(l.intersection(&r).cloned().collect()),
886 (Some(l), None) => Some(l), (None, Some(r)) => Some(r),
888 (None, None) => None,
889 }
890 }
891 BoolOpKind::Or => {
892 match (left, right) {
893 (Some(l), Some(r)) => Some(l.union(&r).cloned().collect()),
894 _ => None, }
896 }
897 }
898 }
899 }
900}
901
902fn resolve_order_aliases(specs: &[OrderSpec], columns: &ColumnList) -> Vec<OrderSpec> {
905 let named = match columns {
906 ColumnList::Named(exprs) => exprs,
907 _ => return specs.to_vec(),
908 };
909
910 let alias_map: HashMap<String, &Expr> = named
912 .iter()
913 .filter_map(|se| match se {
914 SelectExpr::Expr { expr, alias: Some(a) } => Some((a.clone(), expr)),
915 _ => None,
916 })
917 .collect();
918
919 specs
920 .iter()
921 .map(|spec| {
922 if let Some(expr) = alias_map.get(&spec.column) {
924 OrderSpec {
925 column: spec.column.clone(),
926 expr: Some((*expr).clone()),
927 descending: spec.descending,
928 }
929 } else {
930 spec.clone()
931 }
932 })
933 .collect()
934}
935
936fn sort_rows(rows: &mut Vec<Row>, specs: &[OrderSpec]) {
937 rows.sort_by(|a, b| {
938 for spec in specs {
939 let (va, vb) = if let Some(ref expr) = spec.expr {
940 (evaluate_expr(expr, a), evaluate_expr(expr, b))
941 } else {
942 (
943 a.get(&spec.column).cloned().unwrap_or(Value::Null),
944 b.get(&spec.column).cloned().unwrap_or(Value::Null),
945 )
946 };
947
948 let ordering = match (&va, &vb) {
950 (Value::Null, Value::Null) => Ordering::Equal,
951 (Value::Null, _) => Ordering::Greater,
952 (_, Value::Null) => Ordering::Less,
953 (a_val, b_val) => {
954 compare_model_values(a_val, b_val).unwrap_or(Ordering::Equal)
955 }
956 };
957
958 let ordering = if spec.descending {
959 ordering.reverse()
960 } else {
961 ordering
962 };
963
964 if ordering != Ordering::Equal {
965 return ordering;
966 }
967 }
968 Ordering::Equal
969 });
970}
971
972pub(crate) fn sql_value_to_value(sql_val: &SqlValue) -> Value {
974 match sql_val {
975 SqlValue::Null => Value::Null,
976 SqlValue::String(s) => Value::String(s.clone()),
977 SqlValue::Int(n) => Value::Int(*n),
978 SqlValue::Float(f) => Value::Float(*f),
979 SqlValue::List(items) => {
980 let strings: Vec<String> = items
981 .iter()
982 .filter_map(|v| match v {
983 SqlValue::String(s) => Some(s.clone()),
984 _ => None,
985 })
986 .collect();
987 Value::List(strings)
988 }
989 }
990}
991
992#[cfg(test)]
993mod tests {
994 use super::*;
995
996 fn make_rows() -> Vec<Row> {
997 vec![
998 Row::from([
999 ("path".into(), Value::String("a.md".into())),
1000 ("title".into(), Value::String("Alpha".into())),
1001 ("count".into(), Value::Int(10)),
1002 ]),
1003 Row::from([
1004 ("path".into(), Value::String("b.md".into())),
1005 ("title".into(), Value::String("Beta".into())),
1006 ("count".into(), Value::Int(5)),
1007 ]),
1008 Row::from([
1009 ("path".into(), Value::String("c.md".into())),
1010 ("title".into(), Value::String("Gamma".into())),
1011 ("count".into(), Value::Int(20)),
1012 ]),
1013 ]
1014 }
1015
1016 #[test]
1017 fn test_select_all() {
1018 let q = SelectQuery {
1019 columns: ColumnList::All,
1020 table: "test".into(),
1021 table_alias: None,
1022 subquery: None,
1023 joins: vec![],
1024 where_clause: None,
1025 group_by: None,
1026 having: None,
1027 order_by: None,
1028 limit: None,
1029 };
1030 let (rows, _cols) = execute_inner(&q, &make_rows(), None).unwrap();
1031 assert_eq!(rows.len(), 3);
1032 }
1033
1034 #[test]
1035 fn test_where_gt() {
1036 let q = SelectQuery {
1037 columns: ColumnList::All,
1038 table: "test".into(),
1039 table_alias: None,
1040 subquery: None,
1041 joins: vec![],
1042 where_clause: Some(WhereClause::Comparison(Comparison {
1043 column: "count".into(),
1044 op: CmpOp::Gt,
1045 value: Some(SqlValue::Int(5)),
1046 left_expr: Some(Expr::Column("count".into())),
1047 right_expr: Some(Expr::Literal(SqlValue::Int(5))),
1048 })),
1049 group_by: None,
1050 having: None,
1051 order_by: None,
1052 limit: None,
1053 };
1054 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1055 assert_eq!(rows.len(), 2);
1056 }
1057
1058 #[test]
1059 fn test_order_by_desc() {
1060 let q = SelectQuery {
1061 columns: ColumnList::All,
1062 table: "test".into(),
1063 table_alias: None,
1064 subquery: None,
1065 joins: vec![],
1066 where_clause: None,
1067 group_by: None,
1068 having: None,
1069 order_by: Some(vec![OrderSpec {
1070 column: "count".into(),
1071 expr: Some(Expr::Column("count".into())),
1072 descending: true,
1073 }]),
1074 limit: None,
1075 };
1076 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1077 assert_eq!(rows[0]["count"], Value::Int(20));
1078 assert_eq!(rows[2]["count"], Value::Int(5));
1079 }
1080
1081 #[test]
1082 fn test_limit() {
1083 let q = SelectQuery {
1084 columns: ColumnList::All,
1085 table: "test".into(),
1086 table_alias: None,
1087 subquery: None,
1088 joins: vec![],
1089 where_clause: None,
1090 group_by: None,
1091 having: None,
1092 order_by: None,
1093 limit: Some(2),
1094 };
1095 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1096 assert_eq!(rows.len(), 2);
1097 }
1098
1099 #[test]
1100 fn test_like() {
1101 let q = SelectQuery {
1102 columns: ColumnList::All,
1103 table: "test".into(),
1104 table_alias: None,
1105 subquery: None,
1106 joins: vec![],
1107 where_clause: Some(WhereClause::Comparison(Comparison {
1108 column: "title".into(),
1109 op: CmpOp::Like,
1110 value: Some(SqlValue::String("%lph%".into())),
1111 left_expr: Some(Expr::Column("title".into())),
1112 right_expr: None,
1113 })),
1114 group_by: None,
1115 having: None,
1116 order_by: None,
1117 limit: None,
1118 };
1119 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1120 assert_eq!(rows.len(), 1);
1121 assert_eq!(rows[0]["title"], Value::String("Alpha".into()));
1122 }
1123
1124 #[test]
1125 fn test_is_null() {
1126 let mut rows = make_rows();
1127 rows[1].insert("optional".into(), Value::Null);
1128
1129 let q = SelectQuery {
1130 columns: ColumnList::All,
1131 table: "test".into(),
1132 table_alias: None,
1133 subquery: None,
1134 joins: vec![],
1135 where_clause: Some(WhereClause::Comparison(Comparison {
1136 column: "optional".into(),
1137 op: CmpOp::IsNull,
1138 value: None,
1139 left_expr: Some(Expr::Column("optional".into())),
1140 right_expr: None,
1141 })),
1142 group_by: None,
1143 having: None,
1144 order_by: None,
1145 limit: None,
1146 };
1147 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1148 assert_eq!(result.len(), 3);
1150 }
1151
1152 #[test]
1155 fn test_evaluate_expr_literal() {
1156 let row = Row::new();
1157 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Int(42)), &row), Value::Int(42));
1158 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Float(3.14)), &row), Value::Float(3.14));
1159 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Null), &row), Value::Null);
1160 }
1161
1162 #[test]
1163 fn test_evaluate_expr_column() {
1164 let row = Row::from([("x".into(), Value::Int(10))]);
1165 assert_eq!(evaluate_expr(&Expr::Column("x".into()), &row), Value::Int(10));
1166 assert_eq!(evaluate_expr(&Expr::Column("missing".into()), &row), Value::Null);
1167 }
1168
1169 #[test]
1170 fn test_evaluate_expr_int_arithmetic() {
1171 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(3))]);
1172 let add = Expr::BinaryOp {
1173 left: Box::new(Expr::Column("a".into())),
1174 op: ArithOp::Add,
1175 right: Box::new(Expr::Column("b".into())),
1176 };
1177 assert_eq!(evaluate_expr(&add, &row), Value::Int(13));
1178
1179 let sub = Expr::BinaryOp {
1180 left: Box::new(Expr::Column("a".into())),
1181 op: ArithOp::Sub,
1182 right: Box::new(Expr::Column("b".into())),
1183 };
1184 assert_eq!(evaluate_expr(&sub, &row), Value::Int(7));
1185
1186 let mul = Expr::BinaryOp {
1187 left: Box::new(Expr::Column("a".into())),
1188 op: ArithOp::Mul,
1189 right: Box::new(Expr::Column("b".into())),
1190 };
1191 assert_eq!(evaluate_expr(&mul, &row), Value::Int(30));
1192
1193 let div = Expr::BinaryOp {
1194 left: Box::new(Expr::Column("a".into())),
1195 op: ArithOp::Div,
1196 right: Box::new(Expr::Column("b".into())),
1197 };
1198 assert_eq!(evaluate_expr(&div, &row), Value::Int(3)); let modulo = Expr::BinaryOp {
1201 left: Box::new(Expr::Column("a".into())),
1202 op: ArithOp::Mod,
1203 right: Box::new(Expr::Column("b".into())),
1204 };
1205 assert_eq!(evaluate_expr(&modulo, &row), Value::Int(1));
1206 }
1207
1208 #[test]
1209 fn test_evaluate_expr_float_coercion() {
1210 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Float(3.0))]);
1211 let add = Expr::BinaryOp {
1212 left: Box::new(Expr::Column("a".into())),
1213 op: ArithOp::Add,
1214 right: Box::new(Expr::Column("b".into())),
1215 };
1216 assert_eq!(evaluate_expr(&add, &row), Value::Float(13.0));
1217 }
1218
1219 #[test]
1220 fn test_evaluate_expr_null_propagation() {
1221 let row = Row::from([("a".into(), Value::Int(10))]);
1222 let add = Expr::BinaryOp {
1223 left: Box::new(Expr::Column("a".into())),
1224 op: ArithOp::Add,
1225 right: Box::new(Expr::Column("missing".into())),
1226 };
1227 assert_eq!(evaluate_expr(&add, &row), Value::Null);
1228 }
1229
1230 #[test]
1231 fn test_evaluate_expr_div_by_zero() {
1232 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(0))]);
1233 let div = Expr::BinaryOp {
1234 left: Box::new(Expr::Column("a".into())),
1235 op: ArithOp::Div,
1236 right: Box::new(Expr::Column("b".into())),
1237 };
1238 assert_eq!(evaluate_expr(&div, &row), Value::Null);
1239 }
1240
1241 #[test]
1242 fn test_evaluate_expr_unary_minus() {
1243 let row = Row::from([("x".into(), Value::Int(5))]);
1244 let neg = Expr::UnaryMinus(Box::new(Expr::Column("x".into())));
1245 assert_eq!(evaluate_expr(&neg, &row), Value::Int(-5));
1246 }
1247
1248 #[test]
1249 fn test_select_with_expression() {
1250 let stmt = crate::query_parser::parse_query(
1252 "SELECT count * 2 AS doubled FROM test"
1253 ).unwrap();
1254 if let crate::query_parser::Statement::Select(q) = stmt {
1255 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1256 assert_eq!(cols, vec!["doubled"]);
1257 assert_eq!(rows.len(), 3);
1258 let values: Vec<Value> = rows.iter().map(|r| r["doubled"].clone()).collect();
1260 assert!(values.contains(&Value::Int(20)));
1261 assert!(values.contains(&Value::Int(10)));
1262 assert!(values.contains(&Value::Int(40)));
1263 } else {
1264 panic!("Expected Select");
1265 }
1266 }
1267
1268 #[test]
1269 fn test_where_with_expression() {
1270 let stmt = crate::query_parser::parse_query(
1272 "SELECT * FROM test WHERE count * 2 > 15"
1273 ).unwrap();
1274 if let crate::query_parser::Statement::Select(q) = stmt {
1275 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1276 assert_eq!(rows.len(), 2);
1278 } else {
1279 panic!("Expected Select");
1280 }
1281 }
1282
1283 #[test]
1284 fn test_order_by_expression() {
1285 let stmt = crate::query_parser::parse_query(
1287 "SELECT title, count FROM test ORDER BY count * -1 ASC"
1288 ).unwrap();
1289 if let crate::query_parser::Statement::Select(q) = stmt {
1290 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1291 assert_eq!(rows[0]["count"], Value::Int(20));
1293 assert_eq!(rows[1]["count"], Value::Int(10));
1294 assert_eq!(rows[2]["count"], Value::Int(5));
1295 } else {
1296 panic!("Expected Select");
1297 }
1298 }
1299
1300 #[test]
1303 fn test_case_when_eval_basic() {
1304 let row = Row::from([("status".into(), Value::String("ACTIVE".into()))]);
1305 let expr = Expr::Case {
1306 whens: vec![(
1307 WhereClause::Comparison(Comparison {
1308 column: "status".into(),
1309 op: CmpOp::Eq,
1310 value: Some(SqlValue::String("ACTIVE".into())),
1311 left_expr: Some(Expr::Column("status".into())),
1312 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1313 }),
1314 Box::new(Expr::Literal(SqlValue::Int(1))),
1315 )],
1316 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1317 };
1318 assert_eq!(evaluate_expr(&expr, &row), Value::Int(1));
1319 }
1320
1321 #[test]
1322 fn test_case_when_eval_else() {
1323 let row = Row::from([("status".into(), Value::String("KILLED".into()))]);
1324 let expr = Expr::Case {
1325 whens: vec![(
1326 WhereClause::Comparison(Comparison {
1327 column: "status".into(),
1328 op: CmpOp::Eq,
1329 value: Some(SqlValue::String("ACTIVE".into())),
1330 left_expr: Some(Expr::Column("status".into())),
1331 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1332 }),
1333 Box::new(Expr::Literal(SqlValue::Int(1))),
1334 )],
1335 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1336 };
1337 assert_eq!(evaluate_expr(&expr, &row), Value::Int(0));
1338 }
1339
1340 #[test]
1341 fn test_case_when_eval_no_else_null() {
1342 let row = Row::from([("x".into(), Value::Int(99))]);
1343 let expr = Expr::Case {
1344 whens: vec![(
1345 WhereClause::Comparison(Comparison {
1346 column: "x".into(),
1347 op: CmpOp::Eq,
1348 value: Some(SqlValue::Int(1)),
1349 left_expr: Some(Expr::Column("x".into())),
1350 right_expr: Some(Expr::Literal(SqlValue::Int(1))),
1351 }),
1352 Box::new(Expr::Literal(SqlValue::String("one".into()))),
1353 )],
1354 else_expr: None,
1355 };
1356 assert_eq!(evaluate_expr(&expr, &row), Value::Null);
1357 }
1358
1359 #[test]
1360 fn test_case_when_in_aggregate_query() {
1361 let stmt = crate::query_parser::parse_query(
1364 "SELECT SUM(CASE WHEN count > 5 THEN count ELSE 0 END) AS total FROM test"
1365 ).unwrap();
1366 if let crate::query_parser::Statement::Select(q) = stmt {
1367 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1368 assert_eq!(cols, vec!["total"]);
1369 assert_eq!(rows.len(), 1);
1370 assert_eq!(rows[0]["total"], Value::Float(30.0));
1371 } else {
1372 panic!("Expected Select");
1373 }
1374 }
1375
1376 #[test]
1377 fn test_case_when_with_unary_minus_in_aggregate() {
1378 let stmt = crate::query_parser::parse_query(
1381 "SELECT SUM(CASE WHEN title = 'Alpha' THEN count ELSE -count END) AS net FROM test"
1382 ).unwrap();
1383 if let crate::query_parser::Statement::Select(q) = stmt {
1384 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1385 assert_eq!(rows.len(), 1);
1386 assert_eq!(rows[0]["net"], Value::Float(-15.0));
1387 } else {
1388 panic!("Expected Select");
1389 }
1390 }
1391
1392 #[test]
1393 fn test_dateadd_with_dict_in_group_by() {
1394 use indexmap::IndexMap;
1396 let mut params = IndexMap::new();
1397 params.insert("exit_days".to_string(), Value::Int(21));
1398
1399 let rows = vec![
1400 Row::from([
1401 ("o.token".into(), Value::String("BTC".into())),
1402 ("o.event_date".into(), Value::Date(
1403 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1404 )),
1405 ("o.size".into(), Value::Int(100)),
1406 ("s.params".into(), Value::Dict(params.clone())),
1407 ]),
1408 Row::from([
1409 ("o.token".into(), Value::String("BTC".into())),
1410 ("o.event_date".into(), Value::Date(
1411 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1412 )),
1413 ("o.size".into(), Value::Int(50)),
1414 ("s.params".into(), Value::Dict(params.clone())),
1415 ]),
1416 ];
1417
1418 let q = SelectQuery {
1419 columns: ColumnList::Named(vec![
1420 SelectExpr::Column("o.token".into()),
1421 SelectExpr::Column("o.event_date".into()),
1422 SelectExpr::Expr {
1423 expr: Expr::DateAdd {
1424 date: Box::new(Expr::Column("o.event_date".into())),
1425 days: Box::new(Expr::Column("s.params.exit_days".into())),
1426 },
1427 alias: Some("exit_date".into()),
1428 },
1429 SelectExpr::Aggregate {
1430 func: AggFunc::Sum,
1431 arg: "o.size".into(),
1432 arg_expr: Some(Expr::Column("o.size".into())),
1433 alias: Some("total".into()),
1434 },
1435 ]),
1436 table: "orders".into(),
1437 table_alias: None,
1438 subquery: None,
1439 joins: vec![],
1440 where_clause: None,
1441 group_by: Some(vec!["o.token".into(), "o.event_date".into()]),
1442 having: None,
1443 order_by: None,
1444 limit: None,
1445 };
1446
1447 let (rows, cols) = execute_inner(&q, &rows, None).unwrap();
1448 assert_eq!(rows.len(), 1);
1449 assert!(cols.contains(&"exit_date".to_string()));
1450 assert_eq!(rows[0]["total"], Value::Float(150.0));
1451 assert_eq!(
1453 rows[0]["exit_date"],
1454 Value::Date(chrono::NaiveDate::from_ymd_opt(2026, 1, 22).unwrap())
1455 );
1456 }
1457
1458 #[test]
1459 fn test_aggregate_arithmetic() {
1460 let stmt = crate::query_parser::parse_query(
1464 "SELECT SUM(count) / COUNT(*) AS avg_count FROM test"
1465 ).unwrap();
1466 if let crate::query_parser::Statement::Select(q) = stmt {
1467 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1468 assert_eq!(cols, vec!["avg_count"]);
1469 assert_eq!(rows.len(), 1);
1470 match &rows[0]["avg_count"] {
1471 Value::Float(f) => assert!((f - 11.666666666666666).abs() < 0.001),
1472 other => panic!("Expected Float, got {:?}", other),
1473 }
1474 } else {
1475 panic!("Expected Select");
1476 }
1477 }
1478
1479 #[test]
1480 fn test_aggregate_subtraction_with_group_by() {
1481 let rows = vec![
1482 {
1483 let mut r = Row::new();
1484 r.insert("token".into(), Value::String("BTC".into()));
1485 r.insert("side".into(), Value::String("BUY".into()));
1486 r.insert("size".into(), Value::Float(100.0));
1487 r
1488 },
1489 {
1490 let mut r = Row::new();
1491 r.insert("token".into(), Value::String("BTC".into()));
1492 r.insert("side".into(), Value::String("SELL".into()));
1493 r.insert("size".into(), Value::Float(60.0));
1494 r
1495 },
1496 ];
1497 let stmt = crate::query_parser::parse_query(
1498 "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"
1499 ).unwrap();
1500 if let crate::query_parser::Statement::Select(q) = stmt {
1501 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1502 assert_eq!(result.len(), 1);
1503 assert_eq!(result[0]["net"], Value::Float(40.0));
1504 } else {
1505 panic!("Expected Select");
1506 }
1507 }
1508
1509 #[test]
1512 fn test_aggregate_subtraction_no_group() {
1513 let stmt = crate::query_parser::parse_query(
1515 "SELECT SUM(count) - COUNT(*) as diff FROM test"
1516 ).unwrap();
1517 if let crate::query_parser::Statement::Select(q) = stmt {
1518 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1519 assert_eq!(cols, vec!["diff"]);
1520 assert_eq!(rows.len(), 1);
1521 assert_eq!(rows[0]["diff"], Value::Float(32.0));
1522 } else {
1523 panic!("Expected Select");
1524 }
1525 }
1526
1527 #[test]
1530 fn test_aggregate_division_with_group_by() {
1531 let rows = vec![
1532 {
1533 let mut r = Row::new();
1534 r.insert("category".into(), Value::String("A".into()));
1535 r.insert("count".into(), Value::Int(10));
1536 r
1537 },
1538 {
1539 let mut r = Row::new();
1540 r.insert("category".into(), Value::String("A".into()));
1541 r.insert("count".into(), Value::Int(20));
1542 r
1543 },
1544 {
1545 let mut r = Row::new();
1546 r.insert("category".into(), Value::String("B".into()));
1547 r.insert("count".into(), Value::Int(6));
1548 r
1549 },
1550 ];
1551 let stmt = crate::query_parser::parse_query(
1554 "SELECT category, SUM(count) / COUNT(*) as ratio FROM test GROUP BY category"
1555 ).unwrap();
1556 if let crate::query_parser::Statement::Select(q) = stmt {
1557 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
1558 assert!(cols.contains(&"ratio".to_string()));
1559 assert_eq!(result.len(), 2);
1560 let group_a = result.iter().find(|r| r["category"] == Value::String("A".into())).unwrap();
1562 let group_b = result.iter().find(|r| r["category"] == Value::String("B".into())).unwrap();
1563 match &group_a["ratio"] {
1564 Value::Float(f) => assert!((f - 15.0).abs() < 0.001),
1565 other => panic!("Expected Float for group A ratio, got {:?}", other),
1566 }
1567 match &group_b["ratio"] {
1568 Value::Float(f) => assert!((f - 6.0).abs() < 0.001),
1569 other => panic!("Expected Float for group B ratio, got {:?}", other),
1570 }
1571 } else {
1572 panic!("Expected Select");
1573 }
1574 }
1575}