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())
2360 .unwrap_or(0)
2361 .min(100_000_000);
2362 } else {
2363 width = usize::try_from(w).unwrap_or(0).min(100_000_000);
2364 }
2365 } else {
2366 while i < chars.len() && chars[i].is_ascii_digit() {
2367 width = width
2368 .saturating_mul(10)
2369 .saturating_add(chars[i] as usize - '0' as usize)
2370 .min(100_000_000); i += 1;
2372 }
2373 }
2374
2375 let mut precision = None;
2379 if i < chars.len() && chars[i] == '.' {
2380 i += 1;
2381 if i < chars.len() && chars[i] == '*' {
2382 i += 1;
2383 let p = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2384 param_idx += 1;
2385 if p >= 0 {
2386 precision = Some(usize::try_from(p).unwrap_or(0).min(100_000_000));
2387 }
2388 } else {
2389 let mut prec = 0usize;
2390 while i < chars.len() && chars[i].is_ascii_digit() {
2391 prec = prec
2392 .saturating_mul(10)
2393 .saturating_add(chars[i] as usize - '0' as usize)
2394 .min(100_000_000); i += 1;
2396 }
2397 precision = Some(prec);
2398 }
2399 }
2400
2401 if i >= chars.len() {
2402 break;
2403 }
2404
2405 let spec = chars[i];
2406 i += 1;
2407
2408 match spec {
2409 '%' => result.push('%'),
2410 'n' => {} 'd' | 'i' => {
2412 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2413 param_idx += 1;
2414 let formatted =
2415 format_integer(val, width, left_align, show_sign, space_sign, zero_pad);
2416 result.push_str(&formatted);
2417 }
2418 'u' => {
2419 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2422 param_idx += 1;
2423 #[allow(clippy::cast_sign_loss)]
2424 let digits = (val as u64).to_string();
2425 let padded = if zero_pad && width > digits.len() {
2426 format!("{}{}", "0".repeat(width - digits.len()), digits)
2427 } else {
2428 pad_string(&digits, width, left_align)
2429 };
2430 result.push_str(&padded);
2431 }
2432 'f' => {
2433 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2434 param_idx += 1;
2435 let prec = precision.unwrap_or(6);
2436 let formatted = format_float_f(
2437 val, prec, width, left_align, show_sign, space_sign, zero_pad,
2438 );
2439 result.push_str(&formatted);
2440 }
2441 'e' | 'E' => {
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 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2446 {
2447 result.push_str(&s);
2448 } else {
2449 let raw = if spec == 'e' {
2450 format!("{val:.prec$e}")
2451 } else {
2452 format!("{val:.prec$E}")
2453 };
2454 let formatted = normalize_exponent(&raw);
2456 result.push_str(&finish_float_padding(
2457 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2458 ));
2459 }
2460 }
2461 'g' | 'G' => {
2462 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2463 param_idx += 1;
2464 let prec = precision.unwrap_or(6);
2465 let sig = prec.max(1);
2466 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2467 {
2468 result.push_str(&s);
2469 } else {
2470 let formatted = format_float_g(val, sig, spec == 'G');
2471 result.push_str(&finish_float_padding(
2472 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2473 ));
2474 }
2475 }
2476 's' | 'z' => {
2477 let param = params.get(param_idx);
2478 param_idx += 1;
2479 let val = match param {
2480 Some(SqliteValue::Null) | None => String::new(),
2482 Some(v) => v.to_text(),
2483 };
2484 let truncated = if let Some(prec) = precision {
2489 if val.len() > prec {
2490 let mut end = prec;
2491 while end > 0 && !val.is_char_boundary(end) {
2492 end -= 1;
2493 }
2494 val[..end].to_owned()
2495 } else {
2496 val
2497 }
2498 } else {
2499 val
2500 };
2501 result.push_str(&pad_string(&truncated, width, left_align));
2502 }
2503 'q' => {
2504 let param = params.get(param_idx);
2506 param_idx += 1;
2507 match param {
2508 Some(SqliteValue::Null) | None => {
2510 result.push_str("(NULL)");
2511 }
2512 Some(v) => {
2513 let val = v.to_text();
2514 let escaped = val.replace('\'', "''");
2515 result.push_str(&escaped);
2516 }
2517 }
2518 }
2519 'Q' => {
2520 let param = params.get(param_idx);
2522 param_idx += 1;
2523 match param {
2524 Some(SqliteValue::Null) | None => result.push_str("NULL"),
2525 Some(v) => {
2526 let val = v.to_text();
2527 let escaped = val.replace('\'', "''");
2528 result.push('\'');
2529 result.push_str(&escaped);
2530 result.push('\'');
2531 }
2532 }
2533 }
2534 'w' => {
2535 let param = params.get(param_idx);
2539 param_idx += 1;
2540 if matches!(param, Some(SqliteValue::Null) | None) {
2541 } else {
2543 let val = param.map(SqliteValue::to_text).unwrap_or_default();
2544 let escaped = val.replace('"', "\"\"");
2545 result.push_str(&escaped);
2546 }
2547 }
2548 'x' | 'X' => {
2549 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2550 param_idx += 1;
2551 #[allow(clippy::cast_sign_loss)]
2552 let digits = if spec == 'x' {
2553 format!("{:x}", val as u64)
2554 } else {
2555 format!("{:X}", val as u64)
2556 };
2557 let prefix = if alt_form && val != 0 {
2559 if spec == 'x' { "0x" } else { "0X" }
2560 } else {
2561 ""
2562 };
2563 let padded = if zero_pad && width > digits.len() {
2568 let pad = "0".repeat(width - digits.len());
2569 format!("{prefix}{pad}{digits}")
2570 } else {
2571 pad_string(&format!("{prefix}{digits}"), width, left_align)
2572 };
2573 result.push_str(&padded);
2574 }
2575 'o' => {
2576 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2577 param_idx += 1;
2578 #[allow(clippy::cast_sign_loss)]
2579 let digits = format!("{:o}", val as u64);
2580 let prefix = if alt_form && val != 0 { "0" } else { "" };
2582 let padded = if zero_pad && width > digits.len() {
2585 let pad = "0".repeat(width - digits.len());
2586 format!("{prefix}{pad}{digits}")
2587 } else {
2588 pad_string(&format!("{prefix}{digits}"), width, left_align)
2589 };
2590 result.push_str(&padded);
2591 }
2592 'c' => {
2593 let param = params.get(param_idx);
2594 param_idx += 1;
2595 let text = match param {
2600 Some(SqliteValue::Null) | None => String::new(),
2601 Some(v) => v.to_text(),
2602 };
2603 if let Some(c) = text.chars().next() {
2604 result.push(c);
2605 }
2606 }
2607 _ => {
2608 result.push('%');
2610 result.push(spec);
2611 }
2612 }
2613 let _ = (left_align, show_sign, space_sign, zero_pad);
2615 }
2616 Ok(result)
2617}
2618
2619fn format_integer(
2620 val: i64,
2621 width: usize,
2622 left_align: bool,
2623 show_sign: bool,
2624 space_sign: bool,
2625 zero_pad: bool,
2626) -> String {
2627 let sign = if val < 0 {
2628 "-".to_owned()
2629 } else if show_sign {
2630 "+".to_owned()
2631 } else if space_sign {
2632 " ".to_owned()
2633 } else {
2634 String::new()
2635 };
2636 let digits = format!("{}", val.unsigned_abs());
2637 let body = format!("{sign}{digits}");
2638 if body.len() >= width {
2639 return body;
2640 }
2641 let pad = width - body.len();
2642 if left_align {
2643 format!("{body}{}", " ".repeat(pad))
2644 } else if zero_pad {
2645 format!("{sign}{}{digits}", "0".repeat(pad))
2646 } else {
2647 format!("{}{body}", " ".repeat(pad))
2648 }
2649}
2650
2651fn nonfinite_float_str(
2655 val: f64,
2656 width: usize,
2657 left_align: bool,
2658 show_sign: bool,
2659 space_sign: bool,
2660) -> Option<String> {
2661 let body = if val.is_nan() {
2662 "NaN".to_owned()
2663 } else if val.is_infinite() {
2664 let sign = if val < 0.0 {
2665 "-"
2666 } else if show_sign {
2667 "+"
2668 } else if space_sign {
2669 " "
2670 } else {
2671 ""
2672 };
2673 format!("{sign}Inf")
2674 } else {
2675 return None;
2676 };
2677 Some(pad_string(&body, width, left_align))
2678}
2679
2680fn finish_float_padding(
2684 body: &str,
2685 width: usize,
2686 left_align: bool,
2687 show_sign: bool,
2688 space_sign: bool,
2689 zero_pad: bool,
2690) -> String {
2691 let (sign, digits) = if let Some(rest) = body.strip_prefix('-') {
2692 ("-", rest)
2693 } else if show_sign {
2694 ("+", body)
2695 } else if space_sign {
2696 (" ", body)
2697 } else {
2698 ("", body)
2699 };
2700 let full_len = sign.len() + digits.len();
2701 if full_len >= width {
2702 return format!("{sign}{digits}");
2703 }
2704 let pad = width - full_len;
2705 if left_align {
2706 format!("{sign}{digits}{}", " ".repeat(pad))
2707 } else if zero_pad {
2708 format!("{sign}{}{digits}", "0".repeat(pad))
2709 } else {
2710 format!("{}{sign}{digits}", " ".repeat(pad))
2711 }
2712}
2713
2714fn format_float_f(
2715 val: f64,
2716 prec: usize,
2717 width: usize,
2718 left_align: bool,
2719 show_sign: bool,
2720 space_sign: bool,
2721 zero_pad: bool,
2722) -> String {
2723 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign) {
2724 return s;
2725 }
2726 let sign = if val.is_sign_negative() {
2728 "-".to_owned()
2729 } else if show_sign {
2730 "+".to_owned()
2731 } else if space_sign {
2732 " ".to_owned()
2733 } else {
2734 String::new()
2735 };
2736 let digits = format!("{:.prec$}", val.abs());
2737 let body = format!("{sign}{digits}");
2738 if body.len() >= width {
2739 return body;
2740 }
2741 let pad = width - body.len();
2742 if left_align {
2743 format!("{body}{}", " ".repeat(pad))
2744 } else if zero_pad {
2745 format!("{sign}{}{digits}", "0".repeat(pad))
2746 } else {
2747 format!("{}{body}", " ".repeat(pad))
2748 }
2749}
2750
2751fn pad_string(s: &str, width: usize, left_align: bool) -> String {
2752 if s.len() >= width {
2753 return s.to_owned();
2754 }
2755 let pad = width - s.len();
2756 if left_align {
2757 format!("{s}{}", " ".repeat(pad))
2758 } else {
2759 format!("{}{s}", " ".repeat(pad))
2760 }
2761}
2762
2763fn normalize_exponent(s: &str) -> String {
2766 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
2767 (&s[..pos], 'e', &s[pos + 1..])
2768 } else if let Some(pos) = s.find('E') {
2769 (&s[..pos], 'E', &s[pos + 1..])
2770 } else {
2771 return s.to_owned();
2772 };
2773 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
2774 ("-", rest)
2775 } else if let Some(rest) = exp_part.strip_prefix('+') {
2776 ("+", rest)
2777 } else {
2778 ("+", exp_part)
2779 };
2780 let padded = if digits.len() < 2 {
2781 format!("0{digits}")
2782 } else {
2783 digits.to_owned()
2784 };
2785 format!("{prefix}{e_char}{sign}{padded}")
2786}
2787
2788fn format_float_g(val: f64, sig: usize, upper: bool) -> String {
2790 if !val.is_finite() {
2791 return format!("{val}");
2792 }
2793 let e_str = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2794 let exp: i32 = e_str
2795 .rsplit_once('e')
2796 .and_then(|(_, e)| e.parse().ok())
2797 .unwrap_or(0);
2798 #[allow(clippy::cast_possible_wrap)]
2799 let formatted = if exp < -4 || exp >= sig as i32 {
2800 let s = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2801 let s = if upper { s.replace('e', "E") } else { s };
2802 let trimmed = if s.contains('.') {
2804 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
2805 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
2806 format!("{mantissa}{}", &s[e_pos..])
2807 } else {
2808 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2809 }
2810 } else {
2811 s
2812 };
2813 normalize_exponent(&trimmed)
2814 } else {
2815 let decimal_places = if exp >= 0 {
2816 sig.saturating_sub((exp + 1) as usize)
2817 } else {
2818 sig + exp.unsigned_abs() as usize - 1
2819 };
2820 let s = format!("{val:.decimal_places$}");
2821 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2822 };
2823 formatted
2824}
2825
2826#[cfg(test)]
2827#[allow(clippy::too_many_lines)]
2828mod tests {
2829 use super::*;
2830
2831 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
2832 f.invoke(&[v])
2833 }
2834
2835 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
2836 f.invoke(&[a, b])
2837 }
2838
2839 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
2840 let function = registry
2841 .find_scalar(name, arity)
2842 .expect("known scalar name with bad arity returns erroring scalar");
2843 let args = vec![SqliteValue::Null; arity.max(0) as usize];
2844 let err = function
2845 .invoke(&args)
2846 .expect_err("wrong arity should return function error");
2847 let expected = format!("wrong number of arguments to function {name}()");
2848 assert!(
2849 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2850 "expected {expected:?}, got {err:?}"
2851 );
2852 }
2853
2854 #[test]
2855 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
2856 let original = get_change_tracking_state();
2857 let expected = ChangeTrackingState {
2858 last_insert_rowid: 17,
2859 last_changes: 23,
2860 total_changes: 42,
2861 };
2862
2863 set_change_tracking_state(expected);
2864 assert_eq!(get_change_tracking_state(), expected);
2865
2866 set_change_tracking_state(original);
2867 }
2868
2869 #[test]
2872 fn test_abs_positive() {
2873 assert_eq!(
2874 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
2875 SqliteValue::Integer(42)
2876 );
2877 }
2878
2879 #[test]
2880 fn test_abs_negative() {
2881 assert_eq!(
2882 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
2883 SqliteValue::Integer(42)
2884 );
2885 }
2886
2887 #[test]
2888 fn test_abs_null() {
2889 assert_eq!(
2890 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
2891 SqliteValue::Null
2892 );
2893 }
2894
2895 #[test]
2896 fn test_abs_min_i64_overflow() {
2897 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
2898 assert!(matches!(err, FrankenError::IntegerOverflow));
2899 }
2900
2901 #[test]
2902 fn test_abs_string_coercion() {
2903 assert_eq!(
2904 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
2905 SqliteValue::Float(7.5)
2906 );
2907 }
2908
2909 #[test]
2910 fn test_abs_whitespace_padded_text() {
2911 assert_eq!(
2913 invoke1(
2914 &AbsFunc,
2915 SqliteValue::Text(SmallText::from_string(" 42 "))
2916 )
2917 .unwrap(),
2918 SqliteValue::Float(42.0)
2919 );
2920 assert_eq!(
2921 invoke1(
2922 &AbsFunc,
2923 SqliteValue::Text(SmallText::from_string(" -7.5 "))
2924 )
2925 .unwrap(),
2926 SqliteValue::Float(7.5)
2927 );
2928 assert_eq!(
2929 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
2930 SqliteValue::Float(0.0)
2931 );
2932 }
2933
2934 #[test]
2935 #[allow(clippy::approx_constant)]
2936 fn test_abs_float() {
2937 assert_eq!(
2938 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
2939 SqliteValue::Float(3.14)
2940 );
2941 }
2942
2943 #[test]
2946 fn test_char_basic() {
2947 let f = CharFunc;
2948 let result = f
2949 .invoke(&[
2950 SqliteValue::Integer(72),
2951 SqliteValue::Integer(101),
2952 SqliteValue::Integer(108),
2953 SqliteValue::Integer(108),
2954 SqliteValue::Integer(111),
2955 ])
2956 .unwrap();
2957 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
2958 }
2959
2960 #[test]
2961 fn test_char_null_skipped() {
2962 let f = CharFunc;
2963 let result = f
2965 .invoke(&[
2966 SqliteValue::Integer(65),
2967 SqliteValue::Null,
2968 SqliteValue::Integer(66),
2969 ])
2970 .unwrap();
2971 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
2972 }
2973
2974 #[test]
2975 fn test_char_invalid_scalar_values_use_replacement_character() {
2976 let f = CharFunc;
2977 let result = f
2978 .invoke(&[
2979 SqliteValue::Integer(-1),
2980 SqliteValue::Integer(65),
2981 SqliteValue::Integer(1_114_112),
2982 ])
2983 .unwrap();
2984 assert_eq!(
2985 result,
2986 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
2987 );
2988 }
2989
2990 #[test]
2993 fn test_coalesce_first_non_null() {
2994 let f = CoalesceFunc;
2995 let result = f
2996 .invoke(&[
2997 SqliteValue::Null,
2998 SqliteValue::Null,
2999 SqliteValue::Integer(3),
3000 SqliteValue::Integer(4),
3001 ])
3002 .unwrap();
3003 assert_eq!(result, SqliteValue::Integer(3));
3004 }
3005
3006 #[test]
3009 fn test_concat_null_as_empty() {
3010 let f = ConcatFunc;
3011 let result = f
3012 .invoke(&[
3013 SqliteValue::Null,
3014 SqliteValue::Text(SmallText::from_string("hello")),
3015 SqliteValue::Null,
3016 ])
3017 .unwrap();
3018 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
3019 }
3020
3021 #[test]
3022 #[ignore = "perf-only benchmark"]
3023 fn perf_concat_text_args() {
3024 use std::hint::black_box;
3025 use std::time::Instant;
3026
3027 const TEXT_ARGS: usize = 24;
3028 const INVOCATIONS: usize = 50_000;
3029 const REPEATS: usize = 5;
3030
3031 let f = ConcatFunc;
3032 let mut args = Vec::with_capacity(TEXT_ARGS);
3033 for _ in 0..TEXT_ARGS {
3034 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3035 }
3036
3037 let mut best_ns = u128::MAX;
3038 let mut result_len = 0usize;
3039 for _ in 0..REPEATS {
3040 let started = Instant::now();
3041 for _ in 0..INVOCATIONS {
3042 let result = black_box(
3043 f.invoke(black_box(args.as_slice()))
3044 .expect("concat benchmark invocation must succeed"),
3045 );
3046 result_len = match result {
3047 SqliteValue::Text(text) => text.len(),
3048 SqliteValue::Null
3049 | SqliteValue::Integer(_)
3050 | SqliteValue::Float(_)
3051 | SqliteValue::Blob(_) => 0,
3052 };
3053 }
3054 best_ns = best_ns.min(started.elapsed().as_nanos());
3055 }
3056
3057 println!(
3058 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3059 );
3060 }
3061
3062 #[test]
3065 fn test_concat_ws_null_skipped() {
3066 let f = ConcatWsFunc;
3067 let result = f
3068 .invoke(&[
3069 SqliteValue::Text(SmallText::from_string(",")),
3070 SqliteValue::Text(SmallText::from_string("a")),
3071 SqliteValue::Null,
3072 SqliteValue::Text(SmallText::from_string("b")),
3073 ])
3074 .unwrap();
3075 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
3076 }
3077
3078 #[test]
3079 fn test_concat_ws_empty_string_is_not_skipped() {
3080 let f = ConcatWsFunc;
3081 let result = f
3082 .invoke(&[
3083 SqliteValue::Text(SmallText::from_string("|")),
3084 SqliteValue::Text(SmallText::new("")),
3085 SqliteValue::Text(SmallText::from_string("x")),
3086 ])
3087 .unwrap();
3088 assert_eq!(result, SqliteValue::Text(SmallText::from_string("|x")));
3089 }
3090
3091 #[test]
3092 #[ignore = "perf-only benchmark"]
3093 fn perf_concat_ws_text_args() {
3094 use std::hint::black_box;
3095 use std::time::Instant;
3096
3097 const TEXT_ARGS: usize = 24;
3098 const INVOCATIONS: usize = 50_000;
3099 const REPEATS: usize = 5;
3100
3101 let f = ConcatWsFunc;
3102 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
3103 args.push(SqliteValue::Text(SmallText::from_string(",")));
3104 for _ in 0..TEXT_ARGS {
3105 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3106 }
3107
3108 let mut best_ns = u128::MAX;
3109 let mut result_len = 0usize;
3110 for _ in 0..REPEATS {
3111 let started = Instant::now();
3112 for _ in 0..INVOCATIONS {
3113 let result = black_box(
3114 f.invoke(black_box(args.as_slice()))
3115 .expect("concat_ws benchmark invocation must succeed"),
3116 );
3117 result_len = match result {
3118 SqliteValue::Text(text) => text.len(),
3119 SqliteValue::Null
3120 | SqliteValue::Integer(_)
3121 | SqliteValue::Float(_)
3122 | SqliteValue::Blob(_) => 0,
3123 };
3124 }
3125 best_ns = best_ns.min(started.elapsed().as_nanos());
3126 }
3127
3128 println!(
3129 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3130 );
3131 }
3132
3133 #[test]
3136 fn test_hex_blob() {
3137 let result = invoke1(
3138 &HexFunc,
3139 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
3140 )
3141 .unwrap();
3142 assert_eq!(
3143 result,
3144 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
3145 );
3146 }
3147
3148 #[test]
3149 fn test_hex_number_via_text() {
3150 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
3152 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
3153 }
3154
3155 #[test]
3156 #[ignore = "perf-only benchmark"]
3157 fn perf_hex_text_blob_args() {
3158 use std::hint::black_box;
3159 use std::time::Instant;
3160
3161 const BYTES: usize = 24;
3162 const INVOCATIONS: usize = 100_000;
3163 const REPEATS: usize = 5;
3164
3165 let f = HexFunc;
3166 let text_args = [SqliteValue::Text(SmallText::from_string(
3167 "payload payload sentinel",
3168 ))];
3169 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
3170
3171 let mut text_best_ns = u128::MAX;
3172 let mut blob_best_ns = u128::MAX;
3173 let mut text_result_len = 0usize;
3174 let mut blob_result_len = 0usize;
3175 for _ in 0..REPEATS {
3176 let started = Instant::now();
3177 for _ in 0..INVOCATIONS {
3178 let result = black_box(
3179 f.invoke(black_box(text_args.as_slice()))
3180 .expect("hex text benchmark invocation must succeed"),
3181 );
3182 text_result_len = match result {
3183 SqliteValue::Text(text) => text.len(),
3184 SqliteValue::Null
3185 | SqliteValue::Integer(_)
3186 | SqliteValue::Float(_)
3187 | SqliteValue::Blob(_) => 0,
3188 };
3189 }
3190 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
3191
3192 let started = Instant::now();
3193 for _ in 0..INVOCATIONS {
3194 let result = black_box(
3195 f.invoke(black_box(blob_args.as_slice()))
3196 .expect("hex blob benchmark invocation must succeed"),
3197 );
3198 blob_result_len = match result {
3199 SqliteValue::Text(text) => text.len(),
3200 SqliteValue::Null
3201 | SqliteValue::Integer(_)
3202 | SqliteValue::Float(_)
3203 | SqliteValue::Blob(_) => 0,
3204 };
3205 }
3206 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
3207 }
3208
3209 println!(
3210 "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}"
3211 );
3212 }
3213
3214 #[test]
3217 fn test_iif_true() {
3218 let f = IifFunc;
3219 let result = f
3220 .invoke(&[
3221 SqliteValue::Integer(1),
3222 SqliteValue::Text(SmallText::from_string("yes")),
3223 SqliteValue::Text(SmallText::from_string("no")),
3224 ])
3225 .unwrap();
3226 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3227 }
3228
3229 #[test]
3230 fn test_iif_false() {
3231 let f = IifFunc;
3232 let result = f
3233 .invoke(&[
3234 SqliteValue::Integer(0),
3235 SqliteValue::Text(SmallText::from_string("yes")),
3236 SqliteValue::Text(SmallText::from_string("no")),
3237 ])
3238 .unwrap();
3239 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
3240 }
3241
3242 #[test]
3243 fn test_iif_whitespace_padded_text_truthy() {
3244 let f = IifFunc;
3247 let result = f
3248 .invoke(&[
3249 SqliteValue::Text(SmallText::from_string(" 5 ")),
3250 SqliteValue::Text(SmallText::from_string("yes")),
3251 SqliteValue::Text(SmallText::from_string("no")),
3252 ])
3253 .unwrap();
3254 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3255 }
3256
3257 #[test]
3260 fn test_ifnull_non_null() {
3261 assert_eq!(
3262 invoke2(
3263 &IfnullFunc,
3264 SqliteValue::Integer(5),
3265 SqliteValue::Integer(10)
3266 )
3267 .unwrap(),
3268 SqliteValue::Integer(5)
3269 );
3270 }
3271
3272 #[test]
3273 fn test_ifnull_null() {
3274 assert_eq!(
3275 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
3276 SqliteValue::Integer(10)
3277 );
3278 }
3279
3280 #[test]
3283 fn test_instr_found() {
3284 assert_eq!(
3285 invoke2(
3286 &InstrFunc,
3287 SqliteValue::Text(SmallText::from_string("hello world")),
3288 SqliteValue::Text(SmallText::from_string("world"))
3289 )
3290 .unwrap(),
3291 SqliteValue::Integer(7)
3292 );
3293 }
3294
3295 #[test]
3296 fn test_instr_not_found() {
3297 assert_eq!(
3298 invoke2(
3299 &InstrFunc,
3300 SqliteValue::Text(SmallText::from_string("hello")),
3301 SqliteValue::Text(SmallText::from_string("xyz"))
3302 )
3303 .unwrap(),
3304 SqliteValue::Integer(0)
3305 );
3306 }
3307
3308 #[test]
3309 fn test_instr_empty_needle_returns_one() {
3310 assert_eq!(
3312 invoke2(
3313 &InstrFunc,
3314 SqliteValue::Text(SmallText::from_string("hello")),
3315 SqliteValue::Text(SmallText::new(""))
3316 )
3317 .unwrap(),
3318 SqliteValue::Integer(1)
3319 );
3320 }
3321
3322 #[test]
3323 fn test_instr_empty_haystack_returns_zero() {
3324 assert_eq!(
3325 invoke2(
3326 &InstrFunc,
3327 SqliteValue::Text(SmallText::new("")),
3328 SqliteValue::Text(SmallText::from_string("x"))
3329 )
3330 .unwrap(),
3331 SqliteValue::Integer(0)
3332 );
3333 }
3334
3335 #[test]
3336 fn test_instr_blob_empty_needle_returns_one() {
3337 assert_eq!(
3339 invoke2(
3340 &InstrFunc,
3341 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
3342 SqliteValue::Blob(Arc::from([].as_slice()))
3343 )
3344 .unwrap(),
3345 SqliteValue::Integer(1)
3346 );
3347 }
3348
3349 #[test]
3350 #[ignore = "perf-only benchmark"]
3351 fn perf_instr_text_args() {
3352 use std::hint::black_box;
3353 use std::time::Instant;
3354
3355 const INVOCATIONS: usize = 100_000;
3356 const REPEATS: usize = 5;
3357
3358 let f = InstrFunc;
3359 let args = [
3360 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
3361 SqliteValue::Text(SmallText::from_string("sentinel")),
3362 ];
3363
3364 let mut best_ns = u128::MAX;
3365 let mut result_value = 0i64;
3366 for _ in 0..REPEATS {
3367 let started = Instant::now();
3368 for _ in 0..INVOCATIONS {
3369 let result = black_box(
3370 f.invoke(black_box(args.as_slice()))
3371 .expect("instr benchmark invocation must succeed"),
3372 );
3373 result_value = match result {
3374 SqliteValue::Integer(value) => value,
3375 SqliteValue::Null
3376 | SqliteValue::Float(_)
3377 | SqliteValue::Text(_)
3378 | SqliteValue::Blob(_) => 0,
3379 };
3380 }
3381 best_ns = best_ns.min(started.elapsed().as_nanos());
3382 }
3383
3384 println!(
3385 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
3386 );
3387 }
3388
3389 #[test]
3392 fn test_length_text_chars() {
3393 assert_eq!(
3395 invoke1(
3396 &LengthFunc,
3397 SqliteValue::Text(SmallText::from_string("café"))
3398 )
3399 .unwrap(),
3400 SqliteValue::Integer(4)
3401 );
3402 }
3403
3404 #[test]
3405 fn test_length_text_stops_at_nul() {
3406 assert_eq!(
3407 invoke1(
3408 &LengthFunc,
3409 SqliteValue::Text(SmallText::from_string("A\0B"))
3410 )
3411 .unwrap(),
3412 SqliteValue::Integer(1)
3413 );
3414 assert_eq!(
3415 invoke1(
3416 &LengthFunc,
3417 SqliteValue::Text(SmallText::from_string("\0A"))
3418 )
3419 .unwrap(),
3420 SqliteValue::Integer(0)
3421 );
3422 }
3423
3424 #[test]
3425 fn test_length_blob_bytes() {
3426 assert_eq!(
3427 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
3428 SqliteValue::Integer(2)
3429 );
3430 }
3431
3432 #[test]
3435 fn test_octet_length_multibyte() {
3436 assert_eq!(
3438 invoke1(
3439 &OctetLengthFunc,
3440 SqliteValue::Text(SmallText::from_string("café"))
3441 )
3442 .unwrap(),
3443 SqliteValue::Integer(5)
3444 );
3445 }
3446
3447 #[test]
3450 fn test_lower_ascii() {
3451 assert_eq!(
3452 invoke1(
3453 &LowerFunc,
3454 SqliteValue::Text(SmallText::from_string("HELLO"))
3455 )
3456 .unwrap(),
3457 SqliteValue::Text(SmallText::from_string("hello"))
3458 );
3459 }
3460
3461 #[test]
3462 fn test_upper_ascii() {
3463 assert_eq!(
3464 invoke1(
3465 &UpperFunc,
3466 SqliteValue::Text(SmallText::from_string("hello"))
3467 )
3468 .unwrap(),
3469 SqliteValue::Text(SmallText::from_string("HELLO"))
3470 );
3471 }
3472
3473 #[test]
3476 fn test_trim_default() {
3477 let f = TrimFunc;
3478 assert_eq!(
3479 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
3480 .unwrap(),
3481 SqliteValue::Text(SmallText::from_string("hello"))
3482 );
3483 }
3484
3485 #[test]
3486 fn test_ltrim_default() {
3487 let f = LtrimFunc;
3488 assert_eq!(
3489 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
3490 .unwrap(),
3491 SqliteValue::Text(SmallText::from_string("hello"))
3492 );
3493 }
3494
3495 #[test]
3496 fn test_ltrim_custom() {
3497 let f = LtrimFunc;
3498 assert_eq!(
3499 f.invoke(&[
3500 SqliteValue::Text(SmallText::from_string("xxhello")),
3501 SqliteValue::Text(SmallText::from_string("x")),
3502 ])
3503 .unwrap(),
3504 SqliteValue::Text(SmallText::from_string("hello"))
3505 );
3506 }
3507
3508 #[test]
3509 #[ignore = "perf-only benchmark"]
3510 fn perf_trim_text_args() {
3511 use std::hint::black_box;
3512 use std::time::Instant;
3513
3514 const INVOCATIONS: usize = 100_000;
3515 const REPEATS: usize = 5;
3516
3517 let trim = TrimFunc;
3518 let ltrim = LtrimFunc;
3519 let rtrim = RtrimFunc;
3520 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
3521 let custom_args = [
3522 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
3523 SqliteValue::Text(SmallText::from_string("x")),
3524 ];
3525
3526 let mut trim_best_ns = u128::MAX;
3527 let mut ltrim_best_ns = u128::MAX;
3528 let mut rtrim_best_ns = u128::MAX;
3529 let mut custom_best_ns = u128::MAX;
3530 let mut result_len = 0usize;
3531
3532 for _ in 0..REPEATS {
3533 let started = Instant::now();
3534 for _ in 0..INVOCATIONS {
3535 let result = black_box(
3536 trim.invoke(black_box(default_args.as_slice()))
3537 .expect("trim benchmark invocation must succeed"),
3538 );
3539 result_len = match result {
3540 SqliteValue::Text(text) => text.len(),
3541 SqliteValue::Null
3542 | SqliteValue::Integer(_)
3543 | SqliteValue::Float(_)
3544 | SqliteValue::Blob(_) => 0,
3545 };
3546 }
3547 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
3548
3549 let started = Instant::now();
3550 for _ in 0..INVOCATIONS {
3551 let result = black_box(
3552 ltrim
3553 .invoke(black_box(default_args.as_slice()))
3554 .expect("ltrim benchmark invocation must succeed"),
3555 );
3556 result_len = match result {
3557 SqliteValue::Text(text) => text.len(),
3558 SqliteValue::Null
3559 | SqliteValue::Integer(_)
3560 | SqliteValue::Float(_)
3561 | SqliteValue::Blob(_) => 0,
3562 };
3563 }
3564 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
3565
3566 let started = Instant::now();
3567 for _ in 0..INVOCATIONS {
3568 let result = black_box(
3569 rtrim
3570 .invoke(black_box(default_args.as_slice()))
3571 .expect("rtrim benchmark invocation must succeed"),
3572 );
3573 result_len = match result {
3574 SqliteValue::Text(text) => text.len(),
3575 SqliteValue::Null
3576 | SqliteValue::Integer(_)
3577 | SqliteValue::Float(_)
3578 | SqliteValue::Blob(_) => 0,
3579 };
3580 }
3581 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
3582
3583 let started = Instant::now();
3584 for _ in 0..INVOCATIONS {
3585 let result = black_box(
3586 trim.invoke(black_box(custom_args.as_slice()))
3587 .expect("custom trim benchmark invocation must succeed"),
3588 );
3589 result_len = match result {
3590 SqliteValue::Text(text) => text.len(),
3591 SqliteValue::Null
3592 | SqliteValue::Integer(_)
3593 | SqliteValue::Float(_)
3594 | SqliteValue::Blob(_) => 0,
3595 };
3596 }
3597 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
3598 }
3599
3600 println!(
3601 "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}"
3602 );
3603 }
3604
3605 #[test]
3608 fn test_nullif_equal() {
3609 assert_eq!(
3610 invoke2(
3611 &NullifFunc,
3612 SqliteValue::Integer(5),
3613 SqliteValue::Integer(5)
3614 )
3615 .unwrap(),
3616 SqliteValue::Null
3617 );
3618 }
3619
3620 #[test]
3621 fn test_nullif_different() {
3622 assert_eq!(
3623 invoke2(
3624 &NullifFunc,
3625 SqliteValue::Integer(5),
3626 SqliteValue::Integer(3)
3627 )
3628 .unwrap(),
3629 SqliteValue::Integer(5)
3630 );
3631 }
3632
3633 #[test]
3636 fn test_typeof_each() {
3637 assert_eq!(
3638 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
3639 SqliteValue::Text(SmallText::from_string("null"))
3640 );
3641 assert_eq!(
3642 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
3643 SqliteValue::Text(SmallText::from_string("integer"))
3644 );
3645 assert_eq!(
3646 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
3647 SqliteValue::Text(SmallText::from_string("real"))
3648 );
3649 assert_eq!(
3650 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
3651 SqliteValue::Text(SmallText::from_string("text"))
3652 );
3653 assert_eq!(
3654 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
3655 SqliteValue::Text(SmallText::from_string("blob"))
3656 );
3657 }
3658
3659 #[test]
3662 fn test_subtype_null_returns_zero() {
3663 assert_eq!(
3664 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
3665 SqliteValue::Integer(0)
3666 );
3667 }
3668
3669 #[test]
3672 fn test_replace_basic() {
3673 let f = ReplaceFunc;
3674 assert_eq!(
3675 f.invoke(&[
3676 SqliteValue::Text(SmallText::from_string("hello world")),
3677 SqliteValue::Text(SmallText::from_string("world")),
3678 SqliteValue::Text(SmallText::from_string("earth")),
3679 ])
3680 .unwrap(),
3681 SqliteValue::Text(SmallText::from_string("hello earth"))
3682 );
3683 }
3684
3685 #[test]
3686 fn test_replace_empty_y() {
3687 let f = ReplaceFunc;
3688 assert_eq!(
3689 f.invoke(&[
3690 SqliteValue::Text(SmallText::from_string("hello")),
3691 SqliteValue::Text(SmallText::new("")),
3692 SqliteValue::Text(SmallText::from_string("x")),
3693 ])
3694 .unwrap(),
3695 SqliteValue::Text(SmallText::from_string("hello"))
3696 );
3697 }
3698
3699 #[test]
3700 #[ignore = "perf-only benchmark"]
3701 fn perf_replace_text_args() {
3702 use std::hint::black_box;
3703 use std::time::Instant;
3704
3705 const INVOCATIONS: usize = 100_000;
3706 const REPEATS: usize = 5;
3707
3708 let f = ReplaceFunc;
3709 let args = [
3710 SqliteValue::Text(SmallText::from_string("payload payload payload")),
3711 SqliteValue::Text(SmallText::from_string("zz")),
3712 SqliteValue::Text(SmallText::from_string("replacement")),
3713 ];
3714
3715 let mut best_ns = u128::MAX;
3716 let mut result_len = 0usize;
3717 for _ in 0..REPEATS {
3718 let started = Instant::now();
3719 for _ in 0..INVOCATIONS {
3720 let result = black_box(
3721 f.invoke(black_box(args.as_slice()))
3722 .expect("replace benchmark invocation must succeed"),
3723 );
3724 result_len = match result {
3725 SqliteValue::Text(text) => text.len(),
3726 SqliteValue::Null
3727 | SqliteValue::Integer(_)
3728 | SqliteValue::Float(_)
3729 | SqliteValue::Blob(_) => 0,
3730 };
3731 }
3732 best_ns = best_ns.min(started.elapsed().as_nanos());
3733 }
3734
3735 println!(
3736 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3737 );
3738 }
3739
3740 #[test]
3743 #[allow(clippy::float_cmp)]
3744 fn test_round_half_away() {
3745 assert_eq!(
3747 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
3748 SqliteValue::Float(3.0)
3749 );
3750 assert_eq!(
3751 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
3752 SqliteValue::Float(-3.0)
3753 );
3754 }
3755
3756 #[test]
3757 #[allow(clippy::float_cmp, clippy::approx_constant)]
3758 fn test_round_precision() {
3759 assert_eq!(
3760 RoundFunc
3761 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
3762 .unwrap(),
3763 SqliteValue::Float(3.14)
3764 );
3765 }
3766
3767 #[test]
3768 #[allow(clippy::float_cmp)]
3769 fn test_round_extreme_n_clamped() {
3770 assert_eq!(
3772 RoundFunc
3773 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
3774 .unwrap(),
3775 RoundFunc
3776 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
3777 .unwrap(),
3778 );
3779 assert_eq!(
3781 RoundFunc
3782 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
3783 .unwrap(),
3784 SqliteValue::Float(3.0)
3785 );
3786 let result = RoundFunc
3788 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
3789 .unwrap();
3790 if let SqliteValue::Float(v) = result {
3791 assert!(!v.is_nan(), "round must never return NaN");
3792 }
3793 }
3794
3795 #[test]
3796 #[allow(clippy::float_cmp)]
3797 fn test_round_large_value_no_fractional() {
3798 let big = 9_007_199_254_740_993.0_f64;
3800 assert_eq!(
3801 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
3802 SqliteValue::Float(big)
3803 );
3804 assert_eq!(
3805 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
3806 SqliteValue::Float(-big)
3807 );
3808 }
3809
3810 #[test]
3813 fn test_sign_positive() {
3814 assert_eq!(
3815 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
3816 SqliteValue::Integer(1)
3817 );
3818 }
3819
3820 #[test]
3821 fn test_sign_negative() {
3822 assert_eq!(
3823 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
3824 SqliteValue::Integer(-1)
3825 );
3826 }
3827
3828 #[test]
3829 fn test_sign_zero() {
3830 assert_eq!(
3831 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
3832 SqliteValue::Integer(0)
3833 );
3834 }
3835
3836 #[test]
3837 fn test_sign_null() {
3838 assert_eq!(
3839 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
3840 SqliteValue::Null
3841 );
3842 }
3843
3844 #[test]
3845 fn test_sign_non_numeric() {
3846 assert_eq!(
3848 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3849 SqliteValue::Null
3850 );
3851 }
3852
3853 #[test]
3854 fn test_sign_whitespace_padded_text() {
3855 assert_eq!(
3858 invoke1(
3859 &SignFunc,
3860 SqliteValue::Text(SmallText::from_string(" 5 "))
3861 )
3862 .unwrap(),
3863 SqliteValue::Integer(1)
3864 );
3865 assert_eq!(
3866 invoke1(
3867 &SignFunc,
3868 SqliteValue::Text(SmallText::from_string(" -3.14 "))
3869 )
3870 .unwrap(),
3871 SqliteValue::Integer(-1)
3872 );
3873 }
3874
3875 #[test]
3876 fn test_sign_unicode_space_and_blob_return_null() {
3877 assert_eq!(
3878 invoke1(
3879 &SignFunc,
3880 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
3881 )
3882 .unwrap(),
3883 SqliteValue::Null
3884 );
3885 assert_eq!(
3886 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
3887 SqliteValue::Null
3888 );
3889 }
3890
3891 #[test]
3892 fn test_sign_nan_inf_text_returns_null() {
3893 for s in &[
3896 "NaN",
3897 "nan",
3898 "inf",
3899 "-inf",
3900 "Infinity",
3901 "-Infinity",
3902 "INF",
3903 "+nan",
3904 "+inf",
3905 ] {
3906 assert_eq!(
3907 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
3908 SqliteValue::Null,
3909 "sign('{s}') should be NULL"
3910 );
3911 }
3912 }
3913
3914 #[test]
3915 fn test_sign_numeric_overflow_to_infinity() {
3916 assert_eq!(
3919 invoke1(
3920 &SignFunc,
3921 SqliteValue::Text(SmallText::from_string("1e999"))
3922 )
3923 .unwrap(),
3924 SqliteValue::Integer(1)
3925 );
3926 assert_eq!(
3927 invoke1(
3928 &SignFunc,
3929 SqliteValue::Text(SmallText::from_string("-1e999"))
3930 )
3931 .unwrap(),
3932 SqliteValue::Integer(-1)
3933 );
3934 assert_eq!(
3936 invoke1(
3937 &SignFunc,
3938 SqliteValue::Text(SmallText::from_string("1e-999"))
3939 )
3940 .unwrap(),
3941 SqliteValue::Integer(0)
3942 );
3943 }
3944
3945 #[test]
3946 fn test_sign_float_nan_returns_null() {
3947 assert_eq!(
3949 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
3950 SqliteValue::Null
3951 );
3952 }
3953
3954 #[test]
3957 fn test_scalar_max_null() {
3958 let f = ScalarMaxFunc;
3959 let result = f
3960 .invoke(&[
3961 SqliteValue::Integer(1),
3962 SqliteValue::Null,
3963 SqliteValue::Integer(3),
3964 ])
3965 .unwrap();
3966 assert_eq!(result, SqliteValue::Null);
3967 }
3968
3969 #[test]
3970 fn test_scalar_max_values() {
3971 let f = ScalarMaxFunc;
3972 let result = f
3973 .invoke(&[
3974 SqliteValue::Integer(3),
3975 SqliteValue::Integer(1),
3976 SqliteValue::Integer(2),
3977 ])
3978 .unwrap();
3979 assert_eq!(result, SqliteValue::Integer(3));
3980 }
3981
3982 #[test]
3983 fn test_scalar_min_null() {
3984 let f = ScalarMinFunc;
3985 let result = f
3986 .invoke(&[
3987 SqliteValue::Integer(1),
3988 SqliteValue::Null,
3989 SqliteValue::Integer(3),
3990 ])
3991 .unwrap();
3992 assert_eq!(result, SqliteValue::Null);
3993 }
3994
3995 #[test]
3998 fn test_quote_text() {
3999 assert_eq!(
4000 invoke1(
4001 &QuoteFunc,
4002 SqliteValue::Text(SmallText::from_string("it's"))
4003 )
4004 .unwrap(),
4005 SqliteValue::Text(SmallText::from_string("'it''s'"))
4006 );
4007 }
4008
4009 #[test]
4010 fn test_quote_null() {
4011 assert_eq!(
4012 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
4013 SqliteValue::Text(SmallText::from_string("NULL"))
4014 );
4015 }
4016
4017 #[test]
4018 fn test_quote_blob() {
4019 assert_eq!(
4020 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
4021 SqliteValue::Text(SmallText::from_string("X'AB'"))
4022 );
4023 }
4024
4025 #[test]
4026 fn test_quote_text_truncates_at_first_nul() {
4027 assert_eq!(
4028 invoke1(
4029 &QuoteFunc,
4030 SqliteValue::Text(SmallText::from_string("A\0B"))
4031 )
4032 .unwrap(),
4033 SqliteValue::Text(SmallText::from_string("'A'"))
4034 );
4035 }
4036
4037 #[test]
4038 fn test_unistr_quote_plain_text_matches_quote() {
4039 assert_eq!(
4040 invoke1(
4041 &UnistrQuoteFunc,
4042 SqliteValue::Text(SmallText::from_string("it's"))
4043 )
4044 .unwrap(),
4045 SqliteValue::Text(SmallText::from_string("'it''s'"))
4046 );
4047 }
4048
4049 #[test]
4050 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
4051 assert_eq!(
4052 invoke1(
4053 &UnistrQuoteFunc,
4054 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
4055 )
4056 .unwrap(),
4057 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
4058 );
4059 }
4060
4061 #[test]
4062 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
4063 assert_eq!(
4064 invoke1(
4065 &UnistrQuoteFunc,
4066 SqliteValue::Text(SmallText::from_string("A\0\nB"))
4067 )
4068 .unwrap(),
4069 SqliteValue::Text(SmallText::from_string("'A'"))
4070 );
4071 }
4072
4073 #[test]
4074 fn test_unistr_decodes_backslash_and_unicode_escapes() {
4075 assert_eq!(
4076 invoke1(
4077 &UnistrFunc,
4078 SqliteValue::Text(SmallText::from_string(
4079 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
4080 ))
4081 )
4082 .unwrap(),
4083 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
4084 );
4085 }
4086
4087 #[test]
4088 fn test_unistr_invalid_escape_returns_error() {
4089 for input in [
4090 "\\u12xz",
4091 "\\12xz",
4092 "\\+00xz",
4093 "\\",
4094 "\\x",
4095 "\\U00110000",
4096 "\\D800",
4097 ] {
4098 let err = invoke1(
4099 &UnistrFunc,
4100 SqliteValue::Text(SmallText::from_string(input)),
4101 )
4102 .unwrap_err();
4103 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
4104 }
4105 }
4106
4107 #[test]
4108 #[ignore = "perf-only benchmark"]
4109 fn perf_unistr_text_args() {
4110 use std::hint::black_box;
4111 use std::time::Instant;
4112
4113 const INVOCATIONS: usize = 500_000;
4114 const REPEATS: usize = 7;
4115
4116 let f = UnistrFunc;
4117 let plain_args = [SqliteValue::Text(SmallText::from_string(
4118 "plain unicode payload",
4119 ))];
4120 let escaped_args = [SqliteValue::Text(SmallText::from_string(
4121 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
4122 ))];
4123
4124 let mut plain_best_ns = u128::MAX;
4125 let mut escaped_best_ns = u128::MAX;
4126 let mut checksum = 0usize;
4127 for _ in 0..REPEATS {
4128 let started = Instant::now();
4129 for _ in 0..INVOCATIONS {
4130 let result = black_box(
4131 f.invoke(black_box(plain_args.as_slice()))
4132 .expect("unistr plain benchmark invocation must succeed"),
4133 );
4134 if let SqliteValue::Text(text) = result {
4135 checksum = checksum.wrapping_add(text.len());
4136 }
4137 }
4138 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4139
4140 let started = Instant::now();
4141 for _ in 0..INVOCATIONS {
4142 let result = black_box(
4143 f.invoke(black_box(escaped_args.as_slice()))
4144 .expect("unistr escaped benchmark invocation must succeed"),
4145 );
4146 if let SqliteValue::Text(text) = result {
4147 checksum = checksum.wrapping_add(text.len());
4148 }
4149 }
4150 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
4151 }
4152
4153 println!(
4154 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
4155 );
4156 }
4157
4158 #[test]
4161 fn test_random_range() {
4162 let f = RandomFunc;
4163 let result = f.invoke(&[]).unwrap();
4164 assert!(matches!(result, SqliteValue::Integer(_)));
4165 }
4166
4167 #[test]
4170 fn test_randomblob_length() {
4171 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
4172 match result {
4173 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
4174 other => unreachable!("expected blob, got {other:?}"),
4175 }
4176 }
4177
4178 #[test]
4179 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
4180 for arg in [
4181 SqliteValue::Null,
4182 SqliteValue::Integer(0),
4183 SqliteValue::Integer(-5),
4184 ] {
4185 let result = invoke1(&RandomblobFunc, arg).unwrap();
4186 match result {
4187 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
4188 other => unreachable!("expected one-byte blob, got {other:?}"),
4189 }
4190 }
4191 }
4192
4193 #[test]
4196 fn test_zeroblob_length() {
4197 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
4198 match result {
4199 SqliteValue::Blob(b) => {
4200 assert_eq!(b.len(), 100);
4201 assert!(b.iter().all(|&x| x == 0));
4202 }
4203 other => unreachable!("expected blob, got {other:?}"),
4204 }
4205 }
4206
4207 #[test]
4210 fn test_unhex_valid() {
4211 let result = invoke1(
4212 &UnhexFunc,
4213 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
4214 )
4215 .unwrap();
4216 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
4217 }
4218
4219 #[test]
4220 fn test_unhex_invalid() {
4221 let result = invoke1(
4222 &UnhexFunc,
4223 SqliteValue::Text(SmallText::from_string("ZZZZ")),
4224 )
4225 .unwrap();
4226 assert_eq!(result, SqliteValue::Null);
4227 }
4228
4229 #[test]
4230 fn test_unhex_ignore_chars() {
4231 let f = UnhexFunc;
4232 let result = f
4233 .invoke(&[
4234 SqliteValue::Text(SmallText::from_string("48-65-6C")),
4235 SqliteValue::Text(SmallText::from_string("-")),
4236 ])
4237 .unwrap();
4238 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
4239 }
4240
4241 #[test]
4242 fn test_unhex_ignore_chars_only_between_byte_pairs() {
4243 let f = UnhexFunc;
4244 let result = f
4245 .invoke(&[
4246 SqliteValue::Text(SmallText::from_string("AB CD")),
4247 SqliteValue::Text(SmallText::from_string(" ")),
4248 ])
4249 .unwrap();
4250 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
4251
4252 let result = f
4253 .invoke(&[
4254 SqliteValue::Text(SmallText::from_string("A BCD")),
4255 SqliteValue::Text(SmallText::from_string(" ")),
4256 ])
4257 .unwrap();
4258 assert_eq!(result, SqliteValue::Null);
4259 }
4260
4261 #[test]
4262 fn test_unhex_null_ignore_argument_returns_null() {
4263 let f = UnhexFunc;
4264 let result = f
4265 .invoke(&[
4266 SqliteValue::Text(SmallText::from_string("41")),
4267 SqliteValue::Null,
4268 ])
4269 .unwrap();
4270 assert_eq!(result, SqliteValue::Null);
4271 }
4272
4273 #[test]
4274 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
4275 let f = UnhexFunc;
4276 let result = f
4277 .invoke(&[
4278 SqliteValue::Text(SmallText::from_string("41")),
4279 SqliteValue::Text(SmallText::from_string("4")),
4280 ])
4281 .unwrap();
4282 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
4283 }
4284
4285 #[test]
4286 #[ignore = "perf-only benchmark"]
4287 fn perf_unhex_text_args() {
4288 use std::hint::black_box;
4289 use std::time::Instant;
4290
4291 const INVOCATIONS: usize = 300_000;
4292 const REPEATS: usize = 7;
4293
4294 let f = UnhexFunc;
4295 let plain_args = [SqliteValue::Text(SmallText::from_string(
4296 "48656C6C6F776F726C64",
4297 ))];
4298 let ignore_args = [
4299 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
4300 SqliteValue::Text(SmallText::from_string("-")),
4301 ];
4302 let mut plain_best_ns = u128::MAX;
4303 let mut ignore_best_ns = u128::MAX;
4304 let mut checksum = 0usize;
4305
4306 for _ in 0..REPEATS {
4307 let started = Instant::now();
4308 for _ in 0..INVOCATIONS {
4309 let result = black_box(
4310 f.invoke(black_box(plain_args.as_slice()))
4311 .expect("unhex benchmark invocation must succeed"),
4312 );
4313 if let SqliteValue::Blob(blob) = result {
4314 checksum = checksum.wrapping_add(blob.len());
4315 }
4316 }
4317 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4318
4319 let started = Instant::now();
4320 for _ in 0..INVOCATIONS {
4321 let result = black_box(
4322 f.invoke(black_box(ignore_args.as_slice()))
4323 .expect("unhex ignore benchmark invocation must succeed"),
4324 );
4325 if let SqliteValue::Blob(blob) = result {
4326 checksum = checksum.wrapping_add(blob.len());
4327 }
4328 }
4329 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
4330 }
4331
4332 println!(
4333 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
4334 );
4335 }
4336
4337 #[test]
4340 fn test_unicode_first_char() {
4341 assert_eq!(
4342 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
4343 SqliteValue::Integer(65)
4344 );
4345 }
4346
4347 #[test]
4348 fn test_unicode_text_stops_at_nul() {
4349 assert_eq!(
4350 invoke1(
4351 &UnicodeFunc,
4352 SqliteValue::Text(SmallText::from_string("\0A"))
4353 )
4354 .unwrap(),
4355 SqliteValue::Null
4356 );
4357 assert_eq!(
4358 invoke1(
4359 &UnicodeFunc,
4360 SqliteValue::Text(SmallText::from_string("A\0"))
4361 )
4362 .unwrap(),
4363 SqliteValue::Integer(65)
4364 );
4365 }
4366
4367 #[test]
4368 fn test_unicode_blob_uses_sqlite_utf8_reader() {
4369 let cases: &[(&[u8], SqliteValue)] = &[
4370 (&[0x00, 0x41], SqliteValue::Null),
4371 (&[0x80], SqliteValue::Integer(128)),
4372 (&[0xC2, 0x80], SqliteValue::Integer(128)),
4373 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
4374 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
4375 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
4376 ];
4377
4378 for (bytes, expected) in cases {
4379 assert_eq!(
4380 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
4381 expected.clone()
4382 );
4383 }
4384 }
4385
4386 #[test]
4387 #[ignore = "perf-only benchmark"]
4388 fn perf_unicode_text_arg() {
4389 use std::hint::black_box;
4390 use std::time::Instant;
4391
4392 const INVOCATIONS: usize = 1_000_000;
4393 const REPEATS: usize = 7;
4394
4395 let f = UnicodeFunc;
4396 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
4397 let mut text_best_ns = u128::MAX;
4398 let mut checksum = 0i64;
4399
4400 for _ in 0..REPEATS {
4401 let started = Instant::now();
4402 for _ in 0..INVOCATIONS {
4403 let result = black_box(
4404 f.invoke(black_box(args.as_slice()))
4405 .expect("unicode benchmark invocation must succeed"),
4406 );
4407 if let SqliteValue::Integer(codepoint) = result {
4408 checksum = checksum.wrapping_add(codepoint);
4409 }
4410 }
4411 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4412 }
4413
4414 println!(
4415 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4416 );
4417 }
4418
4419 #[test]
4422 fn test_soundex_basic() {
4423 assert_eq!(
4424 invoke1(
4425 &SoundexFunc,
4426 SqliteValue::Text(SmallText::from_string("Robert"))
4427 )
4428 .unwrap(),
4429 SqliteValue::Text(SmallText::from_string("R163"))
4430 );
4431 }
4432
4433 #[test]
4434 #[ignore = "perf-only benchmark"]
4435 fn perf_soundex_text_arg() {
4436 use std::hint::black_box;
4437 use std::time::Instant;
4438
4439 const INVOCATIONS: usize = 1_000_000;
4440 const REPEATS: usize = 7;
4441
4442 let f = SoundexFunc;
4443 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
4444 let mut text_best_ns = u128::MAX;
4445 let mut checksum = 0usize;
4446
4447 for _ in 0..REPEATS {
4448 let started = Instant::now();
4449 for _ in 0..INVOCATIONS {
4450 let result = black_box(
4451 f.invoke(black_box(args.as_slice()))
4452 .expect("soundex benchmark invocation must succeed"),
4453 );
4454 if let SqliteValue::Text(text) = result {
4455 checksum = checksum.wrapping_add(text.len());
4456 }
4457 }
4458 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4459 }
4460
4461 println!(
4462 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4463 );
4464 }
4465
4466 #[test]
4469 fn test_substr_basic() {
4470 let f = SubstrFunc;
4471 assert_eq!(
4472 f.invoke(&[
4473 SqliteValue::Text(SmallText::from_string("hello")),
4474 SqliteValue::Integer(2),
4475 SqliteValue::Integer(3),
4476 ])
4477 .unwrap(),
4478 SqliteValue::Text(SmallText::from_string("ell"))
4479 );
4480 }
4481
4482 #[test]
4483 fn test_substr_start_zero_quirk() {
4484 let f = SubstrFunc;
4486 let result = f
4487 .invoke(&[
4488 SqliteValue::Text(SmallText::from_string("hello")),
4489 SqliteValue::Integer(0),
4490 SqliteValue::Integer(3),
4491 ])
4492 .unwrap();
4493 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
4494 }
4495
4496 #[test]
4497 fn test_substr_negative_start() {
4498 let f = SubstrFunc;
4500 let result = f
4501 .invoke(&[
4502 SqliteValue::Text(SmallText::from_string("hello")),
4503 SqliteValue::Integer(-2),
4504 ])
4505 .unwrap();
4506 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
4507 }
4508
4509 #[test]
4510 fn test_substr_negative_length() {
4511 let f = SubstrFunc;
4512 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4513 let i = SqliteValue::Integer;
4514 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
4516 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
4518 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
4520 }
4521
4522 #[test]
4523 fn test_substr_negative_start_negative_length() {
4524 let f = SubstrFunc;
4525 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4526 let i = SqliteValue::Integer;
4527 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
4529 }
4530
4531 #[test]
4532 fn test_substr_edge_cases() {
4533 let f = SubstrFunc;
4534 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4535 let i = SqliteValue::Integer;
4536 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
4538 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
4540 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
4542 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
4544 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
4546 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
4548 }
4549
4550 #[test]
4551 fn test_substr_blob_negative_length() {
4552 let f = SubstrFunc;
4553 let i = SqliteValue::Integer;
4554 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
4555 assert_eq!(
4557 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
4558 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
4559 );
4560 }
4561
4562 #[test]
4565 fn test_like_case_insensitive() {
4566 assert_eq!(
4567 invoke2(
4568 &LikeFunc,
4569 SqliteValue::Text(SmallText::from_string("ABC")),
4570 SqliteValue::Text(SmallText::from_string("abc"))
4571 )
4572 .unwrap(),
4573 SqliteValue::Integer(1)
4574 );
4575 }
4576
4577 #[test]
4578 fn test_like_escape() {
4579 let f = LikeFunc;
4580 let result = f
4581 .invoke(&[
4582 SqliteValue::Text(SmallText::from_string("10\\%")),
4583 SqliteValue::Text(SmallText::from_string("10%")),
4584 SqliteValue::Text(SmallText::from_string("\\")),
4585 ])
4586 .unwrap();
4587 assert_eq!(result, SqliteValue::Integer(1));
4588 }
4589
4590 #[test]
4591 fn test_like_escape_rejects_empty_string() {
4592 let err = LikeFunc
4593 .invoke(&[
4594 SqliteValue::Text(SmallText::from_string("a")),
4595 SqliteValue::Text(SmallText::from_string("a")),
4596 SqliteValue::Text(SmallText::new("")),
4597 ])
4598 .unwrap_err();
4599 assert!(
4600 err.to_string()
4601 .contains("ESCAPE expression must be a single character")
4602 );
4603 }
4604
4605 #[test]
4606 fn test_like_escape_rejects_multi_character_string() {
4607 let err = LikeFunc
4608 .invoke(&[
4609 SqliteValue::Text(SmallText::from_string("a")),
4610 SqliteValue::Text(SmallText::from_string("a")),
4611 SqliteValue::Text(SmallText::from_string("xx")),
4612 ])
4613 .unwrap_err();
4614 assert!(
4615 err.to_string()
4616 .contains("ESCAPE expression must be a single character")
4617 );
4618 }
4619
4620 #[test]
4621 fn test_like_percent() {
4622 assert_eq!(
4623 invoke2(
4624 &LikeFunc,
4625 SqliteValue::Text(SmallText::from_string("%ell%")),
4626 SqliteValue::Text(SmallText::from_string("Hello"))
4627 )
4628 .unwrap(),
4629 SqliteValue::Integer(1)
4630 );
4631 }
4632
4633 #[test]
4636 fn test_glob_star() {
4637 assert_eq!(
4638 invoke2(
4639 &GlobFunc,
4640 SqliteValue::Text(SmallText::from_string("*.txt")),
4641 SqliteValue::Text(SmallText::from_string("file.txt"))
4642 )
4643 .unwrap(),
4644 SqliteValue::Integer(1)
4645 );
4646 }
4647
4648 #[test]
4649 fn test_glob_case_sensitive() {
4650 assert_eq!(
4651 invoke2(
4652 &GlobFunc,
4653 SqliteValue::Text(SmallText::from_string("ABC")),
4654 SqliteValue::Text(SmallText::from_string("abc"))
4655 )
4656 .unwrap(),
4657 SqliteValue::Integer(0)
4658 );
4659 }
4660
4661 #[test]
4664 fn test_format_specifiers() {
4665 let f = FormatFunc;
4666 let result = f
4667 .invoke(&[
4668 SqliteValue::Text(SmallText::from_string("%d %s")),
4669 SqliteValue::Integer(42),
4670 SqliteValue::Text(SmallText::from_string("hello")),
4671 ])
4672 .unwrap();
4673 assert_eq!(
4674 result,
4675 SqliteValue::Text(SmallText::from_string("42 hello"))
4676 );
4677 }
4678
4679 #[test]
4680 fn test_format_n_noop() {
4681 let f = FormatFunc;
4682 let result = f
4684 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
4685 .unwrap();
4686 assert_eq!(
4687 result,
4688 SqliteValue::Text(SmallText::from_string("beforeafter"))
4689 );
4690 }
4691
4692 #[test]
4693 fn test_format_alternate_form_hex_octal() {
4694 let cases: &[(&str, i64, &str)] = &[
4696 ("%#x", 255, "0xff"),
4697 ("%#X", 255, "0XFF"),
4698 ("%#o", 64, "0100"),
4699 ("%#x", 0, "0"), ("%#o", 0, "0"), ("%#5x", 255, " 0xff"), ("%#8x", 255, " 0xff"),
4703 ("%#08x", 255, "0x000000ff"), ("%-#8x", 255, "0xff "), ("%-08x", 255, "000000ff"), ("%#08o", 64, "000000100"),
4707 ("%#x", -1, "0xffffffffffffffff"),
4708 ];
4709 for (fmt, arg, want) in cases {
4710 let f = FormatFunc;
4711 let result = f
4712 .invoke(&[
4713 SqliteValue::Text(SmallText::from_string(*fmt)),
4714 SqliteValue::Integer(*arg),
4715 ])
4716 .unwrap();
4717 assert_eq!(
4718 result,
4719 SqliteValue::Text(SmallText::from_string((*want).to_owned())),
4720 "format({fmt:?}, {arg})"
4721 );
4722 }
4723 }
4724
4725 #[test]
4726 fn test_format_empty_string_is_null() {
4727 let f = FormatFunc;
4731 assert_eq!(
4732 f.invoke(&[SqliteValue::Text(SmallText::from_string(""))])
4733 .unwrap(),
4734 SqliteValue::Null
4735 );
4736 assert_eq!(
4738 f.invoke(&[
4739 SqliteValue::Text(SmallText::from_string("%s")),
4740 SqliteValue::Null,
4741 ])
4742 .unwrap(),
4743 SqliteValue::Text(SmallText::from_string(String::new()))
4744 );
4745 }
4746
4747 #[test]
4750 fn test_sqlite_version_format() {
4751 let result = SqliteVersionFunc.invoke(&[]).unwrap();
4752 match result {
4753 SqliteValue::Text(v) => {
4754 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
4755 }
4756 other => unreachable!("expected text, got {other:?}"),
4757 }
4758 }
4759
4760 #[test]
4761 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
4762 let func = SqliteCompileoptionUsedFunc;
4763 assert_eq!(
4764 invoke1(
4765 &func,
4766 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
4767 )
4768 .unwrap(),
4769 SqliteValue::Integer(1)
4770 );
4771 let expected_icu_enabled = i64::from(cfg!(feature = "ext-icu"));
4772 assert_eq!(
4773 invoke1(
4774 &func,
4775 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
4776 )
4777 .unwrap(),
4778 SqliteValue::Integer(expected_icu_enabled)
4779 );
4780 assert_eq!(
4781 invoke1(
4782 &func,
4783 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
4784 )
4785 .unwrap(),
4786 SqliteValue::Integer(expected_icu_enabled)
4787 );
4788 assert_eq!(
4789 invoke1(
4790 &func,
4791 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
4792 )
4793 .unwrap(),
4794 SqliteValue::Integer(1)
4795 );
4796 assert_eq!(
4797 invoke1(
4798 &func,
4799 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
4800 )
4801 .unwrap(),
4802 SqliteValue::Integer(0)
4803 );
4804 assert_eq!(
4805 invoke1(&func, SqliteValue::Null).unwrap(),
4806 SqliteValue::Null
4807 );
4808 }
4809
4810 #[test]
4811 #[ignore = "perf-only benchmark"]
4812 fn perf_compileoption_used_text_args() {
4813 use std::hint::black_box;
4814 use std::time::Instant;
4815
4816 const INVOCATIONS: usize = 1_000_000;
4817 const REPEATS: usize = 7;
4818
4819 let f = SqliteCompileoptionUsedFunc;
4820 let present_args = [SqliteValue::Text(SmallText::from_string(
4821 "SQLITE_ENABLE_ICU",
4822 ))];
4823 let absent_args = [SqliteValue::Text(SmallText::from_string(
4824 "ENABLE_NOT_PRESENT",
4825 ))];
4826
4827 let mut present_best_ns = u128::MAX;
4828 let mut absent_best_ns = u128::MAX;
4829 let mut checksum = 0i64;
4830 for _ in 0..REPEATS {
4831 let started = Instant::now();
4832 for _ in 0..INVOCATIONS {
4833 let result = black_box(
4834 f.invoke(black_box(present_args.as_slice()))
4835 .expect("compileoption present benchmark invocation must succeed"),
4836 );
4837 if let SqliteValue::Integer(value) = result {
4838 checksum = checksum.wrapping_add(value);
4839 }
4840 }
4841 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
4842
4843 let started = Instant::now();
4844 for _ in 0..INVOCATIONS {
4845 let result = black_box(
4846 f.invoke(black_box(absent_args.as_slice()))
4847 .expect("compileoption absent benchmark invocation must succeed"),
4848 );
4849 if let SqliteValue::Integer(value) = result {
4850 checksum = checksum.wrapping_add(value);
4851 }
4852 }
4853 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
4854 }
4855
4856 println!(
4857 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
4858 );
4859 }
4860
4861 #[test]
4862 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
4863 let func = SqliteCompileoptionGetFunc;
4864 for (index, option) in sqlite_compile_options().iter().enumerate() {
4865 assert_eq!(
4866 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
4867 SqliteValue::Text(SmallText::new(option))
4868 );
4869 }
4870 assert_eq!(
4871 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
4872 SqliteValue::Null
4873 );
4874 assert_eq!(
4875 invoke1(
4876 &func,
4877 SqliteValue::Integer(sqlite_compile_options().len() as i64)
4878 )
4879 .unwrap(),
4880 SqliteValue::Null
4881 );
4882 }
4883
4884 #[test]
4887 fn test_register_builtins_all_present() {
4888 let mut registry = FunctionRegistry::new();
4889 register_builtins(&mut registry);
4890
4891 assert!(registry.find_scalar("abs", 1).is_some());
4893 assert!(registry.find_scalar("typeof", 1).is_some());
4894 assert!(registry.find_scalar("length", 1).is_some());
4895 assert!(registry.find_scalar("lower", 1).is_some());
4896 assert!(registry.find_scalar("upper", 1).is_some());
4897 assert!(registry.find_scalar("hex", 1).is_some());
4898 assert!(registry.find_scalar("coalesce", 3).is_some());
4899 assert!(registry.find_scalar("concat", 2).is_some());
4900 assert!(registry.find_scalar("like", 2).is_some());
4901 assert!(registry.find_scalar("glob", 2).is_some());
4902 assert!(registry.find_scalar("round", 1).is_some());
4903 assert!(registry.find_scalar("substr", 2).is_some());
4904 assert!(registry.find_scalar("substring", 3).is_some());
4905 assert!(registry.find_scalar("sqlite_version", 0).is_some());
4906 assert!(registry.find_scalar("iif", 3).is_some());
4907 assert!(registry.find_scalar("if", 3).is_some());
4908 assert!(registry.find_scalar("format", 1).is_some());
4909 assert!(registry.find_scalar("printf", 1).is_some());
4910 assert!(registry.find_scalar("max", 2).is_some());
4911 assert!(registry.find_scalar("min", 2).is_some());
4912 assert!(registry.find_scalar("sign", 1).is_some());
4913 assert!(registry.find_scalar("random", 0).is_some());
4914
4915 assert!(registry.find_scalar("concat_ws", 3).is_some());
4917 assert!(registry.find_scalar("octet_length", 1).is_some());
4918 assert!(registry.find_scalar("unhex", 1).is_some());
4919 assert!(registry.find_scalar("timediff", 2).is_some());
4920 assert!(registry.find_scalar("unistr", 1).is_some());
4921 assert!(registry.find_scalar("unistr_quote", 1).is_some());
4922
4923 assert!(registry.find_aggregate("median", 1).is_some());
4925 assert!(registry.find_aggregate("percentile", 2).is_some());
4926 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
4927 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
4928
4929 assert!(registry.find_scalar("load_extension", 1).is_none());
4931 assert!(registry.find_scalar("load_extension", 2).is_none());
4932 }
4933
4934 #[test]
4935 fn test_register_builtins_rejects_invalid_variadic_arities() {
4936 let mut registry = FunctionRegistry::new();
4937 register_builtins(&mut registry);
4938
4939 for (name, too_few, valid, too_many) in [
4940 ("coalesce", 1, 2, None),
4941 ("concat", 0, 1, None),
4942 ("concat_ws", 1, 2, None),
4943 ("trim", 0, 1, Some(3)),
4944 ("ltrim", 0, 1, Some(3)),
4945 ("rtrim", 0, 1, Some(3)),
4946 ("round", 0, 1, Some(3)),
4947 ("unhex", 0, 1, Some(3)),
4948 ("substr", 1, 2, Some(4)),
4949 ("substring", 1, 2, Some(4)),
4950 ("max", 0, 1, None),
4951 ("min", 0, 1, None),
4952 ] {
4953 assert_wrong_arg_count(®istry, name, too_few);
4954 assert!(
4955 registry.find_scalar(name, valid).is_some(),
4956 "{name}/{valid} should resolve"
4957 );
4958 if let Some(arity) = too_many {
4959 assert_wrong_arg_count(®istry, name, arity);
4960 }
4961 }
4962
4963 assert!(registry.find_scalar("char", 0).is_some());
4964 assert!(registry.find_scalar("format", 0).is_some());
4965 assert!(registry.find_scalar("printf", 0).is_some());
4966 }
4967
4968 #[test]
4969 fn test_e2e_registry_invoke_through_lookup() {
4970 let mut registry = FunctionRegistry::new();
4971 register_builtins(&mut registry);
4972
4973 let abs = registry.find_scalar("ABS", 1).unwrap();
4975 assert_eq!(
4976 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
4977 SqliteValue::Integer(42)
4978 );
4979
4980 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
4982 assert_eq!(
4983 typeof_fn
4984 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
4985 .unwrap(),
4986 SqliteValue::Text(SmallText::from_string("text"))
4987 );
4988
4989 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
4991 assert_eq!(
4992 coalesce
4993 .invoke(&[
4994 SqliteValue::Null,
4995 SqliteValue::Null,
4996 SqliteValue::Integer(42),
4997 SqliteValue::Integer(99),
4998 ])
4999 .unwrap(),
5000 SqliteValue::Integer(42)
5001 );
5002 }
5003
5004 #[test]
5007 fn test_nondeterministic_functions_flagged() {
5008 assert!(!RandomFunc.is_deterministic());
5011 assert!(!RandomblobFunc.is_deterministic());
5012 assert!(!ChangesFunc.is_deterministic());
5013 assert!(!TotalChangesFunc.is_deterministic());
5014 assert!(!LastInsertRowidFunc.is_deterministic());
5015 }
5016
5017 #[test]
5018 fn test_deterministic_functions_flagged() {
5019 assert!(AbsFunc.is_deterministic());
5021 assert!(LengthFunc.is_deterministic());
5022 assert!(TypeofFunc.is_deterministic());
5023 assert!(UpperFunc.is_deterministic());
5024 assert!(LowerFunc.is_deterministic());
5025 assert!(HexFunc.is_deterministic());
5026 assert!(CoalesceFunc.is_deterministic());
5027 assert!(IifFunc.is_deterministic());
5028 }
5029
5030 #[test]
5031 fn test_random_produces_different_values() {
5032 let a = RandomFunc.invoke(&[]).unwrap();
5035 let b = RandomFunc.invoke(&[]).unwrap();
5036 assert_ne!(a.as_integer(), b.as_integer());
5039 }
5040
5041 #[test]
5042 fn test_registry_nondeterministic_lookup() {
5043 let mut registry = FunctionRegistry::default();
5044 register_builtins(&mut registry);
5045
5046 let random = registry.find_scalar("random", 0).unwrap();
5048 assert!(!random.is_deterministic());
5049
5050 let changes = registry.find_scalar("changes", 0).unwrap();
5051 assert!(!changes.is_deterministic());
5052
5053 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
5054 assert!(!lir.is_deterministic());
5055
5056 let abs = registry.find_scalar("abs", 1).unwrap();
5058 assert!(abs.is_deterministic());
5059 }
5060}