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