1#![allow(
8 clippy::unnecessary_literal_bound,
9 clippy::too_many_lines,
10 clippy::cast_possible_truncation,
11 clippy::cast_possible_wrap,
12 clippy::cast_sign_loss,
13 clippy::fn_params_excessive_bools,
14 clippy::items_after_statements,
15 clippy::match_same_arms,
16 clippy::single_match_else,
17 clippy::manual_let_else,
18 clippy::comparison_chain,
19 clippy::suboptimal_flops,
20 clippy::unnecessary_wraps,
21 clippy::useless_let_if_seq,
22 clippy::redundant_closure_for_method_calls,
23 clippy::manual_ignore_case_cmp
24)]
25
26use std::borrow::Cow;
27use std::fmt::Write as _;
28use std::sync::Arc;
29
30use fsqlite_error::{FrankenError, Result};
31use fsqlite_types::value::{format_sqlite_float, sql_like};
32use fsqlite_types::{SmallText, SqliteValue};
33
34use crate::agg_builtins::register_aggregate_builtins;
35use crate::datetime::register_datetime_builtins;
36use crate::math::register_math_builtins;
37use crate::{FunctionRegistry, ScalarFunction};
38
39thread_local! {
43 static LAST_INSERT_ROWID: std::cell::Cell<i64> = const { std::cell::Cell::new(0) };
44 static LAST_CHANGES: std::cell::Cell<i64> = const { std::cell::Cell::new(0) };
45 static TOTAL_CHANGES: std::cell::Cell<i64> = const { std::cell::Cell::new(0) };
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct ChangeTrackingState {
51 pub last_insert_rowid: i64,
52 pub last_changes: i64,
53 pub total_changes: i64,
54}
55
56pub fn set_change_tracking_state(state: ChangeTrackingState) {
58 LAST_INSERT_ROWID.set(state.last_insert_rowid);
59 LAST_CHANGES.set(state.last_changes);
60 TOTAL_CHANGES.set(state.total_changes);
61}
62
63#[must_use]
65pub fn get_change_tracking_state() -> ChangeTrackingState {
66 ChangeTrackingState {
67 last_insert_rowid: LAST_INSERT_ROWID.get(),
68 last_changes: LAST_CHANGES.get(),
69 total_changes: TOTAL_CHANGES.get(),
70 }
71}
72
73pub fn set_last_insert_rowid(rowid: i64) {
75 LAST_INSERT_ROWID.set(rowid);
76}
77
78pub fn get_last_insert_rowid() -> i64 {
80 LAST_INSERT_ROWID.get()
81}
82
83pub fn set_last_changes(count: i64) {
87 LAST_CHANGES.set(count);
88 TOTAL_CHANGES.set(TOTAL_CHANGES.get().saturating_add(count));
89}
90
91pub fn get_last_changes() -> i64 {
93 LAST_CHANGES.get()
94}
95
96pub fn get_total_changes() -> i64 {
98 TOTAL_CHANGES.get()
99}
100
101pub fn reset_total_changes() {
103 TOTAL_CHANGES.set(0);
104}
105
106const SQLITE_COMPILE_OPTIONS: &[&str] = &[
107 "COMPILER=rustc",
108 "ENABLE_FTS5",
109 "ENABLE_GEOPOLY",
110 "ENABLE_ICU",
111 "ENABLE_JSON1",
112 "ENABLE_RTREE",
113 "FRANKENSQLITE",
114 "OMIT_LOAD_EXTENSION",
115 "THREADSAFE=1",
116];
117
118#[must_use]
120pub fn sqlite_compile_options() -> &'static [&'static str] {
121 SQLITE_COMPILE_OPTIONS
122}
123
124fn is_sqlite_compile_option_match(query: &str, option: &str) -> bool {
125 let trimmed = query.trim();
126 let normalized = if trimmed
127 .get(..7)
128 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("SQLITE_"))
129 {
130 &trimmed[7..]
131 } else {
132 trimmed
133 };
134 if normalized.is_empty() {
135 return false;
136 }
137 if option.eq_ignore_ascii_case(normalized) {
138 return true;
139 }
140 option
141 .get(..normalized.len())
142 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(normalized))
143 && option
144 .as_bytes()
145 .get(normalized.len())
146 .is_none_or(|next| !next.is_ascii_alphanumeric() && *next != b'_')
147}
148
149#[must_use]
152pub fn sqlite_compileoption_used(query: &str) -> bool {
153 sqlite_compile_options()
154 .iter()
155 .any(|option| is_sqlite_compile_option_match(query, option))
156}
157
158fn null_propagate(args: &[SqliteValue]) -> Option<SqliteValue> {
162 if args.iter().any(SqliteValue::is_null) {
163 Some(SqliteValue::Null)
164 } else {
165 None
166 }
167}
168
169pub struct AbsFunc;
172
173impl ScalarFunction for AbsFunc {
174 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
175 if args[0].is_null() {
176 return Ok(SqliteValue::Null);
177 }
178 match &args[0] {
179 SqliteValue::Integer(i) => {
180 if *i == i64::MIN {
181 return Err(FrankenError::IntegerOverflow);
182 }
183 Ok(SqliteValue::Integer(i.abs()))
184 }
185 other => {
186 let f = other.to_float();
187 Ok(SqliteValue::Float(if f < 0.0 { -f } else { f }))
190 }
191 }
192 }
193
194 fn num_args(&self) -> i32 {
195 1
196 }
197
198 fn name(&self) -> &str {
199 "abs"
200 }
201}
202
203pub struct CharFunc;
206
207impl ScalarFunction for CharFunc {
208 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
209 let mut result = String::new();
210 for arg in args {
211 let ch = u32::try_from(arg.to_integer())
213 .ok()
214 .and_then(char::from_u32)
215 .unwrap_or(char::REPLACEMENT_CHARACTER);
216 result.push(ch);
217 }
218 Ok(SqliteValue::Text(SmallText::from_string(result)))
219 }
220
221 fn is_deterministic(&self) -> bool {
222 true
223 }
224
225 fn num_args(&self) -> i32 {
226 -1 }
228
229 fn name(&self) -> &str {
230 "char"
231 }
232}
233
234pub struct CoalesceFunc;
237
238impl ScalarFunction for CoalesceFunc {
239 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
240 for arg in args {
244 if !arg.is_null() {
245 return Ok(arg.clone());
246 }
247 }
248 Ok(SqliteValue::Null)
249 }
250
251 fn num_args(&self) -> i32 {
252 -1
253 }
254
255 fn min_args(&self) -> i32 {
256 2
257 }
258
259 fn name(&self) -> &str {
260 "coalesce"
261 }
262}
263
264pub struct ConcatFunc;
267
268impl ScalarFunction for ConcatFunc {
269 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
270 let mut result = String::new();
271 for arg in args {
272 if !arg.is_null() {
274 result.push_str(text_arg(arg).as_ref());
275 }
276 }
277 Ok(SqliteValue::Text(SmallText::from_string(result)))
278 }
279
280 fn num_args(&self) -> i32 {
281 -1
282 }
283
284 fn min_args(&self) -> i32 {
285 1
286 }
287
288 fn name(&self) -> &str {
289 "concat"
290 }
291}
292
293pub struct ConcatWsFunc;
296
297impl ScalarFunction for ConcatWsFunc {
298 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
299 if args.is_empty() {
300 return Ok(SqliteValue::Text(SmallText::new("")));
301 }
302 if args[0].is_null() {
304 return Ok(SqliteValue::Null);
305 }
306 let sep = text_arg(&args[0]);
307 let mut result = String::new();
308 let mut has_part = false;
309 for arg in &args[1..] {
310 if !arg.is_null() {
312 if has_part {
313 result.push_str(sep.as_ref());
314 }
315 result.push_str(text_arg(arg).as_ref());
316 has_part = true;
317 }
318 }
319 Ok(SqliteValue::Text(SmallText::from_string(result)))
320 }
321
322 fn num_args(&self) -> i32 {
323 -1
324 }
325
326 fn min_args(&self) -> i32 {
327 2
328 }
329
330 fn name(&self) -> &str {
331 "concat_ws"
332 }
333}
334
335pub struct HexFunc;
338
339impl ScalarFunction for HexFunc {
340 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
341 if args[0].is_null() {
345 return Ok(SqliteValue::Text(SmallText::new("")));
346 }
347 let bytes: Cow<'_, [u8]> = match &args[0] {
348 SqliteValue::Blob(b) => Cow::Borrowed(b.as_ref()),
349 SqliteValue::Text(text) => Cow::Borrowed(text.as_bytes_direct()),
350 other => Cow::Owned(other.to_text().into_bytes()),
352 };
353 let mut hex = String::with_capacity(bytes.len() * 2);
354 for b in bytes.as_ref() {
355 let _ = write!(hex, "{b:02X}");
356 }
357 Ok(SqliteValue::Text(SmallText::from_string(hex)))
358 }
359
360 fn num_args(&self) -> i32 {
361 1
362 }
363
364 fn name(&self) -> &str {
365 "hex"
366 }
367}
368
369pub struct IfnullFunc;
372
373impl ScalarFunction for IfnullFunc {
374 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
375 if args[0].is_null() {
376 Ok(args[1].clone())
377 } else {
378 Ok(args[0].clone())
379 }
380 }
381
382 fn num_args(&self) -> i32 {
383 2
384 }
385
386 fn name(&self) -> &str {
387 "ifnull"
388 }
389}
390
391pub struct IifFunc;
394
395impl ScalarFunction for IifFunc {
396 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
397 let cond = &args[0];
398 let is_true = match cond {
401 SqliteValue::Null => false,
402 SqliteValue::Integer(n) => *n != 0,
403 SqliteValue::Float(f) => *f != 0.0,
404 SqliteValue::Text(_) | SqliteValue::Blob(_) => {
405 let i = cond.to_integer();
406 if i != 0 { true } else { cond.to_float() != 0.0 }
407 }
408 };
409 if is_true {
410 Ok(args[1].clone())
411 } else {
412 Ok(args[2].clone())
413 }
414 }
415
416 fn num_args(&self) -> i32 {
417 3
418 }
419
420 fn name(&self) -> &str {
421 "iif"
422 }
423}
424
425pub struct InstrFunc;
428
429impl ScalarFunction for InstrFunc {
430 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
431 if let Some(null) = null_propagate(args) {
432 return Ok(null);
433 }
434 match (&args[0], &args[1]) {
435 (SqliteValue::Blob(haystack), SqliteValue::Blob(needle)) => {
436 if needle.is_empty() {
438 return Ok(SqliteValue::Integer(1));
439 }
440 if haystack.is_empty() {
441 return Ok(SqliteValue::Integer(0));
442 }
443 let pos = find_bytes(haystack, needle).map_or(0, |p| p + 1);
444 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
445 }
446 _ => {
447 let haystack = text_arg(&args[0]);
450 let needle = text_arg(&args[1]);
451 let haystack = haystack.as_ref();
452 let needle = needle.as_ref();
453 if needle.is_empty() {
454 return Ok(SqliteValue::Integer(1));
455 }
456 if haystack.is_empty() {
457 return Ok(SqliteValue::Integer(0));
458 }
459 let pos = haystack
460 .find(needle)
461 .map_or(0, |byte_pos| haystack[..byte_pos].chars().count() + 1);
462 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
463 }
464 }
465 }
466
467 fn num_args(&self) -> i32 {
468 2
469 }
470
471 fn name(&self) -> &str {
472 "instr"
473 }
474}
475
476fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
477 if needle.is_empty() {
478 return Some(0);
479 }
480 haystack.windows(needle.len()).position(|w| w == needle)
481}
482
483fn sqlite_text_until_nul(text: &str) -> &str {
484 text.split_once('\0').map_or(text, |(prefix, _)| prefix)
485}
486
487pub struct LengthFunc;
490
491impl ScalarFunction for LengthFunc {
492 #[allow(clippy::cast_possible_wrap)]
493 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
494 if args[0].is_null() {
495 return Ok(SqliteValue::Null);
496 }
497 let len = match &args[0] {
498 SqliteValue::Text(s) => {
499 let text = sqlite_text_until_nul(s.as_str());
500 if text.is_ascii() {
501 text.len()
502 } else {
503 text.chars().count()
504 }
505 }
506 SqliteValue::Blob(b) => b.len(),
507 other => {
508 let text = other.to_text();
510 let text = sqlite_text_until_nul(&text);
511 if text.is_ascii() {
512 text.len()
513 } else {
514 text.chars().count()
515 }
516 }
517 };
518 Ok(SqliteValue::Integer(len as i64))
519 }
520
521 fn num_args(&self) -> i32 {
522 1
523 }
524
525 fn name(&self) -> &str {
526 "length"
527 }
528}
529
530pub struct OctetLengthFunc;
533
534impl ScalarFunction for OctetLengthFunc {
535 #[allow(clippy::cast_possible_wrap)]
536 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
537 if args[0].is_null() {
538 return Ok(SqliteValue::Null);
539 }
540 let len = match &args[0] {
541 SqliteValue::Text(s) => s.len(),
542 SqliteValue::Blob(b) => b.len(),
543 other => other.to_text().len(),
544 };
545 Ok(SqliteValue::Integer(len as i64))
546 }
547
548 fn num_args(&self) -> i32 {
549 1
550 }
551
552 fn name(&self) -> &str {
553 "octet_length"
554 }
555}
556
557pub struct LowerFunc;
560
561impl ScalarFunction for LowerFunc {
562 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
563 if args[0].is_null() {
564 return Ok(SqliteValue::Null);
565 }
566 let lowered = text_arg(&args[0]).as_ref().to_ascii_lowercase();
567 Ok(SqliteValue::Text(SmallText::from_string(lowered)))
568 }
569
570 fn num_args(&self) -> i32 {
571 1
572 }
573
574 fn name(&self) -> &str {
575 "lower"
576 }
577}
578
579pub struct UpperFunc;
580
581impl ScalarFunction for UpperFunc {
582 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
583 if args[0].is_null() {
584 return Ok(SqliteValue::Null);
585 }
586 let upper = text_arg(&args[0]).as_ref().to_ascii_uppercase();
587 Ok(SqliteValue::Text(SmallText::from_string(upper)))
588 }
589
590 fn num_args(&self) -> i32 {
591 1
592 }
593
594 fn name(&self) -> &str {
595 "upper"
596 }
597}
598
599pub struct TrimFunc;
602pub struct LtrimFunc;
603pub struct RtrimFunc;
604
605fn trim_chars(s: &str, chars: &str) -> String {
606 let char_set: Vec<char> = chars.chars().collect();
607 s.trim_matches(|c: char| char_set.contains(&c)).to_owned()
608}
609
610fn ltrim_chars(s: &str, chars: &str) -> String {
611 let char_set: Vec<char> = chars.chars().collect();
612 s.trim_start_matches(|c: char| char_set.contains(&c))
613 .to_owned()
614}
615
616fn rtrim_chars(s: &str, chars: &str) -> String {
617 let char_set: Vec<char> = chars.chars().collect();
618 s.trim_end_matches(|c: char| char_set.contains(&c))
619 .to_owned()
620}
621
622impl ScalarFunction for TrimFunc {
623 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
624 if args[0].is_null() {
625 return Ok(SqliteValue::Null);
626 }
627 let s = text_arg(&args[0]);
628 let chars = if args.len() > 1 && !args[1].is_null() {
629 text_arg(&args[1])
630 } else {
631 Cow::Borrowed(" ")
632 };
633 Ok(SqliteValue::Text(SmallText::new(
634 trim_chars(s.as_ref(), chars.as_ref()).as_str(),
635 )))
636 }
637
638 fn num_args(&self) -> i32 {
639 -1 }
641
642 fn min_args(&self) -> i32 {
643 1
644 }
645
646 fn max_args(&self) -> Option<i32> {
647 Some(2)
648 }
649
650 fn name(&self) -> &str {
651 "trim"
652 }
653}
654
655impl ScalarFunction for LtrimFunc {
656 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
657 if args[0].is_null() {
658 return Ok(SqliteValue::Null);
659 }
660 let s = text_arg(&args[0]);
661 let chars = if args.len() > 1 && !args[1].is_null() {
662 text_arg(&args[1])
663 } else {
664 Cow::Borrowed(" ")
665 };
666 Ok(SqliteValue::Text(SmallText::new(
667 ltrim_chars(s.as_ref(), chars.as_ref()).as_str(),
668 )))
669 }
670
671 fn num_args(&self) -> i32 {
672 -1
673 }
674
675 fn min_args(&self) -> i32 {
676 1
677 }
678
679 fn max_args(&self) -> Option<i32> {
680 Some(2)
681 }
682
683 fn name(&self) -> &str {
684 "ltrim"
685 }
686}
687
688impl ScalarFunction for RtrimFunc {
689 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
690 if args[0].is_null() {
691 return Ok(SqliteValue::Null);
692 }
693 let s = text_arg(&args[0]);
694 let chars = if args.len() > 1 && !args[1].is_null() {
695 text_arg(&args[1])
696 } else {
697 Cow::Borrowed(" ")
698 };
699 Ok(SqliteValue::Text(SmallText::new(
700 rtrim_chars(s.as_ref(), chars.as_ref()).as_str(),
701 )))
702 }
703
704 fn num_args(&self) -> i32 {
705 -1
706 }
707
708 fn min_args(&self) -> i32 {
709 1
710 }
711
712 fn max_args(&self) -> Option<i32> {
713 Some(2)
714 }
715
716 fn name(&self) -> &str {
717 "rtrim"
718 }
719}
720
721pub struct NullifFunc;
724
725impl ScalarFunction for NullifFunc {
726 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
727 if args[0] == args[1] {
728 Ok(SqliteValue::Null)
729 } else {
730 Ok(args[0].clone())
731 }
732 }
733
734 fn num_args(&self) -> i32 {
735 2
736 }
737
738 fn name(&self) -> &str {
739 "nullif"
740 }
741}
742
743pub struct TypeofFunc;
746
747impl ScalarFunction for TypeofFunc {
748 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
749 let type_name = match &args[0] {
750 SqliteValue::Null => "null",
751 SqliteValue::Integer(_) => "integer",
752 SqliteValue::Float(_) => "real",
753 SqliteValue::Text(_) => "text",
754 SqliteValue::Blob(_) => "blob",
755 };
756 Ok(SqliteValue::Text(SmallText::new(type_name)))
757 }
758
759 fn num_args(&self) -> i32 {
760 1
761 }
762
763 fn name(&self) -> &str {
764 "typeof"
765 }
766}
767
768pub struct SubtypeFunc;
771
772impl ScalarFunction for SubtypeFunc {
773 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
774 Ok(SqliteValue::Integer(0))
777 }
778
779 fn num_args(&self) -> i32 {
780 1
781 }
782
783 fn name(&self) -> &str {
784 "subtype"
785 }
786}
787
788pub struct ReplaceFunc;
791
792impl ScalarFunction for ReplaceFunc {
793 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
794 if let Some(null) = null_propagate(args) {
795 return Ok(null);
796 }
797 let x = text_arg(&args[0]);
798 let y = text_arg(&args[1]);
799 let z = text_arg(&args[2]);
800 if y.is_empty() {
801 return Ok(SqliteValue::Text(SmallText::from_string(x)));
802 }
803
804 if z.len() > y.len() {
806 let occurrences = x.matches(y.as_ref()).count();
807 let final_len = x.len() + occurrences * (z.len() - y.len());
808 if final_len > 1_000_000_000 {
809 return Err(FrankenError::TooBig);
810 }
811 }
812
813 Ok(SqliteValue::Text(SmallText::from_string(
814 x.replace(y.as_ref(), z.as_ref()),
815 )))
816 }
817
818 fn num_args(&self) -> i32 {
819 3
820 }
821
822 fn name(&self) -> &str {
823 "replace"
824 }
825}
826
827pub struct RoundFunc;
830
831impl ScalarFunction for RoundFunc {
832 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
833 if args[0].is_null() {
834 return Ok(SqliteValue::Null);
835 }
836 let x = args[0].to_float();
837 let n = if args.len() > 1 && !args[1].is_null() {
839 args[1].to_integer().clamp(0, 30)
840 } else {
841 0
842 };
843 if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
845 return Ok(SqliteValue::Float(x));
846 }
847 #[allow(clippy::cast_possible_truncation)]
853 let rounded = {
854 let prec = (n as usize) + 15;
855 let full = format!("{x:.prec$}");
856 let dot = full.find('.').unwrap_or(full.len());
857 let rd_idx = dot + 1 + n as usize;
858 if rd_idx >= full.len() {
859 format!("{x:.prec$}", prec = n as usize)
860 .parse::<f64>()
861 .unwrap_or(x)
862 } else {
863 let rd = full.as_bytes()[rd_idx] - b'0';
864 if rd != 5 || !full[rd_idx + 1..].bytes().all(|b| b == b'0') {
865 format!("{x:.prec$}", prec = n as usize)
867 .parse::<f64>()
868 .unwrap_or(x)
869 } else {
870 let mut trunc = full.as_bytes()[..rd_idx].to_vec();
873 if trunc.last() == Some(&b'.') {
875 trunc.pop();
876 }
877 let start = usize::from(trunc.first() == Some(&b'-'));
878 let mut carry = true;
879 for b in trunc[start..].iter_mut().rev() {
880 if *b == b'.' {
881 continue;
882 }
883 if carry {
884 if *b == b'9' {
885 *b = b'0';
886 } else {
887 *b += 1;
888 carry = false;
889 break;
890 }
891 }
892 }
893 if carry {
894 trunc.insert(start, b'1');
895 }
896 String::from_utf8(trunc)
897 .ok()
898 .and_then(|s| s.parse::<f64>().ok())
899 .unwrap_or(x)
900 }
901 }
902 };
903 Ok(SqliteValue::Float(rounded))
904 }
905
906 fn num_args(&self) -> i32 {
907 -1 }
909
910 fn min_args(&self) -> i32 {
911 1
912 }
913
914 fn max_args(&self) -> Option<i32> {
915 Some(2)
916 }
917
918 fn name(&self) -> &str {
919 "round"
920 }
921}
922
923pub struct SignFunc;
926
927impl ScalarFunction for SignFunc {
928 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
929 if args[0].is_null() {
930 return Ok(SqliteValue::Null);
931 }
932 match &args[0] {
933 SqliteValue::Null => Ok(SqliteValue::Null),
934 SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
935 SqliteValue::Float(f) => {
936 if f.is_nan() {
937 Ok(SqliteValue::Null)
938 } else if *f > 0.0 {
939 Ok(SqliteValue::Integer(1))
940 } else if *f < 0.0 {
941 Ok(SqliteValue::Integer(-1))
942 } else {
943 Ok(SqliteValue::Integer(0))
944 }
945 }
946 SqliteValue::Text(s) => {
947 let trimmed = s.trim_matches(|ch: char| ch.is_ascii_whitespace());
949 if trimmed.is_empty() {
950 return Ok(SqliteValue::Null);
951 }
952
953 let stripped = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
959 if stripped.eq_ignore_ascii_case("nan")
960 || stripped.eq_ignore_ascii_case("inf")
961 || stripped.eq_ignore_ascii_case("infinity")
962 {
963 return Ok(SqliteValue::Null);
964 }
965
966 if let Ok(f) = trimmed.parse::<f64>() {
969 if f > 0.0 {
971 Ok(SqliteValue::Integer(1))
972 } else if f < 0.0 {
973 Ok(SqliteValue::Integer(-1))
974 } else {
975 Ok(SqliteValue::Integer(0))
976 }
977 } else if let Ok(i) = trimmed.parse::<i64>() {
978 Ok(SqliteValue::Integer(i.signum()))
980 } else {
981 Ok(SqliteValue::Null)
982 }
983 }
984 SqliteValue::Blob(_) => Ok(SqliteValue::Null),
985 }
986 }
987
988 fn num_args(&self) -> i32 {
989 1
990 }
991
992 fn name(&self) -> &str {
993 "sign"
994 }
995}
996
997pub struct RandomFunc;
1000
1001impl ScalarFunction for RandomFunc {
1002 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1003 let val = simple_random_i64();
1006 Ok(SqliteValue::Integer(val))
1007 }
1008
1009 fn is_deterministic(&self) -> bool {
1010 false
1011 }
1012
1013 fn num_args(&self) -> i32 {
1014 0
1015 }
1016
1017 fn name(&self) -> &str {
1018 "random"
1019 }
1020}
1021
1022fn simple_random_i64() -> i64 {
1024 use std::sync::atomic::{AtomicU64, Ordering};
1029
1030 static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
1031 let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
1032 x ^= x >> 30;
1033 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1034 x ^= x >> 27;
1035 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
1036 x ^= x >> 31;
1037 x as i64
1038}
1039
1040pub struct RandomblobFunc;
1043
1044impl ScalarFunction for RandomblobFunc {
1045 #[allow(clippy::cast_sign_loss)]
1046 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1047 let n_i64 = if args[0].is_null() {
1051 1
1052 } else {
1053 args[0].to_integer().max(1)
1054 };
1055 if n_i64 > 1_000_000_000 {
1056 return Err(FrankenError::TooBig);
1057 }
1058 let n = n_i64 as usize;
1059 let mut buf = vec![0u8; n];
1060 let mut i = 0;
1061 while i < n {
1062 let rnd = simple_random_i64().to_ne_bytes();
1063 let to_copy = (n - i).min(8);
1064 buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
1065 i += to_copy;
1066 }
1067 Ok(SqliteValue::Blob(Arc::from(buf.as_slice())))
1068 }
1069
1070 fn is_deterministic(&self) -> bool {
1071 false
1072 }
1073
1074 fn num_args(&self) -> i32 {
1075 1
1076 }
1077
1078 fn name(&self) -> &str {
1079 "randomblob"
1080 }
1081}
1082
1083pub struct ZeroblobFunc;
1086
1087impl ScalarFunction for ZeroblobFunc {
1088 #[allow(clippy::cast_sign_loss)]
1089 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1090 if args[0].is_null() {
1092 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1093 }
1094 let n_i64 = args[0].to_integer().max(0);
1095 if n_i64 > 1_000_000_000 {
1096 return Err(FrankenError::TooBig);
1097 }
1098 let n = n_i64 as usize;
1099 Ok(SqliteValue::Blob(Arc::from(vec![0u8; n].as_slice())))
1100 }
1101
1102 fn num_args(&self) -> i32 {
1103 1
1104 }
1105
1106 fn name(&self) -> &str {
1107 "zeroblob"
1108 }
1109}
1110
1111pub struct QuoteFunc;
1114
1115impl ScalarFunction for QuoteFunc {
1116 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1117 let result = quote_sql_value(&args[0], false);
1118 Ok(SqliteValue::Text(SmallText::from_string(result)))
1119 }
1120
1121 fn num_args(&self) -> i32 {
1122 1
1123 }
1124
1125 fn name(&self) -> &str {
1126 "quote"
1127 }
1128}
1129
1130pub struct UnistrQuoteFunc;
1133
1134impl ScalarFunction for UnistrQuoteFunc {
1135 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1136 let result = quote_sql_value(&args[0], true);
1137 Ok(SqliteValue::Text(SmallText::from_string(result)))
1138 }
1139
1140 fn num_args(&self) -> i32 {
1141 1
1142 }
1143
1144 fn name(&self) -> &str {
1145 "unistr_quote"
1146 }
1147}
1148
1149fn quote_sql_value(value: &SqliteValue, use_unistr_quote: bool) -> String {
1150 match value {
1151 SqliteValue::Null => "NULL".to_owned(),
1152 SqliteValue::Integer(i) => i.to_string(),
1153 SqliteValue::Float(f) => format_sqlite_float(*f),
1154 SqliteValue::Text(s) => quote_sql_text_literal(s.as_str(), use_unistr_quote),
1155 SqliteValue::Blob(b) => {
1156 let mut hex = String::with_capacity(3 + b.len() * 2);
1157 hex.push_str("X'");
1158 for byte in b.iter() {
1159 let _ = write!(hex, "{byte:02X}");
1160 }
1161 hex.push('\'');
1162 hex
1163 }
1164 }
1165}
1166
1167fn quote_sql_text_literal(text: &str, use_unistr_quote: bool) -> String {
1168 let text = sqlite_text_until_nul(text);
1169 if use_unistr_quote && text.chars().any(is_unistr_control_char) {
1170 return unistr_quote_sql_text_literal(text);
1171 }
1172
1173 let mut quoted = String::with_capacity(text.len() + 2);
1174 quoted.push('\'');
1175 append_sql_string_literal_body(&mut quoted, text);
1176 quoted.push('\'');
1177 quoted
1178}
1179
1180fn unistr_quote_sql_text_literal(text: &str) -> String {
1181 let mut quoted = String::with_capacity(text.len() + 12);
1182 quoted.push_str("unistr('");
1183 for ch in text.chars() {
1184 match ch {
1185 '\'' => quoted.push_str("''"),
1186 '\\' => quoted.push_str("\\\\"),
1187 _ if is_unistr_control_char(ch) => {
1188 let _ = write!(quoted, "\\u{:04x}", ch as u32);
1189 }
1190 _ => quoted.push(ch),
1191 }
1192 }
1193 quoted.push_str("')");
1194 quoted
1195}
1196
1197fn append_sql_string_literal_body(out: &mut String, text: &str) {
1198 for ch in text.chars() {
1199 if ch == '\'' {
1200 out.push_str("''");
1201 } else {
1202 out.push(ch);
1203 }
1204 }
1205}
1206
1207fn is_unistr_control_char(ch: char) -> bool {
1208 matches!(ch, '\u{0001}'..='\u{001F}')
1209}
1210
1211pub struct UnhexFunc;
1214
1215impl ScalarFunction for UnhexFunc {
1216 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1217 if args[0].is_null() {
1218 return Ok(SqliteValue::Null);
1219 }
1220 if args.len() > 1 && args[1].is_null() {
1221 return Ok(SqliteValue::Null);
1222 }
1223 let input = text_arg(&args[0]);
1224 let ignore_chars: Vec<char> = if args.len() > 1 {
1225 text_arg(&args[1])
1226 .chars()
1227 .filter(|&c| hex_digit(c).is_none())
1228 .collect()
1229 } else {
1230 Vec::new()
1231 };
1232
1233 let mut bytes = Vec::with_capacity(input.len() / 2);
1234 let mut hi_nibble = None;
1235 for c in input.as_ref().chars() {
1236 if ignore_chars.contains(&c) {
1237 if hi_nibble.is_some() {
1238 return Ok(SqliteValue::Null);
1239 }
1240 continue;
1241 }
1242 let digit = match hex_digit(c) {
1243 Some(v) => v,
1244 None => return Ok(SqliteValue::Null),
1245 };
1246 if let Some(hi) = hi_nibble.take() {
1247 bytes.push(hi << 4 | digit);
1248 } else {
1249 hi_nibble = Some(digit);
1250 }
1251 }
1252 if hi_nibble.is_some() {
1253 return Ok(SqliteValue::Null);
1254 }
1255 Ok(SqliteValue::Blob(Arc::from(bytes.as_slice())))
1256 }
1257
1258 fn num_args(&self) -> i32 {
1259 -1 }
1261
1262 fn min_args(&self) -> i32 {
1263 1
1264 }
1265
1266 fn max_args(&self) -> Option<i32> {
1267 Some(2)
1268 }
1269
1270 fn name(&self) -> &str {
1271 "unhex"
1272 }
1273}
1274
1275fn hex_digit(c: char) -> Option<u8> {
1276 match c {
1277 '0'..='9' => Some(c as u8 - b'0'),
1278 'a'..='f' => Some(c as u8 - b'a' + 10),
1279 'A'..='F' => Some(c as u8 - b'A' + 10),
1280 _ => None,
1281 }
1282}
1283
1284pub struct UnicodeFunc;
1287
1288impl ScalarFunction for UnicodeFunc {
1289 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1290 if args[0].is_null() {
1291 return Ok(SqliteValue::Null);
1292 }
1293 if let SqliteValue::Blob(bytes) = &args[0] {
1294 return Ok(
1295 sqlite_blob_first_codepoint(bytes).map_or(SqliteValue::Null, SqliteValue::Integer)
1296 );
1297 }
1298 let s = text_arg(&args[0]);
1299 match sqlite_text_until_nul(s.as_ref()).chars().next() {
1300 Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
1301 None => Ok(SqliteValue::Null),
1302 }
1303 }
1304
1305 fn num_args(&self) -> i32 {
1306 1
1307 }
1308
1309 fn name(&self) -> &str {
1310 "unicode"
1311 }
1312}
1313
1314fn sqlite_blob_first_codepoint(bytes: &[u8]) -> Option<i64> {
1315 let first = *bytes.first()?;
1316 if first == 0 {
1317 return None;
1318 }
1319 let mut codepoint = match first {
1320 0x00..=0xBF => u32::from(first),
1321 0xC0..=0xDF => u32::from(first & 0x1F),
1322 0xE0..=0xEF => u32::from(first & 0x0F),
1323 0xF0..=0xF7 => u32::from(first & 0x07),
1324 _ => 0xFFFD,
1325 };
1326
1327 if first >= 0xC0 && first <= 0xF7 {
1328 for byte in bytes
1329 .iter()
1330 .copied()
1331 .skip(1)
1332 .take_while(|byte| byte & 0xC0 == 0x80)
1333 {
1334 codepoint = codepoint
1335 .wrapping_shl(6)
1336 .wrapping_add(u32::from(byte & 0x3F));
1337 }
1338 if codepoint < 0x80
1339 || (codepoint & 0xFFFF_F800) == 0xD800
1340 || (codepoint & 0xFFFF_FFFE) == 0xFFFE
1341 {
1342 codepoint = 0xFFFD;
1343 }
1344 }
1345
1346 Some(i64::from(codepoint))
1347}
1348
1349pub struct SubstrFunc;
1352
1353impl ScalarFunction for SubstrFunc {
1354 #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
1355 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1356 if args[0].is_null() || args[1].is_null() {
1357 return Ok(SqliteValue::Null);
1358 }
1359 let is_blob = matches!(&args[0], SqliteValue::Blob(_));
1360 if is_blob {
1361 return self.invoke_blob(args);
1362 }
1363
1364 let text = text_arg(&args[0]);
1365 let s = text.as_ref();
1366 let ascii_fast_path = s.is_ascii();
1367 let len = if ascii_fast_path {
1368 s.len() as i64
1369 } else {
1370 s.chars().count() as i64
1371 };
1372 let has_length = args.len() > 2 && !args[2].is_null();
1373
1374 let mut p1 = args[1].to_integer();
1375 let mut p2 = if has_length {
1376 args[2].to_integer()
1377 } else {
1378 1_000_000_000
1379 };
1380
1381 let neg_p2 = p2 < 0;
1385 if neg_p2 {
1386 p2 = p2.saturating_neg();
1387 }
1388
1389 if p1 < 0 {
1391 p1 = p1.saturating_add(len);
1392 if p1 < 0 {
1393 p2 = p2.saturating_add(p1);
1394 p1 = 0;
1395 }
1396 } else if p1 > 0 {
1397 p1 -= 1;
1398 } else if p2 > 0 {
1399 p2 -= 1; }
1401
1402 if neg_p2 {
1404 p1 = p1.saturating_sub(p2);
1405 if p1 < 0 {
1406 p2 = p2.saturating_add(p1);
1407 p1 = 0;
1408 }
1409 }
1410
1411 if p1.saturating_add(p2) > len {
1412 p2 = len.saturating_sub(p1);
1413 }
1414 if p2 <= 0 {
1415 return Ok(SqliteValue::Text(SmallText::new("")));
1416 }
1417
1418 if ascii_fast_path {
1419 let start = p1 as usize;
1420 let end = (p1 + p2) as usize;
1421 return Ok(SqliteValue::Text(SmallText::new(&s[start..end])));
1422 }
1423
1424 let chars: Vec<char> = s.chars().collect();
1425 let result: String = chars[p1 as usize..(p1 + p2) as usize].iter().collect();
1426 Ok(SqliteValue::Text(SmallText::from_string(result)))
1427 }
1428
1429 fn num_args(&self) -> i32 {
1430 -1 }
1432
1433 fn min_args(&self) -> i32 {
1434 2
1435 }
1436
1437 fn max_args(&self) -> Option<i32> {
1438 Some(3)
1439 }
1440
1441 fn name(&self) -> &str {
1442 "substr"
1443 }
1444}
1445
1446impl SubstrFunc {
1447 #[allow(
1448 clippy::unused_self,
1449 clippy::cast_sign_loss,
1450 clippy::cast_possible_wrap
1451 )]
1452 fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1453 let blob = match &args[0] {
1454 SqliteValue::Blob(b) => b,
1455 _ => return Ok(SqliteValue::Null),
1456 };
1457 let len = blob.len() as i64;
1458 let has_length = args.len() > 2 && !args[2].is_null();
1459
1460 let mut p1 = args[1].to_integer();
1461 let mut p2 = if has_length {
1462 args[2].to_integer()
1463 } else {
1464 1_000_000_000
1465 };
1466
1467 let neg_p2 = p2 < 0;
1468 if neg_p2 {
1469 p2 = p2.saturating_neg();
1470 }
1471
1472 if p1 < 0 {
1473 p1 = p1.saturating_add(len);
1474 if p1 < 0 {
1475 p2 = p2.saturating_add(p1);
1476 p1 = 0;
1477 }
1478 } else if p1 > 0 {
1479 p1 -= 1;
1480 } else if p2 > 0 {
1481 p2 -= 1;
1482 }
1483
1484 if neg_p2 {
1485 p1 = p1.saturating_sub(p2);
1486 if p1 < 0 {
1487 p2 = p2.saturating_add(p1);
1488 p1 = 0;
1489 }
1490 }
1491
1492 if p1.saturating_add(p2) > len {
1493 p2 = len.saturating_sub(p1);
1494 }
1495 if p2 <= 0 {
1496 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1497 }
1498
1499 Ok(SqliteValue::Blob(Arc::from(
1500 &blob[p1 as usize..(p1 + p2) as usize],
1501 )))
1502 }
1503}
1504
1505pub struct SoundexFunc;
1508
1509impl ScalarFunction for SoundexFunc {
1510 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1511 if args[0].is_null() {
1512 return Ok(SqliteValue::Text(SmallText::new("?000")));
1514 }
1515 let s = text_arg(&args[0]);
1516 Ok(SqliteValue::Text(SmallText::from_string(soundex(
1517 s.as_ref(),
1518 ))))
1519 }
1520
1521 fn num_args(&self) -> i32 {
1522 1
1523 }
1524
1525 fn name(&self) -> &str {
1526 "soundex"
1527 }
1528}
1529
1530fn soundex(s: &str) -> String {
1531 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1532 let first = match chars.next() {
1533 Some(c) => c.to_ascii_uppercase(),
1534 None => return "?000".to_owned(),
1535 };
1536
1537 let code = |c: char| -> Option<char> {
1538 match c.to_ascii_uppercase() {
1539 'B' | 'F' | 'P' | 'V' => Some('1'),
1540 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some('2'),
1541 'D' | 'T' => Some('3'),
1542 'L' => Some('4'),
1543 'M' | 'N' => Some('5'),
1544 'R' => Some('6'),
1545 _ => None, }
1547 };
1548
1549 let mut result = String::with_capacity(4);
1550 result.push(first);
1551 let mut last_code = code(first);
1552
1553 for c in chars {
1554 if result.len() >= 4 {
1555 break;
1556 }
1557 let current = code(c);
1558 if let Some(digit) = current {
1559 if current != last_code {
1560 result.push(digit);
1561 }
1562 }
1563 last_code = current;
1564 }
1565
1566 while result.len() < 4 {
1567 result.push('0');
1568 }
1569 result
1570}
1571
1572pub struct ScalarMaxFunc;
1575
1576impl ScalarFunction for ScalarMaxFunc {
1577 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1578 if let Some(null) = null_propagate(args) {
1580 return Ok(null);
1581 }
1582 let mut max = &args[0];
1583 for arg in &args[1..] {
1584 if arg.partial_cmp(max) == Some(std::cmp::Ordering::Greater) {
1585 max = arg;
1586 }
1587 }
1588 Ok(max.clone())
1589 }
1590
1591 fn num_args(&self) -> i32 {
1592 -1
1593 }
1594
1595 fn min_args(&self) -> i32 {
1596 1
1597 }
1598
1599 fn name(&self) -> &str {
1600 "max"
1601 }
1602}
1603
1604pub struct ScalarMinFunc;
1607
1608impl ScalarFunction for ScalarMinFunc {
1609 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1610 if let Some(null) = null_propagate(args) {
1612 return Ok(null);
1613 }
1614 let mut min = &args[0];
1615 for arg in &args[1..] {
1616 if arg.partial_cmp(min) == Some(std::cmp::Ordering::Less) {
1617 min = arg;
1618 }
1619 }
1620 Ok(min.clone())
1621 }
1622
1623 fn num_args(&self) -> i32 {
1624 -1
1625 }
1626
1627 fn min_args(&self) -> i32 {
1628 1
1629 }
1630
1631 fn name(&self) -> &str {
1632 "min"
1633 }
1634}
1635
1636pub struct LikelihoodFunc;
1639
1640impl ScalarFunction for LikelihoodFunc {
1641 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1642 Ok(args[0].clone())
1644 }
1645
1646 fn num_args(&self) -> i32 {
1647 2
1648 }
1649
1650 fn name(&self) -> &str {
1651 "likelihood"
1652 }
1653}
1654
1655pub struct LikelyFunc;
1656
1657impl ScalarFunction for LikelyFunc {
1658 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1659 Ok(args[0].clone())
1660 }
1661
1662 fn num_args(&self) -> i32 {
1663 1
1664 }
1665
1666 fn name(&self) -> &str {
1667 "likely"
1668 }
1669}
1670
1671pub struct UnlikelyFunc;
1672
1673impl ScalarFunction for UnlikelyFunc {
1674 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1675 Ok(args[0].clone())
1676 }
1677
1678 fn num_args(&self) -> i32 {
1679 1
1680 }
1681
1682 fn name(&self) -> &str {
1683 "unlikely"
1684 }
1685}
1686
1687pub struct SqliteVersionFunc;
1690
1691impl ScalarFunction for SqliteVersionFunc {
1692 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1693 Ok(SqliteValue::Text(SmallText::new(
1694 fsqlite_types::FRANKENSQLITE_SQLITE_VERSION,
1695 )))
1696 }
1697
1698 fn num_args(&self) -> i32 {
1699 0
1700 }
1701
1702 fn name(&self) -> &str {
1703 "sqlite_version"
1704 }
1705}
1706
1707pub struct SqliteSourceIdFunc;
1710
1711impl ScalarFunction for SqliteSourceIdFunc {
1712 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1713 Ok(SqliteValue::Text(SmallText::new(
1714 fsqlite_types::FRANKENSQLITE_SOURCE_ID,
1715 )))
1716 }
1717
1718 fn num_args(&self) -> i32 {
1719 0
1720 }
1721
1722 fn name(&self) -> &str {
1723 "sqlite_source_id"
1724 }
1725}
1726
1727pub struct SqliteCompileoptionUsedFunc;
1730
1731impl ScalarFunction for SqliteCompileoptionUsedFunc {
1732 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1733 if args[0].is_null() {
1734 return Ok(SqliteValue::Null);
1735 }
1736 let query = text_arg(&args[0]);
1737 Ok(SqliteValue::Integer(i64::from(sqlite_compileoption_used(
1738 query.as_ref(),
1739 ))))
1740 }
1741
1742 fn num_args(&self) -> i32 {
1743 1
1744 }
1745
1746 fn name(&self) -> &str {
1747 "sqlite_compileoption_used"
1748 }
1749}
1750
1751pub struct SqliteCompileoptionGetFunc;
1754
1755impl ScalarFunction for SqliteCompileoptionGetFunc {
1756 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1757 if args[0].is_null() {
1758 return Ok(SqliteValue::Null);
1759 }
1760 let n = args[0].to_integer();
1761 #[allow(clippy::cast_sign_loss)]
1762 match sqlite_compile_options().get(n as usize) {
1763 Some(opt) => Ok(SqliteValue::Text(SmallText::new(opt))),
1764 None => Ok(SqliteValue::Null),
1765 }
1766 }
1767
1768 fn num_args(&self) -> i32 {
1769 1
1770 }
1771
1772 fn name(&self) -> &str {
1773 "sqlite_compileoption_get"
1774 }
1775}
1776
1777pub struct LikeFunc;
1780
1781impl ScalarFunction for LikeFunc {
1782 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1783 if let Some(null) = null_propagate(args) {
1784 return Ok(null);
1785 }
1786 let pattern = text_arg(&args[0]);
1787 let string = text_arg(&args[1]);
1788 let escape = if args.len() > 2 && !args[2].is_null() {
1789 Some(single_char_escape(text_arg(&args[2]).as_ref())?)
1790 } else {
1791 None
1792 };
1793 let matched = like_match(pattern.as_ref(), string.as_ref(), escape);
1794 Ok(SqliteValue::Integer(i64::from(matched)))
1795 }
1796
1797 fn num_args(&self) -> i32 {
1798 -1 }
1800
1801 fn name(&self) -> &str {
1802 "like"
1803 }
1804}
1805
1806fn single_char_escape(escape: &str) -> Result<char> {
1807 let mut chars = escape.chars();
1808 match (chars.next(), chars.next()) {
1809 (Some(ch), None) => Ok(ch),
1810 _ => Err(FrankenError::function_error(
1811 "ESCAPE expression must be a single character",
1812 )),
1813 }
1814}
1815
1816fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
1818 sql_like(pattern, string, escape)
1819}
1820
1821pub struct GlobFunc;
1824
1825impl ScalarFunction for GlobFunc {
1826 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1827 if let Some(null) = null_propagate(args) {
1828 return Ok(null);
1829 }
1830 let pattern = text_arg(&args[0]);
1831 let string = text_arg(&args[1]);
1832 let matched = glob_match(pattern.as_ref(), string.as_ref());
1833 Ok(SqliteValue::Integer(i64::from(matched)))
1834 }
1835
1836 fn num_args(&self) -> i32 {
1837 2
1838 }
1839
1840 fn name(&self) -> &str {
1841 "glob"
1842 }
1843}
1844
1845fn glob_match(pattern: &str, string: &str) -> bool {
1847 let pat: Vec<char> = pattern.chars().collect();
1848 let txt: Vec<char> = string.chars().collect();
1849 glob_match_inner(&pat, &txt, 0, 0)
1850}
1851
1852fn text_arg(value: &SqliteValue) -> Cow<'_, str> {
1853 match value.as_text_str() {
1854 Some(text) => Cow::Borrowed(text),
1855 None => Cow::Owned(value.to_text()),
1856 }
1857}
1858
1859fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
1860 while pi < pat.len() {
1861 match pat[pi] {
1862 '*' => {
1863 while pi < pat.len() && pat[pi] == '*' {
1864 pi += 1;
1865 }
1866 if pi >= pat.len() {
1867 return true;
1868 }
1869 for start in ti..=txt.len() {
1870 if glob_match_inner(pat, txt, pi, start) {
1871 return true;
1872 }
1873 }
1874 return false;
1875 }
1876 '?' => {
1877 if ti >= txt.len() {
1878 return false;
1879 }
1880 pi += 1;
1881 ti += 1;
1882 }
1883 '[' => {
1884 if ti >= txt.len() {
1885 return false;
1886 }
1887 pi += 1;
1888 let negate = pi < pat.len() && pat[pi] == '^';
1889 if negate {
1890 pi += 1;
1891 }
1892 let mut found = false;
1893 let mut first = true;
1894 while pi < pat.len() && (first || pat[pi] != ']') {
1895 first = false;
1896 if pi + 2 < pat.len() && pat[pi + 1] == '-' {
1897 let lo = pat[pi];
1898 let hi = pat[pi + 2];
1899 if txt[ti] >= lo && txt[ti] <= hi {
1900 found = true;
1901 }
1902 pi += 3;
1903 } else {
1904 if txt[ti] == pat[pi] {
1905 found = true;
1906 }
1907 pi += 1;
1908 }
1909 }
1910 if pi < pat.len() && pat[pi] == ']' {
1911 pi += 1;
1912 }
1913 if found == negate {
1914 return false;
1915 }
1916 ti += 1;
1917 }
1918 c => {
1919 if ti >= txt.len() || txt[ti] != c {
1920 return false;
1921 }
1922 pi += 1;
1923 ti += 1;
1924 }
1925 }
1926 }
1927 ti >= txt.len()
1928}
1929
1930pub struct UnistrFunc;
1933
1934const INVALID_UNISTR_ESCAPE: &str = "invalid Unicode escape";
1935
1936fn decode_unistr_escape(chars: &mut std::str::Chars<'_>, digits: usize) -> Result<char> {
1937 let mut lookahead = chars.clone();
1938 let mut codepoint = 0u32;
1939 for _ in 0..digits {
1940 let Some(ch) = lookahead.next() else {
1941 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
1942 };
1943 let Some(digit) = hex_digit(ch) else {
1944 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
1945 };
1946 codepoint = (codepoint << 4) | u32::from(digit);
1947 }
1948 for _ in 0..digits {
1949 let _digit = chars.next();
1950 }
1951 char::from_u32(codepoint).ok_or_else(|| FrankenError::function_error(INVALID_UNISTR_ESCAPE))
1952}
1953
1954impl ScalarFunction for UnistrFunc {
1955 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1956 if args[0].is_null() {
1957 return Ok(SqliteValue::Null);
1958 }
1959 let input = text_arg(&args[0]);
1960 let mut result = String::with_capacity(input.len());
1961 let mut chars = input.as_ref().chars();
1962 while let Some(ch) = chars.next() {
1963 if ch == '\\' {
1964 if chars.as_str().starts_with('\\') {
1966 let _ = chars.next();
1967 result.push('\\');
1968 continue;
1969 }
1970 let digits = if chars.as_str().starts_with('+') {
1971 let _plus = chars.next();
1973 6
1974 } else if chars.as_str().starts_with('u') {
1975 let _marker = chars.next();
1977 4
1978 } else if chars.as_str().starts_with('U') {
1979 let _marker = chars.next();
1981 8
1982 } else {
1983 4
1985 };
1986 result.push(decode_unistr_escape(&mut chars, digits)?);
1987 continue;
1988 }
1989 result.push(ch);
1990 }
1991 Ok(SqliteValue::Text(SmallText::from_string(result)))
1992 }
1993
1994 fn num_args(&self) -> i32 {
1995 1
1996 }
1997
1998 fn name(&self) -> &str {
1999 "unistr"
2000 }
2001}
2002
2003pub struct ChangesFunc;
2008
2009impl ScalarFunction for ChangesFunc {
2010 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2011 Ok(SqliteValue::Integer(LAST_CHANGES.get()))
2012 }
2013
2014 fn is_deterministic(&self) -> bool {
2015 false
2016 }
2017
2018 fn num_args(&self) -> i32 {
2019 0
2020 }
2021
2022 fn name(&self) -> &str {
2023 "changes"
2024 }
2025}
2026
2027pub struct TotalChangesFunc;
2028
2029impl ScalarFunction for TotalChangesFunc {
2030 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2031 Ok(SqliteValue::Integer(TOTAL_CHANGES.get()))
2032 }
2033
2034 fn is_deterministic(&self) -> bool {
2035 false
2036 }
2037
2038 fn num_args(&self) -> i32 {
2039 0
2040 }
2041
2042 fn name(&self) -> &str {
2043 "total_changes"
2044 }
2045}
2046
2047pub struct LastInsertRowidFunc;
2048
2049impl ScalarFunction for LastInsertRowidFunc {
2050 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2051 Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
2052 }
2053
2054 fn is_deterministic(&self) -> bool {
2055 false
2056 }
2057
2058 fn num_args(&self) -> i32 {
2059 0
2060 }
2061
2062 fn name(&self) -> &str {
2063 "last_insert_rowid"
2064 }
2065}
2066
2067#[allow(clippy::too_many_lines)]
2071pub fn register_builtins(registry: &mut FunctionRegistry) {
2072 registry.register_scalar(AbsFunc);
2074 registry.register_scalar(SignFunc);
2075 registry.register_scalar(RoundFunc);
2076 registry.register_scalar(RandomFunc);
2077 registry.register_scalar(RandomblobFunc);
2078 registry.register_scalar(ZeroblobFunc);
2079
2080 registry.register_scalar(LowerFunc);
2082 registry.register_scalar(UpperFunc);
2083 registry.register_scalar(LengthFunc);
2084 registry.register_scalar(OctetLengthFunc);
2085 registry.register_scalar(TrimFunc);
2086 registry.register_scalar(LtrimFunc);
2087 registry.register_scalar(RtrimFunc);
2088 registry.register_scalar(ReplaceFunc);
2089 registry.register_scalar(SubstrFunc);
2090 registry.register_scalar(InstrFunc);
2091 registry.register_scalar(CharFunc);
2092 registry.register_scalar(UnicodeFunc);
2093 registry.register_scalar(UnistrFunc);
2094 registry.register_scalar(HexFunc);
2095 registry.register_scalar(UnhexFunc);
2096 registry.register_scalar(QuoteFunc);
2097 registry.register_scalar(UnistrQuoteFunc);
2098 registry.register_scalar(SoundexFunc);
2099
2100 registry.register_scalar(TypeofFunc);
2102 registry.register_scalar(SubtypeFunc);
2103
2104 registry.register_scalar(CoalesceFunc);
2106 registry.register_scalar(IfnullFunc);
2107 registry.register_scalar(NullifFunc);
2108 registry.register_scalar(IifFunc);
2109
2110 registry.register_scalar(ConcatFunc);
2112 registry.register_scalar(ConcatWsFunc);
2113 registry.register_scalar(ScalarMaxFunc);
2114 registry.register_scalar(ScalarMinFunc);
2115
2116 registry.register_scalar(LikelihoodFunc);
2118 registry.register_scalar(LikelyFunc);
2119 registry.register_scalar(UnlikelyFunc);
2120
2121 registry.register_scalar(LikeFunc);
2123 registry.register_scalar(GlobFunc);
2124
2125 registry.register_scalar(SqliteVersionFunc);
2127 registry.register_scalar(SqliteSourceIdFunc);
2128 registry.register_scalar(SqliteCompileoptionUsedFunc);
2129 registry.register_scalar(SqliteCompileoptionGetFunc);
2130
2131 registry.register_scalar(ChangesFunc);
2133 registry.register_scalar(TotalChangesFunc);
2134 registry.register_scalar(LastInsertRowidFunc);
2135
2136 struct IfFunc;
2139 impl ScalarFunction for IfFunc {
2140 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2141 IifFunc.invoke(args)
2142 }
2143
2144 fn num_args(&self) -> i32 {
2145 3
2146 }
2147
2148 fn name(&self) -> &str {
2149 "if"
2150 }
2151 }
2152 registry.register_scalar(IfFunc);
2153
2154 struct SubstringFunc;
2156 impl ScalarFunction for SubstringFunc {
2157 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2158 SubstrFunc.invoke(args)
2159 }
2160
2161 fn num_args(&self) -> i32 {
2162 -1
2163 }
2164
2165 fn min_args(&self) -> i32 {
2166 2
2167 }
2168
2169 fn max_args(&self) -> Option<i32> {
2170 Some(3)
2171 }
2172
2173 fn name(&self) -> &str {
2174 "substring"
2175 }
2176 }
2177 registry.register_scalar(SubstringFunc);
2178
2179 struct PrintfFunc;
2181 impl ScalarFunction for PrintfFunc {
2182 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2183 FormatFunc.invoke(args)
2184 }
2185
2186 fn num_args(&self) -> i32 {
2187 -1
2188 }
2189
2190 fn name(&self) -> &str {
2191 "printf"
2192 }
2193 }
2194 registry.register_scalar(FormatFunc);
2195 registry.register_scalar(PrintfFunc);
2196
2197 register_math_builtins(registry);
2199
2200 register_datetime_builtins(registry);
2202
2203 register_aggregate_builtins(registry);
2205}
2206
2207pub struct FormatFunc;
2210
2211impl ScalarFunction for FormatFunc {
2212 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2213 if args.is_empty() || args[0].is_null() {
2214 return Ok(SqliteValue::Null);
2215 }
2216 let fmt_str = args[0].to_text();
2217 let params = &args[1..];
2218 let result = sqlite_format(&fmt_str, params)?;
2219 Ok(SqliteValue::Text(SmallText::from_string(result)))
2220 }
2221
2222 fn num_args(&self) -> i32 {
2223 -1
2224 }
2225
2226 fn name(&self) -> &str {
2227 "format"
2228 }
2229}
2230
2231fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<String> {
2234 let mut result = String::new();
2235 let chars: Vec<char> = fmt.chars().collect();
2236 let mut i = 0;
2237 let mut param_idx = 0;
2238
2239 while i < chars.len() {
2240 if chars[i] != '%' {
2241 result.push(chars[i]);
2242 i += 1;
2243 continue;
2244 }
2245 i += 1;
2246 if i >= chars.len() {
2247 break;
2248 }
2249
2250 let mut left_align = false;
2252 let mut show_sign = false;
2253 let mut space_sign = false;
2254 let mut zero_pad = false;
2255 loop {
2256 if i >= chars.len() {
2257 break;
2258 }
2259 match chars[i] {
2260 '-' => left_align = true,
2261 '+' => show_sign = true,
2262 ' ' => space_sign = true,
2263 '0' => zero_pad = true,
2264 _ => break,
2265 }
2266 i += 1;
2267 }
2268
2269 let mut width = 0usize;
2271 while i < chars.len() && chars[i].is_ascii_digit() {
2272 width = width
2273 .saturating_mul(10)
2274 .saturating_add(chars[i] as usize - '0' as usize)
2275 .min(100_000_000); i += 1;
2277 }
2278
2279 let mut precision = None;
2281 if i < chars.len() && chars[i] == '.' {
2282 i += 1;
2283 let mut prec = 0usize;
2284 while i < chars.len() && chars[i].is_ascii_digit() {
2285 prec = prec
2286 .saturating_mul(10)
2287 .saturating_add(chars[i] as usize - '0' as usize)
2288 .min(100_000_000); i += 1;
2290 }
2291 precision = Some(prec);
2292 }
2293
2294 if i >= chars.len() {
2295 break;
2296 }
2297
2298 let spec = chars[i];
2299 i += 1;
2300
2301 match spec {
2302 '%' => result.push('%'),
2303 'n' => {} 'd' | 'i' => {
2305 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2306 param_idx += 1;
2307 let formatted =
2308 format_integer(val, width, left_align, show_sign, space_sign, zero_pad);
2309 result.push_str(&formatted);
2310 }
2311 'f' => {
2312 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2313 param_idx += 1;
2314 let prec = precision.unwrap_or(6);
2315 let formatted = format_float_f(
2316 val, prec, width, left_align, show_sign, space_sign, zero_pad,
2317 );
2318 result.push_str(&formatted);
2319 }
2320 'e' | 'E' => {
2321 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2322 param_idx += 1;
2323 let prec = precision.unwrap_or(6);
2324 let raw = if spec == 'e' {
2325 format!("{val:.prec$e}")
2326 } else {
2327 format!("{val:.prec$E}")
2328 };
2329 let formatted = normalize_exponent(&raw);
2331 result.push_str(&pad_string(&formatted, width, left_align));
2332 }
2333 'g' | 'G' => {
2334 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2335 param_idx += 1;
2336 let prec = precision.unwrap_or(6);
2337 let sig = prec.max(1);
2338 let formatted = format_float_g(val, sig, spec == 'G');
2339 result.push_str(&pad_string(&formatted, width, left_align));
2340 }
2341 's' | 'z' => {
2342 let param = params.get(param_idx);
2343 param_idx += 1;
2344 let val = match param {
2345 Some(SqliteValue::Null) | None => String::new(),
2347 Some(v) => v.to_text(),
2348 };
2349 let truncated = if let Some(prec) = precision {
2350 val.chars().take(prec).collect::<String>()
2351 } else {
2352 val
2353 };
2354 result.push_str(&pad_string(&truncated, width, left_align));
2355 }
2356 'q' => {
2357 let param = params.get(param_idx);
2359 param_idx += 1;
2360 match param {
2361 Some(SqliteValue::Null) | None => {
2363 result.push_str("(NULL)");
2364 }
2365 Some(v) => {
2366 let val = v.to_text();
2367 let escaped = val.replace('\'', "''");
2368 result.push_str(&escaped);
2369 }
2370 }
2371 }
2372 'Q' => {
2373 let param = params.get(param_idx);
2375 param_idx += 1;
2376 match param {
2377 Some(SqliteValue::Null) | None => result.push_str("NULL"),
2378 Some(v) => {
2379 let val = v.to_text();
2380 let escaped = val.replace('\'', "''");
2381 result.push('\'');
2382 result.push_str(&escaped);
2383 result.push('\'');
2384 }
2385 }
2386 }
2387 'w' => {
2388 let param = params.get(param_idx);
2392 param_idx += 1;
2393 if matches!(param, Some(SqliteValue::Null) | None) {
2394 } else {
2396 let val = param.map(SqliteValue::to_text).unwrap_or_default();
2397 let escaped = val.replace('"', "\"\"");
2398 result.push_str(&escaped);
2399 }
2400 }
2401 'x' | 'X' => {
2402 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2403 param_idx += 1;
2404 #[allow(clippy::cast_sign_loss)]
2405 let formatted = if spec == 'x' {
2406 format!("{:x}", val as u64)
2407 } else {
2408 format!("{:X}", val as u64)
2409 };
2410 let padded = if zero_pad && width > formatted.len() {
2411 let pad = "0".repeat(width - formatted.len());
2412 format!("{pad}{formatted}")
2413 } else {
2414 pad_string(&formatted, width, left_align)
2415 };
2416 result.push_str(&padded);
2417 }
2418 'o' => {
2419 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2420 param_idx += 1;
2421 #[allow(clippy::cast_sign_loss)]
2422 let formatted = format!("{:o}", val as u64);
2423 let padded = if zero_pad && width > formatted.len() {
2424 let pad = "0".repeat(width - formatted.len());
2425 format!("{pad}{formatted}")
2426 } else {
2427 pad_string(&formatted, width, left_align)
2428 };
2429 result.push_str(&padded);
2430 }
2431 'c' => {
2432 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2433 param_idx += 1;
2434 #[allow(clippy::cast_sign_loss)]
2435 if let Some(c) = char::from_u32(val as u32) {
2436 result.push(c);
2437 }
2438 }
2439 _ => {
2440 result.push('%');
2442 result.push(spec);
2443 }
2444 }
2445 let _ = (left_align, show_sign, space_sign, zero_pad);
2447 }
2448 Ok(result)
2449}
2450
2451fn format_integer(
2452 val: i64,
2453 width: usize,
2454 left_align: bool,
2455 show_sign: bool,
2456 space_sign: bool,
2457 zero_pad: bool,
2458) -> String {
2459 let sign = if val < 0 {
2460 "-".to_owned()
2461 } else if show_sign {
2462 "+".to_owned()
2463 } else if space_sign {
2464 " ".to_owned()
2465 } else {
2466 String::new()
2467 };
2468 let digits = format!("{}", val.unsigned_abs());
2469 let body = format!("{sign}{digits}");
2470 if body.len() >= width {
2471 return body;
2472 }
2473 let pad = width - body.len();
2474 if left_align {
2475 format!("{body}{}", " ".repeat(pad))
2476 } else if zero_pad {
2477 format!("{sign}{}{digits}", "0".repeat(pad))
2478 } else {
2479 format!("{}{body}", " ".repeat(pad))
2480 }
2481}
2482
2483fn format_float_f(
2484 val: f64,
2485 prec: usize,
2486 width: usize,
2487 left_align: bool,
2488 show_sign: bool,
2489 space_sign: bool,
2490 zero_pad: bool,
2491) -> String {
2492 let sign = if val.is_sign_negative() {
2494 "-".to_owned()
2495 } else if show_sign {
2496 "+".to_owned()
2497 } else if space_sign {
2498 " ".to_owned()
2499 } else {
2500 String::new()
2501 };
2502 let digits = format!("{:.prec$}", val.abs());
2503 let body = format!("{sign}{digits}");
2504 if body.len() >= width {
2505 return body;
2506 }
2507 let pad = width - body.len();
2508 if left_align {
2509 format!("{body}{}", " ".repeat(pad))
2510 } else if zero_pad {
2511 format!("{sign}{}{digits}", "0".repeat(pad))
2512 } else {
2513 format!("{}{body}", " ".repeat(pad))
2514 }
2515}
2516
2517fn pad_string(s: &str, width: usize, left_align: bool) -> String {
2518 if s.len() >= width {
2519 return s.to_owned();
2520 }
2521 let pad = width - s.len();
2522 if left_align {
2523 format!("{s}{}", " ".repeat(pad))
2524 } else {
2525 format!("{}{s}", " ".repeat(pad))
2526 }
2527}
2528
2529fn normalize_exponent(s: &str) -> String {
2532 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
2533 (&s[..pos], 'e', &s[pos + 1..])
2534 } else if let Some(pos) = s.find('E') {
2535 (&s[..pos], 'E', &s[pos + 1..])
2536 } else {
2537 return s.to_owned();
2538 };
2539 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
2540 ("-", rest)
2541 } else if let Some(rest) = exp_part.strip_prefix('+') {
2542 ("+", rest)
2543 } else {
2544 ("+", exp_part)
2545 };
2546 let padded = if digits.len() < 2 {
2547 format!("0{digits}")
2548 } else {
2549 digits.to_owned()
2550 };
2551 format!("{prefix}{e_char}{sign}{padded}")
2552}
2553
2554fn format_float_g(val: f64, sig: usize, upper: bool) -> String {
2556 if !val.is_finite() {
2557 return format!("{val}");
2558 }
2559 let e_str = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2560 let exp: i32 = e_str
2561 .rsplit_once('e')
2562 .and_then(|(_, e)| e.parse().ok())
2563 .unwrap_or(0);
2564 #[allow(clippy::cast_possible_wrap)]
2565 let formatted = if exp < -4 || exp >= sig as i32 {
2566 let s = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2567 let s = if upper { s.replace('e', "E") } else { s };
2568 let trimmed = if s.contains('.') {
2570 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
2571 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
2572 format!("{mantissa}{}", &s[e_pos..])
2573 } else {
2574 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2575 }
2576 } else {
2577 s
2578 };
2579 normalize_exponent(&trimmed)
2580 } else {
2581 let decimal_places = if exp >= 0 {
2582 sig.saturating_sub((exp + 1) as usize)
2583 } else {
2584 sig + exp.unsigned_abs() as usize - 1
2585 };
2586 let s = format!("{val:.decimal_places$}");
2587 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2588 };
2589 formatted
2590}
2591
2592#[cfg(test)]
2593#[allow(clippy::too_many_lines)]
2594mod tests {
2595 use super::*;
2596
2597 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
2598 f.invoke(&[v])
2599 }
2600
2601 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
2602 f.invoke(&[a, b])
2603 }
2604
2605 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
2606 let function = registry
2607 .find_scalar(name, arity)
2608 .expect("known scalar name with bad arity returns erroring scalar");
2609 let args = vec![SqliteValue::Null; arity.max(0) as usize];
2610 let err = function
2611 .invoke(&args)
2612 .expect_err("wrong arity should return function error");
2613 let expected = format!("wrong number of arguments to function {name}()");
2614 assert!(
2615 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2616 "expected {expected:?}, got {err:?}"
2617 );
2618 }
2619
2620 #[test]
2621 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
2622 let original = get_change_tracking_state();
2623 let expected = ChangeTrackingState {
2624 last_insert_rowid: 17,
2625 last_changes: 23,
2626 total_changes: 42,
2627 };
2628
2629 set_change_tracking_state(expected);
2630 assert_eq!(get_change_tracking_state(), expected);
2631
2632 set_change_tracking_state(original);
2633 }
2634
2635 #[test]
2638 fn test_abs_positive() {
2639 assert_eq!(
2640 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
2641 SqliteValue::Integer(42)
2642 );
2643 }
2644
2645 #[test]
2646 fn test_abs_negative() {
2647 assert_eq!(
2648 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
2649 SqliteValue::Integer(42)
2650 );
2651 }
2652
2653 #[test]
2654 fn test_abs_null() {
2655 assert_eq!(
2656 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
2657 SqliteValue::Null
2658 );
2659 }
2660
2661 #[test]
2662 fn test_abs_min_i64_overflow() {
2663 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
2664 assert!(matches!(err, FrankenError::IntegerOverflow));
2665 }
2666
2667 #[test]
2668 fn test_abs_string_coercion() {
2669 assert_eq!(
2670 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
2671 SqliteValue::Float(7.5)
2672 );
2673 }
2674
2675 #[test]
2676 fn test_abs_whitespace_padded_text() {
2677 assert_eq!(
2679 invoke1(
2680 &AbsFunc,
2681 SqliteValue::Text(SmallText::from_string(" 42 "))
2682 )
2683 .unwrap(),
2684 SqliteValue::Float(42.0)
2685 );
2686 assert_eq!(
2687 invoke1(
2688 &AbsFunc,
2689 SqliteValue::Text(SmallText::from_string(" -7.5 "))
2690 )
2691 .unwrap(),
2692 SqliteValue::Float(7.5)
2693 );
2694 assert_eq!(
2695 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
2696 SqliteValue::Float(0.0)
2697 );
2698 }
2699
2700 #[test]
2701 #[allow(clippy::approx_constant)]
2702 fn test_abs_float() {
2703 assert_eq!(
2704 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
2705 SqliteValue::Float(3.14)
2706 );
2707 }
2708
2709 #[test]
2712 fn test_char_basic() {
2713 let f = CharFunc;
2714 let result = f
2715 .invoke(&[
2716 SqliteValue::Integer(72),
2717 SqliteValue::Integer(101),
2718 SqliteValue::Integer(108),
2719 SqliteValue::Integer(108),
2720 SqliteValue::Integer(111),
2721 ])
2722 .unwrap();
2723 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
2724 }
2725
2726 #[test]
2727 fn test_char_null_skipped() {
2728 let f = CharFunc;
2729 let result = f
2731 .invoke(&[
2732 SqliteValue::Integer(65),
2733 SqliteValue::Null,
2734 SqliteValue::Integer(66),
2735 ])
2736 .unwrap();
2737 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
2738 }
2739
2740 #[test]
2741 fn test_char_invalid_scalar_values_use_replacement_character() {
2742 let f = CharFunc;
2743 let result = f
2744 .invoke(&[
2745 SqliteValue::Integer(-1),
2746 SqliteValue::Integer(65),
2747 SqliteValue::Integer(1_114_112),
2748 ])
2749 .unwrap();
2750 assert_eq!(
2751 result,
2752 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
2753 );
2754 }
2755
2756 #[test]
2759 fn test_coalesce_first_non_null() {
2760 let f = CoalesceFunc;
2761 let result = f
2762 .invoke(&[
2763 SqliteValue::Null,
2764 SqliteValue::Null,
2765 SqliteValue::Integer(3),
2766 SqliteValue::Integer(4),
2767 ])
2768 .unwrap();
2769 assert_eq!(result, SqliteValue::Integer(3));
2770 }
2771
2772 #[test]
2775 fn test_concat_null_as_empty() {
2776 let f = ConcatFunc;
2777 let result = f
2778 .invoke(&[
2779 SqliteValue::Null,
2780 SqliteValue::Text(SmallText::from_string("hello")),
2781 SqliteValue::Null,
2782 ])
2783 .unwrap();
2784 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
2785 }
2786
2787 #[test]
2788 #[ignore = "perf-only benchmark"]
2789 fn perf_concat_text_args() {
2790 use std::hint::black_box;
2791 use std::time::Instant;
2792
2793 const TEXT_ARGS: usize = 24;
2794 const INVOCATIONS: usize = 50_000;
2795 const REPEATS: usize = 5;
2796
2797 let f = ConcatFunc;
2798 let mut args = Vec::with_capacity(TEXT_ARGS);
2799 for _ in 0..TEXT_ARGS {
2800 args.push(SqliteValue::Text(SmallText::from_string("payload")));
2801 }
2802
2803 let mut best_ns = u128::MAX;
2804 let mut result_len = 0usize;
2805 for _ in 0..REPEATS {
2806 let started = Instant::now();
2807 for _ in 0..INVOCATIONS {
2808 let result = black_box(
2809 f.invoke(black_box(args.as_slice()))
2810 .expect("concat benchmark invocation must succeed"),
2811 );
2812 result_len = match result {
2813 SqliteValue::Text(text) => text.len(),
2814 SqliteValue::Null
2815 | SqliteValue::Integer(_)
2816 | SqliteValue::Float(_)
2817 | SqliteValue::Blob(_) => 0,
2818 };
2819 }
2820 best_ns = best_ns.min(started.elapsed().as_nanos());
2821 }
2822
2823 println!(
2824 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
2825 );
2826 }
2827
2828 #[test]
2831 fn test_concat_ws_null_skipped() {
2832 let f = ConcatWsFunc;
2833 let result = f
2834 .invoke(&[
2835 SqliteValue::Text(SmallText::from_string(",")),
2836 SqliteValue::Text(SmallText::from_string("a")),
2837 SqliteValue::Null,
2838 SqliteValue::Text(SmallText::from_string("b")),
2839 ])
2840 .unwrap();
2841 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
2842 }
2843
2844 #[test]
2845 #[ignore = "perf-only benchmark"]
2846 fn perf_concat_ws_text_args() {
2847 use std::hint::black_box;
2848 use std::time::Instant;
2849
2850 const TEXT_ARGS: usize = 24;
2851 const INVOCATIONS: usize = 50_000;
2852 const REPEATS: usize = 5;
2853
2854 let f = ConcatWsFunc;
2855 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
2856 args.push(SqliteValue::Text(SmallText::from_string(",")));
2857 for _ in 0..TEXT_ARGS {
2858 args.push(SqliteValue::Text(SmallText::from_string("payload")));
2859 }
2860
2861 let mut best_ns = u128::MAX;
2862 let mut result_len = 0usize;
2863 for _ in 0..REPEATS {
2864 let started = Instant::now();
2865 for _ in 0..INVOCATIONS {
2866 let result = black_box(
2867 f.invoke(black_box(args.as_slice()))
2868 .expect("concat_ws benchmark invocation must succeed"),
2869 );
2870 result_len = match result {
2871 SqliteValue::Text(text) => text.len(),
2872 SqliteValue::Null
2873 | SqliteValue::Integer(_)
2874 | SqliteValue::Float(_)
2875 | SqliteValue::Blob(_) => 0,
2876 };
2877 }
2878 best_ns = best_ns.min(started.elapsed().as_nanos());
2879 }
2880
2881 println!(
2882 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
2883 );
2884 }
2885
2886 #[test]
2889 fn test_hex_blob() {
2890 let result = invoke1(
2891 &HexFunc,
2892 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
2893 )
2894 .unwrap();
2895 assert_eq!(
2896 result,
2897 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
2898 );
2899 }
2900
2901 #[test]
2902 fn test_hex_number_via_text() {
2903 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
2905 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
2906 }
2907
2908 #[test]
2909 #[ignore = "perf-only benchmark"]
2910 fn perf_hex_text_blob_args() {
2911 use std::hint::black_box;
2912 use std::time::Instant;
2913
2914 const BYTES: usize = 24;
2915 const INVOCATIONS: usize = 100_000;
2916 const REPEATS: usize = 5;
2917
2918 let f = HexFunc;
2919 let text_args = [SqliteValue::Text(SmallText::from_string(
2920 "payload payload sentinel",
2921 ))];
2922 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
2923
2924 let mut text_best_ns = u128::MAX;
2925 let mut blob_best_ns = u128::MAX;
2926 let mut text_result_len = 0usize;
2927 let mut blob_result_len = 0usize;
2928 for _ in 0..REPEATS {
2929 let started = Instant::now();
2930 for _ in 0..INVOCATIONS {
2931 let result = black_box(
2932 f.invoke(black_box(text_args.as_slice()))
2933 .expect("hex text benchmark invocation must succeed"),
2934 );
2935 text_result_len = match result {
2936 SqliteValue::Text(text) => text.len(),
2937 SqliteValue::Null
2938 | SqliteValue::Integer(_)
2939 | SqliteValue::Float(_)
2940 | SqliteValue::Blob(_) => 0,
2941 };
2942 }
2943 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
2944
2945 let started = Instant::now();
2946 for _ in 0..INVOCATIONS {
2947 let result = black_box(
2948 f.invoke(black_box(blob_args.as_slice()))
2949 .expect("hex blob benchmark invocation must succeed"),
2950 );
2951 blob_result_len = match result {
2952 SqliteValue::Text(text) => text.len(),
2953 SqliteValue::Null
2954 | SqliteValue::Integer(_)
2955 | SqliteValue::Float(_)
2956 | SqliteValue::Blob(_) => 0,
2957 };
2958 }
2959 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
2960 }
2961
2962 println!(
2963 "hex_text_blob_args bytes={BYTES} invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} blob_best_ns={blob_best_ns} text_result_len={text_result_len} blob_result_len={blob_result_len}"
2964 );
2965 }
2966
2967 #[test]
2970 fn test_iif_true() {
2971 let f = IifFunc;
2972 let result = f
2973 .invoke(&[
2974 SqliteValue::Integer(1),
2975 SqliteValue::Text(SmallText::from_string("yes")),
2976 SqliteValue::Text(SmallText::from_string("no")),
2977 ])
2978 .unwrap();
2979 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
2980 }
2981
2982 #[test]
2983 fn test_iif_false() {
2984 let f = IifFunc;
2985 let result = f
2986 .invoke(&[
2987 SqliteValue::Integer(0),
2988 SqliteValue::Text(SmallText::from_string("yes")),
2989 SqliteValue::Text(SmallText::from_string("no")),
2990 ])
2991 .unwrap();
2992 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
2993 }
2994
2995 #[test]
2996 fn test_iif_whitespace_padded_text_truthy() {
2997 let f = IifFunc;
3000 let result = f
3001 .invoke(&[
3002 SqliteValue::Text(SmallText::from_string(" 5 ")),
3003 SqliteValue::Text(SmallText::from_string("yes")),
3004 SqliteValue::Text(SmallText::from_string("no")),
3005 ])
3006 .unwrap();
3007 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3008 }
3009
3010 #[test]
3013 fn test_ifnull_non_null() {
3014 assert_eq!(
3015 invoke2(
3016 &IfnullFunc,
3017 SqliteValue::Integer(5),
3018 SqliteValue::Integer(10)
3019 )
3020 .unwrap(),
3021 SqliteValue::Integer(5)
3022 );
3023 }
3024
3025 #[test]
3026 fn test_ifnull_null() {
3027 assert_eq!(
3028 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
3029 SqliteValue::Integer(10)
3030 );
3031 }
3032
3033 #[test]
3036 fn test_instr_found() {
3037 assert_eq!(
3038 invoke2(
3039 &InstrFunc,
3040 SqliteValue::Text(SmallText::from_string("hello world")),
3041 SqliteValue::Text(SmallText::from_string("world"))
3042 )
3043 .unwrap(),
3044 SqliteValue::Integer(7)
3045 );
3046 }
3047
3048 #[test]
3049 fn test_instr_not_found() {
3050 assert_eq!(
3051 invoke2(
3052 &InstrFunc,
3053 SqliteValue::Text(SmallText::from_string("hello")),
3054 SqliteValue::Text(SmallText::from_string("xyz"))
3055 )
3056 .unwrap(),
3057 SqliteValue::Integer(0)
3058 );
3059 }
3060
3061 #[test]
3062 fn test_instr_empty_needle_returns_one() {
3063 assert_eq!(
3065 invoke2(
3066 &InstrFunc,
3067 SqliteValue::Text(SmallText::from_string("hello")),
3068 SqliteValue::Text(SmallText::new(""))
3069 )
3070 .unwrap(),
3071 SqliteValue::Integer(1)
3072 );
3073 }
3074
3075 #[test]
3076 fn test_instr_empty_haystack_returns_zero() {
3077 assert_eq!(
3078 invoke2(
3079 &InstrFunc,
3080 SqliteValue::Text(SmallText::new("")),
3081 SqliteValue::Text(SmallText::from_string("x"))
3082 )
3083 .unwrap(),
3084 SqliteValue::Integer(0)
3085 );
3086 }
3087
3088 #[test]
3089 fn test_instr_blob_empty_needle_returns_one() {
3090 assert_eq!(
3092 invoke2(
3093 &InstrFunc,
3094 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
3095 SqliteValue::Blob(Arc::from([].as_slice()))
3096 )
3097 .unwrap(),
3098 SqliteValue::Integer(1)
3099 );
3100 }
3101
3102 #[test]
3103 #[ignore = "perf-only benchmark"]
3104 fn perf_instr_text_args() {
3105 use std::hint::black_box;
3106 use std::time::Instant;
3107
3108 const INVOCATIONS: usize = 100_000;
3109 const REPEATS: usize = 5;
3110
3111 let f = InstrFunc;
3112 let args = [
3113 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
3114 SqliteValue::Text(SmallText::from_string("sentinel")),
3115 ];
3116
3117 let mut best_ns = u128::MAX;
3118 let mut result_value = 0i64;
3119 for _ in 0..REPEATS {
3120 let started = Instant::now();
3121 for _ in 0..INVOCATIONS {
3122 let result = black_box(
3123 f.invoke(black_box(args.as_slice()))
3124 .expect("instr benchmark invocation must succeed"),
3125 );
3126 result_value = match result {
3127 SqliteValue::Integer(value) => value,
3128 SqliteValue::Null
3129 | SqliteValue::Float(_)
3130 | SqliteValue::Text(_)
3131 | SqliteValue::Blob(_) => 0,
3132 };
3133 }
3134 best_ns = best_ns.min(started.elapsed().as_nanos());
3135 }
3136
3137 println!(
3138 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
3139 );
3140 }
3141
3142 #[test]
3145 fn test_length_text_chars() {
3146 assert_eq!(
3148 invoke1(
3149 &LengthFunc,
3150 SqliteValue::Text(SmallText::from_string("café"))
3151 )
3152 .unwrap(),
3153 SqliteValue::Integer(4)
3154 );
3155 }
3156
3157 #[test]
3158 fn test_length_text_stops_at_nul() {
3159 assert_eq!(
3160 invoke1(
3161 &LengthFunc,
3162 SqliteValue::Text(SmallText::from_string("A\0B"))
3163 )
3164 .unwrap(),
3165 SqliteValue::Integer(1)
3166 );
3167 assert_eq!(
3168 invoke1(
3169 &LengthFunc,
3170 SqliteValue::Text(SmallText::from_string("\0A"))
3171 )
3172 .unwrap(),
3173 SqliteValue::Integer(0)
3174 );
3175 }
3176
3177 #[test]
3178 fn test_length_blob_bytes() {
3179 assert_eq!(
3180 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
3181 SqliteValue::Integer(2)
3182 );
3183 }
3184
3185 #[test]
3188 fn test_octet_length_multibyte() {
3189 assert_eq!(
3191 invoke1(
3192 &OctetLengthFunc,
3193 SqliteValue::Text(SmallText::from_string("café"))
3194 )
3195 .unwrap(),
3196 SqliteValue::Integer(5)
3197 );
3198 }
3199
3200 #[test]
3203 fn test_lower_ascii() {
3204 assert_eq!(
3205 invoke1(
3206 &LowerFunc,
3207 SqliteValue::Text(SmallText::from_string("HELLO"))
3208 )
3209 .unwrap(),
3210 SqliteValue::Text(SmallText::from_string("hello"))
3211 );
3212 }
3213
3214 #[test]
3215 fn test_upper_ascii() {
3216 assert_eq!(
3217 invoke1(
3218 &UpperFunc,
3219 SqliteValue::Text(SmallText::from_string("hello"))
3220 )
3221 .unwrap(),
3222 SqliteValue::Text(SmallText::from_string("HELLO"))
3223 );
3224 }
3225
3226 #[test]
3229 fn test_trim_default() {
3230 let f = TrimFunc;
3231 assert_eq!(
3232 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
3233 .unwrap(),
3234 SqliteValue::Text(SmallText::from_string("hello"))
3235 );
3236 }
3237
3238 #[test]
3239 fn test_ltrim_default() {
3240 let f = LtrimFunc;
3241 assert_eq!(
3242 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
3243 .unwrap(),
3244 SqliteValue::Text(SmallText::from_string("hello"))
3245 );
3246 }
3247
3248 #[test]
3249 fn test_ltrim_custom() {
3250 let f = LtrimFunc;
3251 assert_eq!(
3252 f.invoke(&[
3253 SqliteValue::Text(SmallText::from_string("xxhello")),
3254 SqliteValue::Text(SmallText::from_string("x")),
3255 ])
3256 .unwrap(),
3257 SqliteValue::Text(SmallText::from_string("hello"))
3258 );
3259 }
3260
3261 #[test]
3262 #[ignore = "perf-only benchmark"]
3263 fn perf_trim_text_args() {
3264 use std::hint::black_box;
3265 use std::time::Instant;
3266
3267 const INVOCATIONS: usize = 100_000;
3268 const REPEATS: usize = 5;
3269
3270 let trim = TrimFunc;
3271 let ltrim = LtrimFunc;
3272 let rtrim = RtrimFunc;
3273 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
3274 let custom_args = [
3275 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
3276 SqliteValue::Text(SmallText::from_string("x")),
3277 ];
3278
3279 let mut trim_best_ns = u128::MAX;
3280 let mut ltrim_best_ns = u128::MAX;
3281 let mut rtrim_best_ns = u128::MAX;
3282 let mut custom_best_ns = u128::MAX;
3283 let mut result_len = 0usize;
3284
3285 for _ in 0..REPEATS {
3286 let started = Instant::now();
3287 for _ in 0..INVOCATIONS {
3288 let result = black_box(
3289 trim.invoke(black_box(default_args.as_slice()))
3290 .expect("trim benchmark invocation must succeed"),
3291 );
3292 result_len = match result {
3293 SqliteValue::Text(text) => text.len(),
3294 SqliteValue::Null
3295 | SqliteValue::Integer(_)
3296 | SqliteValue::Float(_)
3297 | SqliteValue::Blob(_) => 0,
3298 };
3299 }
3300 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
3301
3302 let started = Instant::now();
3303 for _ in 0..INVOCATIONS {
3304 let result = black_box(
3305 ltrim
3306 .invoke(black_box(default_args.as_slice()))
3307 .expect("ltrim benchmark invocation must succeed"),
3308 );
3309 result_len = match result {
3310 SqliteValue::Text(text) => text.len(),
3311 SqliteValue::Null
3312 | SqliteValue::Integer(_)
3313 | SqliteValue::Float(_)
3314 | SqliteValue::Blob(_) => 0,
3315 };
3316 }
3317 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
3318
3319 let started = Instant::now();
3320 for _ in 0..INVOCATIONS {
3321 let result = black_box(
3322 rtrim
3323 .invoke(black_box(default_args.as_slice()))
3324 .expect("rtrim benchmark invocation must succeed"),
3325 );
3326 result_len = match result {
3327 SqliteValue::Text(text) => text.len(),
3328 SqliteValue::Null
3329 | SqliteValue::Integer(_)
3330 | SqliteValue::Float(_)
3331 | SqliteValue::Blob(_) => 0,
3332 };
3333 }
3334 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
3335
3336 let started = Instant::now();
3337 for _ in 0..INVOCATIONS {
3338 let result = black_box(
3339 trim.invoke(black_box(custom_args.as_slice()))
3340 .expect("custom trim benchmark invocation must succeed"),
3341 );
3342 result_len = match result {
3343 SqliteValue::Text(text) => text.len(),
3344 SqliteValue::Null
3345 | SqliteValue::Integer(_)
3346 | SqliteValue::Float(_)
3347 | SqliteValue::Blob(_) => 0,
3348 };
3349 }
3350 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
3351 }
3352
3353 println!(
3354 "trim_text_args invocations={INVOCATIONS} repeats={REPEATS} trim_best_ns={trim_best_ns} ltrim_best_ns={ltrim_best_ns} rtrim_best_ns={rtrim_best_ns} custom_best_ns={custom_best_ns} result_len={result_len}"
3355 );
3356 }
3357
3358 #[test]
3361 fn test_nullif_equal() {
3362 assert_eq!(
3363 invoke2(
3364 &NullifFunc,
3365 SqliteValue::Integer(5),
3366 SqliteValue::Integer(5)
3367 )
3368 .unwrap(),
3369 SqliteValue::Null
3370 );
3371 }
3372
3373 #[test]
3374 fn test_nullif_different() {
3375 assert_eq!(
3376 invoke2(
3377 &NullifFunc,
3378 SqliteValue::Integer(5),
3379 SqliteValue::Integer(3)
3380 )
3381 .unwrap(),
3382 SqliteValue::Integer(5)
3383 );
3384 }
3385
3386 #[test]
3389 fn test_typeof_each() {
3390 assert_eq!(
3391 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
3392 SqliteValue::Text(SmallText::from_string("null"))
3393 );
3394 assert_eq!(
3395 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
3396 SqliteValue::Text(SmallText::from_string("integer"))
3397 );
3398 assert_eq!(
3399 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
3400 SqliteValue::Text(SmallText::from_string("real"))
3401 );
3402 assert_eq!(
3403 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
3404 SqliteValue::Text(SmallText::from_string("text"))
3405 );
3406 assert_eq!(
3407 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
3408 SqliteValue::Text(SmallText::from_string("blob"))
3409 );
3410 }
3411
3412 #[test]
3415 fn test_subtype_null_returns_zero() {
3416 assert_eq!(
3417 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
3418 SqliteValue::Integer(0)
3419 );
3420 }
3421
3422 #[test]
3425 fn test_replace_basic() {
3426 let f = ReplaceFunc;
3427 assert_eq!(
3428 f.invoke(&[
3429 SqliteValue::Text(SmallText::from_string("hello world")),
3430 SqliteValue::Text(SmallText::from_string("world")),
3431 SqliteValue::Text(SmallText::from_string("earth")),
3432 ])
3433 .unwrap(),
3434 SqliteValue::Text(SmallText::from_string("hello earth"))
3435 );
3436 }
3437
3438 #[test]
3439 fn test_replace_empty_y() {
3440 let f = ReplaceFunc;
3441 assert_eq!(
3442 f.invoke(&[
3443 SqliteValue::Text(SmallText::from_string("hello")),
3444 SqliteValue::Text(SmallText::new("")),
3445 SqliteValue::Text(SmallText::from_string("x")),
3446 ])
3447 .unwrap(),
3448 SqliteValue::Text(SmallText::from_string("hello"))
3449 );
3450 }
3451
3452 #[test]
3453 #[ignore = "perf-only benchmark"]
3454 fn perf_replace_text_args() {
3455 use std::hint::black_box;
3456 use std::time::Instant;
3457
3458 const INVOCATIONS: usize = 100_000;
3459 const REPEATS: usize = 5;
3460
3461 let f = ReplaceFunc;
3462 let args = [
3463 SqliteValue::Text(SmallText::from_string("payload payload payload")),
3464 SqliteValue::Text(SmallText::from_string("zz")),
3465 SqliteValue::Text(SmallText::from_string("replacement")),
3466 ];
3467
3468 let mut best_ns = u128::MAX;
3469 let mut result_len = 0usize;
3470 for _ in 0..REPEATS {
3471 let started = Instant::now();
3472 for _ in 0..INVOCATIONS {
3473 let result = black_box(
3474 f.invoke(black_box(args.as_slice()))
3475 .expect("replace benchmark invocation must succeed"),
3476 );
3477 result_len = match result {
3478 SqliteValue::Text(text) => text.len(),
3479 SqliteValue::Null
3480 | SqliteValue::Integer(_)
3481 | SqliteValue::Float(_)
3482 | SqliteValue::Blob(_) => 0,
3483 };
3484 }
3485 best_ns = best_ns.min(started.elapsed().as_nanos());
3486 }
3487
3488 println!(
3489 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3490 );
3491 }
3492
3493 #[test]
3496 #[allow(clippy::float_cmp)]
3497 fn test_round_half_away() {
3498 assert_eq!(
3500 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
3501 SqliteValue::Float(3.0)
3502 );
3503 assert_eq!(
3504 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
3505 SqliteValue::Float(-3.0)
3506 );
3507 }
3508
3509 #[test]
3510 #[allow(clippy::float_cmp, clippy::approx_constant)]
3511 fn test_round_precision() {
3512 assert_eq!(
3513 RoundFunc
3514 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
3515 .unwrap(),
3516 SqliteValue::Float(3.14)
3517 );
3518 }
3519
3520 #[test]
3521 #[allow(clippy::float_cmp)]
3522 fn test_round_extreme_n_clamped() {
3523 assert_eq!(
3525 RoundFunc
3526 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
3527 .unwrap(),
3528 RoundFunc
3529 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
3530 .unwrap(),
3531 );
3532 assert_eq!(
3534 RoundFunc
3535 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
3536 .unwrap(),
3537 SqliteValue::Float(3.0)
3538 );
3539 let result = RoundFunc
3541 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
3542 .unwrap();
3543 if let SqliteValue::Float(v) = result {
3544 assert!(!v.is_nan(), "round must never return NaN");
3545 }
3546 }
3547
3548 #[test]
3549 #[allow(clippy::float_cmp)]
3550 fn test_round_large_value_no_fractional() {
3551 let big = 9_007_199_254_740_993.0_f64;
3553 assert_eq!(
3554 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
3555 SqliteValue::Float(big)
3556 );
3557 assert_eq!(
3558 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
3559 SqliteValue::Float(-big)
3560 );
3561 }
3562
3563 #[test]
3566 fn test_sign_positive() {
3567 assert_eq!(
3568 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
3569 SqliteValue::Integer(1)
3570 );
3571 }
3572
3573 #[test]
3574 fn test_sign_negative() {
3575 assert_eq!(
3576 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
3577 SqliteValue::Integer(-1)
3578 );
3579 }
3580
3581 #[test]
3582 fn test_sign_zero() {
3583 assert_eq!(
3584 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
3585 SqliteValue::Integer(0)
3586 );
3587 }
3588
3589 #[test]
3590 fn test_sign_null() {
3591 assert_eq!(
3592 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
3593 SqliteValue::Null
3594 );
3595 }
3596
3597 #[test]
3598 fn test_sign_non_numeric() {
3599 assert_eq!(
3601 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3602 SqliteValue::Null
3603 );
3604 }
3605
3606 #[test]
3607 fn test_sign_whitespace_padded_text() {
3608 assert_eq!(
3611 invoke1(
3612 &SignFunc,
3613 SqliteValue::Text(SmallText::from_string(" 5 "))
3614 )
3615 .unwrap(),
3616 SqliteValue::Integer(1)
3617 );
3618 assert_eq!(
3619 invoke1(
3620 &SignFunc,
3621 SqliteValue::Text(SmallText::from_string(" -3.14 "))
3622 )
3623 .unwrap(),
3624 SqliteValue::Integer(-1)
3625 );
3626 }
3627
3628 #[test]
3629 fn test_sign_unicode_space_and_blob_return_null() {
3630 assert_eq!(
3631 invoke1(
3632 &SignFunc,
3633 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
3634 )
3635 .unwrap(),
3636 SqliteValue::Null
3637 );
3638 assert_eq!(
3639 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
3640 SqliteValue::Null
3641 );
3642 }
3643
3644 #[test]
3645 fn test_sign_nan_inf_text_returns_null() {
3646 for s in &[
3649 "NaN",
3650 "nan",
3651 "inf",
3652 "-inf",
3653 "Infinity",
3654 "-Infinity",
3655 "INF",
3656 "+nan",
3657 "+inf",
3658 ] {
3659 assert_eq!(
3660 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
3661 SqliteValue::Null,
3662 "sign('{s}') should be NULL"
3663 );
3664 }
3665 }
3666
3667 #[test]
3668 fn test_sign_numeric_overflow_to_infinity() {
3669 assert_eq!(
3672 invoke1(
3673 &SignFunc,
3674 SqliteValue::Text(SmallText::from_string("1e999"))
3675 )
3676 .unwrap(),
3677 SqliteValue::Integer(1)
3678 );
3679 assert_eq!(
3680 invoke1(
3681 &SignFunc,
3682 SqliteValue::Text(SmallText::from_string("-1e999"))
3683 )
3684 .unwrap(),
3685 SqliteValue::Integer(-1)
3686 );
3687 assert_eq!(
3689 invoke1(
3690 &SignFunc,
3691 SqliteValue::Text(SmallText::from_string("1e-999"))
3692 )
3693 .unwrap(),
3694 SqliteValue::Integer(0)
3695 );
3696 }
3697
3698 #[test]
3699 fn test_sign_float_nan_returns_null() {
3700 assert_eq!(
3702 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
3703 SqliteValue::Null
3704 );
3705 }
3706
3707 #[test]
3710 fn test_scalar_max_null() {
3711 let f = ScalarMaxFunc;
3712 let result = f
3713 .invoke(&[
3714 SqliteValue::Integer(1),
3715 SqliteValue::Null,
3716 SqliteValue::Integer(3),
3717 ])
3718 .unwrap();
3719 assert_eq!(result, SqliteValue::Null);
3720 }
3721
3722 #[test]
3723 fn test_scalar_max_values() {
3724 let f = ScalarMaxFunc;
3725 let result = f
3726 .invoke(&[
3727 SqliteValue::Integer(3),
3728 SqliteValue::Integer(1),
3729 SqliteValue::Integer(2),
3730 ])
3731 .unwrap();
3732 assert_eq!(result, SqliteValue::Integer(3));
3733 }
3734
3735 #[test]
3736 fn test_scalar_min_null() {
3737 let f = ScalarMinFunc;
3738 let result = f
3739 .invoke(&[
3740 SqliteValue::Integer(1),
3741 SqliteValue::Null,
3742 SqliteValue::Integer(3),
3743 ])
3744 .unwrap();
3745 assert_eq!(result, SqliteValue::Null);
3746 }
3747
3748 #[test]
3751 fn test_quote_text() {
3752 assert_eq!(
3753 invoke1(
3754 &QuoteFunc,
3755 SqliteValue::Text(SmallText::from_string("it's"))
3756 )
3757 .unwrap(),
3758 SqliteValue::Text(SmallText::from_string("'it''s'"))
3759 );
3760 }
3761
3762 #[test]
3763 fn test_quote_null() {
3764 assert_eq!(
3765 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
3766 SqliteValue::Text(SmallText::from_string("NULL"))
3767 );
3768 }
3769
3770 #[test]
3771 fn test_quote_blob() {
3772 assert_eq!(
3773 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
3774 SqliteValue::Text(SmallText::from_string("X'AB'"))
3775 );
3776 }
3777
3778 #[test]
3779 fn test_quote_text_truncates_at_first_nul() {
3780 assert_eq!(
3781 invoke1(
3782 &QuoteFunc,
3783 SqliteValue::Text(SmallText::from_string("A\0B"))
3784 )
3785 .unwrap(),
3786 SqliteValue::Text(SmallText::from_string("'A'"))
3787 );
3788 }
3789
3790 #[test]
3791 fn test_unistr_quote_plain_text_matches_quote() {
3792 assert_eq!(
3793 invoke1(
3794 &UnistrQuoteFunc,
3795 SqliteValue::Text(SmallText::from_string("it's"))
3796 )
3797 .unwrap(),
3798 SqliteValue::Text(SmallText::from_string("'it''s'"))
3799 );
3800 }
3801
3802 #[test]
3803 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
3804 assert_eq!(
3805 invoke1(
3806 &UnistrQuoteFunc,
3807 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
3808 )
3809 .unwrap(),
3810 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
3811 );
3812 }
3813
3814 #[test]
3815 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
3816 assert_eq!(
3817 invoke1(
3818 &UnistrQuoteFunc,
3819 SqliteValue::Text(SmallText::from_string("A\0\nB"))
3820 )
3821 .unwrap(),
3822 SqliteValue::Text(SmallText::from_string("'A'"))
3823 );
3824 }
3825
3826 #[test]
3827 fn test_unistr_decodes_backslash_and_unicode_escapes() {
3828 assert_eq!(
3829 invoke1(
3830 &UnistrFunc,
3831 SqliteValue::Text(SmallText::from_string(
3832 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
3833 ))
3834 )
3835 .unwrap(),
3836 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
3837 );
3838 }
3839
3840 #[test]
3841 fn test_unistr_invalid_escape_returns_error() {
3842 for input in [
3843 "\\u12xz",
3844 "\\12xz",
3845 "\\+00xz",
3846 "\\",
3847 "\\x",
3848 "\\U00110000",
3849 "\\D800",
3850 ] {
3851 let err = invoke1(
3852 &UnistrFunc,
3853 SqliteValue::Text(SmallText::from_string(input)),
3854 )
3855 .unwrap_err();
3856 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
3857 }
3858 }
3859
3860 #[test]
3861 #[ignore = "perf-only benchmark"]
3862 fn perf_unistr_text_args() {
3863 use std::hint::black_box;
3864 use std::time::Instant;
3865
3866 const INVOCATIONS: usize = 500_000;
3867 const REPEATS: usize = 7;
3868
3869 let f = UnistrFunc;
3870 let plain_args = [SqliteValue::Text(SmallText::from_string(
3871 "plain unicode payload",
3872 ))];
3873 let escaped_args = [SqliteValue::Text(SmallText::from_string(
3874 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
3875 ))];
3876
3877 let mut plain_best_ns = u128::MAX;
3878 let mut escaped_best_ns = u128::MAX;
3879 let mut checksum = 0usize;
3880 for _ in 0..REPEATS {
3881 let started = Instant::now();
3882 for _ in 0..INVOCATIONS {
3883 let result = black_box(
3884 f.invoke(black_box(plain_args.as_slice()))
3885 .expect("unistr plain benchmark invocation must succeed"),
3886 );
3887 if let SqliteValue::Text(text) = result {
3888 checksum = checksum.wrapping_add(text.len());
3889 }
3890 }
3891 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
3892
3893 let started = Instant::now();
3894 for _ in 0..INVOCATIONS {
3895 let result = black_box(
3896 f.invoke(black_box(escaped_args.as_slice()))
3897 .expect("unistr escaped benchmark invocation must succeed"),
3898 );
3899 if let SqliteValue::Text(text) = result {
3900 checksum = checksum.wrapping_add(text.len());
3901 }
3902 }
3903 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
3904 }
3905
3906 println!(
3907 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
3908 );
3909 }
3910
3911 #[test]
3914 fn test_random_range() {
3915 let f = RandomFunc;
3916 let result = f.invoke(&[]).unwrap();
3917 assert!(matches!(result, SqliteValue::Integer(_)));
3918 }
3919
3920 #[test]
3923 fn test_randomblob_length() {
3924 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
3925 match result {
3926 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
3927 other => unreachable!("expected blob, got {other:?}"),
3928 }
3929 }
3930
3931 #[test]
3932 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
3933 for arg in [
3934 SqliteValue::Null,
3935 SqliteValue::Integer(0),
3936 SqliteValue::Integer(-5),
3937 ] {
3938 let result = invoke1(&RandomblobFunc, arg).unwrap();
3939 match result {
3940 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
3941 other => unreachable!("expected one-byte blob, got {other:?}"),
3942 }
3943 }
3944 }
3945
3946 #[test]
3949 fn test_zeroblob_length() {
3950 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
3951 match result {
3952 SqliteValue::Blob(b) => {
3953 assert_eq!(b.len(), 100);
3954 assert!(b.iter().all(|&x| x == 0));
3955 }
3956 other => unreachable!("expected blob, got {other:?}"),
3957 }
3958 }
3959
3960 #[test]
3963 fn test_unhex_valid() {
3964 let result = invoke1(
3965 &UnhexFunc,
3966 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
3967 )
3968 .unwrap();
3969 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
3970 }
3971
3972 #[test]
3973 fn test_unhex_invalid() {
3974 let result = invoke1(
3975 &UnhexFunc,
3976 SqliteValue::Text(SmallText::from_string("ZZZZ")),
3977 )
3978 .unwrap();
3979 assert_eq!(result, SqliteValue::Null);
3980 }
3981
3982 #[test]
3983 fn test_unhex_ignore_chars() {
3984 let f = UnhexFunc;
3985 let result = f
3986 .invoke(&[
3987 SqliteValue::Text(SmallText::from_string("48-65-6C")),
3988 SqliteValue::Text(SmallText::from_string("-")),
3989 ])
3990 .unwrap();
3991 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
3992 }
3993
3994 #[test]
3995 fn test_unhex_ignore_chars_only_between_byte_pairs() {
3996 let f = UnhexFunc;
3997 let result = f
3998 .invoke(&[
3999 SqliteValue::Text(SmallText::from_string("AB CD")),
4000 SqliteValue::Text(SmallText::from_string(" ")),
4001 ])
4002 .unwrap();
4003 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
4004
4005 let result = f
4006 .invoke(&[
4007 SqliteValue::Text(SmallText::from_string("A BCD")),
4008 SqliteValue::Text(SmallText::from_string(" ")),
4009 ])
4010 .unwrap();
4011 assert_eq!(result, SqliteValue::Null);
4012 }
4013
4014 #[test]
4015 fn test_unhex_null_ignore_argument_returns_null() {
4016 let f = UnhexFunc;
4017 let result = f
4018 .invoke(&[
4019 SqliteValue::Text(SmallText::from_string("41")),
4020 SqliteValue::Null,
4021 ])
4022 .unwrap();
4023 assert_eq!(result, SqliteValue::Null);
4024 }
4025
4026 #[test]
4027 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
4028 let f = UnhexFunc;
4029 let result = f
4030 .invoke(&[
4031 SqliteValue::Text(SmallText::from_string("41")),
4032 SqliteValue::Text(SmallText::from_string("4")),
4033 ])
4034 .unwrap();
4035 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
4036 }
4037
4038 #[test]
4039 #[ignore = "perf-only benchmark"]
4040 fn perf_unhex_text_args() {
4041 use std::hint::black_box;
4042 use std::time::Instant;
4043
4044 const INVOCATIONS: usize = 300_000;
4045 const REPEATS: usize = 7;
4046
4047 let f = UnhexFunc;
4048 let plain_args = [SqliteValue::Text(SmallText::from_string(
4049 "48656C6C6F776F726C64",
4050 ))];
4051 let ignore_args = [
4052 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
4053 SqliteValue::Text(SmallText::from_string("-")),
4054 ];
4055 let mut plain_best_ns = u128::MAX;
4056 let mut ignore_best_ns = u128::MAX;
4057 let mut checksum = 0usize;
4058
4059 for _ in 0..REPEATS {
4060 let started = Instant::now();
4061 for _ in 0..INVOCATIONS {
4062 let result = black_box(
4063 f.invoke(black_box(plain_args.as_slice()))
4064 .expect("unhex benchmark invocation must succeed"),
4065 );
4066 if let SqliteValue::Blob(blob) = result {
4067 checksum = checksum.wrapping_add(blob.len());
4068 }
4069 }
4070 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4071
4072 let started = Instant::now();
4073 for _ in 0..INVOCATIONS {
4074 let result = black_box(
4075 f.invoke(black_box(ignore_args.as_slice()))
4076 .expect("unhex ignore benchmark invocation must succeed"),
4077 );
4078 if let SqliteValue::Blob(blob) = result {
4079 checksum = checksum.wrapping_add(blob.len());
4080 }
4081 }
4082 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
4083 }
4084
4085 println!(
4086 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
4087 );
4088 }
4089
4090 #[test]
4093 fn test_unicode_first_char() {
4094 assert_eq!(
4095 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
4096 SqliteValue::Integer(65)
4097 );
4098 }
4099
4100 #[test]
4101 fn test_unicode_text_stops_at_nul() {
4102 assert_eq!(
4103 invoke1(
4104 &UnicodeFunc,
4105 SqliteValue::Text(SmallText::from_string("\0A"))
4106 )
4107 .unwrap(),
4108 SqliteValue::Null
4109 );
4110 assert_eq!(
4111 invoke1(
4112 &UnicodeFunc,
4113 SqliteValue::Text(SmallText::from_string("A\0"))
4114 )
4115 .unwrap(),
4116 SqliteValue::Integer(65)
4117 );
4118 }
4119
4120 #[test]
4121 fn test_unicode_blob_uses_sqlite_utf8_reader() {
4122 let cases: &[(&[u8], SqliteValue)] = &[
4123 (&[0x00, 0x41], SqliteValue::Null),
4124 (&[0x80], SqliteValue::Integer(128)),
4125 (&[0xC2, 0x80], SqliteValue::Integer(128)),
4126 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
4127 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
4128 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
4129 ];
4130
4131 for (bytes, expected) in cases {
4132 assert_eq!(
4133 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
4134 expected.clone()
4135 );
4136 }
4137 }
4138
4139 #[test]
4140 #[ignore = "perf-only benchmark"]
4141 fn perf_unicode_text_arg() {
4142 use std::hint::black_box;
4143 use std::time::Instant;
4144
4145 const INVOCATIONS: usize = 1_000_000;
4146 const REPEATS: usize = 7;
4147
4148 let f = UnicodeFunc;
4149 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
4150 let mut text_best_ns = u128::MAX;
4151 let mut checksum = 0i64;
4152
4153 for _ in 0..REPEATS {
4154 let started = Instant::now();
4155 for _ in 0..INVOCATIONS {
4156 let result = black_box(
4157 f.invoke(black_box(args.as_slice()))
4158 .expect("unicode benchmark invocation must succeed"),
4159 );
4160 if let SqliteValue::Integer(codepoint) = result {
4161 checksum = checksum.wrapping_add(codepoint);
4162 }
4163 }
4164 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4165 }
4166
4167 println!(
4168 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4169 );
4170 }
4171
4172 #[test]
4175 fn test_soundex_basic() {
4176 assert_eq!(
4177 invoke1(
4178 &SoundexFunc,
4179 SqliteValue::Text(SmallText::from_string("Robert"))
4180 )
4181 .unwrap(),
4182 SqliteValue::Text(SmallText::from_string("R163"))
4183 );
4184 }
4185
4186 #[test]
4187 #[ignore = "perf-only benchmark"]
4188 fn perf_soundex_text_arg() {
4189 use std::hint::black_box;
4190 use std::time::Instant;
4191
4192 const INVOCATIONS: usize = 1_000_000;
4193 const REPEATS: usize = 7;
4194
4195 let f = SoundexFunc;
4196 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
4197 let mut text_best_ns = u128::MAX;
4198 let mut checksum = 0usize;
4199
4200 for _ in 0..REPEATS {
4201 let started = Instant::now();
4202 for _ in 0..INVOCATIONS {
4203 let result = black_box(
4204 f.invoke(black_box(args.as_slice()))
4205 .expect("soundex benchmark invocation must succeed"),
4206 );
4207 if let SqliteValue::Text(text) = result {
4208 checksum = checksum.wrapping_add(text.len());
4209 }
4210 }
4211 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4212 }
4213
4214 println!(
4215 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4216 );
4217 }
4218
4219 #[test]
4222 fn test_substr_basic() {
4223 let f = SubstrFunc;
4224 assert_eq!(
4225 f.invoke(&[
4226 SqliteValue::Text(SmallText::from_string("hello")),
4227 SqliteValue::Integer(2),
4228 SqliteValue::Integer(3),
4229 ])
4230 .unwrap(),
4231 SqliteValue::Text(SmallText::from_string("ell"))
4232 );
4233 }
4234
4235 #[test]
4236 fn test_substr_start_zero_quirk() {
4237 let f = SubstrFunc;
4239 let result = f
4240 .invoke(&[
4241 SqliteValue::Text(SmallText::from_string("hello")),
4242 SqliteValue::Integer(0),
4243 SqliteValue::Integer(3),
4244 ])
4245 .unwrap();
4246 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
4247 }
4248
4249 #[test]
4250 fn test_substr_negative_start() {
4251 let f = SubstrFunc;
4253 let result = f
4254 .invoke(&[
4255 SqliteValue::Text(SmallText::from_string("hello")),
4256 SqliteValue::Integer(-2),
4257 ])
4258 .unwrap();
4259 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
4260 }
4261
4262 #[test]
4263 fn test_substr_negative_length() {
4264 let f = SubstrFunc;
4265 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4266 let i = SqliteValue::Integer;
4267 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
4269 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
4271 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
4273 }
4274
4275 #[test]
4276 fn test_substr_negative_start_negative_length() {
4277 let f = SubstrFunc;
4278 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4279 let i = SqliteValue::Integer;
4280 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
4282 }
4283
4284 #[test]
4285 fn test_substr_edge_cases() {
4286 let f = SubstrFunc;
4287 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4288 let i = SqliteValue::Integer;
4289 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
4291 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
4293 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
4295 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
4297 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
4299 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
4301 }
4302
4303 #[test]
4304 fn test_substr_blob_negative_length() {
4305 let f = SubstrFunc;
4306 let i = SqliteValue::Integer;
4307 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
4308 assert_eq!(
4310 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
4311 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
4312 );
4313 }
4314
4315 #[test]
4318 fn test_like_case_insensitive() {
4319 assert_eq!(
4320 invoke2(
4321 &LikeFunc,
4322 SqliteValue::Text(SmallText::from_string("ABC")),
4323 SqliteValue::Text(SmallText::from_string("abc"))
4324 )
4325 .unwrap(),
4326 SqliteValue::Integer(1)
4327 );
4328 }
4329
4330 #[test]
4331 fn test_like_escape() {
4332 let f = LikeFunc;
4333 let result = f
4334 .invoke(&[
4335 SqliteValue::Text(SmallText::from_string("10\\%")),
4336 SqliteValue::Text(SmallText::from_string("10%")),
4337 SqliteValue::Text(SmallText::from_string("\\")),
4338 ])
4339 .unwrap();
4340 assert_eq!(result, SqliteValue::Integer(1));
4341 }
4342
4343 #[test]
4344 fn test_like_escape_rejects_empty_string() {
4345 let err = LikeFunc
4346 .invoke(&[
4347 SqliteValue::Text(SmallText::from_string("a")),
4348 SqliteValue::Text(SmallText::from_string("a")),
4349 SqliteValue::Text(SmallText::new("")),
4350 ])
4351 .unwrap_err();
4352 assert!(
4353 err.to_string()
4354 .contains("ESCAPE expression must be a single character")
4355 );
4356 }
4357
4358 #[test]
4359 fn test_like_escape_rejects_multi_character_string() {
4360 let err = LikeFunc
4361 .invoke(&[
4362 SqliteValue::Text(SmallText::from_string("a")),
4363 SqliteValue::Text(SmallText::from_string("a")),
4364 SqliteValue::Text(SmallText::from_string("xx")),
4365 ])
4366 .unwrap_err();
4367 assert!(
4368 err.to_string()
4369 .contains("ESCAPE expression must be a single character")
4370 );
4371 }
4372
4373 #[test]
4374 fn test_like_percent() {
4375 assert_eq!(
4376 invoke2(
4377 &LikeFunc,
4378 SqliteValue::Text(SmallText::from_string("%ell%")),
4379 SqliteValue::Text(SmallText::from_string("Hello"))
4380 )
4381 .unwrap(),
4382 SqliteValue::Integer(1)
4383 );
4384 }
4385
4386 #[test]
4389 fn test_glob_star() {
4390 assert_eq!(
4391 invoke2(
4392 &GlobFunc,
4393 SqliteValue::Text(SmallText::from_string("*.txt")),
4394 SqliteValue::Text(SmallText::from_string("file.txt"))
4395 )
4396 .unwrap(),
4397 SqliteValue::Integer(1)
4398 );
4399 }
4400
4401 #[test]
4402 fn test_glob_case_sensitive() {
4403 assert_eq!(
4404 invoke2(
4405 &GlobFunc,
4406 SqliteValue::Text(SmallText::from_string("ABC")),
4407 SqliteValue::Text(SmallText::from_string("abc"))
4408 )
4409 .unwrap(),
4410 SqliteValue::Integer(0)
4411 );
4412 }
4413
4414 #[test]
4417 fn test_format_specifiers() {
4418 let f = FormatFunc;
4419 let result = f
4420 .invoke(&[
4421 SqliteValue::Text(SmallText::from_string("%d %s")),
4422 SqliteValue::Integer(42),
4423 SqliteValue::Text(SmallText::from_string("hello")),
4424 ])
4425 .unwrap();
4426 assert_eq!(
4427 result,
4428 SqliteValue::Text(SmallText::from_string("42 hello"))
4429 );
4430 }
4431
4432 #[test]
4433 fn test_format_n_noop() {
4434 let f = FormatFunc;
4435 let result = f
4437 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
4438 .unwrap();
4439 assert_eq!(
4440 result,
4441 SqliteValue::Text(SmallText::from_string("beforeafter"))
4442 );
4443 }
4444
4445 #[test]
4448 fn test_sqlite_version_format() {
4449 let result = SqliteVersionFunc.invoke(&[]).unwrap();
4450 match result {
4451 SqliteValue::Text(v) => {
4452 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
4453 }
4454 other => unreachable!("expected text, got {other:?}"),
4455 }
4456 }
4457
4458 #[test]
4459 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
4460 let func = SqliteCompileoptionUsedFunc;
4461 assert_eq!(
4462 invoke1(
4463 &func,
4464 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
4465 )
4466 .unwrap(),
4467 SqliteValue::Integer(1)
4468 );
4469 assert_eq!(
4470 invoke1(
4471 &func,
4472 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
4473 )
4474 .unwrap(),
4475 SqliteValue::Integer(1)
4476 );
4477 assert_eq!(
4478 invoke1(
4479 &func,
4480 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
4481 )
4482 .unwrap(),
4483 SqliteValue::Integer(1)
4484 );
4485 assert_eq!(
4486 invoke1(
4487 &func,
4488 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
4489 )
4490 .unwrap(),
4491 SqliteValue::Integer(1)
4492 );
4493 assert_eq!(
4494 invoke1(
4495 &func,
4496 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
4497 )
4498 .unwrap(),
4499 SqliteValue::Integer(0)
4500 );
4501 assert_eq!(
4502 invoke1(&func, SqliteValue::Null).unwrap(),
4503 SqliteValue::Null
4504 );
4505 }
4506
4507 #[test]
4508 #[ignore = "perf-only benchmark"]
4509 fn perf_compileoption_used_text_args() {
4510 use std::hint::black_box;
4511 use std::time::Instant;
4512
4513 const INVOCATIONS: usize = 1_000_000;
4514 const REPEATS: usize = 7;
4515
4516 let f = SqliteCompileoptionUsedFunc;
4517 let present_args = [SqliteValue::Text(SmallText::from_string(
4518 "SQLITE_ENABLE_ICU",
4519 ))];
4520 let absent_args = [SqliteValue::Text(SmallText::from_string(
4521 "ENABLE_NOT_PRESENT",
4522 ))];
4523
4524 let mut present_best_ns = u128::MAX;
4525 let mut absent_best_ns = u128::MAX;
4526 let mut checksum = 0i64;
4527 for _ in 0..REPEATS {
4528 let started = Instant::now();
4529 for _ in 0..INVOCATIONS {
4530 let result = black_box(
4531 f.invoke(black_box(present_args.as_slice()))
4532 .expect("compileoption present benchmark invocation must succeed"),
4533 );
4534 if let SqliteValue::Integer(value) = result {
4535 checksum = checksum.wrapping_add(value);
4536 }
4537 }
4538 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
4539
4540 let started = Instant::now();
4541 for _ in 0..INVOCATIONS {
4542 let result = black_box(
4543 f.invoke(black_box(absent_args.as_slice()))
4544 .expect("compileoption absent benchmark invocation must succeed"),
4545 );
4546 if let SqliteValue::Integer(value) = result {
4547 checksum = checksum.wrapping_add(value);
4548 }
4549 }
4550 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
4551 }
4552
4553 println!(
4554 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
4555 );
4556 }
4557
4558 #[test]
4559 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
4560 let func = SqliteCompileoptionGetFunc;
4561 for (index, option) in sqlite_compile_options().iter().enumerate() {
4562 assert_eq!(
4563 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
4564 SqliteValue::Text(SmallText::new(option))
4565 );
4566 }
4567 assert_eq!(
4568 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
4569 SqliteValue::Null
4570 );
4571 assert_eq!(
4572 invoke1(
4573 &func,
4574 SqliteValue::Integer(sqlite_compile_options().len() as i64)
4575 )
4576 .unwrap(),
4577 SqliteValue::Null
4578 );
4579 }
4580
4581 #[test]
4584 fn test_register_builtins_all_present() {
4585 let mut registry = FunctionRegistry::new();
4586 register_builtins(&mut registry);
4587
4588 assert!(registry.find_scalar("abs", 1).is_some());
4590 assert!(registry.find_scalar("typeof", 1).is_some());
4591 assert!(registry.find_scalar("length", 1).is_some());
4592 assert!(registry.find_scalar("lower", 1).is_some());
4593 assert!(registry.find_scalar("upper", 1).is_some());
4594 assert!(registry.find_scalar("hex", 1).is_some());
4595 assert!(registry.find_scalar("coalesce", 3).is_some());
4596 assert!(registry.find_scalar("concat", 2).is_some());
4597 assert!(registry.find_scalar("like", 2).is_some());
4598 assert!(registry.find_scalar("glob", 2).is_some());
4599 assert!(registry.find_scalar("round", 1).is_some());
4600 assert!(registry.find_scalar("substr", 2).is_some());
4601 assert!(registry.find_scalar("substring", 3).is_some());
4602 assert!(registry.find_scalar("sqlite_version", 0).is_some());
4603 assert!(registry.find_scalar("iif", 3).is_some());
4604 assert!(registry.find_scalar("if", 3).is_some());
4605 assert!(registry.find_scalar("format", 1).is_some());
4606 assert!(registry.find_scalar("printf", 1).is_some());
4607 assert!(registry.find_scalar("max", 2).is_some());
4608 assert!(registry.find_scalar("min", 2).is_some());
4609 assert!(registry.find_scalar("sign", 1).is_some());
4610 assert!(registry.find_scalar("random", 0).is_some());
4611
4612 assert!(registry.find_scalar("concat_ws", 3).is_some());
4614 assert!(registry.find_scalar("octet_length", 1).is_some());
4615 assert!(registry.find_scalar("unhex", 1).is_some());
4616 assert!(registry.find_scalar("timediff", 2).is_some());
4617 assert!(registry.find_scalar("unistr", 1).is_some());
4618 assert!(registry.find_scalar("unistr_quote", 1).is_some());
4619
4620 assert!(registry.find_aggregate("median", 1).is_some());
4622 assert!(registry.find_aggregate("percentile", 2).is_some());
4623 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
4624 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
4625
4626 assert!(registry.find_scalar("load_extension", 1).is_none());
4628 assert!(registry.find_scalar("load_extension", 2).is_none());
4629 }
4630
4631 #[test]
4632 fn test_register_builtins_rejects_invalid_variadic_arities() {
4633 let mut registry = FunctionRegistry::new();
4634 register_builtins(&mut registry);
4635
4636 for (name, too_few, valid, too_many) in [
4637 ("coalesce", 1, 2, None),
4638 ("concat", 0, 1, None),
4639 ("concat_ws", 1, 2, None),
4640 ("trim", 0, 1, Some(3)),
4641 ("ltrim", 0, 1, Some(3)),
4642 ("rtrim", 0, 1, Some(3)),
4643 ("round", 0, 1, Some(3)),
4644 ("unhex", 0, 1, Some(3)),
4645 ("substr", 1, 2, Some(4)),
4646 ("substring", 1, 2, Some(4)),
4647 ("max", 0, 1, None),
4648 ("min", 0, 1, None),
4649 ] {
4650 assert_wrong_arg_count(®istry, name, too_few);
4651 assert!(
4652 registry.find_scalar(name, valid).is_some(),
4653 "{name}/{valid} should resolve"
4654 );
4655 if let Some(arity) = too_many {
4656 assert_wrong_arg_count(®istry, name, arity);
4657 }
4658 }
4659
4660 assert!(registry.find_scalar("char", 0).is_some());
4661 assert!(registry.find_scalar("format", 0).is_some());
4662 assert!(registry.find_scalar("printf", 0).is_some());
4663 }
4664
4665 #[test]
4666 fn test_e2e_registry_invoke_through_lookup() {
4667 let mut registry = FunctionRegistry::new();
4668 register_builtins(&mut registry);
4669
4670 let abs = registry.find_scalar("ABS", 1).unwrap();
4672 assert_eq!(
4673 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
4674 SqliteValue::Integer(42)
4675 );
4676
4677 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
4679 assert_eq!(
4680 typeof_fn
4681 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
4682 .unwrap(),
4683 SqliteValue::Text(SmallText::from_string("text"))
4684 );
4685
4686 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
4688 assert_eq!(
4689 coalesce
4690 .invoke(&[
4691 SqliteValue::Null,
4692 SqliteValue::Null,
4693 SqliteValue::Integer(42),
4694 SqliteValue::Integer(99),
4695 ])
4696 .unwrap(),
4697 SqliteValue::Integer(42)
4698 );
4699 }
4700
4701 #[test]
4704 fn test_nondeterministic_functions_flagged() {
4705 assert!(!RandomFunc.is_deterministic());
4708 assert!(!RandomblobFunc.is_deterministic());
4709 assert!(!ChangesFunc.is_deterministic());
4710 assert!(!TotalChangesFunc.is_deterministic());
4711 assert!(!LastInsertRowidFunc.is_deterministic());
4712 }
4713
4714 #[test]
4715 fn test_deterministic_functions_flagged() {
4716 assert!(AbsFunc.is_deterministic());
4718 assert!(LengthFunc.is_deterministic());
4719 assert!(TypeofFunc.is_deterministic());
4720 assert!(UpperFunc.is_deterministic());
4721 assert!(LowerFunc.is_deterministic());
4722 assert!(HexFunc.is_deterministic());
4723 assert!(CoalesceFunc.is_deterministic());
4724 assert!(IifFunc.is_deterministic());
4725 }
4726
4727 #[test]
4728 fn test_random_produces_different_values() {
4729 let a = RandomFunc.invoke(&[]).unwrap();
4732 let b = RandomFunc.invoke(&[]).unwrap();
4733 assert_ne!(a.as_integer(), b.as_integer());
4736 }
4737
4738 #[test]
4739 fn test_registry_nondeterministic_lookup() {
4740 let mut registry = FunctionRegistry::default();
4741 register_builtins(&mut registry);
4742
4743 let random = registry.find_scalar("random", 0).unwrap();
4745 assert!(!random.is_deterministic());
4746
4747 let changes = registry.find_scalar("changes", 0).unwrap();
4748 assert!(!changes.is_deterministic());
4749
4750 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
4751 assert!(!lir.is_deterministic());
4752
4753 let abs = registry.find_scalar("abs", 1).unwrap();
4755 assert!(abs.is_deterministic());
4756 }
4757}