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