Skip to main content

linkrs_query/executor/expression/functions/builtin/
utility.rs

1//! Implementation of practical functions
2
3use crate::executor::expression::ExpressionError;
4use linkrs_core::value::list::List;
5use linkrs_core::value::NullType;
6use linkrs_core::Value;
7use serde_json::Value as JsonValue;
8use std::collections::HashMap;
9
10/// Enumeration of practical functions
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum UtilityFunction {
13    Coalesce,
14    Hash,
15    JsonExtract,
16    JsonBuildObject,
17    JsonBuildArray,
18    JsonObjectKeys,
19    NullIf,
20    Greatest,
21    Least,
22    GenRandomUuid,
23    JsonEach,
24    JsonTypeOf,
25    JsonStripNulls,
26    IfNull,
27    TypeOf,
28    Version,
29    CurrentUser,
30    CurrentDatabase,
31    Corr,
32    CovarPop,
33    CovarSamp,
34    OctetLength,
35    Encode,
36    Decode,
37    UnionValue,
38    UnionTag,
39    UnionExtract,
40}
41
42impl UtilityFunction {
43    pub fn name(&self) -> &str {
44        match self {
45            UtilityFunction::Coalesce => "coalesce",
46            UtilityFunction::Hash => "hash",
47            UtilityFunction::JsonExtract => "json_extract",
48            UtilityFunction::JsonBuildObject => "json_build_object",
49            UtilityFunction::JsonBuildArray => "json_build_array",
50            UtilityFunction::JsonObjectKeys => "json_object_keys",
51            UtilityFunction::NullIf => "nullif",
52            UtilityFunction::Greatest => "greatest",
53            UtilityFunction::Least => "least",
54            UtilityFunction::GenRandomUuid => "gen_random_uuid",
55            UtilityFunction::JsonEach => "json_each",
56            UtilityFunction::JsonTypeOf => "json_typeof",
57            UtilityFunction::JsonStripNulls => "json_strip_nulls",
58            UtilityFunction::IfNull => "ifnull",
59            UtilityFunction::TypeOf => "typeof",
60            UtilityFunction::Version => "version",
61            UtilityFunction::CurrentUser => "current_user",
62            UtilityFunction::CurrentDatabase => "current_database",
63            UtilityFunction::Corr => "corr",
64            UtilityFunction::CovarPop => "covar_pop",
65            UtilityFunction::CovarSamp => "covar_samp",
66            UtilityFunction::OctetLength => "octet_length",
67            UtilityFunction::Encode => "encode",
68            UtilityFunction::Decode => "decode",
69            UtilityFunction::UnionValue => "union_value",
70            UtilityFunction::UnionTag => "union_tag",
71            UtilityFunction::UnionExtract => "union_extract",
72        }
73    }
74
75    pub fn arity(&self) -> usize {
76        match self {
77            UtilityFunction::Coalesce => 1,
78            UtilityFunction::Hash => 1,
79            UtilityFunction::JsonExtract => 2,
80            UtilityFunction::JsonBuildObject => 0,
81            UtilityFunction::JsonBuildArray => 0,
82            UtilityFunction::JsonObjectKeys => 1,
83            UtilityFunction::NullIf => 2,
84            UtilityFunction::Greatest => 2,
85            UtilityFunction::Least => 2,
86            UtilityFunction::GenRandomUuid => 0,
87            UtilityFunction::JsonEach => 1,
88            UtilityFunction::JsonTypeOf => 1,
89            UtilityFunction::JsonStripNulls => 1,
90            UtilityFunction::IfNull => 2,
91            UtilityFunction::TypeOf => 1,
92            UtilityFunction::Version => 0,
93            UtilityFunction::CurrentUser => 0,
94            UtilityFunction::CurrentDatabase => 0,
95            UtilityFunction::Corr => 2,
96            UtilityFunction::CovarPop => 2,
97            UtilityFunction::CovarSamp => 2,
98            UtilityFunction::OctetLength => 1,
99            UtilityFunction::Encode => 1,
100            UtilityFunction::Decode => 1,
101            UtilityFunction::UnionValue => 2,
102            UtilityFunction::UnionTag => 1,
103            UtilityFunction::UnionExtract => 1,
104        }
105    }
106
107    pub fn is_variadic(&self) -> bool {
108        matches!(
109            self,
110            UtilityFunction::Coalesce
111                | UtilityFunction::Greatest
112                | UtilityFunction::Least
113                | UtilityFunction::JsonBuildObject
114                | UtilityFunction::JsonBuildArray
115        )
116    }
117
118    pub fn description(&self) -> &str {
119        match self {
120            UtilityFunction::Coalesce => "Returns the first non-NULL value",
121            UtilityFunction::Hash => "Compute the hash",
122            UtilityFunction::JsonExtract => {
123                "Extract the value of a specified path from a JSON string"
124            }
125            UtilityFunction::JsonBuildObject => "Build a JSON object from key-value pairs",
126            UtilityFunction::JsonBuildArray => "Build a JSON array from a list of values",
127            UtilityFunction::JsonObjectKeys => "Get keys from a JSON object",
128            UtilityFunction::NullIf => "Return NULL if two values are equal",
129            UtilityFunction::Greatest => "Return the largest of the arguments",
130            UtilityFunction::Least => "Return the smallest of the arguments",
131            UtilityFunction::GenRandomUuid => "Generate a random UUID v4",
132            UtilityFunction::JsonEach => "Expand a JSON object into key-value pairs",
133            UtilityFunction::JsonTypeOf => "Return the type of a JSON value",
134            UtilityFunction::JsonStripNulls => "Strip null values from a JSON object or array",
135            UtilityFunction::IfNull => {
136                "Return first argument if not NULL, otherwise second argument"
137            }
138            UtilityFunction::TypeOf => "Return the type name of a value",
139            UtilityFunction::Version => "Return the Linkrs version string",
140            UtilityFunction::CurrentUser => "Return the current user name",
141            UtilityFunction::CurrentDatabase => "Return the current database name",
142            UtilityFunction::Corr => "Return the correlation coefficient of two lists",
143            UtilityFunction::CovarPop => "Return the population covariance of two lists",
144            UtilityFunction::CovarSamp => "Return the sample covariance of two lists",
145            UtilityFunction::OctetLength => "Return the number of bytes in a string or blob",
146            UtilityFunction::Encode => "Encode a string to blob using encoding (UTF8, HEX, BASE64)",
147            UtilityFunction::Decode => "Decode a blob to string using encoding (UTF8, HEX, BASE64)",
148            UtilityFunction::UnionValue => "Create a union value with a tag and a value",
149            UtilityFunction::UnionTag => "Get the tag of a union value",
150            UtilityFunction::UnionExtract => "Extract the value from a union value",
151        }
152    }
153
154    pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
155        if !self.is_variadic() && args.len() != self.arity() {
156            return Err(ExpressionError::invalid_arity(
157                self.name(),
158                self.arity(),
159                args.len(),
160            ));
161        }
162        match self {
163            UtilityFunction::Coalesce => execute_coalesce(args),
164            UtilityFunction::Hash => execute_hash(args),
165            UtilityFunction::JsonExtract => execute_json_extract(args),
166            UtilityFunction::JsonBuildObject => execute_json_build_object(args),
167            UtilityFunction::JsonBuildArray => execute_json_build_array(args),
168            UtilityFunction::JsonObjectKeys => execute_json_object_keys(args),
169            UtilityFunction::NullIf => execute_nullif(args),
170            UtilityFunction::Greatest => execute_greatest(args),
171            UtilityFunction::Least => execute_least(args),
172            UtilityFunction::GenRandomUuid => execute_gen_random_uuid(args),
173            UtilityFunction::JsonEach => execute_json_each(args),
174            UtilityFunction::JsonTypeOf => execute_json_typeof(args),
175            UtilityFunction::JsonStripNulls => execute_json_strip_nulls(args),
176            UtilityFunction::IfNull => execute_ifnull(args),
177            UtilityFunction::TypeOf => execute_typeof(args),
178            UtilityFunction::Version => execute_version(args),
179            UtilityFunction::CurrentUser => execute_current_user(args),
180            UtilityFunction::CurrentDatabase => execute_current_database(args),
181            UtilityFunction::Corr => execute_corr(args),
182            UtilityFunction::CovarPop => execute_covar_pop(args),
183            UtilityFunction::CovarSamp => execute_covar_samp(args),
184            UtilityFunction::OctetLength => execute_octet_length(args),
185            UtilityFunction::Encode => execute_encode(args),
186            UtilityFunction::Decode => execute_decode(args),
187            UtilityFunction::UnionValue => execute_union_value(args),
188            UtilityFunction::UnionTag => execute_union_tag(args),
189            UtilityFunction::UnionExtract => execute_union_extract(args),
190        }
191    }
192}
193
194fn execute_coalesce(args: &[Value]) -> Result<Value, ExpressionError> {
195    for arg in args {
196        match arg {
197            Value::Null(_) => continue,
198            other => return Ok(other.clone()),
199        }
200    }
201    Ok(Value::Null(NullType::Null))
202}
203
204fn execute_hash(args: &[Value]) -> Result<Value, ExpressionError> {
205    use std::collections::hash_map::DefaultHasher;
206    use std::hash::{Hash, Hasher};
207
208    match &args[0] {
209        Value::Null(_) => Ok(Value::Null(NullType::Null)),
210        Value::String(s) => {
211            let mut hasher = DefaultHasher::new();
212            s.hash(&mut hasher);
213            let hash_value = hasher.finish() as i64;
214            Ok(Value::BigInt(hash_value))
215        }
216        Value::Int(i) => {
217            let mut hasher = DefaultHasher::new();
218            i.hash(&mut hasher);
219            let hash_value = hasher.finish() as i64;
220            Ok(Value::BigInt(hash_value))
221        }
222        _ => Err(ExpressionError::type_error(
223            "The hash function requires a string or integer type",
224        )),
225    }
226}
227
228fn execute_json_extract(args: &[Value]) -> Result<Value, ExpressionError> {
229    if args[0].is_null() || args[1].is_null() {
230        return Ok(Value::Null(NullType::Null));
231    }
232    let json_value = arg_to_json_value(&args[0])?;
233    let path = match &args[1] {
234        Value::String(s) => s.as_str(),
235        _ => {
236            return Err(ExpressionError::type_error(
237                "json_extract path must be a string",
238            ))
239        }
240    };
241    let result = extract_json_value(&json_value, path);
242    Ok(json_to_value(result))
243}
244
245fn execute_json_build_object(args: &[Value]) -> Result<Value, ExpressionError> {
246    use serde_json::Map;
247
248    if !args.len().is_multiple_of(2) {
249        return Err(ExpressionError::type_error(
250            "json_build_object requires an even number of arguments (key-value pairs)",
251        ));
252    }
253
254    let mut map = Map::new();
255    for chunk in args.chunks(2) {
256        let key = match &chunk[0] {
257            Value::String(s) => s.to_string(),
258            Value::Null(_) => continue,
259            _ => {
260                return Err(ExpressionError::type_error(
261                    "json_build_object keys must be strings",
262                ))
263            }
264        };
265        let value = value_to_json_value(&chunk[1]);
266        map.insert(key, value);
267    }
268
269    Ok(Value::string(
270        serde_json::to_string(&JsonValue::Object(map))
271            .map_err(|e| ExpressionError::type_error(format!("JSON serialization error: {}", e)))?,
272    ))
273}
274
275fn execute_json_build_array(args: &[Value]) -> Result<Value, ExpressionError> {
276    let arr: Vec<JsonValue> = args.iter().map(value_to_json_value).collect();
277    Ok(Value::string(
278        serde_json::to_string(&JsonValue::Array(arr))
279            .map_err(|e| ExpressionError::type_error(format!("JSON serialization error: {}", e)))?,
280    ))
281}
282
283fn execute_json_object_keys(args: &[Value]) -> Result<Value, ExpressionError> {
284    use linkrs_core::value::list::List;
285
286    if args[0].is_null() {
287        return Ok(Value::Null(NullType::Null));
288    }
289    let json_value = arg_to_json_value(&args[0])?;
290
291    match &json_value {
292        JsonValue::Object(map) => {
293            let keys: Vec<Value> = map.keys().map(|k| Value::string(k.clone())).collect();
294            Ok(Value::list(List { values: keys }))
295        }
296        _ => Err(ExpressionError::type_error(
297            "json_object_keys requires a JSON object",
298        )),
299    }
300}
301
302fn value_to_json_value(value: &Value) -> JsonValue {
303    match value {
304        Value::Null(_) => JsonValue::Null,
305        Value::Bool(b) => JsonValue::Bool(*b),
306        Value::SmallInt(i) => JsonValue::Number((*i).into()),
307        Value::Int(i) => JsonValue::Number((*i).into()),
308        Value::BigInt(i) => JsonValue::Number((*i).into()),
309        Value::Float(f) => JsonValue::Number(
310            serde_json::Number::from_f64(*f as f64).unwrap_or(serde_json::Number::from(0)),
311        ),
312        Value::Double(f) => JsonValue::Number(
313            serde_json::Number::from_f64(*f).unwrap_or(serde_json::Number::from(0)),
314        ),
315        Value::String(s) => JsonValue::String(s.to_string()),
316        Value::List(list) => {
317            JsonValue::Array(list.values.iter().map(value_to_json_value).collect())
318        }
319        Value::Map(map) => {
320            let obj: serde_json::Map<String, JsonValue> = map
321                .iter()
322                .map(|(k, v)| {
323                    // JSON object keys must be strings; non-string keys use
324                    // their readable serialization.
325                    let key = match k {
326                        Value::String(s) => s.to_string(),
327                        other => format!("{}", other),
328                    };
329                    (key, value_to_json_value(v))
330                })
331                .collect();
332            JsonValue::Object(obj)
333        }
334        Value::Json(j) => j.to_value().unwrap_or(JsonValue::Null),
335        Value::JsonB(j) => j.as_value().clone(),
336        _ => JsonValue::Null,
337    }
338}
339
340/// Extract a serde_json::Value from any JSON-compatible value type.
341fn arg_to_json_value(arg: &Value) -> Result<JsonValue, ExpressionError> {
342    match arg {
343        Value::Null(_) => Err(ExpressionError::type_error("Null JSON value")),
344        Value::String(s) => {
345            serde_json::from_str(s).map_err(|_| ExpressionError::type_error("Invalid JSON string"))
346        }
347        Value::Json(j) => j
348            .to_value()
349            .map_err(|e| ExpressionError::type_error(format!("Invalid JSON: {}", e))),
350        Value::JsonB(j) => Ok(j.as_value().clone()),
351        _ => Err(ExpressionError::type_error(
352            "Expected a JSON-compatible value (string, json, or jsonb)",
353        )),
354    }
355}
356
357fn extract_json_value<'a>(json: &'a JsonValue, path: &str) -> &'a JsonValue {
358    let parts: Vec<&str> = path.split('.').collect();
359    let mut current = json;
360
361    for part in parts {
362        if part.is_empty() {
363            continue;
364        }
365
366        current = match current {
367            JsonValue::Object(map) => map.get(part).unwrap_or(&JsonValue::Null),
368            JsonValue::Array(arr) => {
369                if let Ok(index) = part.parse::<usize>() {
370                    arr.get(index).unwrap_or(&JsonValue::Null)
371                } else {
372                    &JsonValue::Null
373                }
374            }
375            _ => &JsonValue::Null,
376        };
377    }
378
379    current
380}
381
382fn json_to_value(json: &JsonValue) -> Value {
383    match json {
384        JsonValue::Null => Value::Null(NullType::Null),
385        JsonValue::Bool(b) => Value::Bool(*b),
386        JsonValue::Number(n) => {
387            if let Some(i) = n.as_i64() {
388                Value::BigInt(i)
389            } else if let Some(f) = n.as_f64() {
390                Value::Double(f)
391            } else {
392                Value::Null(NullType::Null)
393            }
394        }
395        JsonValue::String(s) => Value::string(s.clone()),
396        JsonValue::Array(arr) => {
397            let values: Vec<Value> = arr.iter().map(json_to_value).collect();
398            Value::list(List { values })
399        }
400        JsonValue::Object(obj) => {
401            let map: std::collections::HashMap<Value, Value> = obj
402                .iter()
403                .map(|(k, v)| (Value::string(k.clone()), json_to_value(v)))
404                .collect();
405            Value::map(map)
406        }
407    }
408}
409
410fn execute_nullif(args: &[Value]) -> Result<Value, ExpressionError> {
411    match (&args[0], &args[1]) {
412        (Value::Null(_), _) | (_, Value::Null(_)) => Ok(Value::Null(NullType::Null)),
413        (a, b) => {
414            if values_equal(a, b) {
415                Ok(Value::Null(NullType::Null))
416            } else {
417                Ok(a.clone())
418            }
419        }
420    }
421}
422
423fn values_equal(a: &Value, b: &Value) -> bool {
424    match (a, b) {
425        (Value::Int(a), Value::BigInt(b)) => *a as i64 == *b,
426        (Value::BigInt(a), Value::Int(b)) => *a == *b as i64,
427        (Value::Float(a), Value::Double(b)) => *a as f64 == *b,
428        (Value::Double(a), Value::Float(b)) => *a == *b as f64,
429        _ => a == b,
430    }
431}
432
433fn values_less_than(a: &Value, b: &Value) -> bool {
434    match (a, b) {
435        (Value::Int(a), Value::Int(b)) => a < b,
436        (Value::BigInt(a), Value::BigInt(b)) => a < b,
437        (Value::Float(a), Value::Float(b)) => a < b,
438        (Value::Double(a), Value::Double(b)) => a < b,
439        (Value::String(a), Value::String(b)) => a < b,
440        (Value::Int(a), Value::BigInt(b)) => (*a as i64) < *b,
441        (Value::BigInt(a), Value::Int(b)) => *a < (*b as i64),
442        (Value::Float(a), Value::Double(b)) => (*a as f64) < *b,
443        (Value::Double(a), Value::Float(b)) => *a < (*b as f64),
444        _ => false,
445    }
446}
447
448fn execute_greatest(args: &[Value]) -> Result<Value, ExpressionError> {
449    if args.is_empty() {
450        return Err(ExpressionError::type_error(
451            "greatest requires at least 1 argument",
452        ));
453    }
454    let mut result = &args[0];
455    for arg in &args[1..] {
456        if values_less_than(result, arg) {
457            result = arg;
458        }
459    }
460    Ok(result.clone())
461}
462
463fn execute_least(args: &[Value]) -> Result<Value, ExpressionError> {
464    if args.is_empty() {
465        return Err(ExpressionError::type_error(
466            "least requires at least 1 argument",
467        ));
468    }
469    let mut result = &args[0];
470    for arg in &args[1..] {
471        if values_less_than(arg, result) {
472            result = arg;
473        }
474    }
475    Ok(result.clone())
476}
477
478fn execute_gen_random_uuid(_args: &[Value]) -> Result<Value, ExpressionError> {
479    use linkrs_core::value::uuid::UuidValue;
480    Ok(Value::Uuid(UuidValue::new_v4()))
481}
482
483fn execute_json_each(args: &[Value]) -> Result<Value, ExpressionError> {
484    if args[0].is_null() {
485        return Ok(Value::Null(NullType::Null));
486    }
487    let json_value = arg_to_json_value(&args[0])?;
488
489    match json_value {
490        JsonValue::Object(map) => {
491            let entries: Vec<Value> = map
492                .into_iter()
493                .map(|(k, v)| {
494                    Value::list(List {
495                        values: vec![Value::string(k), json_to_value(&v)],
496                    })
497                })
498                .collect();
499            Ok(Value::list(List { values: entries }))
500        }
501        JsonValue::Array(arr) => {
502            let entries: Vec<Value> = arr
503                .into_iter()
504                .enumerate()
505                .map(|(i, v)| {
506                    Value::list(List {
507                        values: vec![Value::BigInt(i as i64), json_to_value(&v)],
508                    })
509                })
510                .collect();
511            Ok(Value::list(List { values: entries }))
512        }
513        _ => Err(ExpressionError::type_error(
514            "json_each requires a JSON object or array",
515        )),
516    }
517}
518
519fn execute_json_typeof(args: &[Value]) -> Result<Value, ExpressionError> {
520    if args[0].is_null() {
521        return Ok(Value::Null(NullType::Null));
522    }
523    let json_value = arg_to_json_value(&args[0])?;
524
525    let type_str = match &json_value {
526        JsonValue::Null => "null",
527        JsonValue::Bool(_) => "boolean",
528        JsonValue::Number(_) => "number",
529        JsonValue::String(_) => "string",
530        JsonValue::Array(_) => "array",
531        JsonValue::Object(_) => "object",
532    };
533    Ok(Value::string(type_str))
534}
535
536fn execute_json_strip_nulls(args: &[Value]) -> Result<Value, ExpressionError> {
537    if args[0].is_null() {
538        return Ok(Value::Null(NullType::Null));
539    }
540    let json_value = arg_to_json_value(&args[0])?;
541    let stripped = strip_nulls_from_json(json_value);
542    let output = serde_json::to_string(&stripped)
543        .map_err(|e| ExpressionError::type_error(format!("JSON serialization error: {}", e)))?;
544    Ok(Value::string(output))
545}
546
547fn strip_nulls_from_json(value: JsonValue) -> JsonValue {
548    match value {
549        JsonValue::Object(map) => {
550            let cleaned: serde_json::Map<String, JsonValue> = map
551                .into_iter()
552                .filter(|(_, v)| !v.is_null())
553                .map(|(k, v)| (k, strip_nulls_from_json(v)))
554                .collect();
555            JsonValue::Object(cleaned)
556        }
557        JsonValue::Array(arr) => {
558            let cleaned: Vec<JsonValue> = arr
559                .into_iter()
560                .filter(|v| !v.is_null())
561                .map(strip_nulls_from_json)
562                .collect();
563            JsonValue::Array(cleaned)
564        }
565        other => other,
566    }
567}
568
569fn execute_ifnull(args: &[Value]) -> Result<Value, ExpressionError> {
570    match &args[0] {
571        Value::Null(_) => Ok(args[1].clone()),
572        other => Ok(other.clone()),
573    }
574}
575
576fn execute_typeof(args: &[Value]) -> Result<Value, ExpressionError> {
577    let type_name = match &args[0] {
578        Value::Null(_) => "null",
579        Value::Bool(_) => "bool",
580        Value::SmallInt(_) => "smallint",
581        Value::Int(_) => "int",
582        Value::BigInt(_) => "bigint",
583        Value::Float(_) => "float",
584        Value::Double(_) => "double",
585        Value::String(_) => "string",
586        Value::Date(_) => "date",
587        Value::Time(_) => "time",
588        Value::DateTime(_) => "datetime",
589        Value::List(_) => "list",
590        Value::Map(_) => "map",
591        Value::Set(_) => "set",
592        Value::Vertex(_) => "vertex",
593        Value::Edge(_) | Value::EdgeHeader(_) => "edge",
594        Value::Path(_) => "path",
595        Value::Uuid(_) => "uuid",
596        Value::Vector(_) => "vector",
597        Value::Geography(_) => "geography",
598        _ => "unknown",
599    };
600    Ok(Value::string(type_name))
601}
602
603fn execute_version(_args: &[Value]) -> Result<Value, ExpressionError> {
604    Ok(Value::string("Linkrs 0.1.0"))
605}
606
607fn execute_current_user(_args: &[Value]) -> Result<Value, ExpressionError> {
608    Ok(Value::string("root"))
609}
610
611fn execute_current_database(_args: &[Value]) -> Result<Value, ExpressionError> {
612    Ok(Value::string("default"))
613}
614
615fn extract_numeric_pairs(args: &[Value]) -> Result<(Vec<f64>, Vec<f64>), ExpressionError> {
616    let xs = match &args[0] {
617        Value::List(list) => list
618            .values
619            .iter()
620            .map(|v| match v {
621                Value::SmallInt(i) => Ok(*i as f64),
622                Value::Int(i) => Ok(*i as f64),
623                Value::BigInt(i) => Ok(*i as f64),
624                Value::Float(f) => Ok(*f as f64),
625                Value::Double(f) => Ok(*f),
626                Value::Null(_) => Ok(f64::NAN),
627                _ => Err(ExpressionError::type_error("Non-numeric value in list")),
628            })
629            .collect::<Result<Vec<f64>, _>>(),
630        Value::Null(_) => return Ok((vec![], vec![])),
631        _ => return Err(ExpressionError::type_error("First argument must be a list")),
632    }?;
633    let ys = match &args[1] {
634        Value::List(list) => list
635            .values
636            .iter()
637            .map(|v| match v {
638                Value::SmallInt(i) => Ok(*i as f64),
639                Value::Int(i) => Ok(*i as f64),
640                Value::BigInt(i) => Ok(*i as f64),
641                Value::Float(f) => Ok(*f as f64),
642                Value::Double(f) => Ok(*f),
643                Value::Null(_) => Ok(f64::NAN),
644                _ => Err(ExpressionError::type_error("Non-numeric value in list")),
645            })
646            .collect::<Result<Vec<f64>, _>>(),
647        Value::Null(_) => return Ok((vec![], vec![])),
648        _ => {
649            return Err(ExpressionError::type_error(
650                "Second argument must be a list",
651            ))
652        }
653    }?;
654    if xs.len() != ys.len() {
655        return Err(ExpressionError::type_error(
656            "Lists must have the same length",
657        ));
658    }
659    Ok((xs, ys))
660}
661
662fn execute_corr(args: &[Value]) -> Result<Value, ExpressionError> {
663    let (xs, ys) = extract_numeric_pairs(args)?;
664    if xs.is_empty() {
665        return Ok(Value::Null(NullType::Null));
666    }
667    let n = xs.len() as f64;
668    let sum_x: f64 = xs.iter().filter(|v| !v.is_nan()).sum();
669    let sum_y: f64 = ys.iter().filter(|v| !v.is_nan()).sum();
670    let sum_xy: f64 = xs
671        .iter()
672        .zip(ys.iter())
673        .filter(|(x, y)| !x.is_nan() && !y.is_nan())
674        .map(|(x, y)| x * y)
675        .sum();
676    let sum_x2: f64 = xs.iter().filter(|v| !v.is_nan()).map(|x| x * x).sum();
677    let sum_y2: f64 = ys.iter().filter(|v| !v.is_nan()).map(|y| y * y).sum();
678    let numerator = n * sum_xy - sum_x * sum_y;
679    let denom = (n * sum_x2 - sum_x * sum_x).sqrt() * (n * sum_y2 - sum_y * sum_y).sqrt();
680    if denom == 0.0 {
681        Ok(Value::Null(NullType::Null))
682    } else {
683        Ok(Value::Double(numerator / denom))
684    }
685}
686
687fn execute_covar_pop(args: &[Value]) -> Result<Value, ExpressionError> {
688    let (xs, ys) = extract_numeric_pairs(args)?;
689    if xs.is_empty() {
690        return Ok(Value::Null(NullType::Null));
691    }
692    let n = xs.len() as f64;
693    let mean_x: f64 = xs.iter().sum::<f64>() / n;
694    let mean_y: f64 = ys.iter().sum::<f64>() / n;
695    let covar: f64 = xs
696        .iter()
697        .zip(ys.iter())
698        .map(|(x, y)| (x - mean_x) * (y - mean_y))
699        .sum::<f64>()
700        / n;
701    Ok(Value::Double(covar))
702}
703
704fn execute_covar_samp(args: &[Value]) -> Result<Value, ExpressionError> {
705    let (xs, ys) = extract_numeric_pairs(args)?;
706    if xs.len() < 2 {
707        return Ok(Value::Null(NullType::Null));
708    }
709    let n = xs.len() as f64;
710    let mean_x: f64 = xs.iter().sum::<f64>() / n;
711    let mean_y: f64 = ys.iter().sum::<f64>() / n;
712    let covar: f64 = xs
713        .iter()
714        .zip(ys.iter())
715        .map(|(x, y)| (x - mean_x) * (y - mean_y))
716        .sum::<f64>()
717        / (n - 1.0);
718    Ok(Value::Double(covar))
719}
720
721fn execute_octet_length(args: &[Value]) -> Result<Value, ExpressionError> {
722    match &args[0] {
723        Value::String(s) => Ok(Value::BigInt(s.len() as i64)),
724        Value::Blob(b) => Ok(Value::BigInt(b.len() as i64)),
725        Value::Null(_) => Ok(Value::Null(NullType::Null)),
726        _ => Err(ExpressionError::type_error(
727            "octet_length requires a string or blob type",
728        )),
729    }
730}
731
732fn execute_encode(args: &[Value]) -> Result<Value, ExpressionError> {
733    match &args[0] {
734        Value::String(s) => Ok(Value::Blob(s.as_bytes().to_vec())),
735        Value::Null(_) => Ok(Value::Null(NullType::Null)),
736        _ => Err(ExpressionError::type_error("encode requires a string type")),
737    }
738}
739
740fn execute_decode(args: &[Value]) -> Result<Value, ExpressionError> {
741    match &args[0] {
742        Value::Blob(b) => {
743            let s = String::from_utf8(b.to_vec()).map_err(|_| {
744                ExpressionError::type_error("decode failed: invalid UTF-8 sequence")
745            })?;
746            Ok(Value::string(s))
747        }
748        Value::Null(_) => Ok(Value::Null(NullType::Null)),
749        _ => Err(ExpressionError::type_error("decode requires a blob type")),
750    }
751}
752
753fn execute_union_value(args: &[Value]) -> Result<Value, ExpressionError> {
754    let tag = match &args[0] {
755        Value::Int(i) => *i,
756        Value::SmallInt(i) => *i as i32,
757        Value::BigInt(i) => *i as i32,
758        Value::Null(_) => return Ok(Value::Null(NullType::Null)),
759        _ => {
760            return Err(ExpressionError::type_error(
761                "union_value first argument must be an integer tag",
762            ))
763        }
764    };
765    if tag < 0 {
766        return Err(ExpressionError::type_error(
767            "union tag must be non-negative",
768        ));
769    }
770    let value = args[1].clone();
771    let mut map = HashMap::new();
772    map.insert(Value::string("__tag"), Value::Int(tag));
773    map.insert(Value::string("__value"), value);
774    Ok(Value::map(map))
775}
776
777fn execute_union_tag(args: &[Value]) -> Result<Value, ExpressionError> {
778    match &args[0] {
779        Value::Map(m) => {
780            let tag_key = Value::string("__tag");
781            match m.get(&tag_key) {
782                Some(Value::Int(tag)) => Ok(Value::Int(*tag)),
783                Some(Value::SmallInt(tag)) => Ok(Value::Int(*tag as i32)),
784                Some(Value::BigInt(tag)) => Ok(Value::Int(*tag as i32)),
785                Some(_) => Err(ExpressionError::type_error("union tag must be an integer")),
786                None => Err(ExpressionError::type_error(
787                    "not a union value: missing __tag",
788                )),
789            }
790        }
791        Value::Null(_) => Ok(Value::Null(NullType::Null)),
792        _ => Err(ExpressionError::type_error(
793            "union_tag requires a union value (map)",
794        )),
795    }
796}
797
798fn execute_union_extract(args: &[Value]) -> Result<Value, ExpressionError> {
799    match &args[0] {
800        Value::Map(m) => {
801            let value_key = Value::string("__value");
802            match m.get(&value_key) {
803                Some(v) => Ok(v.clone()),
804                None => Err(ExpressionError::type_error(
805                    "not a union value: missing __value",
806                )),
807            }
808        }
809        Value::Null(_) => Ok(Value::Null(NullType::Null)),
810        _ => Err(ExpressionError::type_error(
811            "union_extract requires a union value (map)",
812        )),
813    }
814}
815
816#[cfg(test)]
817mod tests;