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