Skip to main content

dkit_core/query/
functions.rs

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