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