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