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::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
32pub 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 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
79pub struct GenerateSeriesCursor {
81 current: i64,
82 start: i64,
83 stop: i64,
84 step: i64,
85 done: bool,
86}
87
88impl GenerateSeriesCursor {
89 #[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 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 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}
173fn 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
185fn 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
218fn add_unsigned(int_a: &[u8], frac_a: &[u8], int_b: &[u8], frac_b: &[u8]) -> (Vec<u8>, Vec<u8>) {
222 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 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 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 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
259fn 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
301fn cmp_unsigned(int_a: &[u8], frac_a: &[u8], int_b: &[u8], frac_b: &[u8]) -> Ordering {
303 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 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 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 &digits[digits.len().saturating_sub(1)..]
339 } else {
340 &digits[start..]
341 }
342}
343
344fn 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 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
377fn 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 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 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
423fn 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 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
440fn 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 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 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 let product: Vec<u8> = product
471 .iter()
472 .map(|&d| u8::try_from(d).unwrap_or(0))
473 .collect();
474
475 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
484fn 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 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
516pub 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
546pub 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
577pub 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
608pub 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
639pub 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
670fn generate_uuid_v4() -> String {
676 let mut bytes = [0u8; 16];
677 rand::rng().fill_bytes(&mut bytes);
678
679 bytes[6] = (bytes[6] & 0x0F) | 0x40; bytes[8] = (bytes[8] & 0x3F) | 0x80; 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
721fn 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
758fn 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
787pub 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 }
805
806 fn num_args(&self) -> i32 {
807 0
808 }
809
810 fn name(&self) -> &'static str {
811 "uuid"
812 }
813}
814
815pub 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 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
854pub 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
883pub 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#[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 #[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 #[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 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 let result = decimal_mul_impl("19.99", "100");
1211 assert_eq!(result, Some("1999".to_owned()));
1212
1213 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 #[test]
1223 fn test_uuid_v4_format() {
1224 let uuid = generate_uuid_v4();
1225 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 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 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 #[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 #[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 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 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 let mut ctx = ColumnContext::new();
1390 cursor.column(&mut ctx, 0).unwrap();
1391 assert_eq!(ctx.take_value(), Some(SqliteValue::Integer(10)));
1392 let mut ctx2 = ColumnContext::new();
1394 cursor.column(&mut ctx2, 2).unwrap();
1395 assert_eq!(ctx2.take_value(), Some(SqliteValue::Integer(20)));
1396 let mut ctx3 = ColumnContext::new();
1398 cursor.column(&mut ctx3, 3).unwrap();
1399 assert_eq!(ctx3.take_value(), Some(SqliteValue::Integer(5)));
1400 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 let table = GenerateSeriesTable;
1442 let mut cursor = table.open().unwrap();
1443 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 assert!(
1455 iterations < 10,
1456 "generate_series should terminate on overflow, got {} iterations",
1457 iterations
1458 );
1459 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 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 #[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 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 #[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 #[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 #[test]
1629 fn test_uuid_str_to_blob_invalid_length() {
1630 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 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 #[test]
1711 fn test_uuid_all_lowercase_hex() {
1712 let uuid = generate_uuid_v4();
1713 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 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 #[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}