Skip to main content

fsqlite_ext_misc/
lib.rs

1//! Miscellaneous extensions: generate_series, decimal, uuid (§14.7).
2//!
3//! Provides three independent extension families:
4//!
5//! 1. **generate_series(START \[, STOP [, STEP]])**: virtual table that
6//!    generates a sequence of integers, commonly used in joins and CTEs.
7//!
8//! 2. **Decimal arithmetic**: exact string-based decimal operations that avoid
9//!    floating-point precision loss. Functions: `decimal`, `decimal_add`,
10//!    `decimal_sub`, `decimal_mul`, `decimal_cmp`.
11//!
12//! 3. **UUID generation**: `uuid()` generates random UUID v4 strings,
13//!    `uuid_str` converts blob to string, `uuid_blob` converts string to blob.
14
15use std::cmp::Ordering;
16use std::sync::Arc;
17
18use fsqlite_error::{FrankenError, Result};
19use fsqlite_func::FunctionRegistry;
20use fsqlite_func::scalar::ScalarFunction;
21use fsqlite_func::vtab::{ColumnContext, IndexInfo, VirtualTable, VirtualTableCursor};
22use fsqlite_types::cx::Cx;
23use fsqlite_types::value::{SmallText, SqliteValue};
24use rand::Rng;
25use tracing::{debug, info};
26
27#[must_use]
28pub const fn extension_name() -> &'static str {
29    "misc"
30}
31
32// ══════════════════════════════════════════════════════════════════════
33// generate_series virtual table
34// ══════════════════════════════════════════════════════════════════════
35
36/// Virtual table that generates a sequence of integers.
37///
38/// Usage: `SELECT value FROM generate_series(1, 10)` produces rows 1..=10.
39/// `START` is required, `STOP` defaults to `4294967295`, `STEP` defaults to
40/// `1`, and `STEP=0` is treated as `1` to match SQLite.
41pub struct GenerateSeriesTable;
42
43const GENERATE_SERIES_DEFAULT_STOP: i64 = u32::MAX as i64;
44
45const fn normalize_generate_series_step(step: i64) -> i64 {
46    if step == 0 { 1 } else { step }
47}
48
49impl VirtualTable for GenerateSeriesTable {
50    type Cursor = GenerateSeriesCursor;
51
52    fn create(_cx: &Cx, _args: &[&str]) -> Result<Self> {
53        Ok(Self)
54    }
55
56    fn connect(_cx: &Cx, _args: &[&str]) -> Result<Self> {
57        Ok(Self)
58    }
59
60    fn best_index(&self, info: &mut IndexInfo) -> Result<()> {
61        // generate_series accepts 1-3 equality constraints on hidden columns
62        // start (col=1), stop (col=2), step (col=3)
63        info.estimated_cost = 1.0;
64        info.estimated_rows = 1000;
65        Ok(())
66    }
67
68    fn open(&self) -> Result<Self::Cursor> {
69        Ok(GenerateSeriesCursor {
70            current: 0,
71            start: 0,
72            stop: 0,
73            step: 1,
74            done: true,
75        })
76    }
77}
78
79/// Cursor for iterating over a generated integer series.
80pub struct GenerateSeriesCursor {
81    current: i64,
82    start: i64,
83    stop: i64,
84    step: i64,
85    done: bool,
86}
87
88impl GenerateSeriesCursor {
89    /// Initialize the cursor from explicit start/stop/step values.
90    #[allow(clippy::similar_names)]
91    pub fn init(&mut self, start: i64, stop: i64, step: i64) -> Result<()> {
92        let step = normalize_generate_series_step(step);
93        self.start = start;
94        self.current = start;
95        self.stop = stop;
96        self.step = step;
97        self.done = if step > 0 { start > stop } else { start < stop };
98        debug!(start, stop, step, "generate_series: initialized cursor");
99        Ok(())
100    }
101}
102
103impl VirtualTableCursor for GenerateSeriesCursor {
104    fn filter(
105        &mut self,
106        _cx: &Cx,
107        _idx_num: i32,
108        _idx_str: Option<&str>,
109        args: &[SqliteValue],
110    ) -> Result<()> {
111        let start = args
112            .first()
113            .map(SqliteValue::to_integer)
114            .ok_or_else(|| FrankenError::internal("generate_series: start argument is required"))?;
115        let end = args
116            .get(1)
117            .map_or(GENERATE_SERIES_DEFAULT_STOP, SqliteValue::to_integer);
118        let step = args.get(2).map_or(1, |value| {
119            normalize_generate_series_step(value.to_integer())
120        });
121        self.init(start, end, step)
122    }
123
124    fn next(&mut self, _cx: &Cx) -> Result<()> {
125        if self.done {
126            return Ok(());
127        }
128        // Use checked_add to detect overflow and terminate gracefully.
129        // saturating_add would cause an infinite loop when current hits i64::MAX/MIN
130        // because `current > stop` would remain false while current stays saturated.
131        match self.current.checked_add(self.step) {
132            Some(next_val) => {
133                self.current = next_val;
134                self.done = if self.step > 0 {
135                    self.current > self.stop
136                } else {
137                    self.current < self.stop
138                };
139            }
140            None => {
141                // Overflow — we've exhausted the range, terminate iteration
142                self.done = true;
143            }
144        }
145        Ok(())
146    }
147
148    fn eof(&self) -> bool {
149        self.done
150    }
151
152    fn column(&self, ctx: &mut ColumnContext, col: i32) -> Result<()> {
153        if self.done {
154            ctx.set_value(SqliteValue::Null);
155            return Ok(());
156        }
157
158        let val = match col {
159            0 => SqliteValue::Integer(self.current),
160            1 => SqliteValue::Integer(self.start),
161            2 => SqliteValue::Integer(self.stop),
162            3 => SqliteValue::Integer(self.step),
163            _ => SqliteValue::Null,
164        };
165        ctx.set_value(val);
166        Ok(())
167    }
168
169    fn rowid(&self) -> Result<i64> {
170        Ok(if self.done { 0 } else { self.current })
171    }
172}
173// ══════════════════════════════════════════════════════════════════════
174// Decimal extension — exact string-based arithmetic
175// ══════════════════════════════════════════════════════════════════════
176/// Normalize a decimal string to canonical form.
177///
178/// Strips leading zeros (except the one before the decimal point),
179/// ensures there's at least a "0" if the integer part is empty.
180fn decimal_normalize(s: &str) -> Option<String> {
181    let (negative, int_digits, frac_digits) = parse_decimal(s)?;
182    Some(format_decimal(negative, &int_digits, &frac_digits))
183}
184
185/// Parse a decimal string into (negative, integer_digits, fractional_digits).
186fn parse_decimal(s: &str) -> Option<(bool, Vec<u8>, Vec<u8>)> {
187    let s = s.trim();
188    let (negative, s) = if let Some(stripped) = s.strip_prefix('-') {
189        (true, stripped)
190    } else if let Some(stripped) = s.strip_prefix('+') {
191        (false, stripped)
192    } else {
193        (false, s)
194    };
195
196    if s.is_empty() {
197        return None;
198    }
199
200    let (int_str, frac_str) = match s.split_once('.') {
201        Some((i, f)) => (i, f),
202        None => (s, ""),
203    };
204
205    if !int_str.is_empty() && !int_str.bytes().all(|b| b.is_ascii_digit()) {
206        return None;
207    }
208    if !frac_str.is_empty() && !frac_str.bytes().all(|b| b.is_ascii_digit()) {
209        return None;
210    }
211
212    let int_digits: Vec<u8> = int_str.bytes().map(|b| b - b'0').collect();
213    let frac_digits: Vec<u8> = frac_str.bytes().map(|b| b - b'0').collect();
214
215    Some((negative, int_digits, frac_digits))
216}
217
218/// Add two non-negative decimal digit sequences (aligned by decimal point).
219///
220/// Returns (integer_digits, fractional_digits) of the sum.
221fn add_unsigned(int_a: &[u8], frac_a: &[u8], int_b: &[u8], frac_b: &[u8]) -> (Vec<u8>, Vec<u8>) {
222    // Pad fractional parts to equal length
223    let frac_len = frac_a.len().max(frac_b.len());
224    let mut fa: Vec<u8> = frac_a.to_vec();
225    fa.resize(frac_len, 0);
226    let mut fb: Vec<u8> = frac_b.to_vec();
227    fb.resize(frac_len, 0);
228
229    // Add fractional part right-to-left
230    let mut carry: u8 = 0;
231    let mut frac_result = vec![0u8; frac_len];
232    for i in (0..frac_len).rev() {
233        let sum = fa[i] + fb[i] + carry;
234        frac_result[i] = sum % 10;
235        carry = sum / 10;
236    }
237
238    // Pad integer parts to equal length
239    let int_len = int_a.len().max(int_b.len());
240    let mut ia = vec![0u8; int_len - int_a.len()];
241    ia.extend_from_slice(int_a);
242    let mut ib = vec![0u8; int_len - int_b.len()];
243    ib.extend_from_slice(int_b);
244
245    // Add integer part right-to-left
246    let mut int_result = vec![0u8; int_len];
247    for i in (0..int_len).rev() {
248        let sum = ia[i] + ib[i] + carry;
249        int_result[i] = sum % 10;
250        carry = sum / 10;
251    }
252    if carry > 0 {
253        int_result.insert(0, carry);
254    }
255
256    (int_result, frac_result)
257}
258
259/// Subtract unsigned b from unsigned a (assumes a >= b).
260fn sub_unsigned(int_a: &[u8], frac_a: &[u8], int_b: &[u8], frac_b: &[u8]) -> (Vec<u8>, Vec<u8>) {
261    let frac_len = frac_a.len().max(frac_b.len());
262    let mut fa: Vec<u8> = frac_a.to_vec();
263    fa.resize(frac_len, 0);
264    let mut fb: Vec<u8> = frac_b.to_vec();
265    fb.resize(frac_len, 0);
266
267    let mut borrow: i16 = 0;
268    let mut frac_result = vec![0u8; frac_len];
269    for i in (0..frac_len).rev() {
270        let diff = i16::from(fa[i]) - i16::from(fb[i]) - borrow;
271        if diff < 0 {
272            frac_result[i] = u8::try_from(diff + 10).unwrap_or(0);
273            borrow = 1;
274        } else {
275            frac_result[i] = u8::try_from(diff).unwrap_or(0);
276            borrow = 0;
277        }
278    }
279
280    let int_len = int_a.len().max(int_b.len());
281    let mut ia = vec![0u8; int_len - int_a.len()];
282    ia.extend_from_slice(int_a);
283    let mut ib = vec![0u8; int_len - int_b.len()];
284    ib.extend_from_slice(int_b);
285
286    let mut int_result = vec![0u8; int_len];
287    for i in (0..int_len).rev() {
288        let diff = i16::from(ia[i]) - i16::from(ib[i]) - borrow;
289        if diff < 0 {
290            int_result[i] = u8::try_from(diff + 10).unwrap_or(0);
291            borrow = 1;
292        } else {
293            int_result[i] = u8::try_from(diff).unwrap_or(0);
294            borrow = 0;
295        }
296    }
297
298    (int_result, frac_result)
299}
300
301/// Compare two unsigned decimal values.
302fn cmp_unsigned(int_a: &[u8], frac_a: &[u8], int_b: &[u8], frac_b: &[u8]) -> Ordering {
303    // Compare by number of significant integer digits first
304    let ia = strip_leading_zeros(int_a);
305    let ib = strip_leading_zeros(int_b);
306
307    match ia.len().cmp(&ib.len()) {
308        Ordering::Equal => {}
309        ord => return ord,
310    }
311
312    // Same length integer parts — compare digit by digit
313    for (a, b) in ia.iter().zip(ib.iter()) {
314        match a.cmp(b) {
315            Ordering::Equal => {}
316            ord => return ord,
317        }
318    }
319
320    // Integer parts equal — compare fractional parts
321    let frac_len = frac_a.len().max(frac_b.len());
322    for i in 0..frac_len {
323        let a = frac_a.get(i).copied().unwrap_or(0);
324        let b = frac_b.get(i).copied().unwrap_or(0);
325        match a.cmp(&b) {
326            Ordering::Equal => {}
327            ord => return ord,
328        }
329    }
330
331    Ordering::Equal
332}
333
334fn strip_leading_zeros(digits: &[u8]) -> &[u8] {
335    let start = digits.iter().position(|&d| d != 0).unwrap_or(digits.len());
336    if start == digits.len() {
337        // All zeros — return single zero
338        &digits[digits.len().saturating_sub(1)..]
339    } else {
340        &digits[start..]
341    }
342}
343
344/// Format digit vectors back to a decimal string.
345fn format_decimal(negative: bool, int_digits: &[u8], frac_digits: &[u8]) -> String {
346    let int_str: String = strip_leading_zeros(int_digits)
347        .iter()
348        .map(|d| char::from(b'0' + d))
349        .collect();
350    let int_str = if int_str.is_empty() {
351        "0".to_owned()
352    } else {
353        int_str
354    };
355
356    // Trim trailing zeros from fractional part
357    let frac_end = frac_digits
358        .iter()
359        .rposition(|&d| d != 0)
360        .map_or(0, |p| p + 1);
361    let frac = &frac_digits[..frac_end];
362
363    let result = if frac.is_empty() {
364        int_str
365    } else {
366        let frac_str: String = frac.iter().map(|d| char::from(b'0' + d)).collect();
367        format!("{int_str}.{frac_str}")
368    };
369
370    if negative && result != "0" {
371        format!("-{result}")
372    } else {
373        result
374    }
375}
376
377/// Perform decimal addition: a + b.
378fn decimal_add_impl(a: &str, b: &str) -> Option<String> {
379    let (neg_a, int_a, frac_a) = parse_decimal(a)?;
380    let (neg_b, int_b, frac_b) = parse_decimal(b)?;
381
382    let result = match (neg_a, neg_b) {
383        (false, false) => {
384            let (ir, fr) = add_unsigned(&int_a, &frac_a, &int_b, &frac_b);
385            format_decimal(false, &ir, &fr)
386        }
387        (true, true) => {
388            let (ir, fr) = add_unsigned(&int_a, &frac_a, &int_b, &frac_b);
389            format_decimal(true, &ir, &fr)
390        }
391        (false, true) => {
392            // a - |b|
393            match cmp_unsigned(&int_a, &frac_a, &int_b, &frac_b) {
394                Ordering::Less => {
395                    let (ir, fr) = sub_unsigned(&int_b, &frac_b, &int_a, &frac_a);
396                    format_decimal(true, &ir, &fr)
397                }
398                Ordering::Equal => "0".to_owned(),
399                Ordering::Greater => {
400                    let (ir, fr) = sub_unsigned(&int_a, &frac_a, &int_b, &frac_b);
401                    format_decimal(false, &ir, &fr)
402                }
403            }
404        }
405        (true, false) => {
406            // -|a| + b = b - |a|
407            match cmp_unsigned(&int_b, &frac_b, &int_a, &frac_a) {
408                Ordering::Less => {
409                    let (ir, fr) = sub_unsigned(&int_a, &frac_a, &int_b, &frac_b);
410                    format_decimal(true, &ir, &fr)
411                }
412                Ordering::Equal => "0".to_owned(),
413                Ordering::Greater => {
414                    let (ir, fr) = sub_unsigned(&int_b, &frac_b, &int_a, &frac_a);
415                    format_decimal(false, &ir, &fr)
416                }
417            }
418        }
419    };
420    Some(result)
421}
422
423/// Perform decimal subtraction: a - b.
424fn decimal_sub_impl(a: &str, b: &str) -> Option<String> {
425    let b_str = b.trim();
426    if b_str.is_empty() {
427        return None;
428    }
429    // a - b = a + (-b)
430    let neg_b = if let Some(stripped) = b_str.strip_prefix('-') {
431        stripped.to_owned()
432    } else if let Some(stripped) = b_str.strip_prefix('+') {
433        format!("-{stripped}")
434    } else {
435        format!("-{b_str}")
436    };
437    decimal_add_impl(a, &neg_b)
438}
439
440/// Perform decimal multiplication: a * b.
441fn decimal_mul_impl(a: &str, b: &str) -> Option<String> {
442    let (neg_a, int_a, frac_a) = parse_decimal(a)?;
443    let (neg_b, int_b, frac_b) = parse_decimal(b)?;
444
445    let result_negative = neg_a != neg_b;
446    let frac_places = frac_a.len() + frac_b.len();
447
448    // Combine integer and fractional into a single digit sequence
449    let mut digits_a: Vec<u8> = int_a;
450    digits_a.extend_from_slice(&frac_a);
451    let mut digits_b: Vec<u8> = int_b;
452    digits_b.extend_from_slice(&frac_b);
453
454    // Grade-school multiplication
455    let len_a = digits_a.len();
456    let len_b = digits_b.len();
457    let mut product = vec![0u16; len_a + len_b];
458
459    for (i, &da) in digits_a.iter().enumerate().rev() {
460        for (j, &db) in digits_b.iter().enumerate().rev() {
461            let pos = i + j + 1;
462            product[pos] += u16::from(da) * u16::from(db);
463            product[i + j] += product[pos] / 10;
464            product[pos] %= 10;
465        }
466    }
467
468    // Convert to u8
469    // Each cell is guaranteed to be 0-9 after carry propagation
470    let product: Vec<u8> = product
471        .iter()
472        .map(|&d| u8::try_from(d).unwrap_or(0))
473        .collect();
474
475    // Split at decimal point
476    let total_len = product.len();
477    let int_end = total_len.saturating_sub(frac_places);
478    let int_digits = &product[..int_end];
479    let frac_digits = &product[int_end..];
480
481    Some(format_decimal(result_negative, int_digits, frac_digits))
482}
483
484/// Compare two decimal values, returning -1, 0, or 1.
485fn decimal_cmp_impl(a: &str, b: &str) -> Option<i64> {
486    let (neg_a, int_a, frac_a) = parse_decimal(a)?;
487    let (neg_b, int_b, frac_b) = parse_decimal(b)?;
488
489    let a_is_zero = int_a.iter().all(|&d| d == 0) && frac_a.iter().all(|&d| d == 0);
490    let b_is_zero = int_b.iter().all(|&d| d == 0) && frac_b.iter().all(|&d| d == 0);
491
492    if a_is_zero && b_is_zero {
493        return Some(0);
494    }
495
496    let result = match (neg_a && !a_is_zero, neg_b && !b_is_zero) {
497        (true, false) => -1,
498        (false, true) => 1,
499        (true, true) => {
500            // Both negative — larger magnitude is smaller
501            match cmp_unsigned(&int_a, &frac_a, &int_b, &frac_b) {
502                Ordering::Less => 1,
503                Ordering::Equal => 0,
504                Ordering::Greater => -1,
505            }
506        }
507        (false, false) => match cmp_unsigned(&int_a, &frac_a, &int_b, &frac_b) {
508            Ordering::Less => -1,
509            Ordering::Equal => 0,
510            Ordering::Greater => 1,
511        },
512    };
513    Some(result)
514}
515
516// ── Decimal scalar functions ─────────────────────────────────────────
517
518/// `decimal(X)` — convert a value to canonical decimal text.
519pub struct DecimalFunc;
520
521impl ScalarFunction for DecimalFunc {
522    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
523        if args.len() != 1 {
524            return Err(FrankenError::internal(
525                "decimal requires exactly 1 argument",
526            ));
527        }
528        if args[0].is_null() {
529            return Ok(SqliteValue::Null);
530        }
531        let text = args[0].to_text();
532        Ok(SqliteValue::Text(SmallText::from_string(
533            decimal_normalize(&text).unwrap_or_else(|| text.clone()),
534        )))
535    }
536
537    fn num_args(&self) -> i32 {
538        1
539    }
540
541    fn name(&self) -> &'static str {
542        "decimal"
543    }
544}
545
546/// `decimal_add(X, Y)` — exact decimal addition.
547pub struct DecimalAddFunc;
548
549impl ScalarFunction for DecimalAddFunc {
550    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
551        if args.len() != 2 {
552            return Err(FrankenError::internal(
553                "decimal_add requires exactly 2 arguments",
554            ));
555        }
556        if args[0].is_null() || args[1].is_null() {
557            return Ok(SqliteValue::Null);
558        }
559        let a = args[0].to_text();
560        let b = args[1].to_text();
561        debug!(a = %a, b = %b, "decimal_add invoked");
562        Ok(match decimal_add_impl(&a, &b) {
563            Some(result) => SqliteValue::Text(SmallText::from_string(result)),
564            None => SqliteValue::Null,
565        })
566    }
567
568    fn num_args(&self) -> i32 {
569        2
570    }
571
572    fn name(&self) -> &'static str {
573        "decimal_add"
574    }
575}
576
577/// `decimal_sub(X, Y)` — exact decimal subtraction.
578pub struct DecimalSubFunc;
579
580impl ScalarFunction for DecimalSubFunc {
581    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
582        if args.len() != 2 {
583            return Err(FrankenError::internal(
584                "decimal_sub requires exactly 2 arguments",
585            ));
586        }
587        if args[0].is_null() || args[1].is_null() {
588            return Ok(SqliteValue::Null);
589        }
590        let a = args[0].to_text();
591        let b = args[1].to_text();
592        debug!(a = %a, b = %b, "decimal_sub invoked");
593        Ok(match decimal_sub_impl(&a, &b) {
594            Some(result) => SqliteValue::Text(SmallText::from_string(result)),
595            None => SqliteValue::Null,
596        })
597    }
598
599    fn num_args(&self) -> i32 {
600        2
601    }
602
603    fn name(&self) -> &'static str {
604        "decimal_sub"
605    }
606}
607
608/// `decimal_mul(X, Y)` — exact decimal multiplication.
609pub struct DecimalMulFunc;
610
611impl ScalarFunction for DecimalMulFunc {
612    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
613        if args.len() != 2 {
614            return Err(FrankenError::internal(
615                "decimal_mul requires exactly 2 arguments",
616            ));
617        }
618        if args[0].is_null() || args[1].is_null() {
619            return Ok(SqliteValue::Null);
620        }
621        let a = args[0].to_text();
622        let b = args[1].to_text();
623        debug!(a = %a, b = %b, "decimal_mul invoked");
624        Ok(match decimal_mul_impl(&a, &b) {
625            Some(result) => SqliteValue::Text(SmallText::from_string(result)),
626            None => SqliteValue::Null,
627        })
628    }
629
630    fn num_args(&self) -> i32 {
631        2
632    }
633
634    fn name(&self) -> &'static str {
635        "decimal_mul"
636    }
637}
638
639/// `decimal_cmp(X, Y)` — compare two decimals, returning -1, 0, or 1.
640pub struct DecimalCmpFunc;
641
642impl ScalarFunction for DecimalCmpFunc {
643    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
644        if args.len() != 2 {
645            return Err(FrankenError::internal(
646                "decimal_cmp requires exactly 2 arguments",
647            ));
648        }
649        if args[0].is_null() || args[1].is_null() {
650            return Ok(SqliteValue::Null);
651        }
652        let a = args[0].to_text();
653        let b = args[1].to_text();
654        debug!(a = %a, b = %b, "decimal_cmp invoked");
655        Ok(match decimal_cmp_impl(&a, &b) {
656            Some(result) => SqliteValue::Integer(result),
657            None => SqliteValue::Null,
658        })
659    }
660
661    fn num_args(&self) -> i32 {
662        2
663    }
664
665    fn name(&self) -> &'static str {
666        "decimal_cmp"
667    }
668}
669
670// ══════════════════════════════════════════════════════════════════════
671// UUID extension
672// ══════════════════════════════════════════════════════════════════════
673
674/// Generate a random UUID v4 string.
675fn generate_uuid_v4() -> String {
676    let mut bytes = [0u8; 16];
677    rand::rng().fill_bytes(&mut bytes);
678
679    // Set version (4) and variant (10xx)
680    bytes[6] = (bytes[6] & 0x0F) | 0x40; // version 4
681    bytes[8] = (bytes[8] & 0x3F) | 0x80; // variant 10xx
682
683    format!(
684        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
685        bytes[0],
686        bytes[1],
687        bytes[2],
688        bytes[3],
689        bytes[4],
690        bytes[5],
691        bytes[6],
692        bytes[7],
693        bytes[8],
694        bytes[9],
695        bytes[10],
696        bytes[11],
697        bytes[12],
698        bytes[13],
699        bytes[14],
700        bytes[15],
701    )
702}
703
704fn decode_uuid_nibble(byte: u8, position: usize) -> Result<u8> {
705    match byte {
706        b'0'..=b'9' => Ok(byte - b'0'),
707        b'a'..=b'f' => Ok(byte - b'a' + 10),
708        b'A'..=b'F' => Ok(byte - b'A' + 10),
709        _ => Err(FrankenError::internal(format!(
710            "invalid UUID character at position {position}: {byte:?}",
711        ))),
712    }
713}
714
715fn decode_uuid_hex_pair(bytes: &[u8], index: usize) -> Result<u8> {
716    let high = decode_uuid_nibble(bytes[index], index)?;
717    let low = decode_uuid_nibble(bytes[index + 1], index + 1)?;
718    Ok((high << 4) | low)
719}
720
721/// Parse a UUID string into 16 bytes.
722fn uuid_str_to_blob(s: &str) -> Result<Vec<u8>> {
723    let ascii = s.as_bytes();
724    let hex_digits: Vec<u8> = match ascii.len() {
725        32 => ascii.to_vec(),
726        36 => {
727            for hyphen_index in [8usize, 13, 18, 23] {
728                if ascii[hyphen_index] != b'-' {
729                    return Err(FrankenError::internal(format!(
730                        "invalid UUID string: expected '-' at position {hyphen_index}",
731                    )));
732                }
733            }
734
735            let mut digits = Vec::with_capacity(32);
736            for (index, byte) in ascii.iter().copied().enumerate() {
737                if matches!(index, 8 | 13 | 18 | 23) {
738                    continue;
739                }
740                digits.push(byte);
741            }
742            digits
743        }
744        len => {
745            return Err(FrankenError::internal(format!(
746                "invalid UUID string length {len}: expected 32 or 36 characters",
747            )));
748        }
749    };
750
751    let mut bytes = Vec::with_capacity(16);
752    for i in (0..hex_digits.len()).step_by(2) {
753        bytes.push(decode_uuid_hex_pair(&hex_digits, i)?);
754    }
755    Ok(bytes)
756}
757
758/// Format 16 bytes as a UUID string.
759fn blob_to_uuid_str(bytes: &[u8]) -> Result<String> {
760    if bytes.len() != 16 {
761        return Err(FrankenError::internal(format!(
762            "uuid_str: expected 16-byte blob, got {} bytes",
763            bytes.len()
764        )));
765    }
766    Ok(format!(
767        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
768        bytes[0],
769        bytes[1],
770        bytes[2],
771        bytes[3],
772        bytes[4],
773        bytes[5],
774        bytes[6],
775        bytes[7],
776        bytes[8],
777        bytes[9],
778        bytes[10],
779        bytes[11],
780        bytes[12],
781        bytes[13],
782        bytes[14],
783        bytes[15],
784    ))
785}
786
787// ── UUID scalar functions ────────────────────────────────────────────
788
789/// `uuid()` — generate a random UUID v4 string.
790pub struct UuidFunc;
791
792impl ScalarFunction for UuidFunc {
793    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
794        if !args.is_empty() {
795            return Err(FrankenError::internal("uuid takes no arguments"));
796        }
797        let uuid = generate_uuid_v4();
798        debug!(uuid = %uuid, "uuid() generated");
799        Ok(SqliteValue::Text(SmallText::from_string(uuid)))
800    }
801
802    fn is_deterministic(&self) -> bool {
803        false // each call returns a new UUID
804    }
805
806    fn num_args(&self) -> i32 {
807        0
808    }
809
810    fn name(&self) -> &'static str {
811        "uuid"
812    }
813}
814
815/// `uuid_str(X)` — convert a 16-byte UUID blob to its string representation.
816pub struct UuidStrFunc;
817
818impl ScalarFunction for UuidStrFunc {
819    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
820        if args.len() != 1 {
821            return Err(FrankenError::internal(
822                "uuid_str requires exactly 1 argument",
823            ));
824        }
825        if args[0].is_null() {
826            return Ok(SqliteValue::Null);
827        }
828        match &args[0] {
829            SqliteValue::Blob(b) => {
830                let s = blob_to_uuid_str(b)?;
831                Ok(SqliteValue::Text(SmallText::from_string(s)))
832            }
833            SqliteValue::Text(s) => {
834                // If already a string, normalize it
835                let blob = uuid_str_to_blob(s)?;
836                let normalized = blob_to_uuid_str(&blob)?;
837                Ok(SqliteValue::Text(SmallText::from_string(normalized)))
838            }
839            _ => Err(FrankenError::internal(
840                "uuid_str: argument must be a blob or text",
841            )),
842        }
843    }
844
845    fn num_args(&self) -> i32 {
846        1
847    }
848
849    fn name(&self) -> &'static str {
850        "uuid_str"
851    }
852}
853
854/// `uuid_blob(X)` — convert a UUID string to a 16-byte blob.
855pub struct UuidBlobFunc;
856
857impl ScalarFunction for UuidBlobFunc {
858    fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
859        if args.len() != 1 {
860            return Err(FrankenError::internal(
861                "uuid_blob requires exactly 1 argument",
862            ));
863        }
864        if args[0].is_null() {
865            return Ok(SqliteValue::Null);
866        }
867        let Some(s) = args[0].as_text() else {
868            return Err(FrankenError::internal("uuid_blob: argument must be text"));
869        };
870        let blob = uuid_str_to_blob(s)?;
871        Ok(SqliteValue::Blob(Arc::from(blob.as_slice())))
872    }
873
874    fn num_args(&self) -> i32 {
875        1
876    }
877
878    fn name(&self) -> &'static str {
879        "uuid_blob"
880    }
881}
882
883// ══════════════════════════════════════════════════════════════════════
884// Registration
885// ══════════════════════════════════════════════════════════════════════
886
887/// Register all miscellaneous scalar functions.
888pub fn register_misc_scalars(registry: &mut FunctionRegistry) {
889    info!("misc extension: registering scalar functions");
890    registry.register_scalar(DecimalFunc);
891    registry.register_scalar(DecimalAddFunc);
892    registry.register_scalar(DecimalSubFunc);
893    registry.register_scalar(DecimalMulFunc);
894    registry.register_scalar(DecimalCmpFunc);
895    registry.register_scalar(UuidFunc);
896    registry.register_scalar(UuidStrFunc);
897    registry.register_scalar(UuidBlobFunc);
898}
899
900// ══════════════════════════════════════════════════════════════════════
901// Tests
902// ══════════════════════════════════════════════════════════════════════
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907
908    #[test]
909    fn test_extension_name_matches_crate_suffix() {
910        let expected = env!("CARGO_PKG_NAME")
911            .strip_prefix("fsqlite-ext-")
912            .expect("extension crates should use fsqlite-ext-* naming");
913        assert_eq!(extension_name(), expected);
914    }
915
916    // ── generate_series ──────────────────────────────────────────────
917
918    #[test]
919    fn test_generate_series_basic() {
920        let table = GenerateSeriesTable;
921        let mut cursor = table.open().unwrap();
922        cursor.init(1, 5, 1).unwrap();
923
924        let mut values = Vec::new();
925        let cx = Cx::new();
926        while !cursor.eof() {
927            let mut ctx = ColumnContext::new();
928            cursor.column(&mut ctx, 0).unwrap();
929            if let Some(SqliteValue::Integer(v)) = ctx.take_value() {
930                values.push(v);
931            }
932            cursor.next(&cx).unwrap();
933        }
934        assert_eq!(values, vec![1, 2, 3, 4, 5]);
935    }
936
937    #[test]
938    fn test_generate_series_step() {
939        let table = GenerateSeriesTable;
940        let mut cursor = table.open().unwrap();
941        cursor.init(0, 10, 2).unwrap();
942
943        let mut values = Vec::new();
944        let cx = Cx::new();
945        while !cursor.eof() {
946            let mut ctx = ColumnContext::new();
947            cursor.column(&mut ctx, 0).unwrap();
948            if let Some(SqliteValue::Integer(v)) = ctx.take_value() {
949                values.push(v);
950            }
951            cursor.next(&cx).unwrap();
952        }
953        assert_eq!(values, vec![0, 2, 4, 6, 8, 10]);
954    }
955
956    #[test]
957    fn test_generate_series_negative_step() {
958        let table = GenerateSeriesTable;
959        let mut cursor = table.open().unwrap();
960        cursor.init(5, 1, -1).unwrap();
961
962        let mut values = Vec::new();
963        let cx = Cx::new();
964        while !cursor.eof() {
965            let mut ctx = ColumnContext::new();
966            cursor.column(&mut ctx, 0).unwrap();
967            if let Some(SqliteValue::Integer(v)) = ctx.take_value() {
968                values.push(v);
969            }
970            cursor.next(&cx).unwrap();
971        }
972        assert_eq!(values, vec![5, 4, 3, 2, 1]);
973    }
974
975    #[test]
976    fn test_generate_series_single() {
977        let table = GenerateSeriesTable;
978        let mut cursor = table.open().unwrap();
979        cursor.init(5, 5, 1).unwrap();
980
981        let mut values = Vec::new();
982        let cx = Cx::new();
983        while !cursor.eof() {
984            let mut ctx = ColumnContext::new();
985            cursor.column(&mut ctx, 0).unwrap();
986            if let Some(SqliteValue::Integer(v)) = ctx.take_value() {
987                values.push(v);
988            }
989            cursor.next(&cx).unwrap();
990        }
991        assert_eq!(values, vec![5]);
992    }
993
994    #[test]
995    fn test_generate_series_empty() {
996        let table = GenerateSeriesTable;
997        let mut cursor = table.open().unwrap();
998        cursor.init(5, 1, 1).unwrap();
999        assert!(
1000            cursor.eof(),
1001            "positive step with start > stop should be empty"
1002        );
1003    }
1004
1005    #[test]
1006    fn test_generate_series_step_zero_defaults_to_one() {
1007        let table = GenerateSeriesTable;
1008        let mut cursor = table.open().unwrap();
1009        cursor.init(1, 3, 0).unwrap();
1010
1011        let mut values = Vec::new();
1012        let cx = Cx::new();
1013        while !cursor.eof() {
1014            values.push(cursor.current);
1015            cursor.next(&cx).unwrap();
1016        }
1017        assert_eq!(values, vec![1, 2, 3]);
1018    }
1019
1020    #[test]
1021    fn test_generate_series_filter() {
1022        let table = GenerateSeriesTable;
1023        let mut cursor = table.open().unwrap();
1024        let cx = Cx::new();
1025        cursor
1026            .filter(
1027                &cx,
1028                0,
1029                None,
1030                &[
1031                    SqliteValue::Integer(1),
1032                    SqliteValue::Integer(3),
1033                    SqliteValue::Integer(1),
1034                ],
1035            )
1036            .unwrap();
1037
1038        let mut values = Vec::new();
1039        while !cursor.eof() {
1040            let mut ctx = ColumnContext::new();
1041            cursor.column(&mut ctx, 0).unwrap();
1042            if let Some(SqliteValue::Integer(v)) = ctx.take_value() {
1043                values.push(v);
1044            }
1045            cursor.next(&cx).unwrap();
1046        }
1047        assert_eq!(values, vec![1, 2, 3]);
1048    }
1049
1050    #[test]
1051    fn test_generate_series_filter_requires_start_argument() {
1052        let table = GenerateSeriesTable;
1053        let mut cursor = table.open().unwrap();
1054        let error = cursor.filter(&Cx::new(), 0, None, &[]).unwrap_err();
1055        assert!(
1056            error
1057                .to_string()
1058                .contains("generate_series: start argument is required"),
1059            "unexpected error: {error}",
1060        );
1061    }
1062
1063    #[test]
1064    fn test_generate_series_filter_defaults_stop_and_step() {
1065        let table = GenerateSeriesTable;
1066        let mut cursor = table.open().unwrap();
1067        cursor
1068            .filter(&Cx::new(), 0, None, &[SqliteValue::Integer(5)])
1069            .unwrap();
1070
1071        assert_eq!(cursor.current, 5);
1072        assert_eq!(cursor.start, 5);
1073        assert_eq!(cursor.stop, GENERATE_SERIES_DEFAULT_STOP);
1074        assert_eq!(cursor.step, 1);
1075        assert!(!cursor.eof());
1076    }
1077    // ── decimal ──────────────────────────────────────────────────────
1078
1079    #[test]
1080    fn test_decimal_normalize() {
1081        assert_eq!(decimal_normalize("1.23"), Some("1.23".to_owned()));
1082        assert_eq!(decimal_normalize("001.230"), Some("1.23".to_owned()));
1083        assert_eq!(decimal_normalize("0.0"), Some("0".to_owned()));
1084        assert_eq!(decimal_normalize("-1.50"), Some("-1.5".to_owned()));
1085        assert_eq!(decimal_normalize("42"), Some("42".to_owned()));
1086    }
1087
1088    #[test]
1089    fn test_decimal_func_basic() {
1090        let args = [SqliteValue::Text("1.23".into())];
1091        let result = DecimalFunc.invoke(&args).unwrap();
1092        assert_eq!(result, SqliteValue::Text("1.23".into()));
1093    }
1094
1095    #[test]
1096    fn test_decimal_func_null() {
1097        let args = [SqliteValue::Null];
1098        let result = DecimalFunc.invoke(&args).unwrap();
1099        assert_eq!(result, SqliteValue::Null);
1100    }
1101
1102    #[test]
1103    fn test_decimal_add() {
1104        let args = [
1105            SqliteValue::Text("1.1".into()),
1106            SqliteValue::Text("2.2".into()),
1107        ];
1108        let result = DecimalAddFunc.invoke(&args).unwrap();
1109        assert_eq!(result, SqliteValue::Text("3.3".into()));
1110    }
1111
1112    #[test]
1113    fn test_decimal_add_no_fp_loss() {
1114        // This would be 0.30000000000000004 in floating point
1115        let args = [
1116            SqliteValue::Text("0.1".into()),
1117            SqliteValue::Text("0.2".into()),
1118        ];
1119        let result = DecimalAddFunc.invoke(&args).unwrap();
1120        assert_eq!(
1121            result,
1122            SqliteValue::Text("0.3".into()),
1123            "decimal_add should avoid floating-point precision loss"
1124        );
1125    }
1126
1127    #[test]
1128    fn test_decimal_sub() {
1129        let args = [
1130            SqliteValue::Text("5.00".into()),
1131            SqliteValue::Text("1.23".into()),
1132        ];
1133        let result = DecimalSubFunc.invoke(&args).unwrap();
1134        assert_eq!(result, SqliteValue::Text("3.77".into()));
1135    }
1136
1137    #[test]
1138    fn test_decimal_sub_negative_result() {
1139        let args = [
1140            SqliteValue::Text("1.0".into()),
1141            SqliteValue::Text("3.0".into()),
1142        ];
1143        let result = DecimalSubFunc.invoke(&args).unwrap();
1144        assert_eq!(result, SqliteValue::Text("-2".into()));
1145    }
1146
1147    #[test]
1148    fn test_decimal_mul() {
1149        let args = [
1150            SqliteValue::Text("1.5".into()),
1151            SqliteValue::Text("2.5".into()),
1152        ];
1153        let result = DecimalMulFunc.invoke(&args).unwrap();
1154        assert_eq!(result, SqliteValue::Text("3.75".into()));
1155    }
1156
1157    #[test]
1158    fn test_decimal_mul_large() {
1159        let args = [
1160            SqliteValue::Text("1.1".into()),
1161            SqliteValue::Text("2.0".into()),
1162        ];
1163        let result = DecimalMulFunc.invoke(&args).unwrap();
1164        assert_eq!(result, SqliteValue::Text("2.2".into()));
1165    }
1166
1167    #[test]
1168    fn test_decimal_cmp_less() {
1169        let args = [
1170            SqliteValue::Text("1.23".into()),
1171            SqliteValue::Text("4.56".into()),
1172        ];
1173        let result = DecimalCmpFunc.invoke(&args).unwrap();
1174        assert_eq!(result, SqliteValue::Integer(-1));
1175    }
1176
1177    #[test]
1178    fn test_decimal_cmp_greater() {
1179        let args = [
1180            SqliteValue::Text("4.56".into()),
1181            SqliteValue::Text("1.23".into()),
1182        ];
1183        let result = DecimalCmpFunc.invoke(&args).unwrap();
1184        assert_eq!(result, SqliteValue::Integer(1));
1185    }
1186
1187    #[test]
1188    fn test_decimal_cmp_equal() {
1189        let args = [
1190            SqliteValue::Text("1.0".into()),
1191            SqliteValue::Text("1.0".into()),
1192        ];
1193        let result = DecimalCmpFunc.invoke(&args).unwrap();
1194        assert_eq!(result, SqliteValue::Integer(0));
1195    }
1196
1197    #[test]
1198    fn test_decimal_cmp_negative() {
1199        let args = [
1200            SqliteValue::Text("-5".into()),
1201            SqliteValue::Text("3".into()),
1202        ];
1203        let result = DecimalCmpFunc.invoke(&args).unwrap();
1204        assert_eq!(result, SqliteValue::Integer(-1));
1205    }
1206
1207    #[test]
1208    fn test_decimal_precision_financial() {
1209        // Common financial precision test: 19.99 * 100 = 1999
1210        let result = decimal_mul_impl("19.99", "100");
1211        assert_eq!(result, Some("1999".to_owned()));
1212
1213        // Chained operations: (10.50 + 3.75) * 2 = 28.50
1214        let sum = decimal_add_impl("10.50", "3.75");
1215        assert_eq!(sum, Some("14.25".to_owned()));
1216        let product = decimal_mul_impl(sum.as_ref().unwrap(), "2");
1217        assert_eq!(product, Some("28.5".to_owned()));
1218    }
1219
1220    // ── uuid ─────────────────────────────────────────────────────────
1221
1222    #[test]
1223    fn test_uuid_v4_format() {
1224        let uuid = generate_uuid_v4();
1225        // UUID v4 format: 8-4-4-4-12 hex characters
1226        let parts: Vec<&str> = uuid.split('-').collect();
1227        assert_eq!(parts.len(), 5, "UUID should have 5 dash-separated parts");
1228        assert_eq!(parts[0].len(), 8);
1229        assert_eq!(parts[1].len(), 4);
1230        assert_eq!(parts[2].len(), 4);
1231        assert_eq!(parts[3].len(), 4);
1232        assert_eq!(parts[4].len(), 12);
1233    }
1234
1235    #[test]
1236    fn test_uuid_v4_version() {
1237        let uuid = generate_uuid_v4();
1238        // Version nibble is the first character of the third group
1239        let version_char = uuid.as_bytes()[14] as char;
1240        assert_eq!(version_char, '4', "UUID v4 must have version nibble = 4");
1241    }
1242
1243    #[test]
1244    fn test_uuid_v4_variant() {
1245        let uuid = generate_uuid_v4();
1246        // Variant bits are the first character of the fourth group
1247        let variant_char = uuid.as_bytes()[19] as char;
1248        let variant_nibble = u8::from_str_radix(&variant_char.to_string(), 16).unwrap();
1249        assert!(
1250            (0x8..=0xB).contains(&variant_nibble),
1251            "UUID v4 variant bits should be 10xx, got {variant_nibble:#X}"
1252        );
1253    }
1254
1255    #[test]
1256    fn test_uuid_uniqueness() {
1257        let mut uuids: Vec<String> = (0..100).map(|_| generate_uuid_v4()).collect();
1258        uuids.sort();
1259        uuids.dedup();
1260        assert_eq!(
1261            uuids.len(),
1262            100,
1263            "100 uuid() calls should produce 100 unique values"
1264        );
1265    }
1266
1267    #[test]
1268    fn test_uuid_func() {
1269        let result = UuidFunc.invoke(&[]).unwrap();
1270        if let SqliteValue::Text(s) = result {
1271            assert_eq!(s.len(), 36, "UUID string should be 36 characters");
1272        } else {
1273            panic!("uuid() should return Text");
1274        }
1275    }
1276
1277    #[test]
1278    fn test_uuid_str_blob_roundtrip() {
1279        let uuid_str = generate_uuid_v4();
1280        let blob = uuid_str_to_blob(&uuid_str).unwrap();
1281        assert_eq!(blob.len(), 16);
1282        let back = blob_to_uuid_str(&blob).unwrap();
1283        assert_eq!(back, uuid_str, "uuid_str(uuid_blob(X)) should roundtrip");
1284    }
1285
1286    #[test]
1287    fn test_uuid_blob_length() {
1288        let result = UuidBlobFunc
1289            .invoke(&[SqliteValue::Text(SmallText::from_string(
1290                generate_uuid_v4().as_str(),
1291            ))])
1292            .unwrap();
1293        if let SqliteValue::Blob(b) = result {
1294            assert_eq!(b.len(), 16, "uuid_blob should return 16-byte blob");
1295        } else {
1296            panic!("uuid_blob should return Blob");
1297        }
1298    }
1299
1300    #[test]
1301    fn test_uuid_str_func() {
1302        let uuid = generate_uuid_v4();
1303        let blob = uuid_str_to_blob(&uuid).unwrap();
1304        let result = UuidStrFunc
1305            .invoke(&[SqliteValue::Blob(Arc::from(blob.as_slice()))])
1306            .unwrap();
1307        assert_eq!(
1308            result,
1309            SqliteValue::Text(SmallText::from_string(uuid.as_str()))
1310        );
1311    }
1312
1313    // ── registration ─────────────────────────────────────────────────
1314
1315    #[test]
1316    fn test_register_misc_scalars() {
1317        let mut registry = FunctionRegistry::new();
1318        register_misc_scalars(&mut registry);
1319        assert!(registry.find_scalar("decimal", 1).is_some());
1320        assert!(registry.find_scalar("decimal_add", 2).is_some());
1321        assert!(registry.find_scalar("decimal_sub", 2).is_some());
1322        assert!(registry.find_scalar("decimal_mul", 2).is_some());
1323        assert!(registry.find_scalar("decimal_cmp", 2).is_some());
1324        assert!(registry.find_scalar("uuid", 0).is_some());
1325        assert!(registry.find_scalar("uuid_str", 1).is_some());
1326        assert!(registry.find_scalar("uuid_blob", 1).is_some());
1327    }
1328
1329    // ── generate_series: additional edge cases ───────────────────────────
1330
1331    #[test]
1332    fn test_generate_series_large_step() {
1333        let table = GenerateSeriesTable;
1334        let mut cursor = table.open().unwrap();
1335        cursor.init(0, 100, 50).unwrap();
1336        let mut values = Vec::new();
1337        while !cursor.eof() {
1338            values.push(cursor.current);
1339            cursor.next(&Cx::default()).unwrap();
1340        }
1341        assert_eq!(values, vec![0, 50, 100]);
1342    }
1343
1344    #[test]
1345    fn test_generate_series_negative_range() {
1346        let table = GenerateSeriesTable;
1347        let mut cursor = table.open().unwrap();
1348        cursor.init(-5, -1, 1).unwrap();
1349        let mut count = 0;
1350        while !cursor.eof() {
1351            count += 1;
1352            cursor.next(&Cx::default()).unwrap();
1353        }
1354        assert_eq!(count, 5);
1355    }
1356
1357    #[test]
1358    fn test_generate_series_reverse_with_wrong_step_empty() {
1359        let table = GenerateSeriesTable;
1360        let mut cursor = table.open().unwrap();
1361        // start > stop with positive step → empty
1362        cursor.init(10, 1, 1).unwrap();
1363        assert!(cursor.eof());
1364    }
1365
1366    #[test]
1367    fn test_generate_series_forward_with_negative_step_empty() {
1368        let table = GenerateSeriesTable;
1369        let mut cursor = table.open().unwrap();
1370        // start < stop with negative step → empty
1371        cursor.init(1, 10, -1).unwrap();
1372        assert!(cursor.eof());
1373    }
1374
1375    #[test]
1376    fn test_generate_series_rowid() {
1377        let table = GenerateSeriesTable;
1378        let mut cursor = table.open().unwrap();
1379        cursor.init(42, 42, 1).unwrap();
1380        assert_eq!(cursor.rowid().unwrap(), 42);
1381    }
1382
1383    #[test]
1384    fn test_generate_series_column_values() {
1385        let table = GenerateSeriesTable;
1386        let mut cursor = table.open().unwrap();
1387        cursor.init(10, 20, 5).unwrap();
1388        // Column 0 = value (current)
1389        let mut ctx = ColumnContext::new();
1390        cursor.column(&mut ctx, 0).unwrap();
1391        assert_eq!(ctx.take_value(), Some(SqliteValue::Integer(10)));
1392        // Column 2 = stop
1393        let mut ctx2 = ColumnContext::new();
1394        cursor.column(&mut ctx2, 2).unwrap();
1395        assert_eq!(ctx2.take_value(), Some(SqliteValue::Integer(20)));
1396        // Column 3 = step
1397        let mut ctx3 = ColumnContext::new();
1398        cursor.column(&mut ctx3, 3).unwrap();
1399        assert_eq!(ctx3.take_value(), Some(SqliteValue::Integer(5)));
1400        // Column out of range = Null
1401        let mut ctx4 = ColumnContext::new();
1402        cursor.column(&mut ctx4, 99).unwrap();
1403        assert_eq!(ctx4.take_value(), Some(SqliteValue::Null));
1404    }
1405
1406    #[test]
1407    fn test_generate_series_past_end_returns_null_and_zero_rowid() {
1408        let table = GenerateSeriesTable;
1409        let mut cursor = table.open().unwrap();
1410        cursor.init(5, 5, 1).unwrap();
1411        let cx = Cx::new();
1412        cursor.next(&cx).unwrap();
1413        assert!(cursor.eof());
1414
1415        let mut ctx = ColumnContext::new();
1416        cursor.column(&mut ctx, 0).unwrap();
1417        assert_eq!(ctx.take_value(), Some(SqliteValue::Null));
1418        assert_eq!(cursor.rowid().unwrap(), 0);
1419    }
1420
1421    #[test]
1422    fn test_generate_series_vtable_create_connect() {
1423        let cx = Cx::default();
1424        let _ = GenerateSeriesTable::create(&cx, &[]).unwrap();
1425        let _ = GenerateSeriesTable::connect(&cx, &[]).unwrap();
1426    }
1427
1428    #[test]
1429    fn test_generate_series_best_index() {
1430        let table = GenerateSeriesTable;
1431        let mut info = IndexInfo::new(Vec::new(), Vec::new());
1432        table.best_index(&mut info).unwrap();
1433        assert!(info.estimated_cost > 0.0);
1434        assert!(info.estimated_rows > 0);
1435    }
1436
1437    #[test]
1438    fn test_generate_series_overflow_terminates() {
1439        // Regression test: saturating_add caused infinite loop when current hit i64::MAX.
1440        // With checked_add fix, overflow should terminate iteration gracefully.
1441        let table = GenerateSeriesTable;
1442        let mut cursor = table.open().unwrap();
1443        // Start near i64::MAX with a step that causes overflow
1444        cursor.init(i64::MAX - 2, i64::MAX, 10).unwrap();
1445
1446        let mut values = Vec::new();
1447        let mut iterations = 0;
1448        while !cursor.eof() && iterations < 100 {
1449            values.push(cursor.current);
1450            cursor.next(&Cx::default()).unwrap();
1451            iterations += 1;
1452        }
1453        // Should terminate within a few iterations, not loop forever
1454        assert!(
1455            iterations < 10,
1456            "generate_series should terminate on overflow, got {} iterations",
1457            iterations
1458        );
1459        // Should yield at least the start value
1460        assert!(!values.is_empty(), "should yield at least the start value");
1461        assert_eq!(values[0], i64::MAX - 2);
1462    }
1463
1464    #[test]
1465    fn test_generate_series_negative_overflow_terminates() {
1466        // Same test but for underflow with negative step
1467        let table = GenerateSeriesTable;
1468        let mut cursor = table.open().unwrap();
1469        cursor.init(i64::MIN + 2, i64::MIN, -10).unwrap();
1470
1471        let mut values = Vec::new();
1472        let mut iterations = 0;
1473        while !cursor.eof() && iterations < 100 {
1474            values.push(cursor.current);
1475            cursor.next(&Cx::default()).unwrap();
1476            iterations += 1;
1477        }
1478        assert!(
1479            iterations < 10,
1480            "generate_series should terminate on underflow, got {} iterations",
1481            iterations
1482        );
1483        assert!(!values.is_empty());
1484        assert_eq!(values[0], i64::MIN + 2);
1485    }
1486
1487    // ── Decimal: normalization edge cases ─────────────────────────────────
1488
1489    #[test]
1490    fn test_decimal_normalize_zero() {
1491        assert_eq!(decimal_normalize("0"), Some("0".to_owned()));
1492        assert_eq!(decimal_normalize("0.0"), Some("0".to_owned()));
1493        assert_eq!(decimal_normalize("000.000"), Some("0".to_owned()));
1494    }
1495
1496    #[test]
1497    fn test_decimal_normalize_negative_zero() {
1498        // Negative zero should normalize to "0"
1499        let result = decimal_normalize("-0.0");
1500        assert!(result == Some("0".to_owned()) || result == Some("-0".to_owned()));
1501    }
1502
1503    #[test]
1504    fn test_decimal_normalize_integer() {
1505        assert_eq!(decimal_normalize("42"), Some("42".to_owned()));
1506        assert_eq!(decimal_normalize("00042"), Some("42".to_owned()));
1507    }
1508
1509    #[test]
1510    fn test_decimal_normalize_trailing_zeros() {
1511        assert_eq!(decimal_normalize("1.50000"), Some("1.5".to_owned()));
1512        assert_eq!(decimal_normalize("3.14000"), Some("3.14".to_owned()));
1513    }
1514
1515    // ── Decimal: arithmetic edge cases ───────────────────────────────────
1516
1517    #[test]
1518    fn test_decimal_add_zeros() {
1519        assert_eq!(decimal_add_impl("0", "0"), Some("0".to_owned()));
1520    }
1521
1522    #[test]
1523    fn test_decimal_add_negative_plus_positive() {
1524        let result = decimal_add_impl("-5", "3");
1525        assert_eq!(result, Some("-2".to_owned()));
1526    }
1527
1528    #[test]
1529    fn test_decimal_add_positive_plus_negative() {
1530        let result = decimal_add_impl("3", "-5");
1531        assert_eq!(result, Some("-2".to_owned()));
1532    }
1533
1534    #[test]
1535    fn test_decimal_sub_same_number() {
1536        assert_eq!(decimal_sub_impl("42.5", "42.5"), Some("0".to_owned()));
1537    }
1538
1539    #[test]
1540    fn test_decimal_sub_produces_negative() {
1541        let result = decimal_sub_impl("1", "5");
1542        assert_eq!(result, Some("-4".to_owned()));
1543    }
1544
1545    #[test]
1546    fn test_decimal_mul_by_zero() {
1547        assert_eq!(decimal_mul_impl("12345.6789", "0"), Some("0".to_owned()));
1548    }
1549
1550    #[test]
1551    fn test_decimal_mul_by_one() {
1552        assert_eq!(decimal_mul_impl("3.14", "1"), Some("3.14".to_owned()));
1553    }
1554
1555    #[test]
1556    fn test_decimal_mul_negative_times_negative() {
1557        let result = decimal_mul_impl("-3", "-4");
1558        assert_eq!(result, Some("12".to_owned()));
1559    }
1560
1561    #[test]
1562    fn test_decimal_mul_small_decimals() {
1563        let result = decimal_mul_impl("0.001", "0.001");
1564        assert_eq!(result, Some("0.000001".to_owned()));
1565    }
1566
1567    #[test]
1568    fn test_decimal_cmp_equal_values() {
1569        assert_eq!(decimal_cmp_impl("3.14", "3.14"), Some(0));
1570    }
1571
1572    #[test]
1573    fn test_decimal_cmp_leading_zeros_equal() {
1574        assert_eq!(decimal_cmp_impl("007.50", "7.5"), Some(0));
1575    }
1576
1577    #[test]
1578    fn test_decimal_cmp_negative_ordering() {
1579        assert_eq!(decimal_cmp_impl("-10", "-5"), Some(-1));
1580        assert_eq!(decimal_cmp_impl("-5", "-10"), Some(1));
1581    }
1582
1583    // ── Decimal: scalar function null handling ───────────────────────────
1584
1585    #[test]
1586    fn test_decimal_add_func_null_propagation() {
1587        let result = DecimalAddFunc
1588            .invoke(&[
1589                SqliteValue::Null,
1590                SqliteValue::Text(SmallText::from_string("1")),
1591            ])
1592            .unwrap();
1593        assert_eq!(result, SqliteValue::Null);
1594    }
1595
1596    #[test]
1597    fn test_decimal_sub_func_null_propagation() {
1598        let result = DecimalSubFunc
1599            .invoke(&[
1600                SqliteValue::Text(SmallText::from_string("1")),
1601                SqliteValue::Null,
1602            ])
1603            .unwrap();
1604        assert_eq!(result, SqliteValue::Null);
1605    }
1606
1607    #[test]
1608    fn test_decimal_mul_func_null_propagation() {
1609        let result = DecimalMulFunc
1610            .invoke(&[SqliteValue::Null, SqliteValue::Null])
1611            .unwrap();
1612        assert_eq!(result, SqliteValue::Null);
1613    }
1614
1615    #[test]
1616    fn test_decimal_cmp_func_null_propagation() {
1617        let result = DecimalCmpFunc
1618            .invoke(&[
1619                SqliteValue::Null,
1620                SqliteValue::Text(SmallText::from_string("1")),
1621            ])
1622            .unwrap();
1623        assert_eq!(result, SqliteValue::Null);
1624    }
1625
1626    // ── UUID: error cases ────────────────────────────────────────────────
1627
1628    #[test]
1629    fn test_uuid_str_to_blob_invalid_length() {
1630        // Too short
1631        assert!(uuid_str_to_blob("abc").is_err());
1632    }
1633
1634    #[test]
1635    fn test_uuid_str_to_blob_invalid_hex() {
1636        assert!(uuid_str_to_blob("ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ").is_err());
1637    }
1638
1639    #[test]
1640    fn test_uuid_str_to_blob_accepts_compact_hex() {
1641        let uuid = "1234567812344abc8def1234567890ab";
1642        let blob = uuid_str_to_blob(uuid).unwrap();
1643        assert_eq!(
1644            blob_to_uuid_str(&blob).unwrap(),
1645            "12345678-1234-4abc-8def-1234567890ab"
1646        );
1647    }
1648
1649    #[test]
1650    fn test_uuid_str_to_blob_rejects_trailing_garbage() {
1651        assert!(uuid_str_to_blob("12345678-1234-4abc-8def-1234567890ab!!").is_err());
1652    }
1653
1654    #[test]
1655    fn test_uuid_str_to_blob_rejects_misplaced_hyphen() {
1656        assert!(uuid_str_to_blob("1234567-81234-4abc-8def-1234567890ab").is_err());
1657    }
1658
1659    #[test]
1660    fn test_blob_to_uuid_str_wrong_length() {
1661        assert!(blob_to_uuid_str(&[0u8; 15]).is_err());
1662        assert!(blob_to_uuid_str(&[0u8; 17]).is_err());
1663    }
1664
1665    #[test]
1666    fn test_uuid_func_with_args_errors() {
1667        let result = UuidFunc.invoke(&[SqliteValue::Integer(1)]);
1668        assert!(result.is_err());
1669    }
1670
1671    #[test]
1672    fn test_uuid_str_func_null_returns_null() {
1673        let result = UuidStrFunc.invoke(&[SqliteValue::Null]).unwrap();
1674        assert_eq!(result, SqliteValue::Null);
1675    }
1676
1677    #[test]
1678    fn test_uuid_blob_func_null_returns_null() {
1679        let result = UuidBlobFunc.invoke(&[SqliteValue::Null]).unwrap();
1680        assert_eq!(result, SqliteValue::Null);
1681    }
1682
1683    #[test]
1684    fn test_uuid_blob_func_non_text_errors() {
1685        let result = UuidBlobFunc.invoke(&[SqliteValue::Integer(42)]);
1686        assert!(result.is_err());
1687    }
1688
1689    #[test]
1690    fn test_uuid_str_func_normalizes_text() {
1691        // uuid_str with text input should normalize via blob roundtrip
1692        let uuid = generate_uuid_v4();
1693        let result = UuidStrFunc
1694            .invoke(&[SqliteValue::Text(SmallText::from_string(uuid.as_str()))])
1695            .unwrap();
1696        assert_eq!(
1697            result,
1698            SqliteValue::Text(SmallText::from_string(uuid.as_str()))
1699        );
1700    }
1701
1702    #[test]
1703    fn test_uuid_str_func_non_blob_non_text_errors() {
1704        let result = UuidStrFunc.invoke(&[SqliteValue::Integer(42)]);
1705        assert!(result.is_err());
1706    }
1707
1708    // ── UUID: format validation ──────────────────────────────────────────
1709
1710    #[test]
1711    fn test_uuid_all_lowercase_hex() {
1712        let uuid = generate_uuid_v4();
1713        // UUID should contain only lowercase hex and dashes
1714        assert!(uuid.chars().all(|c| c.is_ascii_hexdigit() || c == '-'));
1715        assert!(!uuid.contains(|c: char| c.is_ascii_uppercase()));
1716    }
1717
1718    #[test]
1719    fn test_uuid_v4_multiple_unique() {
1720        let uuids: Vec<String> = (0..50).map(|_| generate_uuid_v4()).collect();
1721        // All should be unique
1722        let mut sorted = uuids.clone();
1723        sorted.sort();
1724        sorted.dedup();
1725        assert_eq!(sorted.len(), uuids.len(), "all UUIDs should be unique");
1726    }
1727
1728    // ── Scalar function names ────────────────────────────────────────────
1729
1730    #[test]
1731    fn test_scalar_function_names() {
1732        assert_eq!(DecimalFunc.name(), "decimal");
1733        assert_eq!(DecimalAddFunc.name(), "decimal_add");
1734        assert_eq!(DecimalSubFunc.name(), "decimal_sub");
1735        assert_eq!(DecimalMulFunc.name(), "decimal_mul");
1736        assert_eq!(DecimalCmpFunc.name(), "decimal_cmp");
1737        assert_eq!(UuidFunc.name(), "uuid");
1738        assert_eq!(UuidStrFunc.name(), "uuid_str");
1739        assert_eq!(UuidBlobFunc.name(), "uuid_blob");
1740    }
1741
1742    #[test]
1743    fn test_scalar_function_arg_counts() {
1744        assert_eq!(DecimalFunc.num_args(), 1);
1745        assert_eq!(DecimalAddFunc.num_args(), 2);
1746        assert_eq!(DecimalSubFunc.num_args(), 2);
1747        assert_eq!(DecimalMulFunc.num_args(), 2);
1748        assert_eq!(DecimalCmpFunc.num_args(), 2);
1749        assert_eq!(UuidFunc.num_args(), 0);
1750        assert_eq!(UuidStrFunc.num_args(), 1);
1751        assert_eq!(UuidBlobFunc.num_args(), 1);
1752    }
1753
1754    #[test]
1755    fn test_uuid_func_not_deterministic() {
1756        assert!(!UuidFunc.is_deterministic());
1757    }
1758}