Skip to main content

dkit_core/query/
functions.rs

1use crate::error::DkitError;
2use crate::query::parser::{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    }
22}
23
24/// 표현식에서 출력 키(필드명)를 자동으로 결정
25pub fn expr_default_key(expr: &Expr) -> String {
26    match expr {
27        Expr::Field(f) => f.clone(),
28        Expr::Literal(_) => "value".to_string(),
29        Expr::FuncCall { name, args } => {
30            if let Some(first) = args.first() {
31                format!("{}_{}", name, expr_default_key(first))
32            } else {
33                name.clone()
34            }
35        }
36    }
37}
38
39fn literal_to_value(lit: &LiteralValue) -> Value {
40    match lit {
41        LiteralValue::String(s) => Value::String(s.clone()),
42        LiteralValue::Integer(n) => Value::Integer(*n),
43        LiteralValue::Float(f) => Value::Float(*f),
44        LiteralValue::Bool(b) => Value::Bool(*b),
45        LiteralValue::Null => Value::Null,
46    }
47}
48
49/// 내장 함수 호출
50fn call_function(name: &str, args: Vec<Value>) -> Result<Value, DkitError> {
51    match name {
52        // --- 문자열 함수 ---
53        "upper" => {
54            let s = require_one_string(name, &args)?;
55            Ok(Value::String(s.to_uppercase()))
56        }
57        "lower" => {
58            let s = require_one_string(name, &args)?;
59            Ok(Value::String(s.to_lowercase()))
60        }
61        "trim" => {
62            let s = require_one_string(name, &args)?;
63            Ok(Value::String(s.trim().to_string()))
64        }
65        "ltrim" => {
66            let s = require_one_string(name, &args)?;
67            Ok(Value::String(s.trim_start().to_string()))
68        }
69        "rtrim" => {
70            let s = require_one_string(name, &args)?;
71            Ok(Value::String(s.trim_end().to_string()))
72        }
73        "length" => match args.as_slice() {
74            [Value::String(s)] => Ok(Value::Integer(s.chars().count() as i64)),
75            [Value::Array(a)] => Ok(Value::Integer(a.len() as i64)),
76            [Value::Null] => Ok(Value::Integer(0)),
77            [v] => Err(DkitError::QueryError(format!(
78                "length() requires a string or array argument, got {}",
79                value_type_name(v)
80            ))),
81            _ => Err(DkitError::QueryError(format!(
82                "length() takes 1 argument, got {}",
83                args.len()
84            ))),
85        },
86        "substr" => {
87            if args.len() < 2 || args.len() > 3 {
88                return Err(DkitError::QueryError(format!(
89                    "substr() takes 2 or 3 arguments, got {}",
90                    args.len()
91                )));
92            }
93            let s = match &args[0] {
94                Value::String(s) => s.clone(),
95                Value::Null => return Ok(Value::Null),
96                v => {
97                    return Err(DkitError::QueryError(format!(
98                        "substr() first argument must be a string, got {}",
99                        value_type_name(v)
100                    )))
101                }
102            };
103            let start = require_integer_arg("substr", &args[1], "start")? as usize;
104            let chars: Vec<char> = s.chars().collect();
105            let start = start.min(chars.len());
106            if args.len() == 3 {
107                let len = require_integer_arg("substr", &args[2], "length")? as usize;
108                let end = (start + len).min(chars.len());
109                Ok(Value::String(chars[start..end].iter().collect()))
110            } else {
111                Ok(Value::String(chars[start..].iter().collect()))
112            }
113        }
114        "concat" => {
115            if args.is_empty() {
116                return Ok(Value::String(String::new()));
117            }
118            let mut result = String::new();
119            for arg in &args {
120                match arg {
121                    Value::String(s) => result.push_str(s),
122                    Value::Null => {}
123                    v => result.push_str(&v.to_string()),
124                }
125            }
126            Ok(Value::String(result))
127        }
128        "replace" => {
129            if args.len() != 3 {
130                return Err(DkitError::QueryError(format!(
131                    "replace() takes 3 arguments (string, from, to), got {}",
132                    args.len()
133                )));
134            }
135            let s = match &args[0] {
136                Value::String(s) => s.clone(),
137                Value::Null => return Ok(Value::Null),
138                v => {
139                    return Err(DkitError::QueryError(format!(
140                        "replace() first argument must be a string, got {}",
141                        value_type_name(v)
142                    )))
143                }
144            };
145            let from = match &args[1] {
146                Value::String(s) => s.clone(),
147                v => {
148                    return Err(DkitError::QueryError(format!(
149                        "replace() second argument must be a string, got {}",
150                        value_type_name(v)
151                    )))
152                }
153            };
154            let to = match &args[2] {
155                Value::String(s) => s.clone(),
156                v => {
157                    return Err(DkitError::QueryError(format!(
158                        "replace() third argument must be a string, got {}",
159                        value_type_name(v)
160                    )))
161                }
162            };
163            Ok(Value::String(s.replace(&*from, &to)))
164        }
165        "split" => {
166            if args.len() != 2 {
167                return Err(DkitError::QueryError(format!(
168                    "split() takes 2 arguments (string, separator), got {}",
169                    args.len()
170                )));
171            }
172            let s = match &args[0] {
173                Value::String(s) => s.clone(),
174                Value::Null => return Ok(Value::Array(vec![])),
175                v => {
176                    return Err(DkitError::QueryError(format!(
177                        "split() first argument must be a string, got {}",
178                        value_type_name(v)
179                    )))
180                }
181            };
182            let sep = match &args[1] {
183                Value::String(s) => s.clone(),
184                v => {
185                    return Err(DkitError::QueryError(format!(
186                        "split() second argument must be a string, got {}",
187                        value_type_name(v)
188                    )))
189                }
190            };
191            Ok(Value::Array(
192                s.split(&*sep)
193                    .map(|p| Value::String(p.to_string()))
194                    .collect(),
195            ))
196        }
197
198        // --- 수학 함수 ---
199        "round" => {
200            let n = require_numeric_arg(name, &args)?;
201            if args.len() == 2 {
202                let decimals = require_integer_arg("round", &args[1], "decimals")?;
203                let factor = 10_f64.powi(decimals as i32);
204                Ok(Value::Float((n * factor).round() / factor))
205            } else if args.len() == 1 {
206                Ok(Value::Integer(n.round() as i64))
207            } else {
208                Err(DkitError::QueryError(format!(
209                    "round() takes 1 or 2 arguments, got {}",
210                    args.len()
211                )))
212            }
213        }
214        "ceil" => {
215            let n = require_numeric_arg(name, &args)?;
216            Ok(Value::Integer(n.ceil() as i64))
217        }
218        "floor" => {
219            let n = require_numeric_arg(name, &args)?;
220            Ok(Value::Integer(n.floor() as i64))
221        }
222        "abs" => match args.as_slice() {
223            [Value::Integer(n)] => Ok(Value::Integer(n.abs())),
224            [Value::Float(f)] => Ok(Value::Float(f.abs())),
225            [Value::Null] => Ok(Value::Null),
226            [v] => Err(DkitError::QueryError(format!(
227                "abs() requires a numeric argument, got {}",
228                value_type_name(v)
229            ))),
230            _ => Err(DkitError::QueryError(format!(
231                "abs() takes 1 argument, got {}",
232                args.len()
233            ))),
234        },
235        "sqrt" => {
236            let n = require_numeric_arg(name, &args)?;
237            if n < 0.0 {
238                return Err(DkitError::QueryError(
239                    "sqrt() requires a non-negative argument".to_string(),
240                ));
241            }
242            Ok(Value::Float(n.sqrt()))
243        }
244        "pow" => {
245            if args.len() != 2 {
246                return Err(DkitError::QueryError(format!(
247                    "pow() takes 2 arguments, got {}",
248                    args.len()
249                )));
250            }
251            let base = require_numeric_arg("pow base", &args[..1])?;
252            let exp = require_numeric_arg("pow exp", &args[1..])?;
253            Ok(Value::Float(base.powf(exp)))
254        }
255
256        // --- 날짜 함수 ---
257        "now" => {
258            if !args.is_empty() {
259                return Err(DkitError::QueryError(
260                    "now() takes no arguments".to_string(),
261                ));
262            }
263            Ok(Value::String(current_datetime_utc()))
264        }
265        "date" => {
266            let s = require_one_string(name, &args)?;
267            // 날짜 파싱 검증 (ISO 8601 형식 기대)
268            let normalized = normalize_date_str(&s)?;
269            Ok(Value::String(normalized))
270        }
271        "year" => {
272            let s = require_one_string(name, &args)?;
273            let y = extract_year(&s)?;
274            Ok(Value::Integer(y))
275        }
276        "month" => {
277            let s = require_one_string(name, &args)?;
278            let m = extract_month(&s)?;
279            Ok(Value::Integer(m))
280        }
281        "day" => {
282            let s = require_one_string(name, &args)?;
283            let d = extract_day(&s)?;
284            Ok(Value::Integer(d))
285        }
286
287        // --- 타입 변환 ---
288        "to_int" | "int" => match args.as_slice() {
289            [Value::Integer(n)] => Ok(Value::Integer(*n)),
290            [Value::Float(f)] => Ok(Value::Integer(*f as i64)),
291            [Value::String(s)] => s.trim().parse::<i64>().map(Value::Integer).map_err(|_| {
292                DkitError::QueryError(format!("to_int(): cannot parse '{}' as integer", s))
293            }),
294            [Value::Bool(b)] => Ok(Value::Integer(if *b { 1 } else { 0 })),
295            [Value::Null] => Ok(Value::Null),
296            [v] => Err(DkitError::QueryError(format!(
297                "to_int() cannot convert {}",
298                value_type_name(v)
299            ))),
300            _ => Err(DkitError::QueryError(format!(
301                "to_int() takes 1 argument, got {}",
302                args.len()
303            ))),
304        },
305        "to_float" | "float" => match args.as_slice() {
306            [Value::Float(f)] => Ok(Value::Float(*f)),
307            [Value::Integer(n)] => Ok(Value::Float(*n as f64)),
308            [Value::String(s)] => s.trim().parse::<f64>().map(Value::Float).map_err(|_| {
309                DkitError::QueryError(format!("to_float(): cannot parse '{}' as float", s))
310            }),
311            [Value::Bool(b)] => Ok(Value::Float(if *b { 1.0 } else { 0.0 })),
312            [Value::Null] => Ok(Value::Null),
313            [v] => Err(DkitError::QueryError(format!(
314                "to_float() cannot convert {}",
315                value_type_name(v)
316            ))),
317            _ => Err(DkitError::QueryError(format!(
318                "to_float() takes 1 argument, got {}",
319                args.len()
320            ))),
321        },
322        "to_string" | "str" => match args.as_slice() {
323            [Value::String(s)] => Ok(Value::String(s.clone())),
324            [Value::Integer(n)] => Ok(Value::String(n.to_string())),
325            [Value::Float(f)] => Ok(Value::String(f.to_string())),
326            [Value::Bool(b)] => Ok(Value::String(b.to_string())),
327            [Value::Null] => Ok(Value::String("null".to_string())),
328            [v] => Ok(Value::String(v.to_string())),
329            _ => Err(DkitError::QueryError(format!(
330                "to_string() takes 1 argument, got {}",
331                args.len()
332            ))),
333        },
334        "to_bool" | "bool" => match args.as_slice() {
335            [Value::Bool(b)] => Ok(Value::Bool(*b)),
336            [Value::Integer(n)] => Ok(Value::Bool(*n != 0)),
337            [Value::Float(f)] => Ok(Value::Bool(*f != 0.0)),
338            [Value::String(s)] => match s.trim().to_lowercase().as_str() {
339                "true" | "yes" | "1" | "on" => Ok(Value::Bool(true)),
340                "false" | "no" | "0" | "off" | "" => Ok(Value::Bool(false)),
341                _ => Err(DkitError::QueryError(format!(
342                    "to_bool(): cannot parse '{}' as boolean",
343                    s
344                ))),
345            },
346            [Value::Null] => Ok(Value::Bool(false)),
347            [v] => Err(DkitError::QueryError(format!(
348                "to_bool() cannot convert {}",
349                value_type_name(v)
350            ))),
351            _ => Err(DkitError::QueryError(format!(
352                "to_bool() takes 1 argument, got {}",
353                args.len()
354            ))),
355        },
356
357        // --- 기타 유틸 ---
358        "coalesce" => {
359            for arg in &args {
360                if !matches!(arg, Value::Null) {
361                    return Ok(arg.clone());
362                }
363            }
364            Ok(Value::Null)
365        }
366        "if_null" => match args.as_slice() {
367            [Value::Null, default] => Ok(default.clone()),
368            [v, _] => Ok(v.clone()),
369            _ => Err(DkitError::QueryError(format!(
370                "if_null() takes 2 arguments, got {}",
371                args.len()
372            ))),
373        },
374
375        _ => Err(DkitError::QueryError(format!(
376            "unknown function '{}'",
377            name
378        ))),
379    }
380}
381
382// --- 헬퍼 함수 ---
383
384fn require_one_string(func: &str, args: &[Value]) -> Result<String, DkitError> {
385    match args {
386        [Value::String(s)] => Ok(s.clone()),
387        [Value::Null] => Err(DkitError::QueryError(format!(
388            "{}() argument is null",
389            func
390        ))),
391        [v] => Err(DkitError::QueryError(format!(
392            "{}() requires a string argument, got {}",
393            func,
394            value_type_name(v)
395        ))),
396        _ => Err(DkitError::QueryError(format!(
397            "{}() takes 1 argument, got {}",
398            func,
399            args.len()
400        ))),
401    }
402}
403
404fn require_numeric_arg(func: &str, args: &[Value]) -> Result<f64, DkitError> {
405    match args.first() {
406        Some(Value::Float(f)) => Ok(*f),
407        Some(Value::Integer(n)) => Ok(*n as f64),
408        Some(Value::Null) => Err(DkitError::QueryError(format!(
409            "{}() argument is null",
410            func
411        ))),
412        Some(v) => Err(DkitError::QueryError(format!(
413            "{}() requires a numeric argument, got {}",
414            func,
415            value_type_name(v)
416        ))),
417        None => Err(DkitError::QueryError(format!(
418            "{}() takes at least 1 argument",
419            func
420        ))),
421    }
422}
423
424fn require_integer_arg(func: &str, val: &Value, param: &str) -> Result<i64, DkitError> {
425    match val {
426        Value::Integer(n) => Ok(*n),
427        Value::Float(f) => Ok(*f as i64),
428        v => Err(DkitError::QueryError(format!(
429            "{}() '{}' argument must be an integer, got {}",
430            func,
431            param,
432            value_type_name(v)
433        ))),
434    }
435}
436
437fn value_type_name(v: &Value) -> &'static str {
438    match v {
439        Value::Null => "null",
440        Value::Bool(_) => "bool",
441        Value::Integer(_) => "integer",
442        Value::Float(_) => "float",
443        Value::String(_) => "string",
444        Value::Array(_) => "array",
445        Value::Object(_) => "object",
446    }
447}
448
449// --- 날짜 헬퍼 ---
450
451/// ISO 8601 날짜 문자열에서 연도 추출 (예: "2024-03-15" → 2024)
452fn extract_year(s: &str) -> Result<i64, DkitError> {
453    let date_part = s.split('T').next().unwrap_or(s);
454    let parts: Vec<&str> = date_part.split('-').collect();
455    if parts.is_empty() {
456        return Err(DkitError::QueryError(format!(
457            "year(): cannot parse date from '{}'",
458            s
459        )));
460    }
461    parts[0]
462        .parse::<i64>()
463        .map_err(|_| DkitError::QueryError(format!("year(): cannot parse year from '{}'", s)))
464}
465
466/// ISO 8601 날짜 문자열에서 월 추출 (1-12)
467fn extract_month(s: &str) -> Result<i64, DkitError> {
468    let date_part = s.split('T').next().unwrap_or(s);
469    let parts: Vec<&str> = date_part.split('-').collect();
470    if parts.len() < 2 {
471        return Err(DkitError::QueryError(format!(
472            "month(): cannot parse month from '{}'",
473            s
474        )));
475    }
476    parts[1]
477        .parse::<i64>()
478        .map_err(|_| DkitError::QueryError(format!("month(): cannot parse month from '{}'", s)))
479}
480
481/// ISO 8601 날짜 문자열에서 일 추출 (1-31)
482fn extract_day(s: &str) -> Result<i64, DkitError> {
483    let date_part = s.split('T').next().unwrap_or(s);
484    let parts: Vec<&str> = date_part.split('-').collect();
485    if parts.len() < 3 {
486        return Err(DkitError::QueryError(format!(
487            "day(): cannot parse day from '{}'",
488            s
489        )));
490    }
491    // Day part may have trailing time info
492    let day_str = parts[2].split(&['T', ' '][..]).next().unwrap_or(parts[2]);
493    day_str
494        .parse::<i64>()
495        .map_err(|_| DkitError::QueryError(format!("day(): cannot parse day from '{}'", s)))
496}
497
498/// 날짜 문자열 정규화 (yyyy-MM-dd 형식 확인)
499fn normalize_date_str(s: &str) -> Result<String, DkitError> {
500    let date_part = s.split('T').next().unwrap_or(s).trim();
501    let parts: Vec<&str> = date_part.split('-').collect();
502    if parts.len() != 3 {
503        return Err(DkitError::QueryError(format!(
504            "date(): expected yyyy-MM-dd format, got '{}'",
505            s
506        )));
507    }
508    let year = parts[0]
509        .parse::<i32>()
510        .map_err(|_| DkitError::QueryError(format!("date(): invalid year in '{}'", s)))?;
511    let month = parts[1]
512        .parse::<u32>()
513        .map_err(|_| DkitError::QueryError(format!("date(): invalid month in '{}'", s)))?;
514    let day = parts[2]
515        .split(&[' ', 'T'][..])
516        .next()
517        .unwrap_or(parts[2])
518        .parse::<u32>()
519        .map_err(|_| DkitError::QueryError(format!("date(): invalid day in '{}'", s)))?;
520    if !(1..=12).contains(&month) {
521        return Err(DkitError::QueryError(format!(
522            "date(): month {} out of range in '{}'",
523            month, s
524        )));
525    }
526    if !(1..=31).contains(&day) {
527        return Err(DkitError::QueryError(format!(
528            "date(): day {} out of range in '{}'",
529            day, s
530        )));
531    }
532    Ok(format!("{:04}-{:02}-{:02}", year, month, day))
533}
534
535/// 현재 UTC 시각을 ISO 8601 문자열로 반환
536fn current_datetime_utc() -> String {
537    use std::time::{SystemTime, UNIX_EPOCH};
538    let secs = SystemTime::now()
539        .duration_since(UNIX_EPOCH)
540        .unwrap_or_default()
541        .as_secs();
542    // 간단한 UTC 변환 (초 → 날짜/시간)
543    let days_since_epoch = secs / 86400;
544    let time_of_day = secs % 86400;
545    let (year, month, day) = days_to_ymd(days_since_epoch);
546    let hour = time_of_day / 3600;
547    let minute = (time_of_day % 3600) / 60;
548    let second = time_of_day % 60;
549    format!(
550        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
551        year, month, day, hour, minute, second
552    )
553}
554
555/// Unix epoch 일수를 (year, month, day)로 변환
556fn days_to_ymd(days: u64) -> (i64, u64, u64) {
557    // Gregorian calendar algorithm
558    let z = days as i64 + 719468;
559    let era = if z >= 0 { z } else { z - 146096 } / 146097;
560    let doe = z - era * 146097;
561    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
562    let y = yoe + era * 400;
563    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
564    let mp = (5 * doy + 2) / 153;
565    let d = doy - (153 * mp + 2) / 5 + 1;
566    let m = if mp < 10 { mp + 3 } else { mp - 9 };
567    let y = if m <= 2 { y + 1 } else { y };
568    (y, m as u64, d as u64)
569}
570
571#[cfg(test)]
572mod tests {
573    use super::*;
574    use crate::query::parser::Expr;
575    use indexmap::IndexMap;
576
577    fn obj(fields: &[(&str, Value)]) -> Value {
578        let mut map = IndexMap::new();
579        for (k, v) in fields {
580            map.insert(k.to_string(), v.clone());
581        }
582        Value::Object(map)
583    }
584
585    fn eval(row: &Value, expr: &Expr) -> Result<Value, DkitError> {
586        evaluate_expr(row, expr)
587    }
588
589    fn func(name: &str, args: Vec<Expr>) -> Expr {
590        Expr::FuncCall {
591            name: name.to_string(),
592            args,
593        }
594    }
595
596    fn field(name: &str) -> Expr {
597        Expr::Field(name.to_string())
598    }
599
600    fn lit_str(s: &str) -> Expr {
601        Expr::Literal(LiteralValue::String(s.to_string()))
602    }
603
604    fn lit_int(n: i64) -> Expr {
605        Expr::Literal(LiteralValue::Integer(n))
606    }
607
608    // --- 문자열 함수 ---
609
610    #[test]
611    fn test_upper() {
612        let row = obj(&[("name", Value::String("hello".to_string()))]);
613        let result = eval(&row, &func("upper", vec![field("name")])).unwrap();
614        assert_eq!(result, Value::String("HELLO".to_string()));
615    }
616
617    #[test]
618    fn test_lower() {
619        let row = obj(&[("name", Value::String("WORLD".to_string()))]);
620        let result = eval(&row, &func("lower", vec![field("name")])).unwrap();
621        assert_eq!(result, Value::String("world".to_string()));
622    }
623
624    #[test]
625    fn test_trim() {
626        let row = obj(&[("name", Value::String("  hello  ".to_string()))]);
627        let result = eval(&row, &func("trim", vec![field("name")])).unwrap();
628        assert_eq!(result, Value::String("hello".to_string()));
629    }
630
631    #[test]
632    fn test_length_string() {
633        let row = obj(&[("name", Value::String("hello".to_string()))]);
634        let result = eval(&row, &func("length", vec![field("name")])).unwrap();
635        assert_eq!(result, Value::Integer(5));
636    }
637
638    #[test]
639    fn test_substr() {
640        let row = obj(&[("name", Value::String("hello world".to_string()))]);
641        let result = eval(
642            &row,
643            &func("substr", vec![field("name"), lit_int(0), lit_int(5)]),
644        )
645        .unwrap();
646        assert_eq!(result, Value::String("hello".to_string()));
647    }
648
649    #[test]
650    fn test_concat() {
651        let row = obj(&[
652            ("first", Value::String("hello".to_string())),
653            ("last", Value::String(" world".to_string())),
654        ]);
655        let result = eval(&row, &func("concat", vec![field("first"), field("last")])).unwrap();
656        assert_eq!(result, Value::String("hello world".to_string()));
657    }
658
659    #[test]
660    fn test_replace() {
661        let row = obj(&[("name", Value::String("hello world".to_string()))]);
662        let result = eval(
663            &row,
664            &func(
665                "replace",
666                vec![field("name"), lit_str("world"), lit_str("dkit")],
667            ),
668        )
669        .unwrap();
670        assert_eq!(result, Value::String("hello dkit".to_string()));
671    }
672
673    // --- 수학 함수 ---
674
675    #[test]
676    fn test_round_no_decimals() {
677        let row = obj(&[("price", Value::Float(3.7))]);
678        let result = eval(&row, &func("round", vec![field("price")])).unwrap();
679        assert_eq!(result, Value::Integer(4));
680    }
681
682    #[test]
683    fn test_round_with_decimals() {
684        let row = obj(&[("price", Value::Float(3.14159))]);
685        let result = eval(&row, &func("round", vec![field("price"), lit_int(2)])).unwrap();
686        match result {
687            Value::Float(v) => assert!((v - 3.14).abs() < 1e-10, "expected ~3.14, got {v}"),
688            other => panic!("expected Float, got {other:?}"),
689        }
690    }
691
692    #[test]
693    fn test_ceil() {
694        let row = obj(&[("price", Value::Float(3.2))]);
695        let result = eval(&row, &func("ceil", vec![field("price")])).unwrap();
696        assert_eq!(result, Value::Integer(4));
697    }
698
699    #[test]
700    fn test_floor() {
701        let row = obj(&[("price", Value::Float(3.9))]);
702        let result = eval(&row, &func("floor", vec![field("price")])).unwrap();
703        assert_eq!(result, Value::Integer(3));
704    }
705
706    #[test]
707    fn test_abs_negative() {
708        let row = obj(&[("score", Value::Integer(-5))]);
709        let result = eval(&row, &func("abs", vec![field("score")])).unwrap();
710        assert_eq!(result, Value::Integer(5));
711    }
712
713    // --- 날짜 함수 ---
714
715    #[test]
716    fn test_year() {
717        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
718        let result = eval(&row, &func("year", vec![field("date")])).unwrap();
719        assert_eq!(result, Value::Integer(2024));
720    }
721
722    #[test]
723    fn test_month() {
724        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
725        let result = eval(&row, &func("month", vec![field("date")])).unwrap();
726        assert_eq!(result, Value::Integer(3));
727    }
728
729    #[test]
730    fn test_day() {
731        let row = obj(&[("date", Value::String("2024-03-15".to_string()))]);
732        let result = eval(&row, &func("day", vec![field("date")])).unwrap();
733        assert_eq!(result, Value::Integer(15));
734    }
735
736    #[test]
737    fn test_date_normalize() {
738        let row = obj(&[("d", Value::String("2024-3-5".to_string()))]);
739        let result = eval(&row, &func("date", vec![field("d")])).unwrap();
740        assert_eq!(result, Value::String("2024-03-05".to_string()));
741    }
742
743    #[test]
744    fn test_now_returns_string() {
745        let row = obj(&[]);
746        let result = eval(&row, &func("now", vec![])).unwrap();
747        assert!(matches!(result, Value::String(_)));
748        if let Value::String(s) = result {
749            assert!(s.contains('T'), "now() should return ISO 8601 format");
750        }
751    }
752
753    // --- 타입 변환 ---
754
755    #[test]
756    fn test_to_int_from_string() {
757        let row = obj(&[("n", Value::String("42".to_string()))]);
758        let result = eval(&row, &func("to_int", vec![field("n")])).unwrap();
759        assert_eq!(result, Value::Integer(42));
760    }
761
762    #[test]
763    fn test_to_float_from_int() {
764        let row = obj(&[("n", Value::Integer(5))]);
765        let result = eval(&row, &func("to_float", vec![field("n")])).unwrap();
766        assert_eq!(result, Value::Float(5.0));
767    }
768
769    #[test]
770    fn test_to_string_from_int() {
771        let row = obj(&[("n", Value::Integer(42))]);
772        let result = eval(&row, &func("to_string", vec![field("n")])).unwrap();
773        assert_eq!(result, Value::String("42".to_string()));
774    }
775
776    #[test]
777    fn test_to_bool_from_string() {
778        let row = obj(&[("flag", Value::String("true".to_string()))]);
779        let result = eval(&row, &func("to_bool", vec![field("flag")])).unwrap();
780        assert_eq!(result, Value::Bool(true));
781    }
782
783    // --- 중첩 함수 호출 ---
784
785    #[test]
786    fn test_nested_upper_trim() {
787        let row = obj(&[("name", Value::String("  hello  ".to_string()))]);
788        let result = eval(
789            &row,
790            &func("upper", vec![func("trim", vec![field("name")])]),
791        )
792        .unwrap();
793        assert_eq!(result, Value::String("HELLO".to_string()));
794    }
795
796    // --- expr_default_key ---
797
798    #[test]
799    fn test_default_key_field() {
800        assert_eq!(expr_default_key(&Expr::Field("name".to_string())), "name");
801    }
802
803    #[test]
804    fn test_default_key_func() {
805        let expr = Expr::FuncCall {
806            name: "upper".to_string(),
807            args: vec![Expr::Field("name".to_string())],
808        };
809        assert_eq!(expr_default_key(&expr), "upper_name");
810    }
811
812    #[test]
813    fn test_default_key_nested_func() {
814        let expr = Expr::FuncCall {
815            name: "upper".to_string(),
816            args: vec![Expr::FuncCall {
817                name: "trim".to_string(),
818                args: vec![Expr::Field("name".to_string())],
819            }],
820        };
821        assert_eq!(expr_default_key(&expr), "upper_trim_name");
822    }
823}