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