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 if let ColumnList::Named(_) = &query.columns {
119 let mut seen = std::collections::HashSet::new();
120 for c in &columns {
121 if !seen.insert(c.as_str()) {
122 return Err(MdqlError::QueryExecution(format!(
123 "duplicate output column '{}' — give each projection a unique name with AS",
124 c
125 )));
126 }
127 }
128 }
129
130 let filtered: Vec<Row> = if let Some(ref wc) = query.where_clause {
132 let candidate_paths = index.and_then(|idx| try_index_filter(wc, idx));
133 if let Some(paths) = candidate_paths {
134 rows.iter()
135 .filter(|r| {
136 r.get("path")
137 .and_then(|v| v.as_str())
138 .map_or(false, |p| paths.contains(p))
139 })
140 .filter(|r| evaluate_with_fts(wc, r, fts))
141 .cloned()
142 .collect()
143 } else {
144 rows.iter()
145 .filter(|r| evaluate_with_fts(wc, r, fts))
146 .cloned()
147 .collect()
148 }
149 } else {
150 rows.to_vec()
151 };
152
153 let mut result = if has_aggregates || query.group_by.is_some() {
155 let exprs = match &query.columns {
156 ColumnList::Named(exprs) => exprs.clone(),
157 _ => return Err(MdqlError::QueryExecution(
158 "SELECT * with GROUP BY is not supported".into(),
159 )),
160 };
161 let group_keys = query.group_by.as_deref().unwrap_or(&[]);
162 aggregate_rows(&filtered, &exprs, group_keys)?
163 } else {
164 filtered
165 };
166
167 if let Some(ref having) = query.having {
169 result.retain(|row| evaluate(having, row));
170 }
171
172 let has_windows = match &query.columns {
174 ColumnList::Named(exprs) => exprs.iter().any(|e| match e {
175 SelectExpr::Expr { expr, .. } => expr.contains_window(),
176 _ => false,
177 }),
178 _ => false,
179 };
180 if has_windows {
181 if let ColumnList::Named(ref exprs) = query.columns {
182 compute_windows(&mut result, exprs)?;
183 }
184 }
185
186 if query.distinct {
189 let mut seen = std::collections::HashSet::new();
190 result.retain(|row| seen.insert(distinct_key(row, &query.columns, &columns)));
191 }
192
193 if let Some(ref order_by) = query.order_by {
195 let resolved = resolve_order_aliases(order_by, &query.columns);
196 sort_rows(&mut result, &resolved);
197 }
198
199 if let Some(limit) = query.limit {
201 result.truncate(limit as usize);
202 }
203
204 if !matches!(query.columns, ColumnList::All) {
206 let named_exprs = match &query.columns {
207 ColumnList::Named(exprs) => exprs,
208 _ => unreachable!(),
209 };
210
211 let has_expr_cols = named_exprs.iter().any(|e| matches!(e, SelectExpr::Expr { .. }));
215 let already_aggregated = has_aggregates || query.group_by.is_some();
216 if has_expr_cols && !already_aggregated {
217 for row in &mut result {
218 for expr in named_exprs {
219 if let SelectExpr::Expr { expr: e, alias } = expr {
220 if e.contains_window() { continue; }
221 let name = alias.clone().unwrap_or_else(|| e.display_name());
222 let val = evaluate_expr(e, row);
223 row.insert(name, val);
224 }
225 }
226 }
227 }
228
229 if !already_aggregated {
234 for row in &mut result {
235 for expr in named_exprs {
236 if let SelectExpr::Column(name) = expr {
237 if name.contains('.') && !row.contains_key(name) {
238 if let Some(v) = resolve_column(row, name) {
239 row.insert(name.clone(), v);
240 }
241 }
242 }
243 }
244 }
245 }
246
247 let col_set: std::collections::HashSet<&str> =
248 columns.iter().map(|s| s.as_str()).collect();
249 for row in &mut result {
250 row.retain(|k, _| col_set.contains(k.as_str()));
251 }
252 }
253
254 for row in &mut result {
261 for col in &columns {
262 if !row.contains_key(col) {
263 row.insert(col.clone(), Value::Null);
264 }
265 }
266 }
267
268 Ok((result, columns))
269}
270
271fn distinct_key(row: &Row, column_spec: &ColumnList, header: &[String]) -> String {
278 let mut parts: Vec<String> = Vec::new();
279 match column_spec {
280 ColumnList::All => {
281 for col in header {
282 parts.push(format!("{:?}", row.get(col).unwrap_or(&Value::Null)));
283 }
284 }
285 ColumnList::Named(exprs) => {
286 for se in exprs {
287 let val = match se {
288 SelectExpr::Column(name) => {
289 resolve_column(row, name).unwrap_or(Value::Null)
290 }
291 SelectExpr::Aggregate { .. } => {
292 row.get(&se.output_name()).cloned().unwrap_or(Value::Null)
293 }
294 SelectExpr::Expr { expr, .. } => {
295 if expr.contains_window() {
296 row.get(&se.output_name()).cloned().unwrap_or(Value::Null)
297 } else {
298 evaluate_expr(expr, row)
299 }
300 }
301 };
302 parts.push(format!("{:?}", val));
303 }
304 }
305 }
306 parts.join("\u{1f}")
307}
308
309fn aggregate_rows(
310 rows: &[Row],
311 exprs: &[SelectExpr],
312 group_keys: &[String],
313) -> crate::errors::Result<Vec<Row>> {
314 let mut groups: Vec<(Vec<Value>, Vec<&Row>)> = Vec::new();
316 let mut key_index: HashMap<Vec<String>, usize> = HashMap::new();
317
318 if group_keys.is_empty() {
319 let all_refs: Vec<&Row> = rows.iter().collect();
321 groups.push((vec![], all_refs));
322 } else {
323 for row in rows {
324 let key: Vec<String> = group_keys
325 .iter()
326 .map(|k| {
327 resolve_column(row, k)
328 .map(|v| v.to_display_string())
329 .unwrap_or_default()
330 })
331 .collect();
332 let key_vals: Vec<Value> = group_keys
333 .iter()
334 .map(|k| resolve_column(row, k).unwrap_or(Value::Null))
335 .collect();
336 if let Some(&idx) = key_index.get(&key) {
337 groups[idx].1.push(row);
338 } else {
339 let idx = groups.len();
340 key_index.insert(key, idx);
341 groups.push((key_vals, vec![row]));
342 }
343 }
344 }
345
346 let mut result = Vec::new();
348 for (key_vals, group_rows) in &groups {
349 let mut out = Row::new();
350
351 for (i, k) in group_keys.iter().enumerate() {
353 out.insert(k.clone(), key_vals[i].clone());
354 }
355
356 for expr in exprs {
358 match expr {
359 SelectExpr::Column(name) => {
360 if !out.contains_key(name) {
363 if let Some(first) = group_rows.first() {
364 out.insert(
365 name.clone(),
366 resolve_column(first, name).unwrap_or(Value::Null),
367 );
368 }
369 }
370 }
371 SelectExpr::Aggregate { func, arg, arg_expr, alias } => {
372 let out_name = alias
373 .clone()
374 .unwrap_or_else(|| expr.output_name());
375 let val = compute_aggregate(func, arg, arg_expr.as_ref(), group_rows);
376 out.insert(out_name, val);
377 }
378 SelectExpr::Expr { expr: e, alias } => {
379 let out_name = alias.clone().unwrap_or_else(|| e.display_name());
380 if e.contains_aggregate() {
381 let val = evaluate_agg_expr(e, group_rows);
382 out.insert(out_name, val);
383 } else if let Some(first) = group_rows.first() {
384 let val = evaluate_expr(e, first);
385 out.insert(out_name, val);
386 }
387 }
388 }
389 }
390
391 result.push(out);
392 }
393
394 Ok(result)
395}
396
397fn resolve_agg_value<'a>(arg: &str, arg_expr: Option<&Expr>, row: &'a Row) -> Value {
400 if let Some(expr) = arg_expr {
401 evaluate_expr(expr, row)
402 } else {
403 row.get(arg).cloned().unwrap_or(Value::Null)
404 }
405}
406
407fn compute_aggregate(func: &AggFunc, arg: &str, arg_expr: Option<&Expr>, rows: &[&Row]) -> Value {
408 match func {
409 AggFunc::Count => {
410 if arg == "*" && arg_expr.is_none() {
411 Value::Int(rows.len() as i64)
412 } else {
413 let count = rows
414 .iter()
415 .filter(|r| {
416 let v = resolve_agg_value(arg, arg_expr, r);
417 !v.is_null()
418 })
419 .count();
420 Value::Int(count as i64)
421 }
422 }
423 AggFunc::Sum => {
424 let mut total = 0.0f64;
425 let mut has_any = false;
426 for r in rows {
427 let v = resolve_agg_value(arg, arg_expr, r);
428 match v {
429 Value::Int(n) => { total += n as f64; has_any = true; }
430 Value::Float(f) => { total += f; has_any = true; }
431 _ => {}
432 }
433 }
434 if has_any { Value::Float(total) } else { Value::Null }
435 }
436 AggFunc::Avg => {
437 let mut total = 0.0f64;
438 let mut count = 0usize;
439 for r in rows {
440 let v = resolve_agg_value(arg, arg_expr, r);
441 match v {
442 Value::Int(n) => { total += n as f64; count += 1; }
443 Value::Float(f) => { total += f; count += 1; }
444 _ => {}
445 }
446 }
447 if count > 0 { Value::Float(total / count as f64) } else { Value::Null }
448 }
449 AggFunc::Min => {
450 let mut min_val: Option<Value> = None;
451 for r in rows {
452 let v = resolve_agg_value(arg, arg_expr, r);
453 if v.is_null() { continue; }
454 min_val = Some(match min_val {
455 None => v,
456 Some(ref current) => {
457 if v.partial_cmp(current) == Some(std::cmp::Ordering::Less) {
458 v
459 } else {
460 current.clone()
461 }
462 }
463 });
464 }
465 min_val.unwrap_or(Value::Null)
466 }
467 AggFunc::Max => {
468 let mut max_val: Option<Value> = None;
469 for r in rows {
470 let v = resolve_agg_value(arg, arg_expr, r);
471 if v.is_null() { continue; }
472 max_val = Some(match max_val {
473 None => v,
474 Some(ref current) => {
475 if v.partial_cmp(current) == Some(std::cmp::Ordering::Greater) {
476 v
477 } else {
478 current.clone()
479 }
480 }
481 });
482 }
483 max_val.unwrap_or(Value::Null)
484 }
485 }
486}
487
488fn compute_windows(rows: &mut Vec<Row>, select_exprs: &[SelectExpr]) -> crate::errors::Result<()> {
489 for se in select_exprs {
490 if let SelectExpr::Expr { expr, alias } = se {
491 if let Expr::Window { func, args, over } = expr {
492 let col_name = alias.clone().unwrap_or_else(|| expr.display_name());
493 compute_single_window(rows, func, args, over, &col_name)?;
494 }
495 }
496 }
497 Ok(())
498}
499
500fn compute_single_window(
501 rows: &mut Vec<Row>,
502 func: &WindowFunc,
503 args: &[Expr],
504 over: &WindowSpec,
505 col_name: &str,
506) -> crate::errors::Result<()> {
507 let mut partitions: Vec<Vec<usize>> = Vec::new();
508 let mut partition_map: HashMap<Vec<String>, usize> = HashMap::new();
509
510 for (i, row) in rows.iter().enumerate() {
511 let key: Vec<String> = over.partition_by.iter()
512 .map(|col| row.get(col).map(|v| v.to_display_string()).unwrap_or_default())
513 .collect();
514 if let Some(&idx) = partition_map.get(&key) {
515 partitions[idx].push(i);
516 } else {
517 let idx = partitions.len();
518 partition_map.insert(key, idx);
519 partitions.push(vec![i]);
520 }
521 }
522
523 for partition in &mut partitions {
524 if !over.order_by.is_empty() {
525 partition.sort_by(|&a, &b| {
526 for spec in &over.order_by {
527 let (va, vb) = if let Some(ref expr) = spec.expr {
528 (evaluate_expr(expr, &rows[a]), evaluate_expr(expr, &rows[b]))
529 } else {
530 (
531 rows[a].get(&spec.column).cloned().unwrap_or(Value::Null),
532 rows[b].get(&spec.column).cloned().unwrap_or(Value::Null),
533 )
534 };
535 let ordering = match (&va, &vb) {
536 (Value::Null, Value::Null) => Ordering::Equal,
537 (Value::Null, _) => Ordering::Greater,
538 (_, Value::Null) => Ordering::Less,
539 (a_val, b_val) => compare_model_values(a_val, b_val).unwrap_or(Ordering::Equal),
540 };
541 let ordering = if spec.descending { ordering.reverse() } else { ordering };
542 if ordering != Ordering::Equal {
543 return ordering;
544 }
545 }
546 Ordering::Equal
547 });
548 }
549 }
550
551 let mut values: Vec<(usize, Value)> = Vec::new();
552
553 for partition in &partitions {
554 match func {
555 WindowFunc::RowNumber => {
556 for (i, &row_idx) in partition.iter().enumerate() {
557 values.push((row_idx, Value::Int((i + 1) as i64)));
558 }
559 }
560 WindowFunc::Rank => {
561 let mut rank = 1usize;
562 for (i, &row_idx) in partition.iter().enumerate() {
563 if i > 0 {
564 let prev_idx = partition[i - 1];
565 let same = over.order_by.iter().all(|spec| {
566 let va = if let Some(ref expr) = spec.expr {
567 evaluate_expr(expr, &rows[prev_idx])
568 } else {
569 rows[prev_idx].get(&spec.column).cloned().unwrap_or(Value::Null)
570 };
571 let vb = if let Some(ref expr) = spec.expr {
572 evaluate_expr(expr, &rows[row_idx])
573 } else {
574 rows[row_idx].get(&spec.column).cloned().unwrap_or(Value::Null)
575 };
576 va == vb
577 });
578 if !same {
579 rank = i + 1;
580 }
581 }
582 values.push((row_idx, Value::Int(rank as i64)));
583 }
584 }
585 WindowFunc::DenseRank => {
586 let mut rank = 1usize;
587 for (i, &row_idx) in partition.iter().enumerate() {
588 if i > 0 {
589 let prev_idx = partition[i - 1];
590 let same = over.order_by.iter().all(|spec| {
591 let va = if let Some(ref expr) = spec.expr {
592 evaluate_expr(expr, &rows[prev_idx])
593 } else {
594 rows[prev_idx].get(&spec.column).cloned().unwrap_or(Value::Null)
595 };
596 let vb = if let Some(ref expr) = spec.expr {
597 evaluate_expr(expr, &rows[row_idx])
598 } else {
599 rows[row_idx].get(&spec.column).cloned().unwrap_or(Value::Null)
600 };
601 va == vb
602 });
603 if !same {
604 rank += 1;
605 }
606 }
607 values.push((row_idx, Value::Int(rank as i64)));
608 }
609 }
610 WindowFunc::Lag => {
611 let offset = if args.len() > 1 {
612 if let Expr::Literal(SqlValue::Int(n)) = &args[1] { *n as usize } else { 1 }
613 } else {
614 1
615 };
616 for (i, &row_idx) in partition.iter().enumerate() {
617 let val = if i >= offset && !args.is_empty() {
618 evaluate_expr(&args[0], &rows[partition[i - offset]])
619 } else {
620 Value::Null
621 };
622 values.push((row_idx, val));
623 }
624 }
625 WindowFunc::Lead => {
626 let offset = if args.len() > 1 {
627 if let Expr::Literal(SqlValue::Int(n)) = &args[1] { *n as usize } else { 1 }
628 } else {
629 1
630 };
631 for (i, &row_idx) in partition.iter().enumerate() {
632 let val = if i + offset < partition.len() && !args.is_empty() {
633 evaluate_expr(&args[0], &rows[partition[i + offset]])
634 } else {
635 Value::Null
636 };
637 values.push((row_idx, val));
638 }
639 }
640 WindowFunc::Agg(agg_func) => {
641 let partition_rows: Vec<&Row> = partition.iter().map(|&i| &rows[i]).collect();
642 let (arg_name, arg_expr_opt) = if args.is_empty() {
643 ("*".to_string(), None)
644 } else {
645 (args[0].display_name(), Some(&args[0]))
646 };
647 let agg_val = compute_aggregate(agg_func, &arg_name, arg_expr_opt, &partition_rows);
648 for &row_idx in partition {
649 values.push((row_idx, agg_val.clone()));
650 }
651 }
652 }
653 }
654
655 for (row_idx, val) in values {
656 rows[row_idx].insert(col_name.to_string(), val);
657 }
658
659 Ok(())
660}
661
662fn evaluate_with_fts(clause: &WhereClause, row: &Row, fts: &FtsResults) -> bool {
663 match clause {
664 WhereClause::BoolOp(bop) => {
665 let left = evaluate_with_fts(&bop.left, row, fts);
666 match bop.op {
667 BoolOpKind::And => left && evaluate_with_fts(&bop.right, row, fts),
668 BoolOpKind::Or => left || evaluate_with_fts(&bop.right, row, fts),
669 }
670 }
671 WhereClause::Comparison(cmp) => {
672 if cmp.op == CmpOp::Like || cmp.op == CmpOp::NotLike {
674 if let Some(SqlValue::String(pattern)) = &cmp.value {
675 let key = (cmp.column.clone(), pattern.clone());
676 if let Some(matching_paths) = fts.get(&key) {
677 let row_path = row.get("path").and_then(|v| v.as_str()).unwrap_or("");
678 let matched = matching_paths.contains(row_path);
679 return if cmp.op == CmpOp::Like { matched } else { !matched };
680 }
681 }
682 }
683 evaluate_comparison(cmp, row)
684 }
685 }
686}
687
688pub use crate::query_join::execute_join_query;
689
690pub(crate) fn execute_inner(
691 query: &SelectQuery,
692 rows: &[Row],
693 index: Option<&crate::index::TableIndex>,
694) -> crate::errors::Result<(Vec<Row>, Vec<String>)> {
695 let empty_fts = HashMap::new();
696 execute_with_fts(query, rows, index, &empty_fts)
697}
698
699pub fn evaluate(clause: &WhereClause, row: &Row) -> bool {
700 match clause {
701 WhereClause::BoolOp(bop) => {
702 let left = evaluate(&bop.left, row);
703 match bop.op {
704 BoolOpKind::And => left && evaluate(&bop.right, row),
705 BoolOpKind::Or => left || evaluate(&bop.right, row),
706 }
707 }
708 WhereClause::Comparison(cmp) => evaluate_comparison(cmp, row),
709 }
710}
711
712pub(crate) fn resolve_column(row: &Row, name: &str) -> Option<Value> {
720 if let Some(val) = row.get(name) {
721 return Some(val.clone());
722 }
723 for (i, _) in name.match_indices('.') {
725 if let Some(Value::Dict(map)) = row.get(&name[..i]) {
726 return map.get(&name[i + 1..]).cloned();
727 }
728 }
729 None
730}
731
732pub(crate) fn evaluate_expr(expr: &Expr, row: &Row) -> Value {
734 match expr {
735 Expr::Literal(SqlValue::Int(n)) => Value::Int(*n),
736 Expr::Literal(SqlValue::Float(f)) => Value::Float(*f),
737 Expr::Literal(SqlValue::String(s)) => Value::String(s.clone()),
738 Expr::Literal(SqlValue::Bool(b)) => Value::Bool(*b),
739 Expr::Literal(SqlValue::Null) => Value::Null,
740 Expr::Literal(SqlValue::List(_)) => Value::Null,
741 Expr::Column(name) => resolve_column(row, name).unwrap_or(Value::Null),
742 Expr::UnaryMinus(inner) => {
743 match evaluate_expr(inner, row) {
744 Value::Int(n) => Value::Int(-n),
745 Value::Float(f) => Value::Float(-f),
746 Value::Null => Value::Null,
747 _ => Value::Null, }
749 }
750 Expr::BinaryOp { left, op, right } => {
751 let lv = evaluate_expr(left, row);
752 let rv = evaluate_expr(right, row);
753
754 if lv.is_null() || rv.is_null() {
756 return Value::Null;
757 }
758
759 match (&lv, &rv) {
761 (Value::Int(a), Value::Int(b)) => {
762 match op {
763 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
764 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
765 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
766 ArithOp::Div => {
767 if *b == 0 { Value::Null } else { Value::Int(a / b) }
768 }
769 ArithOp::Mod => {
770 if *b == 0 { Value::Null } else { Value::Int(a % b) }
771 }
772 }
773 }
774 _ => {
775 let a = match &lv {
777 Value::Int(n) => *n as f64,
778 Value::Float(f) => *f,
779 _ => return Value::Null,
780 };
781 let b = match &rv {
782 Value::Int(n) => *n as f64,
783 Value::Float(f) => *f,
784 _ => return Value::Null,
785 };
786 match op {
787 ArithOp::Add => Value::Float(a + b),
788 ArithOp::Sub => Value::Float(a - b),
789 ArithOp::Mul => Value::Float(a * b),
790 ArithOp::Div => {
791 if b == 0.0 { Value::Null } else { Value::Float(a / b) }
792 }
793 ArithOp::Mod => {
794 if b == 0.0 { Value::Null } else { Value::Float(a % b) }
795 }
796 }
797 }
798 }
799 }
800 Expr::Case { whens, else_expr } => {
801 for (condition, result) in whens {
802 if evaluate(condition, row) {
803 return evaluate_expr(result, row);
804 }
805 }
806 match else_expr {
807 Some(e) => evaluate_expr(e, row),
808 None => Value::Null,
809 }
810 }
811 Expr::CurrentDate => {
812 Value::Date(chrono::Local::now().naive_local().date())
813 }
814 Expr::CurrentTimestamp => {
815 Value::DateTime(chrono::Local::now().naive_local())
816 }
817 Expr::DateAdd { date, days } => {
818 let date_val = evaluate_expr(date, row);
819 let days_val = evaluate_expr(days, row);
820 let n = match &days_val {
821 Value::Int(n) => *n,
822 Value::Float(f) => *f as i64,
823 _ => return Value::Null,
824 };
825 let duration = chrono::Duration::days(n);
826 match date_val {
827 Value::Date(d) => {
828 match d.checked_add_signed(duration) {
829 Some(result) => Value::Date(result),
830 None => Value::Null,
831 }
832 }
833 Value::DateTime(dt) => {
834 match dt.checked_add_signed(duration) {
835 Some(result) => Value::DateTime(result),
836 None => Value::Null,
837 }
838 }
839 _ => Value::Null,
840 }
841 }
842 Expr::DateDiff { left, right } => {
843 let lv = evaluate_expr(left, row);
844 let rv = evaluate_expr(right, row);
845 let left_date = match &lv {
846 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
847 Value::DateTime(dt) => *dt,
848 _ => return Value::Null,
849 };
850 let right_date = match &rv {
851 Value::Date(d) => d.and_hms_opt(0, 0, 0).unwrap(),
852 Value::DateTime(dt) => *dt,
853 _ => return Value::Null,
854 };
855 Value::Int((left_date - right_date).num_days())
856 }
857 Expr::Aggregate { func, arg, .. } => {
858 let func_name = match func {
860 AggFunc::Count => "COUNT",
861 AggFunc::Sum => "SUM",
862 AggFunc::Avg => "AVG",
863 AggFunc::Min => "MIN",
864 AggFunc::Max => "MAX",
865 };
866 let col = format!("{}({})", func_name, arg);
867 row.get(&col).cloned().unwrap_or(Value::Null)
868 }
869 Expr::Subquery(_) => Value::Null,
870 Expr::Window { .. } => {
871 let display = expr.display_name();
872 row.get(&display).cloned().unwrap_or(Value::Null)
873 }
874 }
875}
876
877fn evaluate_agg_expr(expr: &Expr, group_rows: &[&Row]) -> Value {
878 match expr {
879 Expr::Aggregate { func, arg, arg_expr } => {
880 compute_aggregate(func, arg, arg_expr.as_deref(), group_rows)
881 }
882 Expr::BinaryOp { left, op, right } => {
883 let lv = evaluate_agg_expr(left, group_rows);
884 let rv = evaluate_agg_expr(right, group_rows);
885 apply_arith_op(op, &lv, &rv)
886 }
887 Expr::UnaryMinus(inner) => {
888 match evaluate_agg_expr(inner, group_rows) {
889 Value::Int(n) => Value::Int(-n),
890 Value::Float(f) => Value::Float(-f),
891 _ => Value::Null,
892 }
893 }
894 other => {
895 if let Some(first) = group_rows.first() {
896 evaluate_expr(other, first)
897 } else {
898 Value::Null
899 }
900 }
901 }
902}
903
904fn apply_arith_op(op: &ArithOp, lv: &Value, rv: &Value) -> Value {
905 if lv.is_null() || rv.is_null() {
906 return Value::Null;
907 }
908 match (lv, rv) {
909 (Value::Int(a), Value::Int(b)) => match op {
910 ArithOp::Add => Value::Int(a.wrapping_add(*b)),
911 ArithOp::Sub => Value::Int(a.wrapping_sub(*b)),
912 ArithOp::Mul => Value::Int(a.wrapping_mul(*b)),
913 ArithOp::Div => if *b == 0 { Value::Null } else { Value::Int(a / b) },
914 ArithOp::Mod => if *b == 0 { Value::Null } else { Value::Int(a % b) },
915 },
916 _ => {
917 let a = match lv {
918 Value::Int(n) => *n as f64,
919 Value::Float(f) => *f,
920 _ => return Value::Null,
921 };
922 let b = match rv {
923 Value::Int(n) => *n as f64,
924 Value::Float(f) => *f,
925 _ => return Value::Null,
926 };
927 match op {
928 ArithOp::Add => Value::Float(a + b),
929 ArithOp::Sub => Value::Float(a - b),
930 ArithOp::Mul => Value::Float(a * b),
931 ArithOp::Div => if b == 0.0 { Value::Null } else { Value::Float(a / b) },
932 ArithOp::Mod => if b == 0.0 { Value::Null } else { Value::Float(a % b) },
933 }
934 }
935 }
936}
937
938fn evaluate_comparison(cmp: &Comparison, row: &Row) -> bool {
939 if let (Some(left_expr), Some(right_expr)) = (&cmp.left_expr, &cmp.right_expr) {
941 if matches!(cmp.op, CmpOp::Eq | CmpOp::Ne | CmpOp::Lt | CmpOp::Gt | CmpOp::Le | CmpOp::Ge) {
942 let left_val = evaluate_expr(left_expr, row);
943 let right_val = evaluate_expr(right_expr, row);
944
945 if left_val.is_null() || right_val.is_null() {
947 return false;
948 }
949
950 let ord = compare_model_values(&left_val, &right_val);
952
953 return match cmp.op {
954 CmpOp::Eq => ord == Some(Ordering::Equal),
955 CmpOp::Ne => ord != Some(Ordering::Equal),
956 CmpOp::Lt => ord == Some(Ordering::Less),
957 CmpOp::Gt => ord == Some(Ordering::Greater),
958 CmpOp::Le => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
959 CmpOp::Ge => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
960 _ => false,
961 };
962 }
963 }
964
965 let actual_owned = resolve_column(row, &cmp.column);
969 let actual = actual_owned.as_ref();
970
971 if cmp.op == CmpOp::IsNull {
972 return actual.map_or(true, |v| v.is_null());
973 }
974 if cmp.op == CmpOp::IsNotNull {
975 return actual.map_or(false, |v| !v.is_null());
976 }
977
978 let actual = match actual {
979 Some(v) if !v.is_null() => v,
980 _ => return false,
981 };
982
983 let expected = match &cmp.value {
984 Some(v) => v,
985 None => return false,
986 };
987
988 match cmp.op {
989 CmpOp::Eq => eq_match(actual, expected),
990 CmpOp::Ne => !eq_match(actual, expected),
991 CmpOp::Lt => compare_values(actual, expected) == Some(Ordering::Less),
992 CmpOp::Gt => compare_values(actual, expected) == Some(Ordering::Greater),
993 CmpOp::Le => matches!(compare_values(actual, expected), Some(Ordering::Less | Ordering::Equal)),
994 CmpOp::Ge => matches!(compare_values(actual, expected), Some(Ordering::Greater | Ordering::Equal)),
995 CmpOp::Like => like_match(actual, expected),
996 CmpOp::NotLike => !like_match(actual, expected),
997 CmpOp::In => {
998 if let SqlValue::List(items) = expected {
999 items.iter().any(|v| eq_match(actual, v))
1000 } else {
1001 eq_match(actual, expected)
1002 }
1003 }
1004 CmpOp::IsNull | CmpOp::IsNotNull => unreachable!(),
1005 }
1006}
1007
1008fn compare_model_values(a: &Value, b: &Value) -> Option<Ordering> {
1010 match (a, b) {
1011 (Value::Int(x), Value::Float(y)) => (*x as f64).partial_cmp(y),
1012 (Value::Float(x), Value::Int(y)) => x.partial_cmp(&(*y as f64)),
1013 _ => a.partial_cmp(b),
1014 }
1015}
1016
1017fn coerce_sql_to_value(sql_val: &SqlValue, target: &Value) -> Value {
1018 match sql_val {
1019 SqlValue::Null => Value::Null,
1020 SqlValue::String(s) => {
1021 match target {
1022 Value::Int(_) => s.parse::<i64>().map(Value::Int).unwrap_or(Value::String(s.clone())),
1023 Value::Float(_) => s.parse::<f64>().map(Value::Float).unwrap_or(Value::String(s.clone())),
1024 Value::Date(_) => {
1025 chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
1026 .map(Value::Date)
1027 .unwrap_or(Value::String(s.clone()))
1028 }
1029 Value::DateTime(_) => {
1030 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")
1031 .or_else(|_| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f"))
1032 .map(Value::DateTime)
1033 .unwrap_or(Value::String(s.clone()))
1034 }
1035 _ => Value::String(s.clone()),
1036 }
1037 }
1038 SqlValue::Int(n) => {
1039 match target {
1040 Value::Float(_) => Value::Float(*n as f64),
1041 _ => Value::Int(*n),
1042 }
1043 }
1044 SqlValue::Float(f) => Value::Float(*f),
1045 SqlValue::Bool(b) => Value::Bool(*b),
1046 SqlValue::List(_) => Value::Null, }
1048}
1049
1050fn eq_match(actual: &Value, expected: &SqlValue) -> bool {
1051 if let Value::List(items) = actual {
1053 if let SqlValue::String(s) = expected {
1054 return items.contains(s);
1055 }
1056 }
1057
1058 let coerced = coerce_sql_to_value(expected, actual);
1059 actual == &coerced
1060}
1061
1062fn like_match(actual: &Value, pattern: &SqlValue) -> bool {
1063 let pattern_str = match pattern {
1064 SqlValue::String(s) => s,
1065 _ => return false,
1066 };
1067
1068 let mut regex_str = String::from("(?is)^");
1070 for ch in pattern_str.chars() {
1071 match ch {
1072 '%' => regex_str.push_str(".*"),
1073 '_' => regex_str.push('.'),
1074 c => {
1075 if regex::escape(&c.to_string()) != c.to_string() {
1076 regex_str.push_str(®ex::escape(&c.to_string()));
1077 } else {
1078 regex_str.push(c);
1079 }
1080 }
1081 }
1082 }
1083 regex_str.push('$');
1084
1085 let re = match Regex::new(®ex_str) {
1086 Ok(r) => r,
1087 Err(_) => return false,
1088 };
1089
1090 match actual {
1091 Value::List(items) => items.iter().any(|item| re.is_match(item)),
1092 _ => re.is_match(&actual.to_display_string()),
1093 }
1094}
1095
1096fn compare_values(actual: &Value, expected: &SqlValue) -> Option<Ordering> {
1097 let coerced = coerce_sql_to_value(expected, actual);
1098 actual.partial_cmp(&coerced)
1099}
1100
1101fn sql_value_to_index_value(sv: &SqlValue) -> Value {
1103 match sv {
1104 SqlValue::String(s) => {
1105 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") {
1107 return Value::DateTime(dt);
1108 }
1109 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.f") {
1110 return Value::DateTime(dt);
1111 }
1112 if let Ok(d) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
1114 return Value::Date(d);
1115 }
1116 Value::String(s.clone())
1117 }
1118 SqlValue::Int(n) => Value::Int(*n),
1119 SqlValue::Float(f) => Value::Float(*f),
1120 SqlValue::Bool(b) => Value::Bool(*b),
1121 SqlValue::Null => Value::Null,
1122 SqlValue::List(_) => Value::Null,
1123 }
1124}
1125
1126fn try_index_filter(
1130 clause: &WhereClause,
1131 index: &crate::index::TableIndex,
1132) -> Option<std::collections::HashSet<String>> {
1133 match clause {
1134 WhereClause::Comparison(cmp) => {
1135 if !index.has_index(&cmp.column) {
1136 return None;
1137 }
1138 match cmp.op {
1139 CmpOp::Eq => {
1140 let val = sql_value_to_index_value(cmp.value.as_ref()?);
1141 let paths = index.lookup_eq(&cmp.column, &val);
1142 Some(paths.into_iter().map(|s| s.to_string()).collect())
1143 }
1144 CmpOp::Lt => {
1145 let val = sql_value_to_index_value(cmp.value.as_ref()?);
1146 let range_paths = index.lookup_range(&cmp.column, None, Some(&val));
1149 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
1150 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
1151 }
1152 CmpOp::Gt => {
1153 let val = sql_value_to_index_value(cmp.value.as_ref()?);
1154 let range_paths = index.lookup_range(&cmp.column, Some(&val), None);
1155 let eq_paths: std::collections::HashSet<&str> = index.lookup_eq(&cmp.column, &val).into_iter().collect();
1156 Some(range_paths.into_iter().filter(|p| !eq_paths.contains(p)).map(|s| s.to_string()).collect())
1157 }
1158 CmpOp::Le => {
1159 let val = sql_value_to_index_value(cmp.value.as_ref()?);
1160 let paths = index.lookup_range(&cmp.column, None, Some(&val));
1161 Some(paths.into_iter().map(|s| s.to_string()).collect())
1162 }
1163 CmpOp::Ge => {
1164 let val = sql_value_to_index_value(cmp.value.as_ref()?);
1165 let paths = index.lookup_range(&cmp.column, Some(&val), None);
1166 Some(paths.into_iter().map(|s| s.to_string()).collect())
1167 }
1168 CmpOp::In => {
1169 if let Some(SqlValue::List(items)) = &cmp.value {
1170 let vals: Vec<Value> = items.iter().map(sql_value_to_index_value).collect();
1171 let paths = index.lookup_in(&cmp.column, &vals);
1172 Some(paths.into_iter().map(|s| s.to_string()).collect())
1173 } else {
1174 None
1175 }
1176 }
1177 _ => None, }
1179 }
1180 WhereClause::BoolOp(bop) => {
1181 let left = try_index_filter(&bop.left, index);
1182 let right = try_index_filter(&bop.right, index);
1183 match bop.op {
1184 BoolOpKind::And => {
1185 match (left, right) {
1186 (Some(l), Some(r)) => Some(l.intersection(&r).cloned().collect()),
1187 (Some(l), None) => Some(l), (None, Some(r)) => Some(r),
1189 (None, None) => None,
1190 }
1191 }
1192 BoolOpKind::Or => {
1193 match (left, right) {
1194 (Some(l), Some(r)) => Some(l.union(&r).cloned().collect()),
1195 _ => None, }
1197 }
1198 }
1199 }
1200 }
1201}
1202
1203fn resolve_order_aliases(specs: &[OrderSpec], columns: &ColumnList) -> Vec<OrderSpec> {
1206 let named = match columns {
1207 ColumnList::Named(exprs) => exprs,
1208 _ => return specs.to_vec(),
1209 };
1210
1211 let alias_map: HashMap<String, &Expr> = named
1213 .iter()
1214 .filter_map(|se| match se {
1215 SelectExpr::Expr { expr, alias: Some(a) } if !expr.contains_window() => {
1216 Some((a.clone(), expr))
1217 }
1218 _ => None,
1219 })
1220 .collect();
1221
1222 specs
1223 .iter()
1224 .map(|spec| {
1225 if let Some(expr) = alias_map.get(&spec.column) {
1227 OrderSpec {
1228 column: spec.column.clone(),
1229 expr: Some((*expr).clone()),
1230 descending: spec.descending,
1231 }
1232 } else {
1233 spec.clone()
1234 }
1235 })
1236 .collect()
1237}
1238
1239fn sort_rows(rows: &mut Vec<Row>, specs: &[OrderSpec]) {
1240 rows.sort_by(|a, b| {
1241 for spec in specs {
1242 let (va, vb) = if let Some(ref expr) = spec.expr {
1243 (evaluate_expr(expr, a), evaluate_expr(expr, b))
1244 } else {
1245 (
1246 a.get(&spec.column).cloned().unwrap_or(Value::Null),
1247 b.get(&spec.column).cloned().unwrap_or(Value::Null),
1248 )
1249 };
1250
1251 let ordering = match (&va, &vb) {
1253 (Value::Null, Value::Null) => Ordering::Equal,
1254 (Value::Null, _) => Ordering::Greater,
1255 (_, Value::Null) => Ordering::Less,
1256 (a_val, b_val) => {
1257 compare_model_values(a_val, b_val).unwrap_or(Ordering::Equal)
1258 }
1259 };
1260
1261 let ordering = if spec.descending {
1262 ordering.reverse()
1263 } else {
1264 ordering
1265 };
1266
1267 if ordering != Ordering::Equal {
1268 return ordering;
1269 }
1270 }
1271 Ordering::Equal
1272 });
1273}
1274
1275pub(crate) fn sql_value_to_value(sql_val: &SqlValue) -> Value {
1277 match sql_val {
1278 SqlValue::Null => Value::Null,
1279 SqlValue::String(s) => Value::String(s.clone()),
1280 SqlValue::Int(n) => Value::Int(*n),
1281 SqlValue::Float(f) => Value::Float(*f),
1282 SqlValue::Bool(b) => Value::Bool(*b),
1283 SqlValue::List(items) => {
1284 let strings: Vec<String> = items
1285 .iter()
1286 .filter_map(|v| match v {
1287 SqlValue::String(s) => Some(s.clone()),
1288 _ => None,
1289 })
1290 .collect();
1291 Value::List(strings)
1292 }
1293 }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299
1300 fn make_rows() -> Vec<Row> {
1301 vec![
1302 Row::from([
1303 ("path".into(), Value::String("a.md".into())),
1304 ("title".into(), Value::String("Alpha".into())),
1305 ("count".into(), Value::Int(10)),
1306 ]),
1307 Row::from([
1308 ("path".into(), Value::String("b.md".into())),
1309 ("title".into(), Value::String("Beta".into())),
1310 ("count".into(), Value::Int(5)),
1311 ]),
1312 Row::from([
1313 ("path".into(), Value::String("c.md".into())),
1314 ("title".into(), Value::String("Gamma".into())),
1315 ("count".into(), Value::Int(20)),
1316 ]),
1317 ]
1318 }
1319
1320 #[test]
1321 fn test_select_all() {
1322 let q = SelectQuery {
1323 distinct: false,
1324 columns: ColumnList::All,
1325 table: "test".into(),
1326 table_alias: None,
1327 subquery: None,
1328 joins: vec![],
1329 where_clause: None,
1330 group_by: None,
1331 having: None,
1332 order_by: None,
1333 limit: None,
1334 ctes: vec![],
1335 };
1336 let (rows, _cols) = execute_inner(&q, &make_rows(), None).unwrap();
1337 assert_eq!(rows.len(), 3);
1338 }
1339
1340 #[test]
1341 fn test_select_nonexistent_column_null_filled() {
1342 let q = parse_query("SELECT title, missing_col, count FROM test").unwrap();
1345 let q = match q {
1346 Statement::Select(s) => s,
1347 _ => panic!("expected SELECT"),
1348 };
1349 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1350 assert_eq!(cols, vec!["title", "missing_col", "count"]);
1351 assert_eq!(rows.len(), 3);
1352 for row in &rows {
1353 assert_eq!(row.len(), cols.len(), "row keys must match header length");
1354 for c in &cols {
1355 assert!(row.contains_key(c), "row missing header column {c}");
1356 }
1357 assert_eq!(row.get("missing_col"), Some(&Value::Null));
1358 }
1359 }
1360
1361 fn make_dict_rows() -> Vec<Row> {
1362 use indexmap::IndexMap;
1363 let mk = |path: &str, ev: &str, sl: i64| {
1365 Row::from([
1366 ("path".into(), Value::String(path.into())),
1367 ("params".into(), Value::Dict(IndexMap::from([
1368 ("event_type".to_string(), Value::String(ev.into())),
1369 ("sl_pct".to_string(), Value::Int(sl)),
1370 ]))),
1371 ])
1372 };
1373 vec![mk("a.md", "buyback", 5), mk("b.md", "unlock", 8), mk("c.md", "buyback", 3)]
1374 }
1375
1376 #[test]
1377 fn test_select_dotted_dict_key_projects_value() {
1378 let q = parse_query("SELECT path, params.event_type FROM test").unwrap();
1381 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1382 let (rows, cols) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1383 assert_eq!(cols, vec!["path", "params.event_type"]);
1384 assert_eq!(rows[0].get("params.event_type"), Some(&Value::String("buyback".into())));
1385 assert_eq!(rows[1].get("params.event_type"), Some(&Value::String("unlock".into())));
1386 }
1387
1388 #[test]
1389 fn test_dotted_dict_key_select_and_where_agree() {
1390 let q = parse_query(
1391 "SELECT path FROM test WHERE params.event_type = 'buyback'").unwrap();
1392 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1393 let (rows, _) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1394 let paths: Vec<_> = rows.iter().map(|r| r.get("path").cloned().unwrap()).collect();
1395 assert_eq!(paths, vec![Value::String("a.md".into()), Value::String("c.md".into())]);
1396 }
1397
1398 #[test]
1399 fn test_missing_dotted_dict_key_null_filled() {
1400 let q = parse_query("SELECT path, params.nope FROM test").unwrap();
1401 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1402 let (rows, cols) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1403 assert_eq!(cols, vec!["path", "params.nope"]);
1404 for row in &rows {
1405 assert_eq!(row.get("params.nope"), Some(&Value::Null));
1406 }
1407 }
1408
1409 #[test]
1410 fn test_dotted_dict_key_is_not_null() {
1411 let q = parse_query(
1412 "SELECT path FROM test WHERE params.event_type IS NOT NULL").unwrap();
1413 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1414 let (rows, _) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1415 assert_eq!(rows.len(), 3); }
1417
1418 #[test]
1419 fn test_dotted_dict_key_in_and_like() {
1420 for (sql, expect) in [
1421 ("SELECT path FROM test WHERE params.event_type IN ('buyback','x')", 2),
1422 ("SELECT path FROM test WHERE params.event_type LIKE 'buy%'", 2),
1423 ("SELECT path FROM test WHERE params.event_type IS NULL", 0),
1424 ] {
1425 let q = parse_query(sql).unwrap();
1426 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1427 let (rows, _) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1428 assert_eq!(rows.len(), expect, "query: {sql}");
1429 }
1430 }
1431
1432 #[test]
1433 fn test_group_by_dotted_dict_key() {
1434 let q = parse_query(
1435 "SELECT params.event_type, COUNT(*) FROM test GROUP BY params.event_type").unwrap();
1436 let q = match q { Statement::Select(s) => s, _ => panic!("expected SELECT") };
1437 let (rows, _) = execute_inner(&q, &make_dict_rows(), None).unwrap();
1438 assert_eq!(rows.len(), 2);
1440 let buyback = rows.iter().find(|r|
1441 r.get("params.event_type") == Some(&Value::String("buyback".into()))).unwrap();
1442 assert_eq!(buyback.get("COUNT(*)"), Some(&Value::Int(2)));
1443 }
1444
1445 #[test]
1446 fn test_select_duplicate_output_column_errors() {
1447 let q = parse_query("SELECT title, title FROM test").unwrap();
1450 let q = match q {
1451 Statement::Select(s) => s,
1452 _ => panic!("expected SELECT"),
1453 };
1454 let err = execute_inner(&q, &make_rows(), None);
1455 assert!(err.is_err());
1456 let msg = err.unwrap_err().to_string();
1457 assert!(msg.contains("duplicate output column"), "got: {msg}");
1458 }
1459
1460 #[test]
1461 fn test_select_all_sparse_rows_aligned() {
1462 let rows = vec![
1465 Row::from([
1466 ("path".into(), Value::String("a.md".into())),
1467 ("title".into(), Value::String("Alpha".into())),
1468 ("kill_reason".into(), Value::String("no edge".into())),
1469 ]),
1470 Row::from([
1471 ("path".into(), Value::String("b.md".into())),
1472 ("title".into(), Value::String("Beta".into())),
1473 ]),
1474 ];
1475 let q = parse_query("SELECT * FROM test").unwrap();
1476 let q = match q {
1477 Statement::Select(s) => s,
1478 _ => panic!("expected SELECT"),
1479 };
1480 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
1481 assert!(cols.contains(&"kill_reason".to_string()));
1482 for row in &result {
1483 assert_eq!(row.len(), cols.len(), "row keys must match header length");
1484 for c in &cols {
1485 assert!(row.contains_key(c), "row missing header column {c}");
1486 }
1487 }
1488 let beta = result.iter().find(|r| r.get("path") == Some(&Value::String("b.md".into()))).unwrap();
1490 assert_eq!(beta.get("kill_reason"), Some(&Value::Null));
1491 }
1492
1493 #[test]
1494 fn test_where_gt() {
1495 let q = SelectQuery {
1496 distinct: false,
1497 columns: ColumnList::All,
1498 table: "test".into(),
1499 table_alias: None,
1500 subquery: None,
1501 joins: vec![],
1502 where_clause: Some(WhereClause::Comparison(Comparison {
1503 column: "count".into(),
1504 op: CmpOp::Gt,
1505 value: Some(SqlValue::Int(5)),
1506 left_expr: Some(Expr::Column("count".into())),
1507 right_expr: Some(Expr::Literal(SqlValue::Int(5))),
1508 })),
1509 group_by: None,
1510 having: None,
1511 order_by: None,
1512 limit: None,
1513 ctes: vec![],
1514 };
1515 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1516 assert_eq!(rows.len(), 2);
1517 }
1518
1519 #[test]
1520 fn test_order_by_desc() {
1521 let q = SelectQuery {
1522 distinct: false,
1523 columns: ColumnList::All,
1524 table: "test".into(),
1525 table_alias: None,
1526 subquery: None,
1527 joins: vec![],
1528 where_clause: None,
1529 group_by: None,
1530 having: None,
1531 order_by: Some(vec![OrderSpec {
1532 column: "count".into(),
1533 expr: Some(Expr::Column("count".into())),
1534 descending: true,
1535 }]),
1536 limit: None,
1537 ctes: vec![],
1538 };
1539 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1540 assert_eq!(rows[0]["count"], Value::Int(20));
1541 assert_eq!(rows[2]["count"], Value::Int(5));
1542 }
1543
1544 #[test]
1545 fn test_limit() {
1546 let q = SelectQuery {
1547 distinct: false,
1548 columns: ColumnList::All,
1549 table: "test".into(),
1550 table_alias: None,
1551 subquery: None,
1552 joins: vec![],
1553 where_clause: None,
1554 group_by: None,
1555 having: None,
1556 order_by: None,
1557 limit: Some(2),
1558 ctes: vec![],
1559 };
1560 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1561 assert_eq!(rows.len(), 2);
1562 }
1563
1564 #[test]
1565 fn test_like() {
1566 let q = SelectQuery {
1567 distinct: false,
1568 columns: ColumnList::All,
1569 table: "test".into(),
1570 table_alias: None,
1571 subquery: None,
1572 joins: vec![],
1573 where_clause: Some(WhereClause::Comparison(Comparison {
1574 column: "title".into(),
1575 op: CmpOp::Like,
1576 value: Some(SqlValue::String("%lph%".into())),
1577 left_expr: Some(Expr::Column("title".into())),
1578 right_expr: None,
1579 })),
1580 group_by: None,
1581 having: None,
1582 order_by: None,
1583 limit: None,
1584 ctes: vec![],
1585 };
1586 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1587 assert_eq!(rows.len(), 1);
1588 assert_eq!(rows[0]["title"], Value::String("Alpha".into()));
1589 }
1590
1591 #[test]
1592 fn test_is_null() {
1593 let mut rows = make_rows();
1594 rows[1].insert("optional".into(), Value::Null);
1595
1596 let q = SelectQuery {
1597 distinct: false,
1598 columns: ColumnList::All,
1599 table: "test".into(),
1600 table_alias: None,
1601 subquery: None,
1602 joins: vec![],
1603 where_clause: Some(WhereClause::Comparison(Comparison {
1604 column: "optional".into(),
1605 op: CmpOp::IsNull,
1606 value: None,
1607 left_expr: Some(Expr::Column("optional".into())),
1608 right_expr: None,
1609 })),
1610 group_by: None,
1611 having: None,
1612 order_by: None,
1613 limit: None,
1614 ctes: vec![],
1615 };
1616 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1617 assert_eq!(result.len(), 3);
1619 }
1620
1621 #[test]
1622 fn test_select_distinct_dedupes_and_projects() {
1623 let rows = vec![
1625 Row::from([("path".into(), Value::String("1.md".into())), ("strategy".into(), Value::String("a".into()))]),
1626 Row::from([("path".into(), Value::String("2.md".into())), ("strategy".into(), Value::String("b".into()))]),
1627 Row::from([("path".into(), Value::String("3.md".into())), ("strategy".into(), Value::String("a".into()))]),
1628 ];
1629 let q = match parse_query("SELECT DISTINCT strategy FROM backtests").unwrap() {
1630 Statement::Select(s) => s,
1631 _ => panic!("expected SELECT"),
1632 };
1633 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
1634 assert_eq!(cols, vec!["strategy"]);
1635 assert_eq!(result.len(), 2);
1636 let values: Vec<_> = result.iter().map(|r| r.get("strategy").unwrap().clone()).collect();
1637 assert_eq!(values, vec![Value::String("a".into()), Value::String("b".into())]);
1638 }
1639
1640 #[test]
1641 fn test_select_distinct_applies_before_limit() {
1642 let rows = vec![
1644 Row::from([("path".into(), Value::String("1.md".into())), ("s".into(), Value::String("a".into()))]),
1645 Row::from([("path".into(), Value::String("2.md".into())), ("s".into(), Value::String("a".into()))]),
1646 Row::from([("path".into(), Value::String("3.md".into())), ("s".into(), Value::String("b".into()))]),
1647 Row::from([("path".into(), Value::String("4.md".into())), ("s".into(), Value::String("c".into()))]),
1648 ];
1649 let q = match parse_query("SELECT DISTINCT s FROM t ORDER BY s LIMIT 2").unwrap() {
1650 Statement::Select(s) => s,
1651 _ => panic!("expected SELECT"),
1652 };
1653 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1654 assert_eq!(result.len(), 2);
1656 assert_eq!(result[0].get("s"), Some(&Value::String("a".into())));
1657 assert_eq!(result[1].get("s"), Some(&Value::String("b".into())));
1658 }
1659
1660 #[test]
1661 fn test_select_distinct_star() {
1662 let rows = vec![
1663 Row::from([("path".into(), Value::String("1.md".into())), ("s".into(), Value::String("a".into()))]),
1664 Row::from([("path".into(), Value::String("1.md".into())), ("s".into(), Value::String("a".into()))]),
1665 Row::from([("path".into(), Value::String("2.md".into())), ("s".into(), Value::String("b".into()))]),
1666 ];
1667 let q = match parse_query("SELECT DISTINCT * FROM t").unwrap() {
1668 Statement::Select(s) => s,
1669 _ => panic!("expected SELECT"),
1670 };
1671 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1672 assert_eq!(result.len(), 2);
1673 }
1674
1675 #[test]
1676 fn test_where_boolean_literal_filters_rows() {
1677 let mut rows = make_rows();
1680 rows[0].insert("flag".into(), Value::Bool(true));
1681 rows[1].insert("flag".into(), Value::Bool(false));
1682 rows[2].insert("flag".into(), Value::Bool(true));
1683
1684 for (sql, expected) in [
1685 ("SELECT path FROM test WHERE flag = true", 2),
1686 ("SELECT path FROM test WHERE flag = FALSE", 1),
1687 ("SELECT path FROM test WHERE flag != true", 1),
1688 ] {
1689 let q = match parse_query(sql).unwrap() {
1690 Statement::Select(s) => s,
1691 _ => panic!("expected SELECT"),
1692 };
1693 let (result, _) = execute_inner(&q, &rows, None).unwrap();
1694 assert_eq!(result.len(), expected, "query: {sql}");
1695 }
1696 }
1697
1698 #[test]
1701 fn test_evaluate_expr_literal() {
1702 let row = Row::new();
1703 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Int(42)), &row), Value::Int(42));
1704 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Float(3.14)), &row), Value::Float(3.14));
1705 assert_eq!(evaluate_expr(&Expr::Literal(SqlValue::Null), &row), Value::Null);
1706 }
1707
1708 #[test]
1709 fn test_evaluate_expr_column() {
1710 let row = Row::from([("x".into(), Value::Int(10))]);
1711 assert_eq!(evaluate_expr(&Expr::Column("x".into()), &row), Value::Int(10));
1712 assert_eq!(evaluate_expr(&Expr::Column("missing".into()), &row), Value::Null);
1713 }
1714
1715 #[test]
1716 fn test_evaluate_expr_int_arithmetic() {
1717 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(3))]);
1718 let add = Expr::BinaryOp {
1719 left: Box::new(Expr::Column("a".into())),
1720 op: ArithOp::Add,
1721 right: Box::new(Expr::Column("b".into())),
1722 };
1723 assert_eq!(evaluate_expr(&add, &row), Value::Int(13));
1724
1725 let sub = Expr::BinaryOp {
1726 left: Box::new(Expr::Column("a".into())),
1727 op: ArithOp::Sub,
1728 right: Box::new(Expr::Column("b".into())),
1729 };
1730 assert_eq!(evaluate_expr(&sub, &row), Value::Int(7));
1731
1732 let mul = Expr::BinaryOp {
1733 left: Box::new(Expr::Column("a".into())),
1734 op: ArithOp::Mul,
1735 right: Box::new(Expr::Column("b".into())),
1736 };
1737 assert_eq!(evaluate_expr(&mul, &row), Value::Int(30));
1738
1739 let div = Expr::BinaryOp {
1740 left: Box::new(Expr::Column("a".into())),
1741 op: ArithOp::Div,
1742 right: Box::new(Expr::Column("b".into())),
1743 };
1744 assert_eq!(evaluate_expr(&div, &row), Value::Int(3)); let modulo = Expr::BinaryOp {
1747 left: Box::new(Expr::Column("a".into())),
1748 op: ArithOp::Mod,
1749 right: Box::new(Expr::Column("b".into())),
1750 };
1751 assert_eq!(evaluate_expr(&modulo, &row), Value::Int(1));
1752 }
1753
1754 #[test]
1755 fn test_evaluate_expr_float_coercion() {
1756 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Float(3.0))]);
1757 let add = Expr::BinaryOp {
1758 left: Box::new(Expr::Column("a".into())),
1759 op: ArithOp::Add,
1760 right: Box::new(Expr::Column("b".into())),
1761 };
1762 assert_eq!(evaluate_expr(&add, &row), Value::Float(13.0));
1763 }
1764
1765 #[test]
1766 fn test_evaluate_expr_null_propagation() {
1767 let row = Row::from([("a".into(), Value::Int(10))]);
1768 let add = Expr::BinaryOp {
1769 left: Box::new(Expr::Column("a".into())),
1770 op: ArithOp::Add,
1771 right: Box::new(Expr::Column("missing".into())),
1772 };
1773 assert_eq!(evaluate_expr(&add, &row), Value::Null);
1774 }
1775
1776 #[test]
1777 fn test_evaluate_expr_div_by_zero() {
1778 let row = Row::from([("a".into(), Value::Int(10)), ("b".into(), Value::Int(0))]);
1779 let div = Expr::BinaryOp {
1780 left: Box::new(Expr::Column("a".into())),
1781 op: ArithOp::Div,
1782 right: Box::new(Expr::Column("b".into())),
1783 };
1784 assert_eq!(evaluate_expr(&div, &row), Value::Null);
1785 }
1786
1787 #[test]
1788 fn test_evaluate_expr_unary_minus() {
1789 let row = Row::from([("x".into(), Value::Int(5))]);
1790 let neg = Expr::UnaryMinus(Box::new(Expr::Column("x".into())));
1791 assert_eq!(evaluate_expr(&neg, &row), Value::Int(-5));
1792 }
1793
1794 #[test]
1795 fn test_select_with_expression() {
1796 let stmt = crate::query_parser::parse_query(
1798 "SELECT count * 2 AS doubled FROM test"
1799 ).unwrap();
1800 if let crate::query_parser::Statement::Select(q) = stmt {
1801 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1802 assert_eq!(cols, vec!["doubled"]);
1803 assert_eq!(rows.len(), 3);
1804 let values: Vec<Value> = rows.iter().map(|r| r["doubled"].clone()).collect();
1806 assert!(values.contains(&Value::Int(20)));
1807 assert!(values.contains(&Value::Int(10)));
1808 assert!(values.contains(&Value::Int(40)));
1809 } else {
1810 panic!("Expected Select");
1811 }
1812 }
1813
1814 #[test]
1815 fn test_where_with_expression() {
1816 let stmt = crate::query_parser::parse_query(
1818 "SELECT * FROM test WHERE count * 2 > 15"
1819 ).unwrap();
1820 if let crate::query_parser::Statement::Select(q) = stmt {
1821 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1822 assert_eq!(rows.len(), 2);
1824 } else {
1825 panic!("Expected Select");
1826 }
1827 }
1828
1829 #[test]
1830 fn test_order_by_expression() {
1831 let stmt = crate::query_parser::parse_query(
1833 "SELECT title, count FROM test ORDER BY count * -1 ASC"
1834 ).unwrap();
1835 if let crate::query_parser::Statement::Select(q) = stmt {
1836 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1837 assert_eq!(rows[0]["count"], Value::Int(20));
1839 assert_eq!(rows[1]["count"], Value::Int(10));
1840 assert_eq!(rows[2]["count"], Value::Int(5));
1841 } else {
1842 panic!("Expected Select");
1843 }
1844 }
1845
1846 #[test]
1849 fn test_case_when_eval_basic() {
1850 let row = Row::from([("status".into(), Value::String("ACTIVE".into()))]);
1851 let expr = Expr::Case {
1852 whens: vec![(
1853 WhereClause::Comparison(Comparison {
1854 column: "status".into(),
1855 op: CmpOp::Eq,
1856 value: Some(SqlValue::String("ACTIVE".into())),
1857 left_expr: Some(Expr::Column("status".into())),
1858 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1859 }),
1860 Box::new(Expr::Literal(SqlValue::Int(1))),
1861 )],
1862 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1863 };
1864 assert_eq!(evaluate_expr(&expr, &row), Value::Int(1));
1865 }
1866
1867 #[test]
1868 fn test_case_when_eval_else() {
1869 let row = Row::from([("status".into(), Value::String("KILLED".into()))]);
1870 let expr = Expr::Case {
1871 whens: vec![(
1872 WhereClause::Comparison(Comparison {
1873 column: "status".into(),
1874 op: CmpOp::Eq,
1875 value: Some(SqlValue::String("ACTIVE".into())),
1876 left_expr: Some(Expr::Column("status".into())),
1877 right_expr: Some(Expr::Literal(SqlValue::String("ACTIVE".into()))),
1878 }),
1879 Box::new(Expr::Literal(SqlValue::Int(1))),
1880 )],
1881 else_expr: Some(Box::new(Expr::Literal(SqlValue::Int(0)))),
1882 };
1883 assert_eq!(evaluate_expr(&expr, &row), Value::Int(0));
1884 }
1885
1886 #[test]
1887 fn test_case_when_eval_no_else_null() {
1888 let row = Row::from([("x".into(), Value::Int(99))]);
1889 let expr = Expr::Case {
1890 whens: vec![(
1891 WhereClause::Comparison(Comparison {
1892 column: "x".into(),
1893 op: CmpOp::Eq,
1894 value: Some(SqlValue::Int(1)),
1895 left_expr: Some(Expr::Column("x".into())),
1896 right_expr: Some(Expr::Literal(SqlValue::Int(1))),
1897 }),
1898 Box::new(Expr::Literal(SqlValue::String("one".into()))),
1899 )],
1900 else_expr: None,
1901 };
1902 assert_eq!(evaluate_expr(&expr, &row), Value::Null);
1903 }
1904
1905 #[test]
1906 fn test_case_when_in_aggregate_query() {
1907 let stmt = crate::query_parser::parse_query(
1910 "SELECT SUM(CASE WHEN count > 5 THEN count ELSE 0 END) AS total FROM test"
1911 ).unwrap();
1912 if let crate::query_parser::Statement::Select(q) = stmt {
1913 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
1914 assert_eq!(cols, vec!["total"]);
1915 assert_eq!(rows.len(), 1);
1916 assert_eq!(rows[0]["total"], Value::Float(30.0));
1917 } else {
1918 panic!("Expected Select");
1919 }
1920 }
1921
1922 #[test]
1923 fn test_case_when_with_unary_minus_in_aggregate() {
1924 let stmt = crate::query_parser::parse_query(
1927 "SELECT SUM(CASE WHEN title = 'Alpha' THEN count ELSE -count END) AS net FROM test"
1928 ).unwrap();
1929 if let crate::query_parser::Statement::Select(q) = stmt {
1930 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
1931 assert_eq!(rows.len(), 1);
1932 assert_eq!(rows[0]["net"], Value::Float(-15.0));
1933 } else {
1934 panic!("Expected Select");
1935 }
1936 }
1937
1938 #[test]
1939 fn test_dateadd_with_dict_in_group_by() {
1940 use indexmap::IndexMap;
1942 let mut params = IndexMap::new();
1943 params.insert("exit_days".to_string(), Value::Int(21));
1944
1945 let rows = vec![
1946 Row::from([
1947 ("o.token".into(), Value::String("BTC".into())),
1948 ("o.event_date".into(), Value::Date(
1949 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1950 )),
1951 ("o.size".into(), Value::Int(100)),
1952 ("s.params".into(), Value::Dict(params.clone())),
1953 ]),
1954 Row::from([
1955 ("o.token".into(), Value::String("BTC".into())),
1956 ("o.event_date".into(), Value::Date(
1957 chrono::NaiveDate::from_ymd_opt(2026, 1, 1).unwrap()
1958 )),
1959 ("o.size".into(), Value::Int(50)),
1960 ("s.params".into(), Value::Dict(params.clone())),
1961 ]),
1962 ];
1963
1964 let q = SelectQuery {
1965 distinct: false,
1966 columns: ColumnList::Named(vec![
1967 SelectExpr::Column("o.token".into()),
1968 SelectExpr::Column("o.event_date".into()),
1969 SelectExpr::Expr {
1970 expr: Expr::DateAdd {
1971 date: Box::new(Expr::Column("o.event_date".into())),
1972 days: Box::new(Expr::Column("s.params.exit_days".into())),
1973 },
1974 alias: Some("exit_date".into()),
1975 },
1976 SelectExpr::Aggregate {
1977 func: AggFunc::Sum,
1978 arg: "o.size".into(),
1979 arg_expr: Some(Expr::Column("o.size".into())),
1980 alias: Some("total".into()),
1981 },
1982 ]),
1983 table: "orders".into(),
1984 table_alias: None,
1985 subquery: None,
1986 joins: vec![],
1987 where_clause: None,
1988 group_by: Some(vec!["o.token".into(), "o.event_date".into()]),
1989 having: None,
1990 order_by: None,
1991 limit: None,
1992 ctes: vec![],
1993 };
1994
1995 let (rows, cols) = execute_inner(&q, &rows, None).unwrap();
1996 assert_eq!(rows.len(), 1);
1997 assert!(cols.contains(&"exit_date".to_string()));
1998 assert_eq!(rows[0]["total"], Value::Float(150.0));
1999 assert_eq!(
2001 rows[0]["exit_date"],
2002 Value::Date(chrono::NaiveDate::from_ymd_opt(2026, 1, 22).unwrap())
2003 );
2004 }
2005
2006 #[test]
2007 fn test_aggregate_arithmetic() {
2008 let stmt = crate::query_parser::parse_query(
2012 "SELECT SUM(count) / COUNT(*) AS avg_count FROM test"
2013 ).unwrap();
2014 if let crate::query_parser::Statement::Select(q) = stmt {
2015 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
2016 assert_eq!(cols, vec!["avg_count"]);
2017 assert_eq!(rows.len(), 1);
2018 match &rows[0]["avg_count"] {
2019 Value::Float(f) => assert!((f - 11.666666666666666).abs() < 0.001),
2020 other => panic!("Expected Float, got {:?}", other),
2021 }
2022 } else {
2023 panic!("Expected Select");
2024 }
2025 }
2026
2027 #[test]
2028 fn test_aggregate_subtraction_with_group_by() {
2029 let rows = vec![
2030 {
2031 let mut r = Row::new();
2032 r.insert("token".into(), Value::String("BTC".into()));
2033 r.insert("side".into(), Value::String("BUY".into()));
2034 r.insert("size".into(), Value::Float(100.0));
2035 r
2036 },
2037 {
2038 let mut r = Row::new();
2039 r.insert("token".into(), Value::String("BTC".into()));
2040 r.insert("side".into(), Value::String("SELL".into()));
2041 r.insert("size".into(), Value::Float(60.0));
2042 r
2043 },
2044 ];
2045 let stmt = crate::query_parser::parse_query(
2046 "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"
2047 ).unwrap();
2048 if let crate::query_parser::Statement::Select(q) = stmt {
2049 let (result, _) = execute_inner(&q, &rows, None).unwrap();
2050 assert_eq!(result.len(), 1);
2051 assert_eq!(result[0]["net"], Value::Float(40.0));
2052 } else {
2053 panic!("Expected Select");
2054 }
2055 }
2056
2057 #[test]
2060 fn test_aggregate_subtraction_no_group() {
2061 let stmt = crate::query_parser::parse_query(
2063 "SELECT SUM(count) - COUNT(*) as diff FROM test"
2064 ).unwrap();
2065 if let crate::query_parser::Statement::Select(q) = stmt {
2066 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
2067 assert_eq!(cols, vec!["diff"]);
2068 assert_eq!(rows.len(), 1);
2069 assert_eq!(rows[0]["diff"], Value::Float(32.0));
2070 } else {
2071 panic!("Expected Select");
2072 }
2073 }
2074
2075 #[test]
2078 fn test_aggregate_division_with_group_by() {
2079 let rows = vec![
2080 {
2081 let mut r = Row::new();
2082 r.insert("category".into(), Value::String("A".into()));
2083 r.insert("count".into(), Value::Int(10));
2084 r
2085 },
2086 {
2087 let mut r = Row::new();
2088 r.insert("category".into(), Value::String("A".into()));
2089 r.insert("count".into(), Value::Int(20));
2090 r
2091 },
2092 {
2093 let mut r = Row::new();
2094 r.insert("category".into(), Value::String("B".into()));
2095 r.insert("count".into(), Value::Int(6));
2096 r
2097 },
2098 ];
2099 let stmt = crate::query_parser::parse_query(
2102 "SELECT category, SUM(count) / COUNT(*) as ratio FROM test GROUP BY category"
2103 ).unwrap();
2104 if let crate::query_parser::Statement::Select(q) = stmt {
2105 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
2106 assert!(cols.contains(&"ratio".to_string()));
2107 assert_eq!(result.len(), 2);
2108 let group_a = result.iter().find(|r| r["category"] == Value::String("A".into())).unwrap();
2110 let group_b = result.iter().find(|r| r["category"] == Value::String("B".into())).unwrap();
2111 match &group_a["ratio"] {
2112 Value::Float(f) => assert!((f - 15.0).abs() < 0.001),
2113 other => panic!("Expected Float for group A ratio, got {:?}", other),
2114 }
2115 match &group_b["ratio"] {
2116 Value::Float(f) => assert!((f - 6.0).abs() < 0.001),
2117 other => panic!("Expected Float for group B ratio, got {:?}", other),
2118 }
2119 } else {
2120 panic!("Expected Select");
2121 }
2122 }
2123
2124 #[test]
2127 fn test_window_row_number() {
2128 let stmt = crate::query_parser::parse_query(
2129 "SELECT title, ROW_NUMBER() OVER (ORDER BY count DESC) AS rn FROM test"
2130 ).unwrap();
2131 if let crate::query_parser::Statement::Select(q) = stmt {
2132 let (rows, cols) = execute_inner(&q, &make_rows(), None).unwrap();
2133 assert_eq!(cols, vec!["title", "rn"]);
2134 assert_eq!(rows.len(), 3);
2135 let by_title: HashMap<String, i64> = rows.iter()
2136 .map(|r| (r["title"].to_display_string(), match &r["rn"] { Value::Int(n) => *n, _ => panic!("Expected Int") }))
2137 .collect();
2138 assert_eq!(by_title["Gamma"], 1); assert_eq!(by_title["Alpha"], 2); assert_eq!(by_title["Beta"], 3); } else {
2142 panic!("Expected Select");
2143 }
2144 }
2145
2146 #[test]
2147 fn test_window_rank_with_ties() {
2148 let mut rows = make_rows();
2149 rows[0].insert("count".into(), Value::Int(10));
2150 rows[1].insert("count".into(), Value::Int(10));
2151 rows[2].insert("count".into(), Value::Int(5));
2152
2153 let stmt = crate::query_parser::parse_query(
2154 "SELECT title, RANK() OVER (ORDER BY count DESC) AS rnk FROM test"
2155 ).unwrap();
2156 if let crate::query_parser::Statement::Select(q) = stmt {
2157 let (result, _) = execute_inner(&q, &rows, None).unwrap();
2158 let ranks: Vec<i64> = result.iter()
2159 .map(|r| match &r["rnk"] { Value::Int(n) => *n, _ => panic!("Expected Int") })
2160 .collect();
2161 assert!(ranks.contains(&1)); assert!(ranks.iter().filter(|&&r| r == 1).count() == 2);
2163 assert!(ranks.contains(&3)); } else {
2165 panic!("Expected Select");
2166 }
2167 }
2168
2169 #[test]
2170 fn test_window_dense_rank() {
2171 let mut rows = make_rows();
2172 rows[0].insert("count".into(), Value::Int(10));
2173 rows[1].insert("count".into(), Value::Int(10));
2174 rows[2].insert("count".into(), Value::Int(5));
2175
2176 let stmt = crate::query_parser::parse_query(
2177 "SELECT title, DENSE_RANK() OVER (ORDER BY count DESC) AS dr FROM test"
2178 ).unwrap();
2179 if let crate::query_parser::Statement::Select(q) = stmt {
2180 let (result, _) = execute_inner(&q, &rows, None).unwrap();
2181 let ranks: Vec<i64> = result.iter()
2182 .map(|r| match &r["dr"] { Value::Int(n) => *n, _ => panic!("Expected Int") })
2183 .collect();
2184 assert!(ranks.iter().filter(|&&r| r == 1).count() == 2);
2185 assert!(ranks.contains(&2)); assert!(!ranks.contains(&3));
2187 } else {
2188 panic!("Expected Select");
2189 }
2190 }
2191
2192 #[test]
2193 fn test_window_lag() {
2194 let stmt = crate::query_parser::parse_query(
2195 "SELECT title, LAG(count, 1) OVER (ORDER BY count ASC) AS prev FROM test"
2196 ).unwrap();
2197 if let crate::query_parser::Statement::Select(q) = stmt {
2198 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
2199 let first = rows.iter().find(|r| r["title"] == Value::String("Beta".into())).unwrap();
2202 assert_eq!(first["prev"], Value::Null);
2203 let second = rows.iter().find(|r| r["title"] == Value::String("Alpha".into())).unwrap();
2204 assert_eq!(second["prev"], Value::Int(5));
2205 let third = rows.iter().find(|r| r["title"] == Value::String("Gamma".into())).unwrap();
2206 assert_eq!(third["prev"], Value::Int(10));
2207 } else {
2208 panic!("Expected Select");
2209 }
2210 }
2211
2212 #[test]
2213 fn test_window_lead() {
2214 let stmt = crate::query_parser::parse_query(
2215 "SELECT title, LEAD(count, 1) OVER (ORDER BY count ASC) AS next FROM test"
2216 ).unwrap();
2217 if let crate::query_parser::Statement::Select(q) = stmt {
2218 let (rows, _) = execute_inner(&q, &make_rows(), None).unwrap();
2219 let first = rows.iter().find(|r| r["title"] == Value::String("Beta".into())).unwrap();
2220 assert_eq!(first["next"], Value::Int(10));
2221 let last = rows.iter().find(|r| r["title"] == Value::String("Gamma".into())).unwrap();
2222 assert_eq!(last["next"], Value::Null);
2223 } else {
2224 panic!("Expected Select");
2225 }
2226 }
2227
2228 #[test]
2229 fn test_window_sum_partition() {
2230 let rows = vec![
2231 Row::from([
2232 ("cat".into(), Value::String("A".into())),
2233 ("val".into(), Value::Int(10)),
2234 ]),
2235 Row::from([
2236 ("cat".into(), Value::String("A".into())),
2237 ("val".into(), Value::Int(20)),
2238 ]),
2239 Row::from([
2240 ("cat".into(), Value::String("B".into())),
2241 ("val".into(), Value::Int(5)),
2242 ]),
2243 ];
2244 let stmt = crate::query_parser::parse_query(
2245 "SELECT cat, val, SUM(val) OVER (PARTITION BY cat) AS cat_total FROM test"
2246 ).unwrap();
2247 if let crate::query_parser::Statement::Select(q) = stmt {
2248 let (result, cols) = execute_inner(&q, &rows, None).unwrap();
2249 assert_eq!(cols, vec!["cat", "val", "cat_total"]);
2250 assert_eq!(result.len(), 3);
2251 let a_rows: Vec<_> = result.iter().filter(|r| r["cat"] == Value::String("A".into())).collect();
2252 assert_eq!(a_rows.len(), 2);
2253 for r in &a_rows {
2254 assert_eq!(r["cat_total"], Value::Float(30.0));
2255 }
2256 let b_row = result.iter().find(|r| r["cat"] == Value::String("B".into())).unwrap();
2257 assert_eq!(b_row["cat_total"], Value::Float(5.0));
2258 } else {
2259 panic!("Expected Select");
2260 }
2261 }
2262
2263 #[test]
2264 fn test_window_with_where_order_limit() {
2265 let stmt = crate::query_parser::parse_query(
2266 "SELECT title, ROW_NUMBER() OVER (ORDER BY count DESC) AS rn FROM test WHERE count > 4 ORDER BY rn LIMIT 2"
2267 ).unwrap();
2268 if let crate::query_parser::Statement::Select(q) = stmt {
2269 let (result, _) = execute_inner(&q, &make_rows(), None).unwrap();
2270 assert_eq!(result.len(), 2);
2271 assert_eq!(result[0]["rn"], Value::Int(1));
2272 assert_eq!(result[1]["rn"], Value::Int(2));
2273 } else {
2274 panic!("Expected Select");
2275 }
2276 }
2277}