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