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