Skip to main content

dkit_core/query/
functions.rs

1use crate::error::DkitError;
2use crate::query::filter::evaluate_condition;
3use crate::query::parser::{ArithmeticOp, Expr, LiteralValue};
4use crate::value::Value;
5
6/// 표현식을 주어진 레코드(행)에 대해 평가하여 Value를 반환
7pub fn evaluate_expr(row: &Value, expr: &Expr) -> Result<Value, DkitError> {
8    match expr {
9        Expr::Field(name) => match row {
10            Value::Object(map) => Ok(map.get(name).cloned().unwrap_or(Value::Null)),
11            _ => Err(DkitError::QueryError(format!(
12                "cannot access field '{}' on non-object value",
13                name
14            ))),
15        },
16        Expr::Literal(lit) => Ok(literal_to_value(lit)),
17        Expr::FuncCall { name, args } => {
18            let evaluated: Result<Vec<Value>, DkitError> =
19                args.iter().map(|a| evaluate_expr(row, a)).collect();
20            call_function(name, evaluated?)
21        }
22        Expr::BinaryOp { op, left, right } => {
23            let lv = evaluate_expr(row, left)?;
24            let rv = evaluate_expr(row, right)?;
25            evaluate_binary_op(op, &lv, &rv)
26        }
27        Expr::If {
28            condition,
29            then_expr,
30            else_expr,
31        } => {
32            if evaluate_condition(row, condition)? {
33                evaluate_expr(row, then_expr)
34            } else {
35                evaluate_expr(row, else_expr)
36            }
37        }
38        Expr::Case { branches, default } => {
39            for (condition, expr) in branches {
40                if evaluate_condition(row, condition)? {
41                    return evaluate_expr(row, expr);
42                }
43            }
44            match default {
45                Some(expr) => evaluate_expr(row, expr),
46                None => Ok(Value::Null),
47            }
48        }
49    }
50}
51
52/// 이항 산술 연산 평가
53fn evaluate_binary_op(op: &ArithmeticOp, left: &Value, right: &Value) -> Result<Value, DkitError> {
54    // String concatenation with +
55    if matches!(op, ArithmeticOp::Add) {
56        if let (Value::String(a), Value::String(b)) = (left, right) {
57            return Ok(Value::String(format!("{}{}", a, b)));
58        }
59        // String + non-string or non-string + String → string concat
60        if matches!(left, Value::String(_)) || matches!(right, Value::String(_)) {
61            let a = value_to_display_string(left);
62            let b = value_to_display_string(right);
63            return Ok(Value::String(format!("{}{}", a, b)));
64        }
65    }
66
67    // Null propagation
68    if matches!(left, Value::Null) || matches!(right, Value::Null) {
69        return Ok(Value::Null);
70    }
71
72    // Numeric operations
73    let (lf, li) = to_numeric(left)?;
74    let (rf, ri) = to_numeric(right)?;
75
76    // Both integers → integer result (except division)
77    if let (Some(a), Some(b)) = (li, ri) {
78        return match op {
79            ArithmeticOp::Add => Ok(Value::Integer(a.wrapping_add(b))),
80            ArithmeticOp::Sub => Ok(Value::Integer(a.wrapping_sub(b))),
81            ArithmeticOp::Mul => Ok(Value::Integer(a.wrapping_mul(b))),
82            ArithmeticOp::Div => {
83                if b == 0 {
84                    return Err(DkitError::QueryError("division by zero".to_string()));
85                }
86                // Use float division if not evenly divisible
87                if a % b == 0 {
88                    Ok(Value::Integer(a / b))
89                } else {
90                    Ok(Value::Float(lf / rf))
91                }
92            }
93        };
94    }
95
96    // At least one float → float result
97    match op {
98        ArithmeticOp::Add => Ok(Value::Float(lf + rf)),
99        ArithmeticOp::Sub => Ok(Value::Float(lf - rf)),
100        ArithmeticOp::Mul => Ok(Value::Float(lf * rf)),
101        ArithmeticOp::Div => {
102            if rf == 0.0 {
103                return Err(DkitError::QueryError("division by zero".to_string()));
104            }
105            Ok(Value::Float(lf / rf))
106        }
107    }
108}
109
110/// Value를 (f64, Option<i64>) 로 변환. i64로 표현 가능하면 Some(i64).
111fn to_numeric(v: &Value) -> Result<(f64, Option<i64>), DkitError> {
112    match v {
113        Value::Integer(n) => Ok((*n as f64, Some(*n))),
114        Value::Float(f) => Ok((*f, None)),
115        Value::Bool(b) => {
116            let n = if *b { 1 } else { 0 };
117            Ok((n as f64, Some(n)))
118        }
119        _ => Err(DkitError::QueryError(format!(
120            "cannot perform arithmetic on {} value",
121            value_type_name(v)
122        ))),
123    }
124}
125
126/// Value를 문자열로 변환 (string concat용)
127fn value_to_display_string(v: &Value) -> String {
128    match v {
129        Value::String(s) => s.clone(),
130        Value::Integer(n) => n.to_string(),
131        Value::Float(f) => f.to_string(),
132        Value::Bool(b) => b.to_string(),
133        Value::Null => "null".to_string(),
134        _ => format!("{}", v),
135    }
136}
137
138/// 표현식에서 출력 키(필드명)를 자동으로 결정
139pub fn expr_default_key(expr: &Expr) -> String {
140    match expr {
141        Expr::Field(f) => f.clone(),
142        Expr::Literal(_) => "value".to_string(),
143        Expr::FuncCall { name, args } => {
144            if let Some(first) = args.first() {
145                format!("{}_{}", name, expr_default_key(first))
146            } else {
147                name.clone()
148            }
149        }
150        Expr::BinaryOp { left, .. } => expr_default_key(left),
151        Expr::If { then_expr, .. } => expr_default_key(then_expr),
152        Expr::Case { branches, .. } => {
153            if let Some((_, expr)) = branches.first() {
154                expr_default_key(expr)
155            } else {
156                "case".to_string()
157            }
158        }
159    }
160}
161
162fn literal_to_value(lit: &LiteralValue) -> Value {
163    match lit {
164        LiteralValue::String(s) => Value::String(s.clone()),
165        LiteralValue::Integer(n) => Value::Integer(*n),
166        LiteralValue::Float(f) => Value::Float(*f),
167        LiteralValue::Bool(b) => Value::Bool(*b),
168        LiteralValue::Null => Value::Null,
169        LiteralValue::List(items) => Value::Array(items.iter().map(literal_to_value).collect()),
170    }
171}
172
173/// 내장 함수 호출
174fn call_function(name: &str, args: Vec<Value>) -> Result<Value, DkitError> {
175    match name {
176        // --- 문자열 함수 ---
177        "upper" => {
178            let s = require_one_string(name, &args)?;
179            Ok(Value::String(s.to_uppercase()))
180        }
181        "lower" => {
182            let s = require_one_string(name, &args)?;
183            Ok(Value::String(s.to_lowercase()))
184        }
185        "trim" => {
186            let s = require_one_string(name, &args)?;
187            Ok(Value::String(s.trim().to_string()))
188        }
189        "ltrim" => {
190            let s = require_one_string(name, &args)?;
191            Ok(Value::String(s.trim_start().to_string()))
192        }
193        "rtrim" => {
194            let s = require_one_string(name, &args)?;
195            Ok(Value::String(s.trim_end().to_string()))
196        }
197        "length" => match args.as_slice() {
198            [Value::String(s)] => Ok(Value::Integer(s.chars().count() as i64)),
199            [Value::Array(a)] => Ok(Value::Integer(a.len() as i64)),
200            [Value::Null] => Ok(Value::Integer(0)),
201            [v] => Err(DkitError::QueryError(format!(
202                "length() requires a string or array argument, got {}",
203                value_type_name(v)
204            ))),
205            _ => Err(DkitError::QueryError(format!(
206                "length() takes 1 argument, got {}",
207                args.len()
208            ))),
209        },
210        "substr" => {
211            if args.len() < 2 || args.len() > 3 {
212                return Err(DkitError::QueryError(format!(
213                    "substr() takes 2 or 3 arguments, got {}",
214                    args.len()
215                )));
216            }
217            let s = match &args[0] {
218                Value::String(s) => s.clone(),
219                Value::Null => return Ok(Value::Null),
220                v => {
221                    return Err(DkitError::QueryError(format!(
222                        "substr() first argument must be a string, got {}",
223                        value_type_name(v)
224                    )))
225                }
226            };
227            let start = require_integer_arg("substr", &args[1], "start")? as usize;
228            let chars: Vec<char> = s.chars().collect();
229            let start = start.min(chars.len());
230            if args.len() == 3 {
231                let len = require_integer_arg("substr", &args[2], "length")? as usize;
232                let end = (start + len).min(chars.len());
233                Ok(Value::String(chars[start..end].iter().collect()))
234            } else {
235                Ok(Value::String(chars[start..].iter().collect()))
236            }
237        }
238        "concat" => {
239            if args.is_empty() {
240                return Ok(Value::String(String::new()));
241            }
242            let mut result = String::new();
243            for arg in &args {
244                match arg {
245                    Value::String(s) => result.push_str(s),
246                    Value::Null => {}
247                    v => result.push_str(&v.to_string()),
248                }
249            }
250            Ok(Value::String(result))
251        }
252        "replace" => {
253            if args.len() != 3 {
254                return Err(DkitError::QueryError(format!(
255                    "replace() takes 3 arguments (string, from, to), got {}",
256                    args.len()
257                )));
258            }
259            let s = match &args[0] {
260                Value::String(s) => s.clone(),
261                Value::Null => return Ok(Value::Null),
262                v => {
263                    return Err(DkitError::QueryError(format!(
264                        "replace() first argument must be a string, got {}",
265                        value_type_name(v)
266                    )))
267                }
268            };
269            let from = match &args[1] {
270                Value::String(s) => s.clone(),
271                v => {
272                    return Err(DkitError::QueryError(format!(
273                        "replace() second argument must be a string, got {}",
274                        value_type_name(v)
275                    )))
276                }
277            };
278            let to = match &args[2] {
279                Value::String(s) => s.clone(),
280                v => {
281                    return Err(DkitError::QueryError(format!(
282                        "replace() third argument must be a string, got {}",
283                        value_type_name(v)
284                    )))
285                }
286            };
287            Ok(Value::String(s.replace(&*from, &to)))
288        }
289        "split" => {
290            if args.len() != 2 {
291                return Err(DkitError::QueryError(format!(
292                    "split() takes 2 arguments (string, separator), got {}",
293                    args.len()
294                )));
295            }
296            let s = match &args[0] {
297                Value::String(s) => s.clone(),
298                Value::Null => return Ok(Value::Array(vec![])),
299                v => {
300                    return Err(DkitError::QueryError(format!(
301                        "split() first argument must be a string, got {}",
302                        value_type_name(v)
303                    )))
304                }
305            };
306            let sep = match &args[1] {
307                Value::String(s) => s.clone(),
308                v => {
309                    return Err(DkitError::QueryError(format!(
310                        "split() second argument must be a string, got {}",
311                        value_type_name(v)
312                    )))
313                }
314            };
315            Ok(Value::Array(
316                s.split(&*sep)
317                    .map(|p| Value::String(p.to_string()))
318                    .collect(),
319            ))
320        }
321
322        "index_of" => {
323            if args.len() != 2 {
324                return Err(DkitError::QueryError(format!(
325                    "index_of() takes 2 arguments (string, substr), got {}",
326                    args.len()
327                )));
328            }
329            let s = match &args[0] {
330                Value::String(s) => s.clone(),
331                Value::Null => return Ok(Value::Integer(-1)),
332                v => {
333                    return Err(DkitError::QueryError(format!(
334                        "index_of() first argument must be a string, got {}",
335                        value_type_name(v)
336                    )))
337                }
338            };
339            let substr = match &args[1] {
340                Value::String(s) => s.clone(),
341                v => {
342                    return Err(DkitError::QueryError(format!(
343                        "index_of() second argument must be a string, got {}",
344                        value_type_name(v)
345                    )))
346                }
347            };
348            match s.find(&*substr) {
349                Some(pos) => {
350                    let char_pos = s[..pos].chars().count() as i64;
351                    Ok(Value::Integer(char_pos))
352                }
353                None => Ok(Value::Integer(-1)),
354            }
355        }
356        "rindex_of" => {
357            if args.len() != 2 {
358                return Err(DkitError::QueryError(format!(
359                    "rindex_of() takes 2 arguments (string, substr), got {}",
360                    args.len()
361                )));
362            }
363            let s = match &args[0] {
364                Value::String(s) => s.clone(),
365                Value::Null => return Ok(Value::Integer(-1)),
366                v => {
367                    return Err(DkitError::QueryError(format!(
368                        "rindex_of() first argument must be a string, got {}",
369                        value_type_name(v)
370                    )))
371                }
372            };
373            let substr = match &args[1] {
374                Value::String(s) => s.clone(),
375                v => {
376                    return Err(DkitError::QueryError(format!(
377                        "rindex_of() second argument must be a string, got {}",
378                        value_type_name(v)
379                    )))
380                }
381            };
382            match s.rfind(&*substr) {
383                Some(pos) => {
384                    let char_pos = s[..pos].chars().count() as i64;
385                    Ok(Value::Integer(char_pos))
386                }
387                None => Ok(Value::Integer(-1)),
388            }
389        }
390        "starts_with" => {
391            if args.len() != 2 {
392                return Err(DkitError::QueryError(format!(
393                    "starts_with() takes 2 arguments (string, prefix), got {}",
394                    args.len()
395                )));
396            }
397            let s = match &args[0] {
398                Value::String(s) => s.clone(),
399                Value::Null => return Ok(Value::Bool(false)),
400                v => {
401                    return Err(DkitError::QueryError(format!(
402                        "starts_with() first argument must be a string, got {}",
403                        value_type_name(v)
404                    )))
405                }
406            };
407            let prefix = match &args[1] {
408                Value::String(s) => s.clone(),
409                v => {
410                    return Err(DkitError::QueryError(format!(
411                        "starts_with() second argument must be a string, got {}",
412                        value_type_name(v)
413                    )))
414                }
415            };
416            Ok(Value::Bool(s.starts_with(&*prefix)))
417        }
418        "ends_with" => {
419            if args.len() != 2 {
420                return Err(DkitError::QueryError(format!(
421                    "ends_with() takes 2 arguments (string, suffix), got {}",
422                    args.len()
423                )));
424            }
425            let s = match &args[0] {
426                Value::String(s) => s.clone(),
427                Value::Null => return Ok(Value::Bool(false)),
428                v => {
429                    return Err(DkitError::QueryError(format!(
430                        "ends_with() first argument must be a string, got {}",
431                        value_type_name(v)
432                    )))
433                }
434            };
435            let suffix = match &args[1] {
436                Value::String(s) => s.clone(),
437                v => {
438                    return Err(DkitError::QueryError(format!(
439                        "ends_with() second argument must be a string, got {}",
440                        value_type_name(v)
441                    )))
442                }
443            };
444            Ok(Value::Bool(s.ends_with(&*suffix)))
445        }
446        "reverse" => {
447            let s = require_one_string(name, &args)?;
448            Ok(Value::String(s.chars().rev().collect()))
449        }
450        "repeat" => {
451            if args.len() != 2 {
452                return Err(DkitError::QueryError(format!(
453                    "repeat() takes 2 arguments (string, count), got {}",
454                    args.len()
455                )));
456            }
457            let s = match &args[0] {
458                Value::String(s) => s.clone(),
459                Value::Null => return Ok(Value::Null),
460                v => {
461                    return Err(DkitError::QueryError(format!(
462                        "repeat() first argument must be a string, got {}",
463                        value_type_name(v)
464                    )))
465                }
466            };
467            let n = require_integer_arg("repeat", &args[1], "count")?;
468            if n < 0 {
469                return Err(DkitError::QueryError(
470                    "repeat() count must be non-negative".to_string(),
471                ));
472            }
473            Ok(Value::String(s.repeat(n as usize)))
474        }
475        "pad_left" => {
476            if args.len() != 3 {
477                return Err(DkitError::QueryError(format!(
478                    "pad_left() takes 3 arguments (string, width, char), got {}",
479                    args.len()
480                )));
481            }
482            let s = match &args[0] {
483                Value::String(s) => s.clone(),
484                Value::Null => return Ok(Value::Null),
485                v => {
486                    return Err(DkitError::QueryError(format!(
487                        "pad_left() first argument must be a string, got {}",
488                        value_type_name(v)
489                    )))
490                }
491            };
492            let width = require_integer_arg("pad_left", &args[1], "width")? as usize;
493            let pad_char = match &args[2] {
494                Value::String(s) => {
495                    let mut chars = s.chars();
496                    match (chars.next(), chars.next()) {
497                        (Some(c), None) => c,
498                        _ => {
499                            return Err(DkitError::QueryError(
500                                "pad_left() third argument must be a single character".to_string(),
501                            ))
502                        }
503                    }
504                }
505                v => {
506                    return Err(DkitError::QueryError(format!(
507                        "pad_left() third argument must be a string, got {}",
508                        value_type_name(v)
509                    )))
510                }
511            };
512            let char_count = s.chars().count();
513            if char_count >= width {
514                Ok(Value::String(s))
515            } else {
516                let padding: String = std::iter::repeat(pad_char)
517                    .take(width - char_count)
518                    .collect();
519                Ok(Value::String(format!("{}{}", padding, s)))
520            }
521        }
522        "pad_right" => {
523            if args.len() != 3 {
524                return Err(DkitError::QueryError(format!(
525                    "pad_right() takes 3 arguments (string, width, char), got {}",
526                    args.len()
527                )));
528            }
529            let s = match &args[0] {
530                Value::String(s) => s.clone(),
531                Value::Null => return Ok(Value::Null),
532                v => {
533                    return Err(DkitError::QueryError(format!(
534                        "pad_right() first argument must be a string, got {}",
535                        value_type_name(v)
536                    )))
537                }
538            };
539            let width = require_integer_arg("pad_right", &args[1], "width")? as usize;
540            let pad_char = match &args[2] {
541                Value::String(s) => {
542                    let mut chars = s.chars();
543                    match (chars.next(), chars.next()) {
544                        (Some(c), None) => c,
545                        _ => {
546                            return Err(DkitError::QueryError(
547                                "pad_right() third argument must be a single character".to_string(),
548                            ))
549                        }
550                    }
551                }
552                v => {
553                    return Err(DkitError::QueryError(format!(
554                        "pad_right() third argument must be a string, got {}",
555                        value_type_name(v)
556                    )))
557                }
558            };
559            let char_count = s.chars().count();
560            if char_count >= width {
561                Ok(Value::String(s))
562            } else {
563                let padding: String = std::iter::repeat(pad_char)
564                    .take(width - char_count)
565                    .collect();
566                Ok(Value::String(format!("{}{}", s, padding)))
567            }
568        }
569
570        // --- 수학 함수 ---
571        "round" => {
572            let n = require_numeric_arg(name, &args)?;
573            if args.len() == 2 {
574                let decimals = require_integer_arg("round", &args[1], "decimals")?;
575                let factor = 10_f64.powi(decimals as i32);
576                Ok(Value::Float((n * factor).round() / factor))
577            } else if args.len() == 1 {
578                Ok(Value::Integer(n.round() as i64))
579            } else {
580                Err(DkitError::QueryError(format!(
581                    "round() takes 1 or 2 arguments, got {}",
582                    args.len()
583                )))
584            }
585        }
586        "ceil" => {
587            let n = require_numeric_arg(name, &args)?;
588            Ok(Value::Integer(n.ceil() as i64))
589        }
590        "floor" => {
591            let n = require_numeric_arg(name, &args)?;
592            Ok(Value::Integer(n.floor() as i64))
593        }
594        "abs" => match args.as_slice() {
595            [Value::Integer(n)] => Ok(Value::Integer(n.abs())),
596            [Value::Float(f)] => Ok(Value::Float(f.abs())),
597            [Value::Null] => Ok(Value::Null),
598            [v] => Err(DkitError::QueryError(format!(
599                "abs() requires a numeric argument, got {}",
600                value_type_name(v)
601            ))),
602            _ => Err(DkitError::QueryError(format!(
603                "abs() takes 1 argument, got {}",
604                args.len()
605            ))),
606        },
607        "sqrt" => {
608            let n = require_numeric_arg(name, &args)?;
609            if n < 0.0 {
610                return Err(DkitError::QueryError(
611                    "sqrt() requires a non-negative argument".to_string(),
612                ));
613            }
614            Ok(Value::Float(n.sqrt()))
615        }
616        "pow" => {
617            if args.len() != 2 {
618                return Err(DkitError::QueryError(format!(
619                    "pow() takes 2 arguments, got {}",
620                    args.len()
621                )));
622            }
623            let base = require_numeric_arg("pow base", &args[..1])?;
624            let exp = require_numeric_arg("pow exp", &args[1..])?;
625            Ok(Value::Float(base.powf(exp)))
626        }
627
628        // --- 날짜 함수 ---
629        "now" => {
630            if !args.is_empty() {
631                return Err(DkitError::QueryError(
632                    "now() takes no arguments".to_string(),
633                ));
634            }
635            Ok(Value::String(current_datetime_utc()))
636        }
637        "date" => {
638            let s = require_one_string(name, &args)?;
639            // 날짜 파싱 검증 (ISO 8601 형식 기대)
640            let normalized = normalize_date_str(&s)?;
641            Ok(Value::String(normalized))
642        }
643        "year" => {
644            let s = require_one_string(name, &args)?;
645            let y = extract_year(&s)?;
646            Ok(Value::Integer(y))
647        }
648        "month" => {
649            let s = require_one_string(name, &args)?;
650            let m = extract_month(&s)?;
651            Ok(Value::Integer(m))
652        }
653        "day" => {
654            let s = require_one_string(name, &args)?;
655            let d = extract_day(&s)?;
656            Ok(Value::Integer(d))
657        }
658
659        // --- 타입 변환 ---
660        "to_int" | "int" => match args.as_slice() {
661            [Value::Integer(n)] => Ok(Value::Integer(*n)),
662            [Value::Float(f)] => Ok(Value::Integer(*f as i64)),
663            [Value::String(s)] => s.trim().parse::<i64>().map(Value::Integer).map_err(|_| {
664                DkitError::QueryError(format!("to_int(): cannot parse '{}' as integer", s))
665            }),
666            [Value::Bool(b)] => Ok(Value::Integer(if *b { 1 } else { 0 })),
667            [Value::Null] => Ok(Value::Null),
668            [v] => Err(DkitError::QueryError(format!(
669                "to_int() cannot convert {}",
670                value_type_name(v)
671            ))),
672            _ => Err(DkitError::QueryError(format!(
673                "to_int() takes 1 argument, got {}",
674                args.len()
675            ))),
676        },
677        "to_float" | "float" => match args.as_slice() {
678            [Value::Float(f)] => Ok(Value::Float(*f)),
679            [Value::Integer(n)] => Ok(Value::Float(*n as f64)),
680            [Value::String(s)] => s.trim().parse::<f64>().map(Value::Float).map_err(|_| {
681                DkitError::QueryError(format!("to_float(): cannot parse '{}' as float", s))
682            }),
683            [Value::Bool(b)] => Ok(Value::Float(if *b { 1.0 } else { 0.0 })),
684            [Value::Null] => Ok(Value::Null),
685            [v] => Err(DkitError::QueryError(format!(
686                "to_float() cannot convert {}",
687                value_type_name(v)
688            ))),
689            _ => Err(DkitError::QueryError(format!(
690                "to_float() takes 1 argument, got {}",
691                args.len()
692            ))),
693        },
694        "to_string" | "str" => match args.as_slice() {
695            [Value::String(s)] => Ok(Value::String(s.clone())),
696            [Value::Integer(n)] => Ok(Value::String(n.to_string())),
697            [Value::Float(f)] => Ok(Value::String(f.to_string())),
698            [Value::Bool(b)] => Ok(Value::String(b.to_string())),
699            [Value::Null] => Ok(Value::String("null".to_string())),
700            [v] => Ok(Value::String(v.to_string())),
701            _ => Err(DkitError::QueryError(format!(
702                "to_string() takes 1 argument, got {}",
703                args.len()
704            ))),
705        },
706        "to_bool" | "bool" => match args.as_slice() {
707            [Value::Bool(b)] => Ok(Value::Bool(*b)),
708            [Value::Integer(n)] => Ok(Value::Bool(*n != 0)),
709            [Value::Float(f)] => Ok(Value::Bool(*f != 0.0)),
710            [Value::String(s)] => match s.trim().to_lowercase().as_str() {
711                "true" | "yes" | "1" | "on" => Ok(Value::Bool(true)),
712                "false" | "no" | "0" | "off" | "" => Ok(Value::Bool(false)),
713                _ => Err(DkitError::QueryError(format!(
714                    "to_bool(): cannot parse '{}' as boolean",
715                    s
716                ))),
717            },
718            [Value::Null] => Ok(Value::Bool(false)),
719            [v] => Err(DkitError::QueryError(format!(
720                "to_bool() cannot convert {}",
721                value_type_name(v)
722            ))),
723            _ => Err(DkitError::QueryError(format!(
724                "to_bool() takes 1 argument, got {}",
725                args.len()
726            ))),
727        },
728
729        // --- 기타 유틸 ---
730        "coalesce" => {
731            for arg in &args {
732                if !matches!(arg, Value::Null) {
733                    return Ok(arg.clone());
734                }
735            }
736            Ok(Value::Null)
737        }
738        "if_null" => match args.as_slice() {
739            [Value::Null, default] => Ok(default.clone()),
740            [v, _] => Ok(v.clone()),
741            _ => Err(DkitError::QueryError(format!(
742                "if_null() takes 2 arguments, got {}",
743                args.len()
744            ))),
745        },
746
747        _ => Err(DkitError::QueryError(format!(
748            "unknown function '{}'",
749            name
750        ))),
751    }
752}
753
754// --- 헬퍼 함수 ---
755
756fn require_one_string(func: &str, args: &[Value]) -> Result<String, DkitError> {
757    match args {
758        [Value::String(s)] => Ok(s.clone()),
759        [Value::Null] => Err(DkitError::QueryError(format!(
760            "{}() argument is null",
761            func
762        ))),
763        [v] => Err(DkitError::QueryError(format!(
764            "{}() requires a string argument, got {}",
765            func,
766            value_type_name(v)
767        ))),
768        _ => Err(DkitError::QueryError(format!(
769            "{}() takes 1 argument, got {}",
770            func,
771            args.len()
772        ))),
773    }
774}
775
776fn require_numeric_arg(func: &str, args: &[Value]) -> Result<f64, DkitError> {
777    match args.first() {
778        Some(Value::Float(f)) => Ok(*f),
779        Some(Value::Integer(n)) => Ok(*n as f64),
780        Some(Value::Null) => Err(DkitError::QueryError(format!(
781            "{}() argument is null",
782            func
783        ))),
784        Some(v) => Err(DkitError::QueryError(format!(
785            "{}() requires a numeric argument, got {}",
786            func,
787            value_type_name(v)
788        ))),
789        None => Err(DkitError::QueryError(format!(
790            "{}() takes at least 1 argument",
791            func
792        ))),
793    }
794}
795
796fn require_integer_arg(func: &str, val: &Value, param: &str) -> Result<i64, DkitError> {
797    match val {
798        Value::Integer(n) => Ok(*n),
799        Value::Float(f) => Ok(*f as i64),
800        v => Err(DkitError::QueryError(format!(
801            "{}() '{}' argument must be an integer, got {}",
802            func,
803            param,
804            value_type_name(v)
805        ))),
806    }
807}
808
809fn value_type_name(v: &Value) -> &'static str {
810    match v {
811        Value::Null => "null",
812        Value::Bool(_) => "bool",
813        Value::Integer(_) => "integer",
814        Value::Float(_) => "float",
815        Value::String(_) => "string",
816        Value::Array(_) => "array",
817        Value::Object(_) => "object",
818    }
819}
820
821// --- 날짜 헬퍼 ---
822
823/// ISO 8601 날짜 문자열에서 연도 추출 (예: "2024-03-15" → 2024)
824fn extract_year(s: &str) -> Result<i64, DkitError> {
825    let date_part = s.split('T').next().unwrap_or(s);
826    let parts: Vec<&str> = date_part.split('-').collect();
827    if parts.is_empty() {
828        return Err(DkitError::QueryError(format!(
829            "year(): cannot parse date from '{}'",
830            s
831        )));
832    }
833    parts[0]
834        .parse::<i64>()
835        .map_err(|_| DkitError::QueryError(format!("year(): cannot parse year from '{}'", s)))
836}
837
838/// ISO 8601 날짜 문자열에서 월 추출 (1-12)
839fn extract_month(s: &str) -> Result<i64, DkitError> {
840    let date_part = s.split('T').next().unwrap_or(s);
841    let parts: Vec<&str> = date_part.split('-').collect();
842    if parts.len() < 2 {
843        return Err(DkitError::QueryError(format!(
844            "month(): cannot parse month from '{}'",
845            s
846        )));
847    }
848    parts[1]
849        .parse::<i64>()
850        .map_err(|_| DkitError::QueryError(format!("month(): cannot parse month from '{}'", s)))
851}
852
853/// ISO 8601 날짜 문자열에서 일 추출 (1-31)
854fn extract_day(s: &str) -> Result<i64, DkitError> {
855    let date_part = s.split('T').next().unwrap_or(s);
856    let parts: Vec<&str> = date_part.split('-').collect();
857    if parts.len() < 3 {
858        return Err(DkitError::QueryError(format!(
859            "day(): cannot parse day from '{}'",
860            s
861        )));
862    }
863    // Day part may have trailing time info
864    let day_str = parts[2].split(&['T', ' '][..]).next().unwrap_or(parts[2]);
865    day_str
866        .parse::<i64>()
867        .map_err(|_| DkitError::QueryError(format!("day(): cannot parse day from '{}'", s)))
868}
869
870/// 날짜 문자열 정규화 (yyyy-MM-dd 형식 확인)
871fn normalize_date_str(s: &str) -> Result<String, DkitError> {
872    let date_part = s.split('T').next().unwrap_or(s).trim();
873    let parts: Vec<&str> = date_part.split('-').collect();
874    if parts.len() != 3 {
875        return Err(DkitError::QueryError(format!(
876            "date(): expected yyyy-MM-dd format, got '{}'",
877            s
878        )));
879    }
880    let year = parts[0]
881        .parse::<i32>()
882        .map_err(|_| DkitError::QueryError(format!("date(): invalid year in '{}'", s)))?;
883    let month = parts[1]
884        .parse::<u32>()
885        .map_err(|_| DkitError::QueryError(format!("date(): invalid month in '{}'", s)))?;
886    let day = parts[2]
887        .split(&[' ', 'T'][..])
888        .next()
889        .unwrap_or(parts[2])
890        .parse::<u32>()
891        .map_err(|_| DkitError::QueryError(format!("date(): invalid day in '{}'", s)))?;
892    if !(1..=12).contains(&month) {
893        return Err(DkitError::QueryError(format!(
894            "date(): month {} out of range in '{}'",
895            month, s
896        )));
897    }
898    if !(1..=31).contains(&day) {
899        return Err(DkitError::QueryError(format!(
900            "date(): day {} out of range in '{}'",
901            day, s
902        )));
903    }
904    Ok(format!("{:04}-{:02}-{:02}", year, month, day))
905}
906
907/// 현재 UTC 시각을 ISO 8601 문자열로 반환
908fn current_datetime_utc() -> String {
909    use std::time::{SystemTime, UNIX_EPOCH};
910    let secs = SystemTime::now()
911        .duration_since(UNIX_EPOCH)
912        .unwrap_or_default()
913        .as_secs();
914    // 간단한 UTC 변환 (초 → 날짜/시간)
915    let days_since_epoch = secs / 86400;
916    let time_of_day = secs % 86400;
917    let (year, month, day) = days_to_ymd(days_since_epoch);
918    let hour = time_of_day / 3600;
919    let minute = (time_of_day % 3600) / 60;
920    let second = time_of_day % 60;
921    format!(
922        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
923        year, month, day, hour, minute, second
924    )
925}
926
927/// Unix epoch 일수를 (year, month, day)로 변환
928fn days_to_ymd(days: u64) -> (i64, u64, u64) {
929    // Gregorian calendar algorithm
930    let z = days as i64 + 719468;
931    let era = if z >= 0 { z } else { z - 146096 } / 146097;
932    let doe = z - era * 146097;
933    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
934    let y = yoe + era * 400;
935    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
936    let mp = (5 * doy + 2) / 153;
937    let d = doy - (153 * mp + 2) / 5 + 1;
938    let m = if mp < 10 { mp + 3 } else { mp - 9 };
939    let y = if m <= 2 { y + 1 } else { y };
940    (y, m as u64, d as u64)
941}
942
943#[cfg(test)]
944mod tests {
945    use super::*;
946    use crate::query::parser::Expr;
947    use indexmap::IndexMap;
948
949    fn obj(fields: &[(&str, Value)]) -> Value {
950        let mut map = IndexMap::new();
951        for (k, v) in fields {
952            map.insert(k.to_string(), v.clone());
953        }
954        Value::Object(map)
955    }
956
957    fn eval(row: &Value, expr: &Expr) -> Result<Value, DkitError> {
958        evaluate_expr(row, expr)
959    }
960
961    fn func(name: &str, args: Vec<Expr>) -> Expr {
962        Expr::FuncCall {
963            name: name.to_string(),
964            args,
965        }
966    }
967
968    fn field(name: &str) -> Expr {
969        Expr::Field(name.to_string())
970    }
971
972    fn lit_str(s: &str) -> Expr {
973        Expr::Literal(LiteralValue::String(s.to_string()))
974    }
975
976    fn lit_int(n: i64) -> Expr {
977        Expr::Literal(LiteralValue::Integer(n))
978    }
979
980    // --- 문자열 함수 ---
981
982    #[test]
983    fn test_upper() {
984        let row = obj(&[("name", Value::String("hello".to_string()))]);
985        let result = eval(&row, &func("upper", vec![field("name")])).unwrap();
986        assert_eq!(result, Value::String("HELLO".to_string()));
987    }
988
989    #[test]
990    fn test_lower() {
991        let row = obj(&[("name", Value::String("WORLD".to_string()))]);
992        let result = eval(&row, &func("lower", vec![field("name")])).unwrap();
993        assert_eq!(result, Value::String("world".to_string()));
994    }
995
996    #[test]
997    fn test_trim() {
998        let row = obj(&[("name", Value::String("  hello  ".to_string()))]);
999        let result = eval(&row, &func("trim", vec![field("name")])).unwrap();
1000        assert_eq!(result, Value::String("hello".to_string()));
1001    }
1002
1003    #[test]
1004    fn test_length_string() {
1005        let row = obj(&[("name", Value::String("hello".to_string()))]);
1006        let result = eval(&row, &func("length", vec![field("name")])).unwrap();
1007        assert_eq!(result, Value::Integer(5));
1008    }
1009
1010    #[test]
1011    fn test_substr() {
1012        let row = obj(&[("name", Value::String("hello world".to_string()))]);
1013        let result = eval(
1014            &row,
1015            &func("substr", vec![field("name"), lit_int(0), lit_int(5)]),
1016        )
1017        .unwrap();
1018        assert_eq!(result, Value::String("hello".to_string()));
1019    }
1020
1021    #[test]
1022    fn test_concat() {
1023        let row = obj(&[
1024            ("first", Value::String("hello".to_string())),
1025            ("last", Value::String(" world".to_string())),
1026        ]);
1027        let result = eval(&row, &func("concat", vec![field("first"), field("last")])).unwrap();
1028        assert_eq!(result, Value::String("hello world".to_string()));
1029    }
1030
1031    #[test]
1032    fn test_replace() {
1033        let row = obj(&[("name", Value::String("hello world".to_string()))]);
1034        let result = eval(
1035            &row,
1036            &func(
1037                "replace",
1038                vec![field("name"), lit_str("world"), lit_str("dkit")],
1039            ),
1040        )
1041        .unwrap();
1042        assert_eq!(result, Value::String("hello dkit".to_string()));
1043    }
1044
1045    #[test]
1046    fn test_index_of() {
1047        let row = obj(&[("email", Value::String("user@example.com".to_string()))]);
1048        let result = eval(&row, &func("index_of", vec![field("email"), lit_str("@")])).unwrap();
1049        assert_eq!(result, Value::Integer(4));
1050    }
1051
1052    #[test]
1053    fn test_index_of_not_found() {
1054        let row = obj(&[("s", Value::String("hello".to_string()))]);
1055        let result = eval(&row, &func("index_of", vec![field("s"), lit_str("xyz")])).unwrap();
1056        assert_eq!(result, Value::Integer(-1));
1057    }
1058
1059    #[test]
1060    fn test_rindex_of() {
1061        let row = obj(&[("s", Value::String("abcabc".to_string()))]);
1062        let result = eval(&row, &func("rindex_of", vec![field("s"), lit_str("abc")])).unwrap();
1063        assert_eq!(result, Value::Integer(3));
1064    }
1065
1066    #[test]
1067    fn test_rindex_of_not_found() {
1068        let row = obj(&[("s", Value::String("hello".to_string()))]);
1069        let result = eval(&row, &func("rindex_of", vec![field("s"), lit_str("xyz")])).unwrap();
1070        assert_eq!(result, Value::Integer(-1));
1071    }
1072
1073    #[test]
1074    fn test_starts_with() {
1075        let row = obj(&[("name", Value::String("Dr. Smith".to_string()))]);
1076        let result = eval(
1077            &row,
1078            &func("starts_with", vec![field("name"), lit_str("Dr.")]),
1079        )
1080        .unwrap();
1081        assert_eq!(result, Value::Bool(true));
1082    }
1083
1084    #[test]
1085    fn test_starts_with_false() {
1086        let row = obj(&[("name", Value::String("Mr. Smith".to_string()))]);
1087        let result = eval(
1088            &row,
1089            &func("starts_with", vec![field("name"), lit_str("Dr.")]),
1090        )
1091        .unwrap();
1092        assert_eq!(result, Value::Bool(false));
1093    }
1094
1095    #[test]
1096    fn test_ends_with() {
1097        let row = obj(&[("file", Value::String("data.json".to_string()))]);
1098        let result = eval(
1099            &row,
1100            &func("ends_with", vec![field("file"), lit_str(".json")]),
1101        )
1102        .unwrap();
1103        assert_eq!(result, Value::Bool(true));
1104    }
1105
1106    #[test]
1107    fn test_ends_with_false() {
1108        let row = obj(&[("file", Value::String("data.csv".to_string()))]);
1109        let result = eval(
1110            &row,
1111            &func("ends_with", vec![field("file"), lit_str(".json")]),
1112        )
1113        .unwrap();
1114        assert_eq!(result, Value::Bool(false));
1115    }
1116
1117    #[test]
1118    fn test_reverse() {
1119        let row = obj(&[("s", Value::String("hello".to_string()))]);
1120        let result = eval(&row, &func("reverse", vec![field("s")])).unwrap();
1121        assert_eq!(result, Value::String("olleh".to_string()));
1122    }
1123
1124    #[test]
1125    fn test_repeat() {
1126        let row = obj(&[("s", Value::String("ab".to_string()))]);
1127        let result = eval(&row, &func("repeat", vec![field("s"), lit_int(3)])).unwrap();
1128        assert_eq!(result, Value::String("ababab".to_string()));
1129    }
1130
1131    #[test]
1132    fn test_repeat_zero() {
1133        let row = obj(&[("s", Value::String("ab".to_string()))]);
1134        let result = eval(&row, &func("repeat", vec![field("s"), lit_int(0)])).unwrap();
1135        assert_eq!(result, Value::String(String::new()));
1136    }
1137
1138    #[test]
1139    fn test_pad_left() {
1140        let row = obj(&[("id", Value::String("42".to_string()))]);
1141        let result = eval(
1142            &row,
1143            &func("pad_left", vec![field("id"), lit_int(5), lit_str("0")]),
1144        )
1145        .unwrap();
1146        assert_eq!(result, Value::String("00042".to_string()));
1147    }
1148
1149    #[test]
1150    fn test_pad_left_no_padding_needed() {
1151        let row = obj(&[("id", Value::String("12345".to_string()))]);
1152        let result = eval(
1153            &row,
1154            &func("pad_left", vec![field("id"), lit_int(3), lit_str("0")]),
1155        )
1156        .unwrap();
1157        assert_eq!(result, Value::String("12345".to_string()));
1158    }
1159
1160    #[test]
1161    fn test_pad_right() {
1162        let row = obj(&[("s", Value::String("hi".to_string()))]);
1163        let result = eval(
1164            &row,
1165            &func("pad_right", vec![field("s"), lit_int(5), lit_str(".")]),
1166        )
1167        .unwrap();
1168        assert_eq!(result, Value::String("hi...".to_string()));
1169    }
1170
1171    #[test]
1172    fn test_pad_right_no_padding_needed() {
1173        let row = obj(&[("s", Value::String("hello".to_string()))]);
1174        let result = eval(
1175            &row,
1176            &func("pad_right", vec![field("s"), lit_int(3), lit_str(".")]),
1177        )
1178        .unwrap();
1179        assert_eq!(result, Value::String("hello".to_string()));
1180    }
1181
1182    // --- 수학 함수 ---
1183
1184    #[test]
1185    fn test_round_no_decimals() {
1186        let row = obj(&[("price", Value::Float(3.7))]);
1187        let result = eval(&row, &func("round", vec![field("price")])).unwrap();
1188        assert_eq!(result, Value::Integer(4));
1189    }
1190
1191    #[test]
1192    fn test_round_with_decimals() {
1193        let row = obj(&[("price", Value::Float(3.14159))]);
1194        let result = eval(&row, &func("round", vec![field("price"), lit_int(2)])).unwrap();
1195        match result {
1196            Value::Float(v) => assert!((v - 3.14).abs() < 1e-10, "expected ~3.14, got {v}"),
1197            other => panic!("expected Float, got {other:?}"),
1198        }
1199    }
1200
1201    #[test]
1202    fn test_ceil() {
1203        let row = obj(&[("price", Value::Float(3.2))]);
1204        let result = eval(&row, &func("ceil", vec![field("price")])).unwrap();
1205        assert_eq!(result, Value::Integer(4));
1206    }
1207
1208    #[test]
1209    fn test_floor() {
1210        let row = obj(&[("price", Value::Float(3.9))]);
1211        let result = eval(&row, &func("floor", vec![field("price")])).unwrap();
1212        assert_eq!(result, Value::Integer(3));
1213    }
1214
1215    #[test]
1216    fn test_abs_negative() {
1217        let row = obj(&[("score", Value::Integer(-5))]);
1218        let result = eval(&row, &func("abs", vec![field("score")])).unwrap();
1219        assert_eq!(result, Value::Integer(5));
1220    }
1221
1222    // --- 날짜 함수 ---
1223
1224    #[test]
1225    fn test_year() {
1226        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
1227        let result = eval(&row, &func("year", vec![field("date")])).unwrap();
1228        assert_eq!(result, Value::Integer(2024));
1229    }
1230
1231    #[test]
1232    fn test_month() {
1233        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
1234        let result = eval(&row, &func("month", vec![field("date")])).unwrap();
1235        assert_eq!(result, Value::Integer(3));
1236    }
1237
1238    #[test]
1239    fn test_day() {
1240        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
1241        let result = eval(&row, &func("day", vec![field("date")])).unwrap();
1242        assert_eq!(result, Value::Integer(15));
1243    }
1244
1245    #[test]
1246    fn test_date_normalize() {
1247        let row = obj(&[("d", Value::String("2024-3-5".to_string()))]);
1248        let result = eval(&row, &func("date", vec![field("d")])).unwrap();
1249        assert_eq!(result, Value::String("2024-03-05".to_string()));
1250    }
1251
1252    #[test]
1253    fn test_now_returns_string() {
1254        let row = obj(&[]);
1255        let result = eval(&row, &func("now", vec![])).unwrap();
1256        assert!(matches!(result, Value::String(_)));
1257        if let Value::String(s) = result {
1258            assert!(s.contains('T'), "now() should return ISO 8601 format");
1259        }
1260    }
1261
1262    // --- 타입 변환 ---
1263
1264    #[test]
1265    fn test_to_int_from_string() {
1266        let row = obj(&[("n", Value::String("42".to_string()))]);
1267        let result = eval(&row, &func("to_int", vec![field("n")])).unwrap();
1268        assert_eq!(result, Value::Integer(42));
1269    }
1270
1271    #[test]
1272    fn test_to_float_from_int() {
1273        let row = obj(&[("n", Value::Integer(5))]);
1274        let result = eval(&row, &func("to_float", vec![field("n")])).unwrap();
1275        assert_eq!(result, Value::Float(5.0));
1276    }
1277
1278    #[test]
1279    fn test_to_string_from_int() {
1280        let row = obj(&[("n", Value::Integer(42))]);
1281        let result = eval(&row, &func("to_string", vec![field("n")])).unwrap();
1282        assert_eq!(result, Value::String("42".to_string()));
1283    }
1284
1285    #[test]
1286    fn test_to_bool_from_string() {
1287        let row = obj(&[("flag", Value::String("true".to_string()))]);
1288        let result = eval(&row, &func("to_bool", vec![field("flag")])).unwrap();
1289        assert_eq!(result, Value::Bool(true));
1290    }
1291
1292    // --- 중첩 함수 호출 ---
1293
1294    #[test]
1295    fn test_nested_upper_trim() {
1296        let row = obj(&[("name", Value::String("  hello  ".to_string()))]);
1297        let result = eval(
1298            &row,
1299            &func("upper", vec![func("trim", vec![field("name")])]),
1300        )
1301        .unwrap();
1302        assert_eq!(result, Value::String("HELLO".to_string()));
1303    }
1304
1305    // --- expr_default_key ---
1306
1307    #[test]
1308    fn test_default_key_field() {
1309        assert_eq!(expr_default_key(&Expr::Field("name".to_string())), "name");
1310    }
1311
1312    #[test]
1313    fn test_default_key_func() {
1314        let expr = Expr::FuncCall {
1315            name: "upper".to_string(),
1316            args: vec![Expr::Field("name".to_string())],
1317        };
1318        assert_eq!(expr_default_key(&expr), "upper_name");
1319    }
1320
1321    #[test]
1322    fn test_default_key_nested_func() {
1323        let expr = Expr::FuncCall {
1324            name: "upper".to_string(),
1325            args: vec![Expr::FuncCall {
1326                name: "trim".to_string(),
1327                args: vec![Expr::Field("name".to_string())],
1328            }],
1329        };
1330        assert_eq!(expr_default_key(&expr), "upper_trim_name");
1331    }
1332
1333    // --- BinaryOp evaluate tests ---
1334
1335    #[test]
1336    fn test_binary_add_integers() {
1337        let row = Value::Object(indexmap::IndexMap::from([
1338            ("a".to_string(), Value::Integer(10)),
1339            ("b".to_string(), Value::Integer(20)),
1340        ]));
1341        let expr = Expr::BinaryOp {
1342            op: ArithmeticOp::Add,
1343            left: Box::new(Expr::Field("a".to_string())),
1344            right: Box::new(Expr::Field("b".to_string())),
1345        };
1346        assert_eq!(evaluate_expr(&row, &expr).unwrap(), Value::Integer(30));
1347    }
1348
1349    #[test]
1350    fn test_binary_mul_float() {
1351        let row = Value::Object(indexmap::IndexMap::from([(
1352            "price".to_string(),
1353            Value::Float(100.0),
1354        )]));
1355        let expr = Expr::BinaryOp {
1356            op: ArithmeticOp::Mul,
1357            left: Box::new(Expr::Field("price".to_string())),
1358            right: Box::new(Expr::Literal(LiteralValue::Float(0.1))),
1359        };
1360        let result = evaluate_expr(&row, &expr).unwrap();
1361        if let Value::Float(f) = result {
1362            assert!((f - 10.0).abs() < 0.001);
1363        } else {
1364            panic!("expected float");
1365        }
1366    }
1367
1368    #[test]
1369    fn test_binary_string_concat() {
1370        let row = Value::Object(indexmap::IndexMap::from([
1371            ("first".to_string(), Value::String("Hello".to_string())),
1372            ("last".to_string(), Value::String("World".to_string())),
1373        ]));
1374        let expr = Expr::BinaryOp {
1375            op: ArithmeticOp::Add,
1376            left: Box::new(Expr::Field("first".to_string())),
1377            right: Box::new(Expr::BinaryOp {
1378                op: ArithmeticOp::Add,
1379                left: Box::new(Expr::Literal(LiteralValue::String(" ".to_string()))),
1380                right: Box::new(Expr::Field("last".to_string())),
1381            }),
1382        };
1383        assert_eq!(
1384            evaluate_expr(&row, &expr).unwrap(),
1385            Value::String("Hello World".to_string())
1386        );
1387    }
1388
1389    #[test]
1390    fn test_binary_div_by_zero() {
1391        let row = Value::Object(indexmap::IndexMap::from([
1392            ("a".to_string(), Value::Integer(10)),
1393            ("b".to_string(), Value::Integer(0)),
1394        ]));
1395        let expr = Expr::BinaryOp {
1396            op: ArithmeticOp::Div,
1397            left: Box::new(Expr::Field("a".to_string())),
1398            right: Box::new(Expr::Field("b".to_string())),
1399        };
1400        assert!(evaluate_expr(&row, &expr).is_err());
1401    }
1402
1403    #[test]
1404    fn test_binary_null_propagation() {
1405        let row = Value::Object(indexmap::IndexMap::from([
1406            ("a".to_string(), Value::Integer(10)),
1407            ("b".to_string(), Value::Null),
1408        ]));
1409        let expr = Expr::BinaryOp {
1410            op: ArithmeticOp::Add,
1411            left: Box::new(Expr::Field("a".to_string())),
1412            right: Box::new(Expr::Field("b".to_string())),
1413        };
1414        assert_eq!(evaluate_expr(&row, &expr).unwrap(), Value::Null);
1415    }
1416
1417    #[test]
1418    fn test_binary_int_div_non_exact() {
1419        let row = Value::Object(indexmap::IndexMap::from([
1420            ("a".to_string(), Value::Integer(10)),
1421            ("b".to_string(), Value::Integer(3)),
1422        ]));
1423        let expr = Expr::BinaryOp {
1424            op: ArithmeticOp::Div,
1425            left: Box::new(Expr::Field("a".to_string())),
1426            right: Box::new(Expr::Field("b".to_string())),
1427        };
1428        // 10 / 3 is not exact, should return float
1429        let result = evaluate_expr(&row, &expr).unwrap();
1430        assert!(matches!(result, Value::Float(_)));
1431    }
1432
1433    #[test]
1434    fn test_binary_mixed_string_number_concat() {
1435        let row = Value::Object(indexmap::IndexMap::from([
1436            ("name".to_string(), Value::String("Item".to_string())),
1437            ("id".to_string(), Value::Integer(42)),
1438        ]));
1439        let expr = Expr::BinaryOp {
1440            op: ArithmeticOp::Add,
1441            left: Box::new(Expr::Field("name".to_string())),
1442            right: Box::new(Expr::Field("id".to_string())),
1443        };
1444        assert_eq!(
1445            evaluate_expr(&row, &expr).unwrap(),
1446            Value::String("Item42".to_string())
1447        );
1448    }
1449
1450    // --- if() expression evaluation tests ---
1451
1452    #[test]
1453    fn test_eval_if_true_branch() {
1454        use crate::query::parser::{CompareOp, Comparison, Condition};
1455        let row = Value::Object(
1456            vec![
1457                ("age".to_string(), Value::Integer(15)),
1458                ("name".to_string(), Value::String("Alice".to_string())),
1459            ]
1460            .into_iter()
1461            .collect(),
1462        );
1463        let expr = Expr::If {
1464            condition: Condition::Comparison(Comparison {
1465                field: "age".to_string(),
1466                op: CompareOp::Lt,
1467                value: LiteralValue::Integer(18),
1468            }),
1469            then_expr: Box::new(Expr::Literal(LiteralValue::String("minor".to_string()))),
1470            else_expr: Box::new(Expr::Literal(LiteralValue::String("adult".to_string()))),
1471        };
1472        assert_eq!(
1473            evaluate_expr(&row, &expr).unwrap(),
1474            Value::String("minor".to_string())
1475        );
1476    }
1477
1478    #[test]
1479    fn test_eval_if_false_branch() {
1480        use crate::query::parser::{CompareOp, Comparison, Condition};
1481        let row = Value::Object(
1482            vec![("age".to_string(), Value::Integer(30))]
1483                .into_iter()
1484                .collect(),
1485        );
1486        let expr = Expr::If {
1487            condition: Condition::Comparison(Comparison {
1488                field: "age".to_string(),
1489                op: CompareOp::Lt,
1490                value: LiteralValue::Integer(18),
1491            }),
1492            then_expr: Box::new(Expr::Literal(LiteralValue::String("minor".to_string()))),
1493            else_expr: Box::new(Expr::Literal(LiteralValue::String("adult".to_string()))),
1494        };
1495        assert_eq!(
1496            evaluate_expr(&row, &expr).unwrap(),
1497            Value::String("adult".to_string())
1498        );
1499    }
1500
1501    #[test]
1502    fn test_eval_if_nested() {
1503        use crate::query::parser::{CompareOp, Comparison, Condition};
1504        let row = Value::Object(
1505            vec![("age".to_string(), Value::Integer(70))]
1506                .into_iter()
1507                .collect(),
1508        );
1509        let expr = Expr::If {
1510            condition: Condition::Comparison(Comparison {
1511                field: "age".to_string(),
1512                op: CompareOp::Lt,
1513                value: LiteralValue::Integer(18),
1514            }),
1515            then_expr: Box::new(Expr::Literal(LiteralValue::String("minor".to_string()))),
1516            else_expr: Box::new(Expr::If {
1517                condition: Condition::Comparison(Comparison {
1518                    field: "age".to_string(),
1519                    op: CompareOp::Lt,
1520                    value: LiteralValue::Integer(65),
1521                }),
1522                then_expr: Box::new(Expr::Literal(LiteralValue::String("adult".to_string()))),
1523                else_expr: Box::new(Expr::Literal(LiteralValue::String("senior".to_string()))),
1524            }),
1525        };
1526        assert_eq!(
1527            evaluate_expr(&row, &expr).unwrap(),
1528            Value::String("senior".to_string())
1529        );
1530    }
1531
1532    // --- case/when expression evaluation tests ---
1533
1534    #[test]
1535    fn test_eval_case_first_branch() {
1536        use crate::query::parser::{CompareOp, Comparison, Condition};
1537        let row = Value::Object(
1538            vec![("status".to_string(), Value::String("active".to_string()))]
1539                .into_iter()
1540                .collect(),
1541        );
1542        let expr = Expr::Case {
1543            branches: vec![
1544                (
1545                    Condition::Comparison(Comparison {
1546                        field: "status".to_string(),
1547                        op: CompareOp::Eq,
1548                        value: LiteralValue::String("active".to_string()),
1549                    }),
1550                    Expr::Literal(LiteralValue::String("A".to_string())),
1551                ),
1552                (
1553                    Condition::Comparison(Comparison {
1554                        field: "status".to_string(),
1555                        op: CompareOp::Eq,
1556                        value: LiteralValue::String("inactive".to_string()),
1557                    }),
1558                    Expr::Literal(LiteralValue::String("I".to_string())),
1559                ),
1560            ],
1561            default: Some(Box::new(Expr::Literal(LiteralValue::String(
1562                "U".to_string(),
1563            )))),
1564        };
1565        assert_eq!(
1566            evaluate_expr(&row, &expr).unwrap(),
1567            Value::String("A".to_string())
1568        );
1569    }
1570
1571    #[test]
1572    fn test_eval_case_second_branch() {
1573        use crate::query::parser::{CompareOp, Comparison, Condition};
1574        let row = Value::Object(
1575            vec![("status".to_string(), Value::String("inactive".to_string()))]
1576                .into_iter()
1577                .collect(),
1578        );
1579        let expr = Expr::Case {
1580            branches: vec![
1581                (
1582                    Condition::Comparison(Comparison {
1583                        field: "status".to_string(),
1584                        op: CompareOp::Eq,
1585                        value: LiteralValue::String("active".to_string()),
1586                    }),
1587                    Expr::Literal(LiteralValue::String("A".to_string())),
1588                ),
1589                (
1590                    Condition::Comparison(Comparison {
1591                        field: "status".to_string(),
1592                        op: CompareOp::Eq,
1593                        value: LiteralValue::String("inactive".to_string()),
1594                    }),
1595                    Expr::Literal(LiteralValue::String("I".to_string())),
1596                ),
1597            ],
1598            default: Some(Box::new(Expr::Literal(LiteralValue::String(
1599                "U".to_string(),
1600            )))),
1601        };
1602        assert_eq!(
1603            evaluate_expr(&row, &expr).unwrap(),
1604            Value::String("I".to_string())
1605        );
1606    }
1607
1608    #[test]
1609    fn test_eval_case_default() {
1610        use crate::query::parser::{CompareOp, Comparison, Condition};
1611        let row = Value::Object(
1612            vec![("status".to_string(), Value::String("pending".to_string()))]
1613                .into_iter()
1614                .collect(),
1615        );
1616        let expr = Expr::Case {
1617            branches: vec![(
1618                Condition::Comparison(Comparison {
1619                    field: "status".to_string(),
1620                    op: CompareOp::Eq,
1621                    value: LiteralValue::String("active".to_string()),
1622                }),
1623                Expr::Literal(LiteralValue::String("A".to_string())),
1624            )],
1625            default: Some(Box::new(Expr::Literal(LiteralValue::String(
1626                "other".to_string(),
1627            )))),
1628        };
1629        assert_eq!(
1630            evaluate_expr(&row, &expr).unwrap(),
1631            Value::String("other".to_string())
1632        );
1633    }
1634
1635    #[test]
1636    fn test_eval_case_no_default_returns_null() {
1637        use crate::query::parser::{CompareOp, Comparison, Condition};
1638        let row = Value::Object(
1639            vec![("status".to_string(), Value::String("pending".to_string()))]
1640                .into_iter()
1641                .collect(),
1642        );
1643        let expr = Expr::Case {
1644            branches: vec![(
1645                Condition::Comparison(Comparison {
1646                    field: "status".to_string(),
1647                    op: CompareOp::Eq,
1648                    value: LiteralValue::String("active".to_string()),
1649                }),
1650                Expr::Literal(LiteralValue::String("A".to_string())),
1651            )],
1652            default: None,
1653        };
1654        assert_eq!(evaluate_expr(&row, &expr).unwrap(), Value::Null);
1655    }
1656}