Skip to main content

fsqlite_func/
builtins.rs

1//! Built-in core scalar functions (§13.1).
2//!
3//! Implements 60+ SQLite scalar functions with exact NULL-propagation
4//! semantics. Functions that need connection state (changes, total_changes,
5//! last_insert_rowid, sqlite_offset) are registered as stubs that will be
6//! wired to connection context when the VDBE is integrated.
7#![allow(
8    clippy::unnecessary_literal_bound,
9    clippy::too_many_lines,
10    clippy::cast_possible_truncation,
11    clippy::cast_possible_wrap,
12    clippy::cast_sign_loss,
13    clippy::fn_params_excessive_bools,
14    clippy::items_after_statements,
15    clippy::match_same_arms,
16    clippy::single_match_else,
17    clippy::manual_let_else,
18    clippy::comparison_chain,
19    clippy::suboptimal_flops,
20    clippy::unnecessary_wraps,
21    clippy::useless_let_if_seq,
22    clippy::redundant_closure_for_method_calls,
23    clippy::manual_ignore_case_cmp
24)]
25
26use std::fmt::Write as _;
27
28use fsqlite_error::{FrankenError, Result};
29use fsqlite_types::SqliteValue;
30
31use crate::agg_builtins::register_aggregate_builtins;
32use crate::datetime::register_datetime_builtins;
33use crate::math::register_math_builtins;
34use crate::{FunctionRegistry, ScalarFunction};
35
36// Thread-local storage for connection state that scalar functions need access to.
37// Set by the Connection during DML operations; read by stub functions like
38// last_insert_rowid(), changes(), total_changes().
39thread_local! {
40    static LAST_INSERT_ROWID: std::cell::Cell<i64> = const { std::cell::Cell::new(0) };
41    static LAST_CHANGES: std::cell::Cell<i64> = const { std::cell::Cell::new(0) };
42}
43
44/// Set the last insert rowid (called by Connection after INSERT).
45pub fn set_last_insert_rowid(rowid: i64) {
46    LAST_INSERT_ROWID.set(rowid);
47}
48
49/// Get the current last insert rowid.
50pub fn get_last_insert_rowid() -> i64 {
51    LAST_INSERT_ROWID.get()
52}
53
54/// Set the last changes count (called by Connection after DML).
55pub fn set_last_changes(count: i64) {
56    LAST_CHANGES.set(count);
57}
58
59/// Get the current last changes count.
60pub fn get_last_changes() -> i64 {
61    LAST_CHANGES.get()
62}
63
64// ── Helpers ───────────────────────────────────────────────────────────────
65
66/// Standard NULL propagation: if any arg is NULL, return NULL.
67fn null_propagate(args: &[SqliteValue]) -> Option<SqliteValue> {
68    if args.iter().any(SqliteValue::is_null) {
69        Some(SqliteValue::Null)
70    } else {
71        None
72    }
73}
74
75/// Try to interpret a value as a numeric (integer preferred, then float).
76fn coerce_numeric(v: &SqliteValue) -> SqliteValue {
77    match v {
78        SqliteValue::Integer(_) | SqliteValue::Float(_) => v.clone(),
79        SqliteValue::Text(s) => {
80            if let Ok(i) = s.parse::<i64>() {
81                SqliteValue::Integer(i)
82            } else if let Ok(f) = s.parse::<f64>() {
83                SqliteValue::Float(f)
84            } else {
85                SqliteValue::Integer(0)
86            }
87        }
88        SqliteValue::Null => SqliteValue::Null,
89        SqliteValue::Blob(_) => SqliteValue::Integer(0),
90    }
91}
92
93// ── abs(X) ────────────────────────────────────────────────────────────────
94
95pub struct AbsFunc;
96
97impl ScalarFunction for AbsFunc {
98    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
99        if args[0].is_null() {
100            return Ok(SqliteValue::Null);
101        }
102        match coerce_numeric(&args[0]) {
103            SqliteValue::Integer(i) => {
104                if i == i64::MIN {
105                    return Err(FrankenError::IntegerOverflow);
106                }
107                Ok(SqliteValue::Integer(i.abs()))
108            }
109            SqliteValue::Float(f) => Ok(SqliteValue::Float(f.abs())),
110            other => Ok(other),
111        }
112    }
113
114    fn num_args(&self) -> i32 {
115        1
116    }
117
118    fn name(&self) -> &str {
119        "abs"
120    }
121}
122
123// ── char(X1, X2, ...) ────────────────────────────────────────────────────
124
125pub struct CharFunc;
126
127impl ScalarFunction for CharFunc {
128    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
129        let mut result = String::new();
130        for arg in args {
131            // NULL args are silently skipped
132            if arg.is_null() {
133                continue;
134            }
135            #[allow(clippy::cast_sign_loss)]
136            let cp = arg.to_integer() as u32;
137            if let Some(c) = char::from_u32(cp) {
138                result.push(c);
139            }
140        }
141        Ok(SqliteValue::Text(result))
142    }
143
144    fn is_deterministic(&self) -> bool {
145        true
146    }
147
148    fn num_args(&self) -> i32 {
149        -1 // variadic
150    }
151
152    fn name(&self) -> &str {
153        "char"
154    }
155}
156
157// ── coalesce(X, Y, ...) ─────────────────────────────────────────────────
158
159pub struct CoalesceFunc;
160
161impl ScalarFunction for CoalesceFunc {
162    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
163        // Return first non-NULL argument.
164        // NOTE: Real short-circuit evaluation happens at the VDBE level.
165        // At the scalar level, all args are already evaluated.
166        for arg in args {
167            if !arg.is_null() {
168                return Ok(arg.clone());
169            }
170        }
171        Ok(SqliteValue::Null)
172    }
173
174    fn num_args(&self) -> i32 {
175        -1
176    }
177
178    fn name(&self) -> &str {
179        "coalesce"
180    }
181}
182
183// ── concat(X, Y, ...) ───────────────────────────────────────────────────
184
185pub struct ConcatFunc;
186
187impl ScalarFunction for ConcatFunc {
188    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
189        let mut result = String::new();
190        for arg in args {
191            // concat treats NULL as empty string (unlike ||)
192            if !arg.is_null() {
193                result.push_str(&arg.to_text());
194            }
195        }
196        Ok(SqliteValue::Text(result))
197    }
198
199    fn num_args(&self) -> i32 {
200        -1
201    }
202
203    fn name(&self) -> &str {
204        "concat"
205    }
206}
207
208// ── concat_ws(SEP, X, Y, ...) ───────────────────────────────────────────
209
210pub struct ConcatWsFunc;
211
212impl ScalarFunction for ConcatWsFunc {
213    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
214        if args.is_empty() {
215            return Ok(SqliteValue::Text(String::new()));
216        }
217        let sep = if args[0].is_null() {
218            String::new()
219        } else {
220            args[0].to_text()
221        };
222        let mut parts = Vec::new();
223        for arg in &args[1..] {
224            // NULL args are skipped entirely
225            if !arg.is_null() {
226                parts.push(arg.to_text());
227            }
228        }
229        Ok(SqliteValue::Text(parts.join(&sep)))
230    }
231
232    fn num_args(&self) -> i32 {
233        -1
234    }
235
236    fn name(&self) -> &str {
237        "concat_ws"
238    }
239}
240
241// ── hex(X) ───────────────────────────────────────────────────────────────
242
243pub struct HexFunc;
244
245impl ScalarFunction for HexFunc {
246    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
247        if args[0].is_null() {
248            return Ok(SqliteValue::Null);
249        }
250        let bytes = match &args[0] {
251            SqliteValue::Blob(b) => b.clone(),
252            // For non-blob: convert to text first, then hex-encode UTF-8 bytes
253            other => other.to_text().into_bytes(),
254        };
255        let mut hex = String::with_capacity(bytes.len() * 2);
256        for b in &bytes {
257            let _ = write!(hex, "{b:02X}");
258        }
259        Ok(SqliteValue::Text(hex))
260    }
261
262    fn num_args(&self) -> i32 {
263        1
264    }
265
266    fn name(&self) -> &str {
267        "hex"
268    }
269}
270
271// ── ifnull(X, Y) ────────────────────────────────────────────────────────
272
273pub struct IfnullFunc;
274
275impl ScalarFunction for IfnullFunc {
276    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
277        if args[0].is_null() {
278            Ok(args[1].clone())
279        } else {
280            Ok(args[0].clone())
281        }
282    }
283
284    fn num_args(&self) -> i32 {
285        2
286    }
287
288    fn name(&self) -> &str {
289        "ifnull"
290    }
291}
292
293// ── iif(COND, TRUE_VAL, FALSE_VAL) ──────────────────────────────────────
294
295pub struct IifFunc;
296
297impl ScalarFunction for IifFunc {
298    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
299        // NOTE: Real short-circuit happens at VDBE level.
300        let cond = &args[0];
301        let is_true = match cond {
302            SqliteValue::Null => false,
303            SqliteValue::Integer(i) => *i != 0,
304            SqliteValue::Float(f) => *f != 0.0,
305            SqliteValue::Text(s) => s.parse::<f64>().is_ok_and(|f| f != 0.0),
306            SqliteValue::Blob(_) => false,
307        };
308        if is_true {
309            Ok(args[1].clone())
310        } else if args.len() > 2 {
311            Ok(args[2].clone())
312        } else {
313            // Two-argument form: iif(COND, X) returns NULL when false
314            Ok(SqliteValue::Null)
315        }
316    }
317
318    fn num_args(&self) -> i32 {
319        -1 // 2 or 3 args
320    }
321
322    fn name(&self) -> &str {
323        "iif"
324    }
325}
326
327// ── instr(X, Y) ─────────────────────────────────────────────────────────
328
329pub struct InstrFunc;
330
331impl ScalarFunction for InstrFunc {
332    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
333        if let Some(null) = null_propagate(args) {
334            return Ok(null);
335        }
336        match (&args[0], &args[1]) {
337            (SqliteValue::Blob(haystack), SqliteValue::Blob(needle)) => {
338                // Blob: byte-level search
339                let pos = find_bytes(haystack, needle).map_or(0, |p| p + 1);
340                Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
341            }
342            _ => {
343                // Text: character-level search
344                let haystack = args[0].to_text();
345                let needle = args[1].to_text();
346                let pos = haystack
347                    .find(&needle)
348                    .map_or(0, |byte_pos| haystack[..byte_pos].chars().count() + 1);
349                Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
350            }
351        }
352    }
353
354    fn num_args(&self) -> i32 {
355        2
356    }
357
358    fn name(&self) -> &str {
359        "instr"
360    }
361}
362
363fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
364    if needle.is_empty() {
365        return Some(0);
366    }
367    haystack.windows(needle.len()).position(|w| w == needle)
368}
369
370// ── length(X) ────────────────────────────────────────────────────────────
371
372pub struct LengthFunc;
373
374impl ScalarFunction for LengthFunc {
375    #[allow(clippy::cast_possible_wrap)]
376    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
377        if args[0].is_null() {
378            return Ok(SqliteValue::Null);
379        }
380        let len = match &args[0] {
381            SqliteValue::Text(s) => s.chars().count(),
382            SqliteValue::Blob(b) => b.len(),
383            // Numbers: length of text representation
384            other => other.to_text().chars().count(),
385        };
386        Ok(SqliteValue::Integer(len as i64))
387    }
388
389    fn num_args(&self) -> i32 {
390        1
391    }
392
393    fn name(&self) -> &str {
394        "length"
395    }
396}
397
398// ── octet_length(X) ─────────────────────────────────────────────────────
399
400pub struct OctetLengthFunc;
401
402impl ScalarFunction for OctetLengthFunc {
403    #[allow(clippy::cast_possible_wrap)]
404    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
405        if args[0].is_null() {
406            return Ok(SqliteValue::Null);
407        }
408        let len = match &args[0] {
409            SqliteValue::Text(s) => s.len(),
410            SqliteValue::Blob(b) => b.len(),
411            other => other.to_text().len(),
412        };
413        Ok(SqliteValue::Integer(len as i64))
414    }
415
416    fn num_args(&self) -> i32 {
417        1
418    }
419
420    fn name(&self) -> &str {
421        "octet_length"
422    }
423}
424
425// ── lower(X) / upper(X) ─────────────────────────────────────────────────
426
427pub struct LowerFunc;
428
429impl ScalarFunction for LowerFunc {
430    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
431        if args[0].is_null() {
432            return Ok(SqliteValue::Null);
433        }
434        Ok(SqliteValue::Text(args[0].to_text().to_ascii_lowercase()))
435    }
436
437    fn num_args(&self) -> i32 {
438        1
439    }
440
441    fn name(&self) -> &str {
442        "lower"
443    }
444}
445
446pub struct UpperFunc;
447
448impl ScalarFunction for UpperFunc {
449    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
450        if args[0].is_null() {
451            return Ok(SqliteValue::Null);
452        }
453        Ok(SqliteValue::Text(args[0].to_text().to_ascii_uppercase()))
454    }
455
456    fn num_args(&self) -> i32 {
457        1
458    }
459
460    fn name(&self) -> &str {
461        "upper"
462    }
463}
464
465// ── trim/ltrim/rtrim ────────────────────────────────────────────────────
466
467pub struct TrimFunc;
468pub struct LtrimFunc;
469pub struct RtrimFunc;
470
471fn trim_chars(s: &str, chars: &str) -> String {
472    let char_set: Vec<char> = chars.chars().collect();
473    s.trim_matches(|c: char| char_set.contains(&c)).to_owned()
474}
475
476fn ltrim_chars(s: &str, chars: &str) -> String {
477    let char_set: Vec<char> = chars.chars().collect();
478    s.trim_start_matches(|c: char| char_set.contains(&c))
479        .to_owned()
480}
481
482fn rtrim_chars(s: &str, chars: &str) -> String {
483    let char_set: Vec<char> = chars.chars().collect();
484    s.trim_end_matches(|c: char| char_set.contains(&c))
485        .to_owned()
486}
487
488impl ScalarFunction for TrimFunc {
489    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
490        if args[0].is_null() {
491            return Ok(SqliteValue::Null);
492        }
493        let s = args[0].to_text();
494        let chars = if args.len() > 1 && !args[1].is_null() {
495            args[1].to_text()
496        } else {
497            " ".to_owned()
498        };
499        Ok(SqliteValue::Text(trim_chars(&s, &chars)))
500    }
501
502    fn num_args(&self) -> i32 {
503        -1 // 1 or 2 args
504    }
505
506    fn name(&self) -> &str {
507        "trim"
508    }
509}
510
511impl ScalarFunction for LtrimFunc {
512    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
513        if args[0].is_null() {
514            return Ok(SqliteValue::Null);
515        }
516        let s = args[0].to_text();
517        let chars = if args.len() > 1 && !args[1].is_null() {
518            args[1].to_text()
519        } else {
520            " ".to_owned()
521        };
522        Ok(SqliteValue::Text(ltrim_chars(&s, &chars)))
523    }
524
525    fn num_args(&self) -> i32 {
526        -1
527    }
528
529    fn name(&self) -> &str {
530        "ltrim"
531    }
532}
533
534impl ScalarFunction for RtrimFunc {
535    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
536        if args[0].is_null() {
537            return Ok(SqliteValue::Null);
538        }
539        let s = args[0].to_text();
540        let chars = if args.len() > 1 && !args[1].is_null() {
541            args[1].to_text()
542        } else {
543            " ".to_owned()
544        };
545        Ok(SqliteValue::Text(rtrim_chars(&s, &chars)))
546    }
547
548    fn num_args(&self) -> i32 {
549        -1
550    }
551
552    fn name(&self) -> &str {
553        "rtrim"
554    }
555}
556
557// ── nullif(X, Y) ────────────────────────────────────────────────────────
558
559pub struct NullifFunc;
560
561impl ScalarFunction for NullifFunc {
562    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
563        if args[0] == args[1] {
564            Ok(SqliteValue::Null)
565        } else {
566            Ok(args[0].clone())
567        }
568    }
569
570    fn num_args(&self) -> i32 {
571        2
572    }
573
574    fn name(&self) -> &str {
575        "nullif"
576    }
577}
578
579// ── typeof(X) ────────────────────────────────────────────────────────────
580
581pub struct TypeofFunc;
582
583impl ScalarFunction for TypeofFunc {
584    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
585        let type_name = match &args[0] {
586            SqliteValue::Null => "null",
587            SqliteValue::Integer(_) => "integer",
588            SqliteValue::Float(_) => "real",
589            SqliteValue::Text(_) => "text",
590            SqliteValue::Blob(_) => "blob",
591        };
592        Ok(SqliteValue::Text(type_name.to_owned()))
593    }
594
595    fn num_args(&self) -> i32 {
596        1
597    }
598
599    fn name(&self) -> &str {
600        "typeof"
601    }
602}
603
604// ── subtype(X) ───────────────────────────────────────────────────────────
605
606pub struct SubtypeFunc;
607
608impl ScalarFunction for SubtypeFunc {
609    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
610        // subtype(NULL) = 0 (does NOT propagate NULL)
611        // Without subtype tags in SqliteValue, always return 0.
612        Ok(SqliteValue::Integer(0))
613    }
614
615    fn num_args(&self) -> i32 {
616        1
617    }
618
619    fn name(&self) -> &str {
620        "subtype"
621    }
622}
623
624// ── replace(X, Y, Z) ────────────────────────────────────────────────────
625
626pub struct ReplaceFunc;
627
628impl ScalarFunction for ReplaceFunc {
629    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
630        if let Some(null) = null_propagate(args) {
631            return Ok(null);
632        }
633        let x = args[0].to_text();
634        let y = args[1].to_text();
635        let z = args[2].to_text();
636        if y.is_empty() {
637            return Ok(SqliteValue::Text(x));
638        }
639        Ok(SqliteValue::Text(x.replace(&y, &z)))
640    }
641
642    fn num_args(&self) -> i32 {
643        3
644    }
645
646    fn name(&self) -> &str {
647        "replace"
648    }
649}
650
651// ── round(X [, N]) ──────────────────────────────────────────────────────
652
653pub struct RoundFunc;
654
655impl ScalarFunction for RoundFunc {
656    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
657        if args[0].is_null() {
658            return Ok(SqliteValue::Null);
659        }
660        let x = args[0].to_float();
661        // Clamp N to [0, 30] matching SQLite behavior.
662        let n = if args.len() > 1 && !args[1].is_null() {
663            args[1].to_integer().clamp(0, 30)
664        } else {
665            0
666        };
667        // Values beyond 2^52 have no fractional part — return unchanged
668        if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
669            return Ok(SqliteValue::Float(x));
670        }
671        // Round half away from zero (NOT banker's rounding).
672        // n is in [0, 30] so the i32 cast is lossless.
673        #[allow(clippy::cast_possible_truncation)]
674        let factor = 10.0_f64.powi(n as i32);
675        let rounded = if x >= 0.0 {
676            (x * factor + 0.5).floor() / factor
677        } else {
678            (x * factor - 0.5).ceil() / factor
679        };
680        Ok(SqliteValue::Float(rounded))
681    }
682
683    fn num_args(&self) -> i32 {
684        -1 // 1 or 2 args
685    }
686
687    fn name(&self) -> &str {
688        "round"
689    }
690}
691
692// ── sign(X) ──────────────────────────────────────────────────────────────
693
694pub struct SignFunc;
695
696impl ScalarFunction for SignFunc {
697    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
698        if args[0].is_null() {
699            return Ok(SqliteValue::Null);
700        }
701        match &args[0] {
702            SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
703            SqliteValue::Float(f) => {
704                if *f > 0.0 {
705                    Ok(SqliteValue::Integer(1))
706                } else if *f < 0.0 {
707                    Ok(SqliteValue::Integer(-1))
708                } else {
709                    Ok(SqliteValue::Integer(0))
710                }
711            }
712            SqliteValue::Text(s) => {
713                // Non-numeric strings => NULL per spec
714                if let Ok(i) = s.parse::<i64>() {
715                    Ok(SqliteValue::Integer(i.signum()))
716                } else if let Ok(f) = s.parse::<f64>() {
717                    if f > 0.0 {
718                        Ok(SqliteValue::Integer(1))
719                    } else if f < 0.0 {
720                        Ok(SqliteValue::Integer(-1))
721                    } else {
722                        Ok(SqliteValue::Integer(0))
723                    }
724                } else {
725                    Ok(SqliteValue::Null)
726                }
727            }
728            SqliteValue::Blob(_) => Ok(SqliteValue::Null),
729            SqliteValue::Null => Ok(SqliteValue::Null),
730        }
731    }
732
733    fn num_args(&self) -> i32 {
734        1
735    }
736
737    fn name(&self) -> &str {
738        "sign"
739    }
740}
741
742// ── random() ─────────────────────────────────────────────────────────────
743
744pub struct RandomFunc;
745
746impl ScalarFunction for RandomFunc {
747    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
748        // Simple PRNG using thread_rng is fine for SQLite's random()
749        // which is explicitly non-cryptographic.
750        let val = simple_random_i64();
751        Ok(SqliteValue::Integer(val))
752    }
753
754    fn is_deterministic(&self) -> bool {
755        false
756    }
757
758    fn num_args(&self) -> i32 {
759        0
760    }
761
762    fn name(&self) -> &str {
763        "random"
764    }
765}
766
767/// Simple deterministic-enough PRNG for SQLite's random().
768fn simple_random_i64() -> i64 {
769    // Deterministic per-process PRNG (no ambient authority).
770    // Not cryptographic, matching SQLite's random()/randomblob() semantics.
771    //
772    // splitmix64: fast, decent statistical properties, and requires only a u64 state.
773    use std::sync::atomic::{AtomicU64, Ordering};
774
775    static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
776    let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
777    x ^= x >> 30;
778    x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
779    x ^= x >> 27;
780    x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
781    x ^= x >> 31;
782    x as i64
783}
784
785// ── randomblob(N) ────────────────────────────────────────────────────────
786
787pub struct RandomblobFunc;
788
789impl ScalarFunction for RandomblobFunc {
790    #[allow(clippy::cast_sign_loss)]
791    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
792        if args[0].is_null() {
793            return Ok(SqliteValue::Null);
794        }
795        let n = args[0].to_integer().max(0) as usize;
796        let mut buf = vec![0u8; n];
797        let mut i = 0;
798        while i < n {
799            let rnd = simple_random_i64().to_ne_bytes();
800            let to_copy = (n - i).min(8);
801            buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
802            i += to_copy;
803        }
804        Ok(SqliteValue::Blob(buf))
805    }
806
807    fn is_deterministic(&self) -> bool {
808        false
809    }
810
811    fn num_args(&self) -> i32 {
812        1
813    }
814
815    fn name(&self) -> &str {
816        "randomblob"
817    }
818}
819
820// ── zeroblob(N) ──────────────────────────────────────────────────────────
821
822pub struct ZeroblobFunc;
823
824impl ScalarFunction for ZeroblobFunc {
825    #[allow(clippy::cast_sign_loss)]
826    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
827        if args[0].is_null() {
828            return Ok(SqliteValue::Null);
829        }
830        let n = args[0].to_integer().max(0) as usize;
831        Ok(SqliteValue::Blob(vec![0u8; n]))
832    }
833
834    fn num_args(&self) -> i32 {
835        1
836    }
837
838    fn name(&self) -> &str {
839        "zeroblob"
840    }
841}
842
843// ── quote(X) ─────────────────────────────────────────────────────────────
844
845pub struct QuoteFunc;
846
847impl ScalarFunction for QuoteFunc {
848    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
849        let result = match &args[0] {
850            SqliteValue::Null => "NULL".to_owned(),
851            SqliteValue::Integer(i) => i.to_string(),
852            SqliteValue::Float(f) => format!("{f}"),
853            SqliteValue::Text(s) => {
854                let escaped = s.replace('\'', "''");
855                format!("'{escaped}'")
856            }
857            SqliteValue::Blob(b) => {
858                let mut hex = String::with_capacity(3 + b.len() * 2);
859                hex.push_str("X'");
860                for byte in b {
861                    let _ = write!(hex, "{byte:02X}");
862                }
863                hex.push('\'');
864                hex
865            }
866        };
867        Ok(SqliteValue::Text(result))
868    }
869
870    fn num_args(&self) -> i32 {
871        1
872    }
873
874    fn name(&self) -> &str {
875        "quote"
876    }
877}
878
879// ── unhex(X [, Y]) ──────────────────────────────────────────────────────
880
881pub struct UnhexFunc;
882
883impl ScalarFunction for UnhexFunc {
884    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
885        if args[0].is_null() {
886            return Ok(SqliteValue::Null);
887        }
888        let input = args[0].to_text();
889        let ignore_chars: Vec<char> = if args.len() > 1 && !args[1].is_null() {
890            args[1].to_text().chars().collect()
891        } else {
892            Vec::new()
893        };
894
895        // Filter out ignored characters
896        let filtered: String = input
897            .chars()
898            .filter(|c| !ignore_chars.contains(c))
899            .collect();
900
901        // Must have even number of hex digits
902        if filtered.len() % 2 != 0 {
903            return Ok(SqliteValue::Null);
904        }
905
906        let mut bytes = Vec::with_capacity(filtered.len() / 2);
907        let chars: Vec<char> = filtered.chars().collect();
908        let mut i = 0;
909        while i < chars.len() {
910            let hi = match hex_digit(chars[i]) {
911                Some(v) => v,
912                None => return Ok(SqliteValue::Null),
913            };
914            let lo = match hex_digit(chars[i + 1]) {
915                Some(v) => v,
916                None => return Ok(SqliteValue::Null),
917            };
918            bytes.push(hi << 4 | lo);
919            i += 2;
920        }
921        Ok(SqliteValue::Blob(bytes))
922    }
923
924    fn num_args(&self) -> i32 {
925        -1 // 1 or 2 args
926    }
927
928    fn name(&self) -> &str {
929        "unhex"
930    }
931}
932
933fn hex_digit(c: char) -> Option<u8> {
934    match c {
935        '0'..='9' => Some(c as u8 - b'0'),
936        'a'..='f' => Some(c as u8 - b'a' + 10),
937        'A'..='F' => Some(c as u8 - b'A' + 10),
938        _ => None,
939    }
940}
941
942// ── unicode(X) ───────────────────────────────────────────────────────────
943
944pub struct UnicodeFunc;
945
946impl ScalarFunction for UnicodeFunc {
947    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
948        if args[0].is_null() {
949            return Ok(SqliteValue::Null);
950        }
951        let s = args[0].to_text();
952        match s.chars().next() {
953            Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
954            None => Ok(SqliteValue::Null),
955        }
956    }
957
958    fn num_args(&self) -> i32 {
959        1
960    }
961
962    fn name(&self) -> &str {
963        "unicode"
964    }
965}
966
967// ── substr(X, START [, LENGTH]) / substring() ───────────────────────────
968
969pub struct SubstrFunc;
970
971impl ScalarFunction for SubstrFunc {
972    #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
973    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
974        if args[0].is_null() {
975            return Ok(SqliteValue::Null);
976        }
977        let is_blob = matches!(&args[0], SqliteValue::Blob(_));
978        if is_blob {
979            return self.invoke_blob(args);
980        }
981
982        let s = args[0].to_text();
983        let chars: Vec<char> = s.chars().collect();
984        let char_count = chars.len() as i64;
985        let start = args[1].to_integer();
986        let has_length = args.len() > 2 && !args[2].is_null();
987        let length = if has_length {
988            args[2].to_integer()
989        } else {
990            char_count + 1
991        };
992
993        // SQLite substr semantics:
994        // start=0 quirk: with length>0, returns max(length-1,0) chars from start
995        // negative start counts from end
996        // negative length returns chars preceding start
997
998        if length < 0 {
999            // Negative length: characters BEFORE the starting position
1000            let abs_len = length.unsigned_abs();
1001            let end_pos = if start > 0 {
1002                (start - 1).min(char_count)
1003            } else if start == 0 {
1004                0
1005            } else {
1006                (char_count + start + 1).max(0).min(char_count)
1007            };
1008            let start_pos = (end_pos - abs_len as i64).max(0);
1009            let result: String = chars[start_pos as usize..end_pos as usize].iter().collect();
1010            return Ok(SqliteValue::Text(result));
1011        }
1012
1013        let (begin, len) = if start > 0 {
1014            ((start - 1).max(0) as usize, length as usize)
1015        } else if start == 0 {
1016            // START=0 quirk: returns max(length-1, 0) chars from beginning
1017            (0, (length - 1).max(0) as usize)
1018        } else {
1019            // Negative start: count from end
1020            let effective = char_count + start; // e.g. -2 on "hello"(5) => 3
1021            if effective < 0 {
1022                let skip = effective.unsigned_abs() as usize;
1023                (0, length as usize - skip.min(length as usize))
1024            } else {
1025                (effective as usize, length as usize)
1026            }
1027        };
1028
1029        let result: String = chars.iter().skip(begin).take(len).collect();
1030        Ok(SqliteValue::Text(result))
1031    }
1032
1033    fn num_args(&self) -> i32 {
1034        -1 // 2 or 3 args
1035    }
1036
1037    fn name(&self) -> &str {
1038        "substr"
1039    }
1040}
1041
1042impl SubstrFunc {
1043    #[allow(clippy::unused_self)]
1044    fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1045        let blob = match &args[0] {
1046            SqliteValue::Blob(b) => b,
1047            _ => return Ok(SqliteValue::Null),
1048        };
1049        let blob_len = blob.len() as i64;
1050        let start = args[1].to_integer();
1051        let has_length = args.len() > 2 && !args[2].is_null();
1052        let length = if has_length {
1053            args[2].to_integer()
1054        } else {
1055            blob_len + 1
1056        };
1057
1058        if length < 0 {
1059            let abs_len = length.unsigned_abs();
1060            let end_pos = if start > 0 {
1061                (start - 1).min(blob_len)
1062            } else if start == 0 {
1063                0
1064            } else {
1065                (blob_len + start + 1).max(0).min(blob_len)
1066            };
1067            let start_pos = (end_pos - abs_len as i64).max(0);
1068            return Ok(SqliteValue::Blob(
1069                blob[start_pos as usize..end_pos as usize].to_vec(),
1070            ));
1071        }
1072
1073        let (begin, len) = if start > 0 {
1074            ((start - 1).max(0) as usize, length as usize)
1075        } else if start == 0 {
1076            (0, (length - 1).max(0) as usize)
1077        } else {
1078            let effective = blob_len + start;
1079            if effective < 0 {
1080                let skip = effective.unsigned_abs() as usize;
1081                (0, length as usize - skip.min(length as usize))
1082            } else {
1083                (effective as usize, length as usize)
1084            }
1085        };
1086
1087        let end = (begin + len).min(blob.len());
1088        Ok(SqliteValue::Blob(blob[begin..end].to_vec()))
1089    }
1090}
1091
1092// ── soundex(X) ───────────────────────────────────────────────────────────
1093
1094pub struct SoundexFunc;
1095
1096impl ScalarFunction for SoundexFunc {
1097    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1098        if args[0].is_null() {
1099            // SQLite returns "?000" for SOUNDEX(NULL), not NULL.
1100            return Ok(SqliteValue::Text("?000".to_owned()));
1101        }
1102        let s = args[0].to_text();
1103        Ok(SqliteValue::Text(soundex(&s)))
1104    }
1105
1106    fn num_args(&self) -> i32 {
1107        1
1108    }
1109
1110    fn name(&self) -> &str {
1111        "soundex"
1112    }
1113}
1114
1115fn soundex(s: &str) -> String {
1116    let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1117    let first = match chars.next() {
1118        Some(c) => c.to_ascii_uppercase(),
1119        None => return "?000".to_owned(),
1120    };
1121
1122    let code = |c: char| -> Option<char> {
1123        match c.to_ascii_uppercase() {
1124            'B' | 'F' | 'P' | 'V' => Some('1'),
1125            'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some('2'),
1126            'D' | 'T' => Some('3'),
1127            'L' => Some('4'),
1128            'M' | 'N' => Some('5'),
1129            'R' => Some('6'),
1130            _ => None, // A, E, I, O, U, H, W, Y
1131        }
1132    };
1133
1134    let mut result = String::with_capacity(4);
1135    result.push(first);
1136    let mut last_code = code(first);
1137
1138    for c in chars {
1139        if result.len() >= 4 {
1140            break;
1141        }
1142        let current = code(c);
1143        if let Some(digit) = current {
1144            if current != last_code {
1145                result.push(digit);
1146            }
1147        }
1148        last_code = current;
1149    }
1150
1151    while result.len() < 4 {
1152        result.push('0');
1153    }
1154    result
1155}
1156
1157// ── scalar max(X, Y, ...) ───────────────────────────────────────────────
1158
1159pub struct ScalarMaxFunc;
1160
1161impl ScalarFunction for ScalarMaxFunc {
1162    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1163        // Scalar max: if ANY argument is NULL, returns NULL
1164        if let Some(null) = null_propagate(args) {
1165            return Ok(null);
1166        }
1167        let mut max = &args[0];
1168        for arg in &args[1..] {
1169            if arg.partial_cmp(max) == Some(std::cmp::Ordering::Greater) {
1170                max = arg;
1171            }
1172        }
1173        Ok(max.clone())
1174    }
1175
1176    fn num_args(&self) -> i32 {
1177        -1
1178    }
1179
1180    fn name(&self) -> &str {
1181        "max"
1182    }
1183}
1184
1185// ── scalar min(X, Y, ...) ───────────────────────────────────────────────
1186
1187pub struct ScalarMinFunc;
1188
1189impl ScalarFunction for ScalarMinFunc {
1190    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1191        // Scalar min: if ANY argument is NULL, returns NULL
1192        if let Some(null) = null_propagate(args) {
1193            return Ok(null);
1194        }
1195        let mut min = &args[0];
1196        for arg in &args[1..] {
1197            if arg.partial_cmp(min) == Some(std::cmp::Ordering::Less) {
1198                min = arg;
1199            }
1200        }
1201        Ok(min.clone())
1202    }
1203
1204    fn num_args(&self) -> i32 {
1205        -1
1206    }
1207
1208    fn name(&self) -> &str {
1209        "min"
1210    }
1211}
1212
1213// ── likelihood/likely/unlikely ──────────────────────────────────────────
1214
1215pub struct LikelihoodFunc;
1216
1217impl ScalarFunction for LikelihoodFunc {
1218    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1219        // Returns X unchanged; P is a planner hint (ignored at runtime).
1220        Ok(args[0].clone())
1221    }
1222
1223    fn num_args(&self) -> i32 {
1224        2
1225    }
1226
1227    fn name(&self) -> &str {
1228        "likelihood"
1229    }
1230}
1231
1232pub struct LikelyFunc;
1233
1234impl ScalarFunction for LikelyFunc {
1235    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1236        Ok(args[0].clone())
1237    }
1238
1239    fn num_args(&self) -> i32 {
1240        1
1241    }
1242
1243    fn name(&self) -> &str {
1244        "likely"
1245    }
1246}
1247
1248pub struct UnlikelyFunc;
1249
1250impl ScalarFunction for UnlikelyFunc {
1251    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1252        Ok(args[0].clone())
1253    }
1254
1255    fn num_args(&self) -> i32 {
1256        1
1257    }
1258
1259    fn name(&self) -> &str {
1260        "unlikely"
1261    }
1262}
1263
1264// ── sqlite_version() ────────────────────────────────────────────────────
1265
1266pub struct SqliteVersionFunc;
1267
1268impl ScalarFunction for SqliteVersionFunc {
1269    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1270        Ok(SqliteValue::Text("3.52.0".to_owned()))
1271    }
1272
1273    fn num_args(&self) -> i32 {
1274        0
1275    }
1276
1277    fn name(&self) -> &str {
1278        "sqlite_version"
1279    }
1280}
1281
1282// ── sqlite_source_id() ──────────────────────────────────────────────────
1283
1284pub struct SqliteSourceIdFunc;
1285
1286impl ScalarFunction for SqliteSourceIdFunc {
1287    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1288        Ok(SqliteValue::Text(
1289            "FrankenSQLite 0.1.0 (compatible with SQLite 3.52.0)".to_owned(),
1290        ))
1291    }
1292
1293    fn num_args(&self) -> i32 {
1294        0
1295    }
1296
1297    fn name(&self) -> &str {
1298        "sqlite_source_id"
1299    }
1300}
1301
1302// ── sqlite_compileoption_used(X) ────────────────────────────────────────
1303
1304pub struct SqliteCompileoptionUsedFunc;
1305
1306impl ScalarFunction for SqliteCompileoptionUsedFunc {
1307    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1308        if args[0].is_null() {
1309            return Ok(SqliteValue::Null);
1310        }
1311        let opt = args[0].to_text().to_ascii_uppercase();
1312        // Report our known compile options
1313        let known = matches!(
1314            opt.as_str(),
1315            "THREADSAFE" | "ENABLE_FTS5" | "ENABLE_JSON1" | "ENABLE_RTREE"
1316        );
1317        Ok(SqliteValue::Integer(i64::from(known)))
1318    }
1319
1320    fn num_args(&self) -> i32 {
1321        1
1322    }
1323
1324    fn name(&self) -> &str {
1325        "sqlite_compileoption_used"
1326    }
1327}
1328
1329// ── sqlite_compileoption_get(N) ─────────────────────────────────────────
1330
1331pub struct SqliteCompileoptionGetFunc;
1332
1333impl ScalarFunction for SqliteCompileoptionGetFunc {
1334    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1335        let n = args[0].to_integer();
1336        let options = [
1337            "THREADSAFE=1",
1338            "ENABLE_FTS5",
1339            "ENABLE_JSON1",
1340            "ENABLE_RTREE",
1341        ];
1342        #[allow(clippy::cast_sign_loss)]
1343        match options.get(n as usize) {
1344            Some(opt) => Ok(SqliteValue::Text((*opt).to_owned())),
1345            None => Ok(SqliteValue::Null),
1346        }
1347    }
1348
1349    fn num_args(&self) -> i32 {
1350        1
1351    }
1352
1353    fn name(&self) -> &str {
1354        "sqlite_compileoption_get"
1355    }
1356}
1357
1358// ── like(PATTERN, STRING [, ESCAPE]) ────────────────────────────────────
1359
1360pub struct LikeFunc;
1361
1362impl ScalarFunction for LikeFunc {
1363    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1364        if let Some(null) = null_propagate(args) {
1365            return Ok(null);
1366        }
1367        let pattern = args[0].to_text();
1368        let string = args[1].to_text();
1369        let escape = if args.len() > 2 && !args[2].is_null() {
1370            args[2].to_text().chars().next()
1371        } else {
1372            None
1373        };
1374        let matched = like_match(&pattern, &string, escape);
1375        Ok(SqliteValue::Integer(i64::from(matched)))
1376    }
1377
1378    fn num_args(&self) -> i32 {
1379        -1 // 2 or 3 args
1380    }
1381
1382    fn name(&self) -> &str {
1383        "like"
1384    }
1385}
1386
1387/// LIKE pattern matching (case-insensitive for ASCII).
1388fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
1389    let pat: Vec<char> = pattern.chars().collect();
1390    let txt: Vec<char> = string.chars().collect();
1391    like_match_inner(&pat, &txt, 0, 0, escape)
1392}
1393
1394fn like_match_inner(
1395    pat: &[char],
1396    txt: &[char],
1397    mut pi: usize,
1398    mut ti: usize,
1399    escape: Option<char>,
1400) -> bool {
1401    while pi < pat.len() {
1402        let pc = pat[pi];
1403
1404        if Some(pc) == escape {
1405            // Next char is literal
1406            pi += 1;
1407            if pi >= pat.len() {
1408                return false;
1409            }
1410            if ti >= txt.len() {
1411                return false;
1412            }
1413            if !ascii_iequal(pat[pi], txt[ti]) {
1414                return false;
1415            }
1416            pi += 1;
1417            ti += 1;
1418            continue;
1419        }
1420
1421        match pc {
1422            '%' => {
1423                // Skip consecutive %
1424                while pi < pat.len() && pat[pi] == '%' {
1425                    pi += 1;
1426                }
1427                if pi >= pat.len() {
1428                    return true; // trailing % matches everything
1429                }
1430                // Try matching rest of pattern at every position
1431                for start in ti..=txt.len() {
1432                    if like_match_inner(pat, txt, pi, start, escape) {
1433                        return true;
1434                    }
1435                }
1436                return false;
1437            }
1438            '_' => {
1439                if ti >= txt.len() {
1440                    return false;
1441                }
1442                pi += 1;
1443                ti += 1;
1444            }
1445            _ => {
1446                if ti >= txt.len() {
1447                    return false;
1448                }
1449                if !ascii_iequal(pc, txt[ti]) {
1450                    return false;
1451                }
1452                pi += 1;
1453                ti += 1;
1454            }
1455        }
1456    }
1457    ti >= txt.len()
1458}
1459
1460fn ascii_iequal(a: char, b: char) -> bool {
1461    a.to_ascii_lowercase() == b.to_ascii_lowercase()
1462}
1463
1464// ── glob(PATTERN, STRING) ───────────────────────────────────────────────
1465
1466pub struct GlobFunc;
1467
1468impl ScalarFunction for GlobFunc {
1469    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1470        if let Some(null) = null_propagate(args) {
1471            return Ok(null);
1472        }
1473        let pattern = args[0].to_text();
1474        let string = args[1].to_text();
1475        let matched = glob_match(&pattern, &string);
1476        Ok(SqliteValue::Integer(i64::from(matched)))
1477    }
1478
1479    fn num_args(&self) -> i32 {
1480        2
1481    }
1482
1483    fn name(&self) -> &str {
1484        "glob"
1485    }
1486}
1487
1488/// GLOB pattern matching (case-sensitive, * and ? wildcards).
1489fn glob_match(pattern: &str, string: &str) -> bool {
1490    let pat: Vec<char> = pattern.chars().collect();
1491    let txt: Vec<char> = string.chars().collect();
1492    glob_match_inner(&pat, &txt, 0, 0)
1493}
1494
1495fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
1496    while pi < pat.len() {
1497        match pat[pi] {
1498            '*' => {
1499                while pi < pat.len() && pat[pi] == '*' {
1500                    pi += 1;
1501                }
1502                if pi >= pat.len() {
1503                    return true;
1504                }
1505                for start in ti..=txt.len() {
1506                    if glob_match_inner(pat, txt, pi, start) {
1507                        return true;
1508                    }
1509                }
1510                return false;
1511            }
1512            '?' => {
1513                if ti >= txt.len() {
1514                    return false;
1515                }
1516                pi += 1;
1517                ti += 1;
1518            }
1519            '[' => {
1520                if ti >= txt.len() {
1521                    return false;
1522                }
1523                pi += 1;
1524                let negate = pi < pat.len() && pat[pi] == '^';
1525                if negate {
1526                    pi += 1;
1527                }
1528                let mut found = false;
1529                let mut first = true;
1530                while pi < pat.len() && (first || pat[pi] != ']') {
1531                    first = false;
1532                    if pi + 2 < pat.len() && pat[pi + 1] == '-' {
1533                        let lo = pat[pi];
1534                        let hi = pat[pi + 2];
1535                        if txt[ti] >= lo && txt[ti] <= hi {
1536                            found = true;
1537                        }
1538                        pi += 3;
1539                    } else {
1540                        if txt[ti] == pat[pi] {
1541                            found = true;
1542                        }
1543                        pi += 1;
1544                    }
1545                }
1546                if pi < pat.len() && pat[pi] == ']' {
1547                    pi += 1;
1548                }
1549                if found == negate {
1550                    return false;
1551                }
1552                ti += 1;
1553            }
1554            c => {
1555                if ti >= txt.len() || txt[ti] != c {
1556                    return false;
1557                }
1558                pi += 1;
1559                ti += 1;
1560            }
1561        }
1562    }
1563    ti >= txt.len()
1564}
1565
1566// ── unistr(X) ───────────────────────────────────────────────────────────
1567
1568pub struct UnistrFunc;
1569
1570impl ScalarFunction for UnistrFunc {
1571    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1572        if args[0].is_null() {
1573            return Ok(SqliteValue::Null);
1574        }
1575        let input = args[0].to_text();
1576        let mut result = String::new();
1577        let chars: Vec<char> = input.chars().collect();
1578        let mut i = 0;
1579        while i < chars.len() {
1580            if chars[i] == '\\' && i + 1 < chars.len() {
1581                if chars[i + 1] == 'u' && i + 5 < chars.len() {
1582                    // \uXXXX
1583                    let hex: String = chars[i + 2..i + 6].iter().collect();
1584                    if let Ok(cp) = u32::from_str_radix(&hex, 16) {
1585                        if let Some(c) = char::from_u32(cp) {
1586                            result.push(c);
1587                            i += 6;
1588                            continue;
1589                        }
1590                    }
1591                } else if chars[i + 1] == 'U' && i + 9 < chars.len() {
1592                    // \UXXXXXXXX
1593                    let hex: String = chars[i + 2..i + 10].iter().collect();
1594                    if let Ok(cp) = u32::from_str_radix(&hex, 16) {
1595                        if let Some(c) = char::from_u32(cp) {
1596                            result.push(c);
1597                            i += 10;
1598                            continue;
1599                        }
1600                    }
1601                }
1602            }
1603            result.push(chars[i]);
1604            i += 1;
1605        }
1606        Ok(SqliteValue::Text(result))
1607    }
1608
1609    fn num_args(&self) -> i32 {
1610        1
1611    }
1612
1613    fn name(&self) -> &str {
1614        "unistr"
1615    }
1616}
1617
1618// ── Connection-state stubs ──────────────────────────────────────────────
1619// These functions need database connection context. They are registered
1620// as placeholders that return NotImplemented until the VDBE integration
1621// provides the connection state.
1622
1623pub struct ChangesFunc;
1624
1625impl ScalarFunction for ChangesFunc {
1626    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1627        Ok(SqliteValue::Integer(LAST_CHANGES.get()))
1628    }
1629
1630    fn is_deterministic(&self) -> bool {
1631        false
1632    }
1633
1634    fn num_args(&self) -> i32 {
1635        0
1636    }
1637
1638    fn name(&self) -> &str {
1639        "changes"
1640    }
1641}
1642
1643pub struct TotalChangesFunc;
1644
1645impl ScalarFunction for TotalChangesFunc {
1646    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1647        Ok(SqliteValue::Integer(LAST_CHANGES.get()))
1648    }
1649
1650    fn is_deterministic(&self) -> bool {
1651        false
1652    }
1653
1654    fn num_args(&self) -> i32 {
1655        0
1656    }
1657
1658    fn name(&self) -> &str {
1659        "total_changes"
1660    }
1661}
1662
1663pub struct LastInsertRowidFunc;
1664
1665impl ScalarFunction for LastInsertRowidFunc {
1666    fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1667        Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
1668    }
1669
1670    fn is_deterministic(&self) -> bool {
1671        false
1672    }
1673
1674    fn num_args(&self) -> i32 {
1675        0
1676    }
1677
1678    fn name(&self) -> &str {
1679        "last_insert_rowid"
1680    }
1681}
1682
1683// ── Register all built-ins ──────────────────────────────────────────────
1684
1685/// Register all core built-in scalar functions into the given registry.
1686#[allow(clippy::too_many_lines)]
1687pub fn register_builtins(registry: &mut FunctionRegistry) {
1688    // Math
1689    registry.register_scalar(AbsFunc);
1690    registry.register_scalar(SignFunc);
1691    registry.register_scalar(RoundFunc);
1692    registry.register_scalar(RandomFunc);
1693    registry.register_scalar(RandomblobFunc);
1694    registry.register_scalar(ZeroblobFunc);
1695
1696    // String
1697    registry.register_scalar(LowerFunc);
1698    registry.register_scalar(UpperFunc);
1699    registry.register_scalar(LengthFunc);
1700    registry.register_scalar(OctetLengthFunc);
1701    registry.register_scalar(TrimFunc);
1702    registry.register_scalar(LtrimFunc);
1703    registry.register_scalar(RtrimFunc);
1704    registry.register_scalar(ReplaceFunc);
1705    registry.register_scalar(SubstrFunc);
1706    registry.register_scalar(InstrFunc);
1707    registry.register_scalar(CharFunc);
1708    registry.register_scalar(UnicodeFunc);
1709    registry.register_scalar(UnistrFunc);
1710    registry.register_scalar(HexFunc);
1711    registry.register_scalar(UnhexFunc);
1712    registry.register_scalar(QuoteFunc);
1713    registry.register_scalar(SoundexFunc);
1714
1715    // Type
1716    registry.register_scalar(TypeofFunc);
1717    registry.register_scalar(SubtypeFunc);
1718
1719    // Conditional
1720    registry.register_scalar(CoalesceFunc);
1721    registry.register_scalar(IfnullFunc);
1722    registry.register_scalar(NullifFunc);
1723    registry.register_scalar(IifFunc);
1724
1725    // Multi-value
1726    registry.register_scalar(ConcatFunc);
1727    registry.register_scalar(ConcatWsFunc);
1728    registry.register_scalar(ScalarMaxFunc);
1729    registry.register_scalar(ScalarMinFunc);
1730
1731    // Planner hints
1732    registry.register_scalar(LikelihoodFunc);
1733    registry.register_scalar(LikelyFunc);
1734    registry.register_scalar(UnlikelyFunc);
1735
1736    // Pattern matching
1737    registry.register_scalar(LikeFunc);
1738    registry.register_scalar(GlobFunc);
1739
1740    // Meta
1741    registry.register_scalar(SqliteVersionFunc);
1742    registry.register_scalar(SqliteSourceIdFunc);
1743    registry.register_scalar(SqliteCompileoptionUsedFunc);
1744    registry.register_scalar(SqliteCompileoptionGetFunc);
1745
1746    // Connection-state stubs
1747    registry.register_scalar(ChangesFunc);
1748    registry.register_scalar(TotalChangesFunc);
1749    registry.register_scalar(LastInsertRowidFunc);
1750
1751    // "if" is an alias for "iif" (3.48+)
1752    // Register same function under alternate name
1753    struct IfFunc;
1754    impl ScalarFunction for IfFunc {
1755        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1756            IifFunc.invoke(args)
1757        }
1758
1759        fn num_args(&self) -> i32 {
1760            -1
1761        }
1762
1763        fn name(&self) -> &str {
1764            "if"
1765        }
1766    }
1767    registry.register_scalar(IfFunc);
1768
1769    // "substring" is an alias for "substr"
1770    struct SubstringFunc;
1771    impl ScalarFunction for SubstringFunc {
1772        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1773            SubstrFunc.invoke(args)
1774        }
1775
1776        fn num_args(&self) -> i32 {
1777            -1
1778        }
1779
1780        fn name(&self) -> &str {
1781            "substring"
1782        }
1783    }
1784    registry.register_scalar(SubstringFunc);
1785
1786    // "printf" is an alias for "format" (both unimplemented format/printf)
1787    // Registered as stub that concatenates args for now.
1788    struct PrintfFunc;
1789    impl ScalarFunction for PrintfFunc {
1790        fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1791            FormatFunc.invoke(args)
1792        }
1793
1794        fn num_args(&self) -> i32 {
1795            -1
1796        }
1797
1798        fn name(&self) -> &str {
1799            "printf"
1800        }
1801    }
1802    registry.register_scalar(FormatFunc);
1803    registry.register_scalar(PrintfFunc);
1804
1805    // §13.2 Math functions (acos, asin, atan, ceil, floor, log, pow, sqrt, etc.)
1806    register_math_builtins(registry);
1807
1808    // §13.3 Date/time functions (date, time, datetime, julianday, unixepoch, strftime, timediff)
1809    register_datetime_builtins(registry);
1810
1811    // §13.4 Aggregate functions (avg, count, group_concat, max, min, sum, total, etc.)
1812    register_aggregate_builtins(registry);
1813}
1814
1815// ── format(FORMAT, ...) / printf(FORMAT, ...) ───────────────────────────
1816
1817pub struct FormatFunc;
1818
1819impl ScalarFunction for FormatFunc {
1820    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1821        if args.is_empty() || args[0].is_null() {
1822            return Ok(SqliteValue::Null);
1823        }
1824        let fmt_str = args[0].to_text();
1825        let params = &args[1..];
1826        let result = sqlite_format(&fmt_str, params)?;
1827        Ok(SqliteValue::Text(result))
1828    }
1829
1830    fn num_args(&self) -> i32 {
1831        -1
1832    }
1833
1834    fn name(&self) -> &str {
1835        "format"
1836    }
1837}
1838
1839/// Simplified SQLite format/printf implementation.
1840/// Supports: %d, %f, %e, %g, %s, %q, %Q, %w, %%, %n (no-op).
1841fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<String> {
1842    let mut result = String::new();
1843    let chars: Vec<char> = fmt.chars().collect();
1844    let mut i = 0;
1845    let mut param_idx = 0;
1846
1847    while i < chars.len() {
1848        if chars[i] != '%' {
1849            result.push(chars[i]);
1850            i += 1;
1851            continue;
1852        }
1853        i += 1;
1854        if i >= chars.len() {
1855            break;
1856        }
1857
1858        // Parse flags
1859        let mut left_align = false;
1860        let mut show_sign = false;
1861        let mut space_sign = false;
1862        let mut zero_pad = false;
1863        loop {
1864            if i >= chars.len() {
1865                break;
1866            }
1867            match chars[i] {
1868                '-' => left_align = true,
1869                '+' => show_sign = true,
1870                ' ' => space_sign = true,
1871                '0' => zero_pad = true,
1872                _ => break,
1873            }
1874            i += 1;
1875        }
1876
1877        // Parse width
1878        let mut width = 0usize;
1879        while i < chars.len() && chars[i].is_ascii_digit() {
1880            width = width * 10 + (chars[i] as usize - '0' as usize);
1881            i += 1;
1882        }
1883
1884        // Parse precision
1885        let mut precision = None;
1886        if i < chars.len() && chars[i] == '.' {
1887            i += 1;
1888            let mut prec = 0usize;
1889            while i < chars.len() && chars[i].is_ascii_digit() {
1890                prec = prec * 10 + (chars[i] as usize - '0' as usize);
1891                i += 1;
1892            }
1893            precision = Some(prec);
1894        }
1895
1896        if i >= chars.len() {
1897            break;
1898        }
1899
1900        let spec = chars[i];
1901        i += 1;
1902
1903        match spec {
1904            '%' => result.push('%'),
1905            'n' => {} // no-op (security: never writes to memory)
1906            'd' | 'i' => {
1907                let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
1908                param_idx += 1;
1909                let formatted =
1910                    format_integer(val, width, left_align, show_sign, space_sign, zero_pad);
1911                result.push_str(&formatted);
1912            }
1913            'f' => {
1914                let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
1915                param_idx += 1;
1916                let prec = precision.unwrap_or(6);
1917                let formatted = format_float_f(
1918                    val, prec, width, left_align, show_sign, space_sign, zero_pad,
1919                );
1920                result.push_str(&formatted);
1921            }
1922            'e' | 'E' => {
1923                let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
1924                param_idx += 1;
1925                let prec = precision.unwrap_or(6);
1926                let formatted = if spec == 'e' {
1927                    format!("{val:.prec$e}")
1928                } else {
1929                    format!("{val:.prec$E}")
1930                };
1931                result.push_str(&pad_string(&formatted, width, left_align));
1932            }
1933            'g' | 'G' => {
1934                let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
1935                param_idx += 1;
1936                let _prec = precision.unwrap_or(6);
1937                // Use shorter of %f and %e
1938                let formatted = format!("{val}");
1939                result.push_str(&pad_string(&formatted, width, left_align));
1940            }
1941            's' | 'z' => {
1942                let val = params
1943                    .get(param_idx)
1944                    .map(SqliteValue::to_text)
1945                    .unwrap_or_default();
1946                param_idx += 1;
1947                let truncated = if let Some(prec) = precision {
1948                    val.chars().take(prec).collect::<String>()
1949                } else {
1950                    val
1951                };
1952                result.push_str(&pad_string(&truncated, width, left_align));
1953            }
1954            'q' => {
1955                // Single-quote escaping
1956                let val = params
1957                    .get(param_idx)
1958                    .map(SqliteValue::to_text)
1959                    .unwrap_or_default();
1960                param_idx += 1;
1961                let escaped = val.replace('\'', "''");
1962                result.push_str(&escaped);
1963            }
1964            'Q' => {
1965                // Like %q but wrapped in quotes, NULL -> "NULL"
1966                let param = params.get(param_idx);
1967                param_idx += 1;
1968                match param {
1969                    Some(SqliteValue::Null) | None => result.push_str("NULL"),
1970                    Some(v) => {
1971                        let val = v.to_text();
1972                        let escaped = val.replace('\'', "''");
1973                        result.push('\'');
1974                        result.push_str(&escaped);
1975                        result.push('\'');
1976                    }
1977                }
1978            }
1979            'w' => {
1980                // Double-quote escaping for identifiers
1981                let val = params
1982                    .get(param_idx)
1983                    .map(SqliteValue::to_text)
1984                    .unwrap_or_default();
1985                param_idx += 1;
1986                let escaped = val.replace('"', "\"\"");
1987                result.push('"');
1988                result.push_str(&escaped);
1989                result.push('"');
1990            }
1991            'c' => {
1992                let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
1993                param_idx += 1;
1994                #[allow(clippy::cast_sign_loss)]
1995                if let Some(c) = char::from_u32(val as u32) {
1996                    result.push(c);
1997                }
1998            }
1999            _ => {
2000                // Unknown specifier: output literally
2001                result.push('%');
2002                result.push(spec);
2003            }
2004        }
2005        // Suppress unused warnings
2006        let _ = (left_align, show_sign, space_sign, zero_pad);
2007    }
2008    Ok(result)
2009}
2010
2011fn format_integer(
2012    val: i64,
2013    width: usize,
2014    left_align: bool,
2015    show_sign: bool,
2016    space_sign: bool,
2017    zero_pad: bool,
2018) -> String {
2019    let sign = if val < 0 {
2020        "-".to_owned()
2021    } else if show_sign {
2022        "+".to_owned()
2023    } else if space_sign {
2024        " ".to_owned()
2025    } else {
2026        String::new()
2027    };
2028    let digits = format!("{}", val.abs());
2029    let body = format!("{sign}{digits}");
2030    if body.len() >= width {
2031        return body;
2032    }
2033    let pad = width - body.len();
2034    if left_align {
2035        format!("{body}{}", " ".repeat(pad))
2036    } else if zero_pad {
2037        format!("{sign}{}{digits}", "0".repeat(pad))
2038    } else {
2039        format!("{}{body}", " ".repeat(pad))
2040    }
2041}
2042
2043fn format_float_f(
2044    val: f64,
2045    prec: usize,
2046    width: usize,
2047    left_align: bool,
2048    show_sign: bool,
2049    space_sign: bool,
2050    zero_pad: bool,
2051) -> String {
2052    let sign = if val < 0.0 {
2053        "-".to_owned()
2054    } else if show_sign {
2055        "+".to_owned()
2056    } else if space_sign {
2057        " ".to_owned()
2058    } else {
2059        String::new()
2060    };
2061    let digits = format!("{:.prec$}", val.abs());
2062    let body = format!("{sign}{digits}");
2063    if body.len() >= width {
2064        return body;
2065    }
2066    let pad = width - body.len();
2067    if left_align {
2068        format!("{body}{}", " ".repeat(pad))
2069    } else if zero_pad {
2070        format!("{sign}{}{digits}", "0".repeat(pad))
2071    } else {
2072        format!("{}{body}", " ".repeat(pad))
2073    }
2074}
2075
2076fn pad_string(s: &str, width: usize, left_align: bool) -> String {
2077    if s.len() >= width {
2078        return s.to_owned();
2079    }
2080    let pad = width - s.len();
2081    if left_align {
2082        format!("{s}{}", " ".repeat(pad))
2083    } else {
2084        format!("{}{s}", " ".repeat(pad))
2085    }
2086}
2087
2088// ── Tests ───────────────────────────────────────────────────────────────
2089
2090#[cfg(test)]
2091#[allow(clippy::too_many_lines)]
2092mod tests {
2093    use super::*;
2094
2095    fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
2096        f.invoke(&[v])
2097    }
2098
2099    fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
2100        f.invoke(&[a, b])
2101    }
2102
2103    // ── abs ──────────────────────────────────────────────────────────────
2104
2105    #[test]
2106    fn test_abs_positive() {
2107        assert_eq!(
2108            invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
2109            SqliteValue::Integer(42)
2110        );
2111    }
2112
2113    #[test]
2114    fn test_abs_negative() {
2115        assert_eq!(
2116            invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
2117            SqliteValue::Integer(42)
2118        );
2119    }
2120
2121    #[test]
2122    fn test_abs_null() {
2123        assert_eq!(
2124            invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
2125            SqliteValue::Null
2126        );
2127    }
2128
2129    #[test]
2130    fn test_abs_min_i64_overflow() {
2131        let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
2132        assert!(matches!(err, FrankenError::IntegerOverflow));
2133    }
2134
2135    #[test]
2136    fn test_abs_string_coercion() {
2137        assert_eq!(
2138            invoke1(&AbsFunc, SqliteValue::Text("-7.5".to_owned())).unwrap(),
2139            SqliteValue::Float(7.5)
2140        );
2141    }
2142
2143    #[test]
2144    #[allow(clippy::approx_constant)]
2145    fn test_abs_float() {
2146        assert_eq!(
2147            invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
2148            SqliteValue::Float(3.14)
2149        );
2150    }
2151
2152    // ── char ─────────────────────────────────────────────────────────────
2153
2154    #[test]
2155    fn test_char_basic() {
2156        let f = CharFunc;
2157        let result = f
2158            .invoke(&[
2159                SqliteValue::Integer(72),
2160                SqliteValue::Integer(101),
2161                SqliteValue::Integer(108),
2162                SqliteValue::Integer(108),
2163                SqliteValue::Integer(111),
2164            ])
2165            .unwrap();
2166        assert_eq!(result, SqliteValue::Text("Hello".to_owned()));
2167    }
2168
2169    #[test]
2170    fn test_char_null_skipped() {
2171        let f = CharFunc;
2172        let result = f
2173            .invoke(&[
2174                SqliteValue::Integer(65),
2175                SqliteValue::Null,
2176                SqliteValue::Integer(66),
2177            ])
2178            .unwrap();
2179        assert_eq!(result, SqliteValue::Text("AB".to_owned()));
2180    }
2181
2182    // ── coalesce ─────────────────────────────────────────────────────────
2183
2184    #[test]
2185    fn test_coalesce_first_non_null() {
2186        let f = CoalesceFunc;
2187        let result = f
2188            .invoke(&[
2189                SqliteValue::Null,
2190                SqliteValue::Null,
2191                SqliteValue::Integer(3),
2192                SqliteValue::Integer(4),
2193            ])
2194            .unwrap();
2195        assert_eq!(result, SqliteValue::Integer(3));
2196    }
2197
2198    // ── concat ───────────────────────────────────────────────────────────
2199
2200    #[test]
2201    fn test_concat_null_as_empty() {
2202        let f = ConcatFunc;
2203        let result = f
2204            .invoke(&[
2205                SqliteValue::Null,
2206                SqliteValue::Text("hello".to_owned()),
2207                SqliteValue::Null,
2208            ])
2209            .unwrap();
2210        assert_eq!(result, SqliteValue::Text("hello".to_owned()));
2211    }
2212
2213    // ── concat_ws ────────────────────────────────────────────────────────
2214
2215    #[test]
2216    fn test_concat_ws_null_skipped() {
2217        let f = ConcatWsFunc;
2218        let result = f
2219            .invoke(&[
2220                SqliteValue::Text(",".to_owned()),
2221                SqliteValue::Text("a".to_owned()),
2222                SqliteValue::Null,
2223                SqliteValue::Text("b".to_owned()),
2224            ])
2225            .unwrap();
2226        assert_eq!(result, SqliteValue::Text("a,b".to_owned()));
2227    }
2228
2229    // ── hex ──────────────────────────────────────────────────────────────
2230
2231    #[test]
2232    fn test_hex_blob() {
2233        let result = invoke1(&HexFunc, SqliteValue::Blob(vec![0xDE, 0xAD, 0xBE, 0xEF])).unwrap();
2234        assert_eq!(result, SqliteValue::Text("DEADBEEF".to_owned()));
2235    }
2236
2237    #[test]
2238    fn test_hex_number_via_text() {
2239        // hex(42) encodes '42' as UTF-8 hex, not raw bits
2240        let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
2241        assert_eq!(result, SqliteValue::Text("3432".to_owned()));
2242    }
2243
2244    // ── iif ──────────────────────────────────────────────────────────────
2245
2246    #[test]
2247    fn test_iif_true() {
2248        let f = IifFunc;
2249        let result = f
2250            .invoke(&[
2251                SqliteValue::Integer(1),
2252                SqliteValue::Text("yes".to_owned()),
2253                SqliteValue::Text("no".to_owned()),
2254            ])
2255            .unwrap();
2256        assert_eq!(result, SqliteValue::Text("yes".to_owned()));
2257    }
2258
2259    #[test]
2260    fn test_iif_false() {
2261        let f = IifFunc;
2262        let result = f
2263            .invoke(&[
2264                SqliteValue::Integer(0),
2265                SqliteValue::Text("yes".to_owned()),
2266                SqliteValue::Text("no".to_owned()),
2267            ])
2268            .unwrap();
2269        assert_eq!(result, SqliteValue::Text("no".to_owned()));
2270    }
2271
2272    // ── ifnull ───────────────────────────────────────────────────────────
2273
2274    #[test]
2275    fn test_ifnull_non_null() {
2276        assert_eq!(
2277            invoke2(
2278                &IfnullFunc,
2279                SqliteValue::Integer(5),
2280                SqliteValue::Integer(10)
2281            )
2282            .unwrap(),
2283            SqliteValue::Integer(5)
2284        );
2285    }
2286
2287    #[test]
2288    fn test_ifnull_null() {
2289        assert_eq!(
2290            invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
2291            SqliteValue::Integer(10)
2292        );
2293    }
2294
2295    // ── instr ────────────────────────────────────────────────────────────
2296
2297    #[test]
2298    fn test_instr_found() {
2299        assert_eq!(
2300            invoke2(
2301                &InstrFunc,
2302                SqliteValue::Text("hello world".to_owned()),
2303                SqliteValue::Text("world".to_owned())
2304            )
2305            .unwrap(),
2306            SqliteValue::Integer(7)
2307        );
2308    }
2309
2310    #[test]
2311    fn test_instr_not_found() {
2312        assert_eq!(
2313            invoke2(
2314                &InstrFunc,
2315                SqliteValue::Text("hello".to_owned()),
2316                SqliteValue::Text("xyz".to_owned())
2317            )
2318            .unwrap(),
2319            SqliteValue::Integer(0)
2320        );
2321    }
2322
2323    // ── length ───────────────────────────────────────────────────────────
2324
2325    #[test]
2326    fn test_length_text_chars() {
2327        // café is 4 characters, 5 bytes
2328        assert_eq!(
2329            invoke1(&LengthFunc, SqliteValue::Text("café".to_owned())).unwrap(),
2330            SqliteValue::Integer(4)
2331        );
2332    }
2333
2334    #[test]
2335    fn test_length_blob_bytes() {
2336        assert_eq!(
2337            invoke1(&LengthFunc, SqliteValue::Blob(vec![1, 2])).unwrap(),
2338            SqliteValue::Integer(2)
2339        );
2340    }
2341
2342    // ── octet_length ─────────────────────────────────────────────────────
2343
2344    #[test]
2345    fn test_octet_length_multibyte() {
2346        // café: 'c'=1, 'a'=1, 'f'=1, 'é'=2 bytes = 5 bytes total
2347        assert_eq!(
2348            invoke1(&OctetLengthFunc, SqliteValue::Text("café".to_owned())).unwrap(),
2349            SqliteValue::Integer(5)
2350        );
2351    }
2352
2353    // ── lower/upper ──────────────────────────────────────────────────────
2354
2355    #[test]
2356    fn test_lower_ascii() {
2357        assert_eq!(
2358            invoke1(&LowerFunc, SqliteValue::Text("HELLO".to_owned())).unwrap(),
2359            SqliteValue::Text("hello".to_owned())
2360        );
2361    }
2362
2363    #[test]
2364    fn test_upper_ascii() {
2365        assert_eq!(
2366            invoke1(&UpperFunc, SqliteValue::Text("hello".to_owned())).unwrap(),
2367            SqliteValue::Text("HELLO".to_owned())
2368        );
2369    }
2370
2371    // ── trim/ltrim/rtrim ─────────────────────────────────────────────────
2372
2373    #[test]
2374    fn test_trim_default() {
2375        let f = TrimFunc;
2376        assert_eq!(
2377            f.invoke(&[SqliteValue::Text("  hello  ".to_owned())])
2378                .unwrap(),
2379            SqliteValue::Text("hello".to_owned())
2380        );
2381    }
2382
2383    #[test]
2384    fn test_ltrim_default() {
2385        let f = LtrimFunc;
2386        assert_eq!(
2387            f.invoke(&[SqliteValue::Text("  hello".to_owned())])
2388                .unwrap(),
2389            SqliteValue::Text("hello".to_owned())
2390        );
2391    }
2392
2393    #[test]
2394    fn test_ltrim_custom() {
2395        let f = LtrimFunc;
2396        assert_eq!(
2397            f.invoke(&[
2398                SqliteValue::Text("xxhello".to_owned()),
2399                SqliteValue::Text("x".to_owned()),
2400            ])
2401            .unwrap(),
2402            SqliteValue::Text("hello".to_owned())
2403        );
2404    }
2405
2406    // ── nullif ───────────────────────────────────────────────────────────
2407
2408    #[test]
2409    fn test_nullif_equal() {
2410        assert_eq!(
2411            invoke2(
2412                &NullifFunc,
2413                SqliteValue::Integer(5),
2414                SqliteValue::Integer(5)
2415            )
2416            .unwrap(),
2417            SqliteValue::Null
2418        );
2419    }
2420
2421    #[test]
2422    fn test_nullif_different() {
2423        assert_eq!(
2424            invoke2(
2425                &NullifFunc,
2426                SqliteValue::Integer(5),
2427                SqliteValue::Integer(3)
2428            )
2429            .unwrap(),
2430            SqliteValue::Integer(5)
2431        );
2432    }
2433
2434    // ── typeof ───────────────────────────────────────────────────────────
2435
2436    #[test]
2437    fn test_typeof_each() {
2438        assert_eq!(
2439            invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
2440            SqliteValue::Text("null".to_owned())
2441        );
2442        assert_eq!(
2443            invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
2444            SqliteValue::Text("integer".to_owned())
2445        );
2446        assert_eq!(
2447            invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
2448            SqliteValue::Text("real".to_owned())
2449        );
2450        assert_eq!(
2451            invoke1(&TypeofFunc, SqliteValue::Text("x".to_owned())).unwrap(),
2452            SqliteValue::Text("text".to_owned())
2453        );
2454        assert_eq!(
2455            invoke1(&TypeofFunc, SqliteValue::Blob(vec![0])).unwrap(),
2456            SqliteValue::Text("blob".to_owned())
2457        );
2458    }
2459
2460    // ── subtype ──────────────────────────────────────────────────────────
2461
2462    #[test]
2463    fn test_subtype_null_returns_zero() {
2464        assert_eq!(
2465            invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
2466            SqliteValue::Integer(0)
2467        );
2468    }
2469
2470    // ── replace ──────────────────────────────────────────────────────────
2471
2472    #[test]
2473    fn test_replace_basic() {
2474        let f = ReplaceFunc;
2475        assert_eq!(
2476            f.invoke(&[
2477                SqliteValue::Text("hello world".to_owned()),
2478                SqliteValue::Text("world".to_owned()),
2479                SqliteValue::Text("earth".to_owned()),
2480            ])
2481            .unwrap(),
2482            SqliteValue::Text("hello earth".to_owned())
2483        );
2484    }
2485
2486    #[test]
2487    fn test_replace_empty_y() {
2488        let f = ReplaceFunc;
2489        assert_eq!(
2490            f.invoke(&[
2491                SqliteValue::Text("hello".to_owned()),
2492                SqliteValue::Text(String::new()),
2493                SqliteValue::Text("x".to_owned()),
2494            ])
2495            .unwrap(),
2496            SqliteValue::Text("hello".to_owned())
2497        );
2498    }
2499
2500    // ── round ────────────────────────────────────────────────────────────
2501
2502    #[test]
2503    #[allow(clippy::float_cmp)]
2504    fn test_round_half_away() {
2505        // round(2.5) = 3.0, round(-2.5) = -3.0
2506        assert_eq!(
2507            RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
2508            SqliteValue::Float(3.0)
2509        );
2510        assert_eq!(
2511            RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
2512            SqliteValue::Float(-3.0)
2513        );
2514    }
2515
2516    #[test]
2517    #[allow(clippy::float_cmp, clippy::approx_constant)]
2518    fn test_round_precision() {
2519        assert_eq!(
2520            RoundFunc
2521                .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
2522                .unwrap(),
2523            SqliteValue::Float(3.14)
2524        );
2525    }
2526
2527    #[test]
2528    #[allow(clippy::float_cmp)]
2529    fn test_round_extreme_n_clamped() {
2530        // N > 30 is clamped to 30 (matches C SQLite)
2531        assert_eq!(
2532            RoundFunc
2533                .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
2534                .unwrap(),
2535            RoundFunc
2536                .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
2537                .unwrap(),
2538        );
2539        // Negative N is clamped to 0 (matches C SQLite)
2540        assert_eq!(
2541            RoundFunc
2542                .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
2543                .unwrap(),
2544            SqliteValue::Float(3.0)
2545        );
2546        // i64::MAX is clamped to 30
2547        let result = RoundFunc
2548            .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
2549            .unwrap();
2550        if let SqliteValue::Float(v) = result {
2551            assert!(!v.is_nan(), "round must never return NaN");
2552        }
2553    }
2554
2555    #[test]
2556    #[allow(clippy::float_cmp)]
2557    fn test_round_large_value_no_fractional() {
2558        // Values beyond 2^52 have no fractional part — returned unchanged
2559        let big = 9_007_199_254_740_993.0_f64;
2560        assert_eq!(
2561            RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
2562            SqliteValue::Float(big)
2563        );
2564        assert_eq!(
2565            RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
2566            SqliteValue::Float(-big)
2567        );
2568    }
2569
2570    // ── sign ─────────────────────────────────────────────────────────────
2571
2572    #[test]
2573    fn test_sign_positive() {
2574        assert_eq!(
2575            invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
2576            SqliteValue::Integer(1)
2577        );
2578    }
2579
2580    #[test]
2581    fn test_sign_negative() {
2582        assert_eq!(
2583            invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
2584            SqliteValue::Integer(-1)
2585        );
2586    }
2587
2588    #[test]
2589    fn test_sign_zero() {
2590        assert_eq!(
2591            invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
2592            SqliteValue::Integer(0)
2593        );
2594    }
2595
2596    #[test]
2597    fn test_sign_null() {
2598        assert_eq!(
2599            invoke1(&SignFunc, SqliteValue::Null).unwrap(),
2600            SqliteValue::Null
2601        );
2602    }
2603
2604    #[test]
2605    fn test_sign_non_numeric() {
2606        assert_eq!(
2607            invoke1(&SignFunc, SqliteValue::Text("abc".to_owned())).unwrap(),
2608            SqliteValue::Null
2609        );
2610    }
2611
2612    // ── scalar max/min ───────────────────────────────────────────────────
2613
2614    #[test]
2615    fn test_scalar_max_null() {
2616        let f = ScalarMaxFunc;
2617        let result = f
2618            .invoke(&[
2619                SqliteValue::Integer(1),
2620                SqliteValue::Null,
2621                SqliteValue::Integer(3),
2622            ])
2623            .unwrap();
2624        assert_eq!(result, SqliteValue::Null);
2625    }
2626
2627    #[test]
2628    fn test_scalar_max_values() {
2629        let f = ScalarMaxFunc;
2630        let result = f
2631            .invoke(&[
2632                SqliteValue::Integer(3),
2633                SqliteValue::Integer(1),
2634                SqliteValue::Integer(2),
2635            ])
2636            .unwrap();
2637        assert_eq!(result, SqliteValue::Integer(3));
2638    }
2639
2640    #[test]
2641    fn test_scalar_min_null() {
2642        let f = ScalarMinFunc;
2643        let result = f
2644            .invoke(&[
2645                SqliteValue::Integer(1),
2646                SqliteValue::Null,
2647                SqliteValue::Integer(3),
2648            ])
2649            .unwrap();
2650        assert_eq!(result, SqliteValue::Null);
2651    }
2652
2653    // ── quote ────────────────────────────────────────────────────────────
2654
2655    #[test]
2656    fn test_quote_text() {
2657        assert_eq!(
2658            invoke1(&QuoteFunc, SqliteValue::Text("it's".to_owned())).unwrap(),
2659            SqliteValue::Text("'it''s'".to_owned())
2660        );
2661    }
2662
2663    #[test]
2664    fn test_quote_null() {
2665        assert_eq!(
2666            invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
2667            SqliteValue::Text("NULL".to_owned())
2668        );
2669    }
2670
2671    #[test]
2672    fn test_quote_blob() {
2673        assert_eq!(
2674            invoke1(&QuoteFunc, SqliteValue::Blob(vec![0xAB])).unwrap(),
2675            SqliteValue::Text("X'AB'".to_owned())
2676        );
2677    }
2678
2679    // ── random ───────────────────────────────────────────────────────────
2680
2681    #[test]
2682    fn test_random_range() {
2683        let f = RandomFunc;
2684        let result = f.invoke(&[]).unwrap();
2685        assert!(matches!(result, SqliteValue::Integer(_)));
2686    }
2687
2688    // ── randomblob ───────────────────────────────────────────────────────
2689
2690    #[test]
2691    fn test_randomblob_length() {
2692        let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
2693        match result {
2694            SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
2695            other => unreachable!("expected blob, got {other:?}"),
2696        }
2697    }
2698
2699    // ── zeroblob ─────────────────────────────────────────────────────────
2700
2701    #[test]
2702    fn test_zeroblob_length() {
2703        let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
2704        match result {
2705            SqliteValue::Blob(b) => {
2706                assert_eq!(b.len(), 100);
2707                assert!(b.iter().all(|&x| x == 0));
2708            }
2709            other => unreachable!("expected blob, got {other:?}"),
2710        }
2711    }
2712
2713    // ── unhex ────────────────────────────────────────────────────────────
2714
2715    #[test]
2716    fn test_unhex_valid() {
2717        let result = invoke1(&UnhexFunc, SqliteValue::Text("48656C6C6F".to_owned())).unwrap();
2718        assert_eq!(result, SqliteValue::Blob(b"Hello".to_vec()));
2719    }
2720
2721    #[test]
2722    fn test_unhex_invalid() {
2723        let result = invoke1(&UnhexFunc, SqliteValue::Text("ZZZZ".to_owned())).unwrap();
2724        assert_eq!(result, SqliteValue::Null);
2725    }
2726
2727    #[test]
2728    fn test_unhex_ignore_chars() {
2729        let f = UnhexFunc;
2730        let result = f
2731            .invoke(&[
2732                SqliteValue::Text("48-65-6C".to_owned()),
2733                SqliteValue::Text("-".to_owned()),
2734            ])
2735            .unwrap();
2736        assert_eq!(result, SqliteValue::Blob(b"Hel".to_vec()));
2737    }
2738
2739    // ── unicode ──────────────────────────────────────────────────────────
2740
2741    #[test]
2742    fn test_unicode_first_char() {
2743        assert_eq!(
2744            invoke1(&UnicodeFunc, SqliteValue::Text("A".to_owned())).unwrap(),
2745            SqliteValue::Integer(65)
2746        );
2747    }
2748
2749    // ── soundex ──────────────────────────────────────────────────────────
2750
2751    #[test]
2752    fn test_soundex_basic() {
2753        assert_eq!(
2754            invoke1(&SoundexFunc, SqliteValue::Text("Robert".to_owned())).unwrap(),
2755            SqliteValue::Text("R163".to_owned())
2756        );
2757    }
2758
2759    // ── substr ───────────────────────────────────────────────────────────
2760
2761    #[test]
2762    fn test_substr_basic() {
2763        let f = SubstrFunc;
2764        assert_eq!(
2765            f.invoke(&[
2766                SqliteValue::Text("hello".to_owned()),
2767                SqliteValue::Integer(2),
2768                SqliteValue::Integer(3),
2769            ])
2770            .unwrap(),
2771            SqliteValue::Text("ell".to_owned())
2772        );
2773    }
2774
2775    #[test]
2776    fn test_substr_start_zero_quirk() {
2777        // substr('hello', 0, 3) returns 2 chars from start
2778        let f = SubstrFunc;
2779        let result = f
2780            .invoke(&[
2781                SqliteValue::Text("hello".to_owned()),
2782                SqliteValue::Integer(0),
2783                SqliteValue::Integer(3),
2784            ])
2785            .unwrap();
2786        assert_eq!(result, SqliteValue::Text("he".to_owned()));
2787    }
2788
2789    #[test]
2790    fn test_substr_negative_start() {
2791        // substr('hello', -2) = 'lo'
2792        let f = SubstrFunc;
2793        let result = f
2794            .invoke(&[
2795                SqliteValue::Text("hello".to_owned()),
2796                SqliteValue::Integer(-2),
2797            ])
2798            .unwrap();
2799        assert_eq!(result, SqliteValue::Text("lo".to_owned()));
2800    }
2801
2802    // ── like ─────────────────────────────────────────────────────────────
2803
2804    #[test]
2805    fn test_like_case_insensitive() {
2806        assert_eq!(
2807            invoke2(
2808                &LikeFunc,
2809                SqliteValue::Text("ABC".to_owned()),
2810                SqliteValue::Text("abc".to_owned())
2811            )
2812            .unwrap(),
2813            SqliteValue::Integer(1)
2814        );
2815    }
2816
2817    #[test]
2818    fn test_like_escape() {
2819        let f = LikeFunc;
2820        let result = f
2821            .invoke(&[
2822                SqliteValue::Text("10\\%".to_owned()),
2823                SqliteValue::Text("10%".to_owned()),
2824                SqliteValue::Text("\\".to_owned()),
2825            ])
2826            .unwrap();
2827        assert_eq!(result, SqliteValue::Integer(1));
2828    }
2829
2830    #[test]
2831    fn test_like_percent() {
2832        assert_eq!(
2833            invoke2(
2834                &LikeFunc,
2835                SqliteValue::Text("%ell%".to_owned()),
2836                SqliteValue::Text("Hello".to_owned())
2837            )
2838            .unwrap(),
2839            SqliteValue::Integer(1)
2840        );
2841    }
2842
2843    // ── glob ─────────────────────────────────────────────────────────────
2844
2845    #[test]
2846    fn test_glob_star() {
2847        assert_eq!(
2848            invoke2(
2849                &GlobFunc,
2850                SqliteValue::Text("*.txt".to_owned()),
2851                SqliteValue::Text("file.txt".to_owned())
2852            )
2853            .unwrap(),
2854            SqliteValue::Integer(1)
2855        );
2856    }
2857
2858    #[test]
2859    fn test_glob_case_sensitive() {
2860        assert_eq!(
2861            invoke2(
2862                &GlobFunc,
2863                SqliteValue::Text("ABC".to_owned()),
2864                SqliteValue::Text("abc".to_owned())
2865            )
2866            .unwrap(),
2867            SqliteValue::Integer(0)
2868        );
2869    }
2870
2871    // ── format ───────────────────────────────────────────────────────────
2872
2873    #[test]
2874    fn test_format_specifiers() {
2875        let f = FormatFunc;
2876        let result = f
2877            .invoke(&[
2878                SqliteValue::Text("%d %s".to_owned()),
2879                SqliteValue::Integer(42),
2880                SqliteValue::Text("hello".to_owned()),
2881            ])
2882            .unwrap();
2883        assert_eq!(result, SqliteValue::Text("42 hello".to_owned()));
2884    }
2885
2886    #[test]
2887    fn test_format_n_noop() {
2888        let f = FormatFunc;
2889        // %n should not crash or do anything
2890        let result = f
2891            .invoke(&[SqliteValue::Text("before%nafter".to_owned())])
2892            .unwrap();
2893        assert_eq!(result, SqliteValue::Text("beforeafter".to_owned()));
2894    }
2895
2896    // ── sqlite_version ───────────────────────────────────────────────────
2897
2898    #[test]
2899    fn test_sqlite_version_format() {
2900        let result = SqliteVersionFunc.invoke(&[]).unwrap();
2901        match result {
2902            SqliteValue::Text(v) => {
2903                assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
2904            }
2905            other => unreachable!("expected text, got {other:?}"),
2906        }
2907    }
2908
2909    // ── register_builtins ────────────────────────────────────────────────
2910
2911    #[test]
2912    fn test_register_builtins_all_present() {
2913        let mut registry = FunctionRegistry::new();
2914        register_builtins(&mut registry);
2915
2916        // Spot-check key functions are registered
2917        assert!(registry.find_scalar("abs", 1).is_some());
2918        assert!(registry.find_scalar("typeof", 1).is_some());
2919        assert!(registry.find_scalar("length", 1).is_some());
2920        assert!(registry.find_scalar("lower", 1).is_some());
2921        assert!(registry.find_scalar("upper", 1).is_some());
2922        assert!(registry.find_scalar("hex", 1).is_some());
2923        assert!(registry.find_scalar("coalesce", 3).is_some());
2924        assert!(registry.find_scalar("concat", 2).is_some());
2925        assert!(registry.find_scalar("like", 2).is_some());
2926        assert!(registry.find_scalar("glob", 2).is_some());
2927        assert!(registry.find_scalar("round", 1).is_some());
2928        assert!(registry.find_scalar("substr", 2).is_some());
2929        assert!(registry.find_scalar("substring", 3).is_some());
2930        assert!(registry.find_scalar("sqlite_version", 0).is_some());
2931        assert!(registry.find_scalar("iif", 3).is_some());
2932        assert!(registry.find_scalar("if", 3).is_some());
2933        assert!(registry.find_scalar("format", 1).is_some());
2934        assert!(registry.find_scalar("printf", 1).is_some());
2935        assert!(registry.find_scalar("max", 2).is_some());
2936        assert!(registry.find_scalar("min", 2).is_some());
2937        assert!(registry.find_scalar("sign", 1).is_some());
2938        assert!(registry.find_scalar("random", 0).is_some());
2939
2940        // Newer SQLite scalar functions (3.41+)
2941        assert!(registry.find_scalar("concat_ws", 3).is_some());
2942        assert!(registry.find_scalar("octet_length", 1).is_some());
2943        assert!(registry.find_scalar("unhex", 1).is_some());
2944        assert!(registry.find_scalar("timediff", 2).is_some());
2945        assert!(registry.find_scalar("unistr", 1).is_some());
2946
2947        // Percentile family enabled by default.
2948        assert!(registry.find_aggregate("median", 1).is_some());
2949        assert!(registry.find_aggregate("percentile", 2).is_some());
2950        assert!(registry.find_aggregate("percentile_cont", 2).is_some());
2951        assert!(registry.find_aggregate("percentile_disc", 2).is_some());
2952
2953        // Loadable extensions are not exposed as SQL function by default.
2954        assert!(registry.find_scalar("load_extension", 1).is_none());
2955        assert!(registry.find_scalar("load_extension", 2).is_none());
2956    }
2957
2958    #[test]
2959    fn test_e2e_registry_invoke_through_lookup() {
2960        let mut registry = FunctionRegistry::new();
2961        register_builtins(&mut registry);
2962
2963        // Look up abs, invoke it
2964        let abs = registry.find_scalar("ABS", 1).unwrap();
2965        assert_eq!(
2966            abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
2967            SqliteValue::Integer(42)
2968        );
2969
2970        // Look up typeof, invoke it
2971        let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
2972        assert_eq!(
2973            typeof_fn
2974                .invoke(&[SqliteValue::Text("hello".to_owned())])
2975                .unwrap(),
2976            SqliteValue::Text("text".to_owned())
2977        );
2978
2979        // Look up coalesce (variadic), invoke with 4 args
2980        let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
2981        assert_eq!(
2982            coalesce
2983                .invoke(&[
2984                    SqliteValue::Null,
2985                    SqliteValue::Null,
2986                    SqliteValue::Integer(42),
2987                    SqliteValue::Integer(99),
2988                ])
2989                .unwrap(),
2990            SqliteValue::Integer(42)
2991        );
2992    }
2993
2994    // ── bd-13r.8: Non-Deterministic Function Evaluation Semantics ──
2995
2996    #[test]
2997    fn test_nondeterministic_functions_flagged() {
2998        // These functions MUST be marked non-deterministic to prevent
2999        // unsafe planner optimizations (hoisting, CSE).
3000        assert!(!RandomFunc.is_deterministic());
3001        assert!(!RandomblobFunc.is_deterministic());
3002        assert!(!ChangesFunc.is_deterministic());
3003        assert!(!TotalChangesFunc.is_deterministic());
3004        assert!(!LastInsertRowidFunc.is_deterministic());
3005    }
3006
3007    #[test]
3008    fn test_deterministic_functions_flagged() {
3009        // Deterministic functions are safe for constant folding/CSE.
3010        assert!(AbsFunc.is_deterministic());
3011        assert!(LengthFunc.is_deterministic());
3012        assert!(TypeofFunc.is_deterministic());
3013        assert!(UpperFunc.is_deterministic());
3014        assert!(LowerFunc.is_deterministic());
3015        assert!(HexFunc.is_deterministic());
3016        assert!(CoalesceFunc.is_deterministic());
3017        assert!(IifFunc.is_deterministic());
3018    }
3019
3020    #[test]
3021    fn test_random_produces_different_values() {
3022        // random() should produce different values on successive calls
3023        // (verifying per-call evaluation, not constant folding).
3024        let a = RandomFunc.invoke(&[]).unwrap();
3025        let b = RandomFunc.invoke(&[]).unwrap();
3026        // With overwhelming probability, two random i64 values differ.
3027        // If they're ever equal, it's a 1-in-2^64 coincidence.
3028        assert_ne!(a.as_integer(), b.as_integer());
3029    }
3030
3031    #[test]
3032    fn test_registry_nondeterministic_lookup() {
3033        let mut registry = FunctionRegistry::default();
3034        register_builtins(&mut registry);
3035
3036        // Non-deterministic functions should be findable and flagged.
3037        let random = registry.find_scalar("random", 0).unwrap();
3038        assert!(!random.is_deterministic());
3039
3040        let changes = registry.find_scalar("changes", 0).unwrap();
3041        assert!(!changes.is_deterministic());
3042
3043        let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
3044        assert!(!lir.is_deterministic());
3045
3046        // Deterministic function check.
3047        let abs = registry.find_scalar("abs", 1).unwrap();
3048        assert!(abs.is_deterministic());
3049    }
3050}