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 self.invoke_with_collation(args, None)
768 }
769
770 fn consumes_argument_collation(&self) -> bool {
771 true
772 }
773
774 fn invoke_with_collation(
775 &self,
776 args: &[SqliteValue],
777 collation: Option<&dyn crate::collation::CollationFunction>,
778 ) -> Result<SqliteValue> {
779 let equal = match (&args[0], &args[1], collation) {
780 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
781 collation.compare(left.as_bytes(), right.as_bytes()) == std::cmp::Ordering::Equal
782 }
783 _ => args[0] == args[1],
784 };
785 if equal {
786 Ok(SqliteValue::Null)
787 } else {
788 Ok(args[0].clone())
789 }
790 }
791
792 fn num_args(&self) -> i32 {
793 2
794 }
795
796 fn name(&self) -> &str {
797 "nullif"
798 }
799}
800
801pub struct TypeofFunc;
804
805impl ScalarFunction for TypeofFunc {
806 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
807 let type_name = match &args[0] {
808 SqliteValue::Null => "null",
809 SqliteValue::Integer(_) => "integer",
810 SqliteValue::Float(_) => "real",
811 SqliteValue::Text(_) => "text",
812 SqliteValue::Blob(_) => "blob",
813 };
814 Ok(SqliteValue::Text(SmallText::new(type_name)))
815 }
816
817 fn num_args(&self) -> i32 {
818 1
819 }
820
821 fn name(&self) -> &str {
822 "typeof"
823 }
824}
825
826pub struct SubtypeFunc;
829
830impl ScalarFunction for SubtypeFunc {
831 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
832 Ok(SqliteValue::Integer(0))
835 }
836
837 fn num_args(&self) -> i32 {
838 1
839 }
840
841 fn name(&self) -> &str {
842 "subtype"
843 }
844}
845
846pub struct ReplaceFunc;
849
850impl ScalarFunction for ReplaceFunc {
851 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
852 if let Some(null) = null_propagate(args) {
853 return Ok(null);
854 }
855 let x = text_arg(&args[0]);
856 let y = text_arg(&args[1]);
857 let z = text_arg(&args[2]);
858 if y.is_empty() {
859 return Ok(SqliteValue::Text(SmallText::from_string(x)));
860 }
861
862 if z.len() > y.len() {
864 let occurrences = x.matches(y.as_ref()).count();
865 let final_len = x.len() + occurrences * (z.len() - y.len());
866 if final_len > 1_000_000_000 {
867 return Err(FrankenError::TooBig);
868 }
869 }
870
871 Ok(SqliteValue::Text(SmallText::from_string(
872 x.replace(y.as_ref(), z.as_ref()),
873 )))
874 }
875
876 fn num_args(&self) -> i32 {
877 3
878 }
879
880 fn name(&self) -> &str {
881 "replace"
882 }
883}
884
885pub struct RoundFunc;
888
889impl ScalarFunction for RoundFunc {
890 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
891 if args[0].is_null() {
892 return Ok(SqliteValue::Null);
893 }
894 if args.len() > 1 && args[1].is_null() {
897 return Ok(SqliteValue::Null);
898 }
899 let x = args[0].to_float();
900 let n = if args.len() > 1 {
902 args[1].to_integer().clamp(0, 30)
903 } else {
904 0
905 };
906 if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
908 return Ok(SqliteValue::Float(x));
909 }
910 #[allow(clippy::cast_possible_truncation)]
916 let rounded = {
917 let prec = (n as usize) + 15;
918 let full = format!("{x:.prec$}");
919 let dot = full.find('.').unwrap_or(full.len());
920 let rd_idx = dot + 1 + n as usize;
921 if rd_idx >= full.len() {
922 format!("{x:.prec$}", prec = n as usize)
923 .parse::<f64>()
924 .unwrap_or(x)
925 } else {
926 let rd = full.as_bytes()[rd_idx] - b'0';
927 if rd != 5 || !full[rd_idx + 1..].bytes().all(|b| b == b'0') {
928 format!("{x:.prec$}", prec = n as usize)
930 .parse::<f64>()
931 .unwrap_or(x)
932 } else {
933 let mut trunc = full.as_bytes()[..rd_idx].to_vec();
936 if trunc.last() == Some(&b'.') {
938 trunc.pop();
939 }
940 let start = usize::from(trunc.first() == Some(&b'-'));
941 let mut carry = true;
942 for b in trunc[start..].iter_mut().rev() {
943 if *b == b'.' {
944 continue;
945 }
946 if carry {
947 if *b == b'9' {
948 *b = b'0';
949 } else {
950 *b += 1;
951 carry = false;
952 break;
953 }
954 }
955 }
956 if carry {
957 trunc.insert(start, b'1');
958 }
959 String::from_utf8(trunc)
960 .ok()
961 .and_then(|s| s.parse::<f64>().ok())
962 .unwrap_or(x)
963 }
964 }
965 };
966 Ok(SqliteValue::Float(rounded))
967 }
968
969 fn num_args(&self) -> i32 {
970 -1 }
972
973 fn min_args(&self) -> i32 {
974 1
975 }
976
977 fn max_args(&self) -> Option<i32> {
978 Some(2)
979 }
980
981 fn name(&self) -> &str {
982 "round"
983 }
984}
985
986pub struct SignFunc;
989
990impl ScalarFunction for SignFunc {
991 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
992 if args[0].is_null() {
993 return Ok(SqliteValue::Null);
994 }
995 match &args[0] {
996 SqliteValue::Null => Ok(SqliteValue::Null),
997 SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
998 SqliteValue::Float(f) => {
999 if f.is_nan() {
1000 Ok(SqliteValue::Null)
1001 } else if *f > 0.0 {
1002 Ok(SqliteValue::Integer(1))
1003 } else if *f < 0.0 {
1004 Ok(SqliteValue::Integer(-1))
1005 } else {
1006 Ok(SqliteValue::Integer(0))
1007 }
1008 }
1009 SqliteValue::Text(s) => {
1010 let trimmed = s.trim_matches(|ch: char| ch.is_ascii_whitespace());
1012 if trimmed.is_empty() {
1013 return Ok(SqliteValue::Null);
1014 }
1015
1016 let stripped = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
1022 if stripped.eq_ignore_ascii_case("nan")
1023 || stripped.eq_ignore_ascii_case("inf")
1024 || stripped.eq_ignore_ascii_case("infinity")
1025 {
1026 return Ok(SqliteValue::Null);
1027 }
1028
1029 if let Ok(f) = trimmed.parse::<f64>() {
1032 if f > 0.0 {
1034 Ok(SqliteValue::Integer(1))
1035 } else if f < 0.0 {
1036 Ok(SqliteValue::Integer(-1))
1037 } else {
1038 Ok(SqliteValue::Integer(0))
1039 }
1040 } else if let Ok(i) = trimmed.parse::<i64>() {
1041 Ok(SqliteValue::Integer(i.signum()))
1043 } else {
1044 Ok(SqliteValue::Null)
1045 }
1046 }
1047 SqliteValue::Blob(_) => Ok(SqliteValue::Null),
1048 }
1049 }
1050
1051 fn num_args(&self) -> i32 {
1052 1
1053 }
1054
1055 fn name(&self) -> &str {
1056 "sign"
1057 }
1058}
1059
1060pub struct RandomFunc;
1063
1064impl ScalarFunction for RandomFunc {
1065 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1066 let val = simple_random_i64();
1069 Ok(SqliteValue::Integer(val))
1070 }
1071
1072 fn is_deterministic(&self) -> bool {
1073 false
1074 }
1075
1076 fn num_args(&self) -> i32 {
1077 0
1078 }
1079
1080 fn name(&self) -> &str {
1081 "random"
1082 }
1083}
1084
1085fn simple_random_i64() -> i64 {
1087 use std::sync::atomic::{AtomicU64, Ordering};
1092
1093 static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
1094 let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
1095 x ^= x >> 30;
1096 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1097 x ^= x >> 27;
1098 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
1099 x ^= x >> 31;
1100 x as i64
1101}
1102
1103pub struct RandomblobFunc;
1106
1107impl ScalarFunction for RandomblobFunc {
1108 #[allow(clippy::cast_sign_loss)]
1109 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1110 let n_i64 = if args[0].is_null() {
1114 1
1115 } else {
1116 args[0].to_integer().max(1)
1117 };
1118 if n_i64 > 1_000_000_000 {
1119 return Err(FrankenError::TooBig);
1120 }
1121 let n = n_i64 as usize;
1122 let mut buf = vec![0u8; n];
1123 let mut i = 0;
1124 while i < n {
1125 let rnd = simple_random_i64().to_ne_bytes();
1126 let to_copy = (n - i).min(8);
1127 buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
1128 i += to_copy;
1129 }
1130 Ok(SqliteValue::Blob(Arc::from(buf.as_slice())))
1131 }
1132
1133 fn is_deterministic(&self) -> bool {
1134 false
1135 }
1136
1137 fn num_args(&self) -> i32 {
1138 1
1139 }
1140
1141 fn name(&self) -> &str {
1142 "randomblob"
1143 }
1144}
1145
1146pub struct ZeroblobFunc;
1149
1150impl ScalarFunction for ZeroblobFunc {
1151 #[allow(clippy::cast_sign_loss)]
1152 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1153 if args[0].is_null() {
1155 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1156 }
1157 let n_i64 = args[0].to_integer().max(0);
1158 if n_i64 > 1_000_000_000 {
1159 return Err(FrankenError::TooBig);
1160 }
1161 let n = n_i64 as usize;
1162 Ok(SqliteValue::Blob(Arc::from(vec![0u8; n].as_slice())))
1163 }
1164
1165 fn num_args(&self) -> i32 {
1166 1
1167 }
1168
1169 fn name(&self) -> &str {
1170 "zeroblob"
1171 }
1172}
1173
1174pub struct QuoteFunc;
1177
1178impl ScalarFunction for QuoteFunc {
1179 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1180 let result = quote_sql_value(&args[0], false);
1181 Ok(SqliteValue::Text(SmallText::from_string(result)))
1182 }
1183
1184 fn num_args(&self) -> i32 {
1185 1
1186 }
1187
1188 fn name(&self) -> &str {
1189 "quote"
1190 }
1191}
1192
1193pub struct UnistrQuoteFunc;
1196
1197impl ScalarFunction for UnistrQuoteFunc {
1198 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1199 let result = quote_sql_value(&args[0], true);
1200 Ok(SqliteValue::Text(SmallText::from_string(result)))
1201 }
1202
1203 fn num_args(&self) -> i32 {
1204 1
1205 }
1206
1207 fn name(&self) -> &str {
1208 "unistr_quote"
1209 }
1210}
1211
1212fn quote_sql_value(value: &SqliteValue, use_unistr_quote: bool) -> String {
1213 match value {
1214 SqliteValue::Null => "NULL".to_owned(),
1215 SqliteValue::Integer(i) => i.to_string(),
1216 SqliteValue::Float(f) => format_sqlite_float(*f),
1217 SqliteValue::Text(s) => quote_sql_text_literal(s.as_str(), use_unistr_quote),
1218 SqliteValue::Blob(b) => {
1219 let mut hex = String::with_capacity(3 + b.len() * 2);
1220 hex.push_str("X'");
1221 for byte in b.iter() {
1222 let _ = write!(hex, "{byte:02X}");
1223 }
1224 hex.push('\'');
1225 hex
1226 }
1227 }
1228}
1229
1230fn quote_sql_text_literal(text: &str, use_unistr_quote: bool) -> String {
1231 let text = sqlite_text_until_nul(text);
1232 if use_unistr_quote && text.chars().any(is_unistr_control_char) {
1233 return unistr_quote_sql_text_literal(text);
1234 }
1235
1236 let mut quoted = String::with_capacity(text.len() + 2);
1237 quoted.push('\'');
1238 append_sql_string_literal_body(&mut quoted, text);
1239 quoted.push('\'');
1240 quoted
1241}
1242
1243fn unistr_quote_sql_text_literal(text: &str) -> String {
1244 let mut quoted = String::with_capacity(text.len() + 12);
1245 quoted.push_str("unistr('");
1246 for ch in text.chars() {
1247 match ch {
1248 '\'' => quoted.push_str("''"),
1249 '\\' => quoted.push_str("\\\\"),
1250 _ if is_unistr_control_char(ch) => {
1251 let _ = write!(quoted, "\\u{:04x}", ch as u32);
1252 }
1253 _ => quoted.push(ch),
1254 }
1255 }
1256 quoted.push_str("')");
1257 quoted
1258}
1259
1260fn append_sql_string_literal_body(out: &mut String, text: &str) {
1261 for ch in text.chars() {
1262 if ch == '\'' {
1263 out.push_str("''");
1264 } else {
1265 out.push(ch);
1266 }
1267 }
1268}
1269
1270fn is_unistr_control_char(ch: char) -> bool {
1271 matches!(ch, '\u{0001}'..='\u{001F}')
1272}
1273
1274pub struct UnhexFunc;
1277
1278impl ScalarFunction for UnhexFunc {
1279 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1280 if args[0].is_null() {
1281 return Ok(SqliteValue::Null);
1282 }
1283 if args.len() > 1 && args[1].is_null() {
1284 return Ok(SqliteValue::Null);
1285 }
1286 let input = text_arg(&args[0]);
1287 let ignore_chars: Vec<char> = if args.len() > 1 {
1288 text_arg(&args[1])
1289 .chars()
1290 .filter(|&c| hex_digit(c).is_none())
1291 .collect()
1292 } else {
1293 Vec::new()
1294 };
1295
1296 let mut bytes = Vec::with_capacity(input.len() / 2);
1297 let mut hi_nibble = None;
1298 for c in input.as_ref().chars() {
1299 if ignore_chars.contains(&c) {
1300 if hi_nibble.is_some() {
1301 return Ok(SqliteValue::Null);
1302 }
1303 continue;
1304 }
1305 let digit = match hex_digit(c) {
1306 Some(v) => v,
1307 None => return Ok(SqliteValue::Null),
1308 };
1309 if let Some(hi) = hi_nibble.take() {
1310 bytes.push(hi << 4 | digit);
1311 } else {
1312 hi_nibble = Some(digit);
1313 }
1314 }
1315 if hi_nibble.is_some() {
1316 return Ok(SqliteValue::Null);
1317 }
1318 Ok(SqliteValue::Blob(Arc::from(bytes.as_slice())))
1319 }
1320
1321 fn num_args(&self) -> i32 {
1322 -1 }
1324
1325 fn min_args(&self) -> i32 {
1326 1
1327 }
1328
1329 fn max_args(&self) -> Option<i32> {
1330 Some(2)
1331 }
1332
1333 fn name(&self) -> &str {
1334 "unhex"
1335 }
1336}
1337
1338fn hex_digit(c: char) -> Option<u8> {
1339 match c {
1340 '0'..='9' => Some(c as u8 - b'0'),
1341 'a'..='f' => Some(c as u8 - b'a' + 10),
1342 'A'..='F' => Some(c as u8 - b'A' + 10),
1343 _ => None,
1344 }
1345}
1346
1347pub struct UnicodeFunc;
1350
1351impl ScalarFunction for UnicodeFunc {
1352 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1353 if args[0].is_null() {
1354 return Ok(SqliteValue::Null);
1355 }
1356 if let SqliteValue::Blob(bytes) = &args[0] {
1357 return Ok(
1358 sqlite_blob_first_codepoint(bytes).map_or(SqliteValue::Null, SqliteValue::Integer)
1359 );
1360 }
1361 let s = text_arg(&args[0]);
1362 match sqlite_text_until_nul(s.as_ref()).chars().next() {
1363 Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
1364 None => Ok(SqliteValue::Null),
1365 }
1366 }
1367
1368 fn num_args(&self) -> i32 {
1369 1
1370 }
1371
1372 fn name(&self) -> &str {
1373 "unicode"
1374 }
1375}
1376
1377fn sqlite_blob_first_codepoint(bytes: &[u8]) -> Option<i64> {
1378 let first = *bytes.first()?;
1379 if first == 0 {
1380 return None;
1381 }
1382 let mut codepoint = match first {
1383 0x00..=0xBF => u32::from(first),
1384 0xC0..=0xDF => u32::from(first & 0x1F),
1385 0xE0..=0xEF => u32::from(first & 0x0F),
1386 0xF0..=0xF7 => u32::from(first & 0x07),
1387 _ => 0xFFFD,
1388 };
1389
1390 if first >= 0xC0 && first <= 0xF7 {
1391 for byte in bytes
1392 .iter()
1393 .copied()
1394 .skip(1)
1395 .take_while(|byte| byte & 0xC0 == 0x80)
1396 {
1397 codepoint = codepoint
1398 .wrapping_shl(6)
1399 .wrapping_add(u32::from(byte & 0x3F));
1400 }
1401 if codepoint < 0x80
1402 || (codepoint & 0xFFFF_F800) == 0xD800
1403 || (codepoint & 0xFFFF_FFFE) == 0xFFFE
1404 {
1405 codepoint = 0xFFFD;
1406 }
1407 }
1408
1409 Some(i64::from(codepoint))
1410}
1411
1412pub struct SubstrFunc;
1415
1416impl ScalarFunction for SubstrFunc {
1417 #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
1418 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1419 if args[0].is_null() || args[1].is_null() {
1420 return Ok(SqliteValue::Null);
1421 }
1422 let is_blob = matches!(&args[0], SqliteValue::Blob(_));
1423 if is_blob {
1424 return self.invoke_blob(args);
1425 }
1426
1427 let text = text_arg(&args[0]);
1428 let s = text.as_ref();
1429 let ascii_fast_path = s.is_ascii();
1430 let len = if ascii_fast_path {
1431 s.len() as i64
1432 } else {
1433 s.chars().count() as i64
1434 };
1435 let has_length = args.len() > 2 && !args[2].is_null();
1436
1437 let mut p1 = args[1].to_integer();
1438 let mut p2 = if has_length {
1439 args[2].to_integer()
1440 } else {
1441 1_000_000_000
1442 };
1443
1444 let neg_p2 = p2 < 0;
1448 if neg_p2 {
1449 p2 = p2.saturating_neg();
1450 }
1451
1452 if p1 < 0 {
1454 p1 = p1.saturating_add(len);
1455 if p1 < 0 {
1456 p2 = p2.saturating_add(p1);
1457 p1 = 0;
1458 }
1459 } else if p1 > 0 {
1460 p1 -= 1;
1461 } else if p2 > 0 {
1462 p2 -= 1; }
1464
1465 if neg_p2 {
1467 p1 = p1.saturating_sub(p2);
1468 if p1 < 0 {
1469 p2 = p2.saturating_add(p1);
1470 p1 = 0;
1471 }
1472 }
1473
1474 if p1.saturating_add(p2) > len {
1475 p2 = len.saturating_sub(p1);
1476 }
1477 if p2 <= 0 {
1478 return Ok(SqliteValue::Text(SmallText::new("")));
1479 }
1480
1481 if ascii_fast_path {
1482 let start = p1 as usize;
1483 let end = (p1 + p2) as usize;
1484 return Ok(SqliteValue::Text(SmallText::new(&s[start..end])));
1485 }
1486
1487 let chars: Vec<char> = s.chars().collect();
1488 let result: String = chars[p1 as usize..(p1 + p2) as usize].iter().collect();
1489 Ok(SqliteValue::Text(SmallText::from_string(result)))
1490 }
1491
1492 fn num_args(&self) -> i32 {
1493 -1 }
1495
1496 fn min_args(&self) -> i32 {
1497 2
1498 }
1499
1500 fn max_args(&self) -> Option<i32> {
1501 Some(3)
1502 }
1503
1504 fn name(&self) -> &str {
1505 "substr"
1506 }
1507}
1508
1509impl SubstrFunc {
1510 #[allow(
1511 clippy::unused_self,
1512 clippy::cast_sign_loss,
1513 clippy::cast_possible_wrap
1514 )]
1515 fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1516 let blob = match &args[0] {
1517 SqliteValue::Blob(b) => b,
1518 _ => return Ok(SqliteValue::Null),
1519 };
1520 let len = blob.len() as i64;
1521 let has_length = args.len() > 2 && !args[2].is_null();
1522
1523 let mut p1 = args[1].to_integer();
1524 let mut p2 = if has_length {
1525 args[2].to_integer()
1526 } else {
1527 1_000_000_000
1528 };
1529
1530 let neg_p2 = p2 < 0;
1531 if neg_p2 {
1532 p2 = p2.saturating_neg();
1533 }
1534
1535 if p1 < 0 {
1536 p1 = p1.saturating_add(len);
1537 if p1 < 0 {
1538 p2 = p2.saturating_add(p1);
1539 p1 = 0;
1540 }
1541 } else if p1 > 0 {
1542 p1 -= 1;
1543 } else if p2 > 0 {
1544 p2 -= 1;
1545 }
1546
1547 if neg_p2 {
1548 p1 = p1.saturating_sub(p2);
1549 if p1 < 0 {
1550 p2 = p2.saturating_add(p1);
1551 p1 = 0;
1552 }
1553 }
1554
1555 if p1.saturating_add(p2) > len {
1556 p2 = len.saturating_sub(p1);
1557 }
1558 if p2 <= 0 {
1559 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1560 }
1561
1562 Ok(SqliteValue::Blob(Arc::from(
1563 &blob[p1 as usize..(p1 + p2) as usize],
1564 )))
1565 }
1566}
1567
1568pub struct SoundexFunc;
1571
1572impl ScalarFunction for SoundexFunc {
1573 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1574 if args[0].is_null() {
1575 return Ok(SqliteValue::Text(SmallText::new("?000")));
1577 }
1578 let s = text_arg(&args[0]);
1579 let code = soundex(s.as_ref());
1580 let text = std::str::from_utf8(&code).expect("Soundex output must be ASCII");
1581 Ok(SqliteValue::Text(SmallText::new(text)))
1582 }
1583
1584 fn num_args(&self) -> i32 {
1585 1
1586 }
1587
1588 fn name(&self) -> &str {
1589 "soundex"
1590 }
1591}
1592
1593fn soundex(s: &str) -> [u8; 4] {
1594 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1595 let first = match chars.next() {
1596 Some(c) => c.to_ascii_uppercase(),
1597 None => return *b"?000",
1598 };
1599
1600 let code = |c: char| -> Option<u8> {
1601 match c.to_ascii_uppercase() {
1602 'B' | 'F' | 'P' | 'V' => Some(b'1'),
1603 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some(b'2'),
1604 'D' | 'T' => Some(b'3'),
1605 'L' => Some(b'4'),
1606 'M' | 'N' => Some(b'5'),
1607 'R' => Some(b'6'),
1608 _ => None, }
1610 };
1611
1612 let mut result = *b"0000";
1613 result[0] = first as u8;
1614 let mut result_len = 1;
1615 let mut last_code = code(first);
1616
1617 for c in chars {
1618 if result_len >= result.len() {
1619 break;
1620 }
1621 let current = code(c);
1622 if let Some(digit) = current
1623 && current != last_code
1624 {
1625 result[result_len] = digit;
1626 result_len += 1;
1627 }
1628 last_code = current;
1629 }
1630
1631 result
1632}
1633
1634pub struct ScalarMaxFunc;
1637
1638impl ScalarFunction for ScalarMaxFunc {
1639 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1640 self.invoke_with_collation(args, None)
1641 }
1642
1643 fn consumes_argument_collation(&self) -> bool {
1644 true
1645 }
1646
1647 fn invoke_with_collation(
1648 &self,
1649 args: &[SqliteValue],
1650 collation: Option<&dyn crate::collation::CollationFunction>,
1651 ) -> Result<SqliteValue> {
1652 if let Some(null) = null_propagate(args) {
1654 return Ok(null);
1655 }
1656 let mut max = &args[0];
1657 for arg in &args[1..] {
1658 let ordering = match (arg, max, collation) {
1659 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1660 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1661 }
1662 _ => arg.partial_cmp(max),
1663 };
1664 if ordering == Some(std::cmp::Ordering::Greater) {
1665 max = arg;
1666 }
1667 }
1668 Ok(max.clone())
1669 }
1670
1671 fn num_args(&self) -> i32 {
1672 -1
1673 }
1674
1675 fn min_args(&self) -> i32 {
1676 1
1677 }
1678
1679 fn name(&self) -> &str {
1680 "max"
1681 }
1682}
1683
1684pub struct ScalarMinFunc;
1687
1688impl ScalarFunction for ScalarMinFunc {
1689 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1690 self.invoke_with_collation(args, None)
1691 }
1692
1693 fn consumes_argument_collation(&self) -> bool {
1694 true
1695 }
1696
1697 fn invoke_with_collation(
1698 &self,
1699 args: &[SqliteValue],
1700 collation: Option<&dyn crate::collation::CollationFunction>,
1701 ) -> Result<SqliteValue> {
1702 if let Some(null) = null_propagate(args) {
1704 return Ok(null);
1705 }
1706 let mut min = &args[0];
1707 for arg in &args[1..] {
1708 let ordering = match (arg, min, collation) {
1709 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1710 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1711 }
1712 _ => arg.partial_cmp(min),
1713 };
1714 if matches!(
1718 ordering,
1719 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
1720 ) {
1721 min = arg;
1722 }
1723 }
1724 Ok(min.clone())
1725 }
1726
1727 fn num_args(&self) -> i32 {
1728 -1
1729 }
1730
1731 fn min_args(&self) -> i32 {
1732 1
1733 }
1734
1735 fn name(&self) -> &str {
1736 "min"
1737 }
1738}
1739
1740pub struct LikelihoodFunc;
1743
1744impl ScalarFunction for LikelihoodFunc {
1745 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1746 Ok(args[0].clone())
1748 }
1749
1750 fn num_args(&self) -> i32 {
1751 2
1752 }
1753
1754 fn name(&self) -> &str {
1755 "likelihood"
1756 }
1757}
1758
1759pub struct LikelyFunc;
1760
1761impl ScalarFunction for LikelyFunc {
1762 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1763 Ok(args[0].clone())
1764 }
1765
1766 fn num_args(&self) -> i32 {
1767 1
1768 }
1769
1770 fn name(&self) -> &str {
1771 "likely"
1772 }
1773}
1774
1775pub struct UnlikelyFunc;
1776
1777impl ScalarFunction for UnlikelyFunc {
1778 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1779 Ok(args[0].clone())
1780 }
1781
1782 fn num_args(&self) -> i32 {
1783 1
1784 }
1785
1786 fn name(&self) -> &str {
1787 "unlikely"
1788 }
1789}
1790
1791pub struct SqliteVersionFunc;
1794
1795impl ScalarFunction for SqliteVersionFunc {
1796 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1797 Ok(SqliteValue::Text(SmallText::new(
1798 fsqlite_types::FRANKENSQLITE_SQLITE_VERSION,
1799 )))
1800 }
1801
1802 fn is_deterministic(&self) -> bool {
1803 false
1804 }
1805
1806 fn num_args(&self) -> i32 {
1807 0
1808 }
1809
1810 fn name(&self) -> &str {
1811 "sqlite_version"
1812 }
1813}
1814
1815pub struct SqliteSourceIdFunc;
1818
1819impl ScalarFunction for SqliteSourceIdFunc {
1820 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1821 Ok(SqliteValue::Text(SmallText::new(
1822 fsqlite_types::FRANKENSQLITE_SOURCE_ID,
1823 )))
1824 }
1825
1826 fn is_deterministic(&self) -> bool {
1827 false
1828 }
1829
1830 fn num_args(&self) -> i32 {
1831 0
1832 }
1833
1834 fn name(&self) -> &str {
1835 "sqlite_source_id"
1836 }
1837}
1838
1839pub struct SqliteCompileoptionUsedFunc;
1842
1843impl ScalarFunction for SqliteCompileoptionUsedFunc {
1844 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1845 if args[0].is_null() {
1846 return Ok(SqliteValue::Null);
1847 }
1848 let query = text_arg(&args[0]);
1849 Ok(SqliteValue::Integer(i64::from(sqlite_compileoption_used(
1850 query.as_ref(),
1851 ))))
1852 }
1853
1854 fn is_deterministic(&self) -> bool {
1855 false
1856 }
1857
1858 fn num_args(&self) -> i32 {
1859 1
1860 }
1861
1862 fn name(&self) -> &str {
1863 "sqlite_compileoption_used"
1864 }
1865}
1866
1867pub struct SqliteCompileoptionGetFunc;
1870
1871impl ScalarFunction for SqliteCompileoptionGetFunc {
1872 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1873 if args[0].is_null() {
1874 return Ok(SqliteValue::Null);
1875 }
1876 let n = args[0].to_integer();
1877 #[allow(clippy::cast_sign_loss)]
1878 match sqlite_compile_options().get(n as usize) {
1879 Some(opt) => Ok(SqliteValue::Text(SmallText::new(opt))),
1880 None => Ok(SqliteValue::Null),
1881 }
1882 }
1883
1884 fn is_deterministic(&self) -> bool {
1885 false
1886 }
1887
1888 fn num_args(&self) -> i32 {
1889 1
1890 }
1891
1892 fn name(&self) -> &str {
1893 "sqlite_compileoption_get"
1894 }
1895}
1896
1897pub struct LikeFunc;
1900
1901impl ScalarFunction for LikeFunc {
1902 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1903 if let Some(null) = null_propagate(args) {
1904 return Ok(null);
1905 }
1906 let pattern = text_arg(&args[0]);
1907 let string = text_arg(&args[1]);
1908 let escape = if args.len() > 2 && !args[2].is_null() {
1909 Some(single_char_escape(text_arg(&args[2]).as_ref())?)
1910 } else {
1911 None
1912 };
1913 let matched = like_match(pattern.as_ref(), string.as_ref(), escape);
1914 Ok(SqliteValue::Integer(i64::from(matched)))
1915 }
1916
1917 fn num_args(&self) -> i32 {
1918 -1 }
1920
1921 fn min_args(&self) -> i32 {
1922 2
1923 }
1924
1925 fn max_args(&self) -> Option<i32> {
1926 Some(3)
1927 }
1928
1929 fn name(&self) -> &str {
1930 "like"
1931 }
1932}
1933
1934#[cfg(test)]
1935mod like_func_pragma_tests {
1936 use super::{LikeFunc, case_sensitive_like_active, set_case_sensitive_like};
1937 use crate::ScalarFunction;
1938 use fsqlite_types::SqliteValue;
1939
1940 fn like(pattern: &str, text: &str) -> i64 {
1941 match LikeFunc
1942 .invoke(&[
1943 SqliteValue::Text(pattern.into()),
1944 SqliteValue::Text(text.into()),
1945 ])
1946 .unwrap()
1947 {
1948 SqliteValue::Integer(n) => n,
1949 other => panic!("expected integer, got {other:?}"),
1950 }
1951 }
1952
1953 #[test]
1954 fn like_honors_case_sensitive_like_thread_local() {
1955 set_case_sensitive_like(false);
1957 assert_eq!(like("a", "A"), 1);
1958 assert_eq!(like("A%", "apple"), 1);
1959 set_case_sensitive_like(true);
1961 assert!(case_sensitive_like_active());
1962 assert_eq!(like("a", "A"), 0);
1963 assert_eq!(like("A%", "apple"), 0);
1964 assert_eq!(like("A%", "Apple"), 1);
1965 set_case_sensitive_like(false);
1967 }
1968}
1969
1970fn single_char_escape(escape: &str) -> Result<char> {
1971 let mut chars = escape.chars();
1972 match (chars.next(), chars.next()) {
1973 (Some(ch), None) => Ok(ch),
1974 _ => Err(FrankenError::function_error(
1975 "ESCAPE expression must be a single character",
1976 )),
1977 }
1978}
1979
1980fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
1984 sql_like_cased(pattern, string, escape, case_sensitive_like_active())
1985}
1986
1987pub struct GlobFunc;
1990
1991impl ScalarFunction for GlobFunc {
1992 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1993 if let Some(null) = null_propagate(args) {
1994 return Ok(null);
1995 }
1996 let pattern = text_arg(&args[0]);
1997 let string = text_arg(&args[1]);
1998 let matched = glob_match(pattern.as_ref(), string.as_ref());
1999 Ok(SqliteValue::Integer(i64::from(matched)))
2000 }
2001
2002 fn num_args(&self) -> i32 {
2003 2
2004 }
2005
2006 fn name(&self) -> &str {
2007 "glob"
2008 }
2009}
2010
2011fn glob_match(pattern: &str, string: &str) -> bool {
2013 let pat: Vec<char> = pattern.chars().collect();
2014 let txt: Vec<char> = string.chars().collect();
2015 glob_match_inner(&pat, &txt, 0, 0)
2016}
2017
2018fn text_arg(value: &SqliteValue) -> Cow<'_, str> {
2019 match value.as_text_str() {
2020 Some(text) => Cow::Borrowed(text),
2021 None => Cow::Owned(value.to_text()),
2022 }
2023}
2024
2025fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
2026 while pi < pat.len() {
2027 match pat[pi] {
2028 '*' => {
2029 while pi < pat.len() && pat[pi] == '*' {
2030 pi += 1;
2031 }
2032 if pi >= pat.len() {
2033 return true;
2034 }
2035 for start in ti..=txt.len() {
2036 if glob_match_inner(pat, txt, pi, start) {
2037 return true;
2038 }
2039 }
2040 return false;
2041 }
2042 '?' => {
2043 if ti >= txt.len() {
2044 return false;
2045 }
2046 pi += 1;
2047 ti += 1;
2048 }
2049 '[' => {
2050 if ti >= txt.len() {
2051 return false;
2052 }
2053 pi += 1;
2054 let negate = pi < pat.len() && pat[pi] == '^';
2055 if negate {
2056 pi += 1;
2057 }
2058 let mut found = false;
2059 let mut first = true;
2060 while pi < pat.len() && (first || pat[pi] != ']') {
2061 first = false;
2062 if pi + 2 < pat.len() && pat[pi + 1] == '-' && pat[pi + 2] != ']' {
2069 let lo = pat[pi];
2070 let hi = pat[pi + 2];
2071 if txt[ti] >= lo && txt[ti] <= hi {
2072 found = true;
2073 }
2074 pi += 3;
2075 } else {
2076 if txt[ti] == pat[pi] {
2077 found = true;
2078 }
2079 pi += 1;
2080 }
2081 }
2082 if pi < pat.len() && pat[pi] == ']' {
2083 pi += 1;
2084 } else {
2085 return false;
2089 }
2090 if found == negate {
2091 return false;
2092 }
2093 ti += 1;
2094 }
2095 c => {
2096 if ti >= txt.len() || txt[ti] != c {
2097 return false;
2098 }
2099 pi += 1;
2100 ti += 1;
2101 }
2102 }
2103 }
2104 ti >= txt.len()
2105}
2106
2107pub struct UnistrFunc;
2110
2111const INVALID_UNISTR_ESCAPE: &str = "invalid Unicode escape";
2112
2113fn decode_unistr_escape(chars: &mut std::str::Chars<'_>, digits: usize) -> Result<char> {
2114 let mut lookahead = chars.clone();
2115 let mut codepoint = 0u32;
2116 for _ in 0..digits {
2117 let Some(ch) = lookahead.next() else {
2118 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2119 };
2120 let Some(digit) = hex_digit(ch) else {
2121 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2122 };
2123 codepoint = (codepoint << 4) | u32::from(digit);
2124 }
2125 for _ in 0..digits {
2126 let _digit = chars.next();
2127 }
2128 char::from_u32(codepoint).ok_or_else(|| FrankenError::function_error(INVALID_UNISTR_ESCAPE))
2129}
2130
2131impl ScalarFunction for UnistrFunc {
2132 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2133 if args[0].is_null() {
2134 return Ok(SqliteValue::Null);
2135 }
2136 let input = text_arg(&args[0]);
2137 let mut result = String::with_capacity(input.len());
2138 let mut chars = input.as_ref().chars();
2139 while let Some(ch) = chars.next() {
2140 if ch == '\\' {
2141 if chars.as_str().starts_with('\\') {
2143 let _ = chars.next();
2144 result.push('\\');
2145 continue;
2146 }
2147 let digits = if chars.as_str().starts_with('+') {
2148 let _plus = chars.next();
2150 6
2151 } else if chars.as_str().starts_with('u') {
2152 let _marker = chars.next();
2154 4
2155 } else if chars.as_str().starts_with('U') {
2156 let _marker = chars.next();
2158 8
2159 } else {
2160 4
2162 };
2163 result.push(decode_unistr_escape(&mut chars, digits)?);
2164 continue;
2165 }
2166 result.push(ch);
2167 }
2168 Ok(SqliteValue::Text(SmallText::from_string(result)))
2169 }
2170
2171 fn num_args(&self) -> i32 {
2172 1
2173 }
2174
2175 fn name(&self) -> &str {
2176 "unistr"
2177 }
2178}
2179
2180pub struct ChangesFunc;
2185
2186impl ScalarFunction for ChangesFunc {
2187 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2188 Ok(SqliteValue::Integer(LAST_CHANGES.get()))
2189 }
2190
2191 fn is_deterministic(&self) -> bool {
2192 false
2193 }
2194
2195 fn num_args(&self) -> i32 {
2196 0
2197 }
2198
2199 fn name(&self) -> &str {
2200 "changes"
2201 }
2202}
2203
2204pub struct TotalChangesFunc;
2205
2206impl ScalarFunction for TotalChangesFunc {
2207 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2208 Ok(SqliteValue::Integer(TOTAL_CHANGES.get()))
2209 }
2210
2211 fn is_deterministic(&self) -> bool {
2212 false
2213 }
2214
2215 fn num_args(&self) -> i32 {
2216 0
2217 }
2218
2219 fn name(&self) -> &str {
2220 "total_changes"
2221 }
2222}
2223
2224pub struct LastInsertRowidFunc;
2225
2226impl ScalarFunction for LastInsertRowidFunc {
2227 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2228 Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
2229 }
2230
2231 fn is_deterministic(&self) -> bool {
2232 false
2233 }
2234
2235 fn num_args(&self) -> i32 {
2236 0
2237 }
2238
2239 fn name(&self) -> &str {
2240 "last_insert_rowid"
2241 }
2242}
2243
2244#[allow(clippy::too_many_lines)]
2248pub fn register_builtins(registry: &mut FunctionRegistry) {
2249 registry.register_scalar(AbsFunc);
2251 registry.register_scalar(SignFunc);
2252 registry.register_scalar(RoundFunc);
2253 registry.register_scalar(RandomFunc);
2254 registry.register_scalar(RandomblobFunc);
2255 registry.register_scalar(ZeroblobFunc);
2256
2257 registry.register_scalar(LowerFunc);
2259 registry.register_scalar(UpperFunc);
2260 registry.register_scalar(LengthFunc);
2261 registry.register_scalar(OctetLengthFunc);
2262 registry.register_scalar(TrimFunc);
2263 registry.register_scalar(LtrimFunc);
2264 registry.register_scalar(RtrimFunc);
2265 registry.register_scalar(ReplaceFunc);
2266 registry.register_scalar(SubstrFunc);
2267 registry.register_scalar(InstrFunc);
2268 registry.register_scalar(CharFunc);
2269 registry.register_scalar(UnicodeFunc);
2270 registry.register_scalar(UnistrFunc);
2271 registry.register_scalar(HexFunc);
2272 registry.register_scalar(UnhexFunc);
2273 registry.register_scalar(QuoteFunc);
2274 registry.register_scalar(UnistrQuoteFunc);
2275 registry.register_scalar(SoundexFunc);
2276
2277 registry.register_scalar(TypeofFunc);
2279 registry.register_scalar(SubtypeFunc);
2280
2281 registry.register_scalar(CoalesceFunc);
2283 registry.register_scalar(IfnullFunc);
2284 registry.register_scalar(NullifFunc);
2285 registry.register_scalar(IifFunc);
2286
2287 registry.register_scalar(ConcatFunc);
2289 registry.register_scalar(ConcatWsFunc);
2290 registry.register_scalar(ScalarMaxFunc);
2291 registry.register_scalar(ScalarMinFunc);
2292
2293 registry.register_scalar(LikelihoodFunc);
2295 registry.register_scalar(LikelyFunc);
2296 registry.register_scalar(UnlikelyFunc);
2297
2298 registry.register_scalar(LikeFunc);
2300 registry.register_scalar(GlobFunc);
2301
2302 registry.register_slow_changing_scalar(SqliteVersionFunc);
2304 registry.register_slow_changing_scalar(SqliteSourceIdFunc);
2305 registry.register_slow_changing_scalar(SqliteCompileoptionUsedFunc);
2306 registry.register_slow_changing_scalar(SqliteCompileoptionGetFunc);
2307
2308 registry.register_scalar(ChangesFunc);
2310 registry.register_scalar(TotalChangesFunc);
2311 registry.register_scalar(LastInsertRowidFunc);
2312
2313 struct IfFunc;
2316 impl ScalarFunction for IfFunc {
2317 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2318 IifFunc.invoke(args)
2319 }
2320
2321 fn num_args(&self) -> i32 {
2322 -1 }
2324
2325 fn min_args(&self) -> i32 {
2326 2
2327 }
2328
2329 fn max_args(&self) -> Option<i32> {
2330 Some(3)
2331 }
2332
2333 fn name(&self) -> &str {
2334 "if"
2335 }
2336 }
2337 registry.register_scalar(IfFunc);
2338
2339 struct SubstringFunc;
2341 impl ScalarFunction for SubstringFunc {
2342 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2343 SubstrFunc.invoke(args)
2344 }
2345
2346 fn num_args(&self) -> i32 {
2347 -1
2348 }
2349
2350 fn min_args(&self) -> i32 {
2351 2
2352 }
2353
2354 fn max_args(&self) -> Option<i32> {
2355 Some(3)
2356 }
2357
2358 fn name(&self) -> &str {
2359 "substring"
2360 }
2361 }
2362 registry.register_scalar(SubstringFunc);
2363
2364 struct PrintfFunc;
2366 impl ScalarFunction for PrintfFunc {
2367 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2368 FormatFunc.invoke(args)
2369 }
2370
2371 fn num_args(&self) -> i32 {
2372 -1
2373 }
2374
2375 fn name(&self) -> &str {
2376 "printf"
2377 }
2378 }
2379 registry.register_scalar(FormatFunc);
2380 registry.register_scalar(PrintfFunc);
2381
2382 register_math_builtins(registry);
2384
2385 register_datetime_builtins(registry);
2387
2388 register_aggregate_builtins(registry);
2390}
2391
2392pub struct FormatFunc;
2395
2396impl ScalarFunction for FormatFunc {
2397 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2398 if args.is_empty() || args[0].is_null() {
2399 return Ok(SqliteValue::Null);
2400 }
2401 let fmt_str = args[0].to_text();
2402 if fmt_str.is_empty() {
2408 return Ok(SqliteValue::Null);
2409 }
2410 let params = &args[1..];
2411 let result = sqlite_format(&fmt_str, params)?;
2412 Ok(SqliteValue::Text(SmallText::from_string(result)))
2413 }
2414
2415 fn num_args(&self) -> i32 {
2416 -1
2417 }
2418
2419 fn name(&self) -> &str {
2420 "format"
2421 }
2422}
2423
2424fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<String> {
2427 let mut result = String::new();
2428 let chars: Vec<char> = fmt.chars().collect();
2429 let mut i = 0;
2430 let mut param_idx = 0;
2431
2432 while i < chars.len() {
2433 if chars[i] != '%' {
2434 result.push(chars[i]);
2435 i += 1;
2436 continue;
2437 }
2438 i += 1;
2439 if i >= chars.len() {
2440 break;
2441 }
2442
2443 let mut left_align = false;
2445 let mut show_sign = false;
2446 let mut space_sign = false;
2447 let mut zero_pad = false;
2448 let mut alt_form = false;
2449 let mut alt_form2 = false;
2450 loop {
2451 if i >= chars.len() {
2452 break;
2453 }
2454 match chars[i] {
2455 '-' => left_align = true,
2456 '+' => show_sign = true,
2457 ' ' => space_sign = true,
2458 '0' => zero_pad = true,
2459 '#' => alt_form = true,
2460 '!' => alt_form2 = true,
2463 _ => break,
2464 }
2465 i += 1;
2466 }
2467
2468 let mut width = 0usize;
2472 if i < chars.len() && chars[i] == '*' {
2473 i += 1;
2474 let w = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2475 param_idx += 1;
2476 if w < 0 {
2477 left_align = true;
2478 width = usize::try_from(w.unsigned_abs())
2479 .unwrap_or(0)
2480 .min(100_000_000);
2481 } else {
2482 width = usize::try_from(w).unwrap_or(0).min(100_000_000);
2483 }
2484 } else {
2485 while i < chars.len() && chars[i].is_ascii_digit() {
2486 width = width
2487 .saturating_mul(10)
2488 .saturating_add(chars[i] as usize - '0' as usize)
2489 .min(100_000_000); i += 1;
2491 }
2492 }
2493
2494 let mut precision = None;
2498 if i < chars.len() && chars[i] == '.' {
2499 i += 1;
2500 if i < chars.len() && chars[i] == '*' {
2501 i += 1;
2502 let p = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2503 param_idx += 1;
2504 if p >= 0 {
2505 precision = Some(usize::try_from(p).unwrap_or(0).min(100_000_000));
2506 }
2507 } else {
2508 let mut prec = 0usize;
2509 while i < chars.len() && chars[i].is_ascii_digit() {
2510 prec = prec
2511 .saturating_mul(10)
2512 .saturating_add(chars[i] as usize - '0' as usize)
2513 .min(100_000_000); i += 1;
2515 }
2516 precision = Some(prec);
2517 }
2518 }
2519
2520 if i >= chars.len() {
2521 break;
2522 }
2523
2524 let spec = chars[i];
2525 i += 1;
2526
2527 match spec {
2528 '%' => result.push('%'),
2529 'n' => {} 'd' | 'i' => {
2531 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2532 param_idx += 1;
2533 let formatted =
2534 format_integer(val, width, left_align, show_sign, space_sign, zero_pad);
2535 result.push_str(&formatted);
2536 }
2537 'u' => {
2538 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2541 param_idx += 1;
2542 #[allow(clippy::cast_sign_loss)]
2543 let digits = (val as u64).to_string();
2544 let padded = if zero_pad && width > digits.len() {
2545 format!("{}{}", "0".repeat(width - digits.len()), digits)
2546 } else {
2547 pad_string(&digits, width, left_align)
2548 };
2549 result.push_str(&padded);
2550 }
2551 'f' => {
2552 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2553 param_idx += 1;
2554 let formatted = if alt_form2 && val.is_finite() {
2555 finish_float_padding(
2559 &format!("{val:?}"),
2560 width,
2561 left_align,
2562 show_sign,
2563 space_sign,
2564 zero_pad,
2565 )
2566 } else {
2567 let prec = precision.unwrap_or(6);
2568 format_float_f(
2569 val, prec, width, left_align, show_sign, space_sign, zero_pad,
2570 )
2571 };
2572 result.push_str(&formatted);
2573 }
2574 'e' | 'E' => {
2575 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2576 param_idx += 1;
2577 let prec = precision.unwrap_or(6);
2578 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2579 {
2580 result.push_str(&s);
2581 } else {
2582 let raw = if spec == 'e' {
2583 format!("{val:.prec$e}")
2584 } else {
2585 format!("{val:.prec$E}")
2586 };
2587 let formatted = normalize_exponent(&raw);
2589 result.push_str(&finish_float_padding(
2590 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2591 ));
2592 }
2593 }
2594 'g' | 'G' => {
2595 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2596 param_idx += 1;
2597 let prec = precision.unwrap_or(6);
2598 let sig = prec.max(1);
2599 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2600 {
2601 result.push_str(&s);
2602 } else if alt_form2 {
2603 let mut s = format!("{val:?}");
2606 if spec == 'G' {
2607 s = s.to_uppercase();
2608 }
2609 result.push_str(&finish_float_padding(
2610 &s, width, left_align, show_sign, space_sign, zero_pad,
2611 ));
2612 } else {
2613 let formatted = format_float_g(val, sig, spec == 'G');
2614 result.push_str(&finish_float_padding(
2615 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2616 ));
2617 }
2618 }
2619 's' | 'z' => {
2620 let param = params.get(param_idx);
2621 param_idx += 1;
2622 let val = match param {
2623 Some(SqliteValue::Null) | None => String::new(),
2625 Some(v) => v.to_text(),
2626 };
2627 let truncated = if let Some(prec) = precision {
2632 if val.len() > prec {
2633 let mut end = prec;
2634 while end > 0 && !val.is_char_boundary(end) {
2635 end -= 1;
2636 }
2637 val[..end].to_owned()
2638 } else {
2639 val
2640 }
2641 } else {
2642 val
2643 };
2644 result.push_str(&pad_string(&truncated, width, left_align));
2645 }
2646 'q' => {
2647 let param = params.get(param_idx);
2649 param_idx += 1;
2650 match param {
2651 Some(SqliteValue::Null) | None => {
2653 result.push_str("(NULL)");
2654 }
2655 Some(v) => {
2656 let val = v.to_text();
2657 let escaped = val.replace('\'', "''");
2658 result.push_str(&escaped);
2659 }
2660 }
2661 }
2662 'Q' => {
2663 let param = params.get(param_idx);
2665 param_idx += 1;
2666 match param {
2667 Some(SqliteValue::Null) | None => result.push_str("NULL"),
2668 Some(v) => {
2669 let val = v.to_text();
2670 let escaped = val.replace('\'', "''");
2671 result.push('\'');
2672 result.push_str(&escaped);
2673 result.push('\'');
2674 }
2675 }
2676 }
2677 'w' => {
2678 let param = params.get(param_idx);
2682 param_idx += 1;
2683 if matches!(param, Some(SqliteValue::Null) | None) {
2684 } else {
2686 let val = param.map(SqliteValue::to_text).unwrap_or_default();
2687 let escaped = val.replace('"', "\"\"");
2688 result.push_str(&escaped);
2689 }
2690 }
2691 'x' | 'X' => {
2692 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2693 param_idx += 1;
2694 #[allow(clippy::cast_sign_loss)]
2695 let digits = if spec == 'x' {
2696 format!("{:x}", val as u64)
2697 } else {
2698 format!("{:X}", val as u64)
2699 };
2700 let prefix = if alt_form && val != 0 {
2702 if spec == 'x' { "0x" } else { "0X" }
2703 } else {
2704 ""
2705 };
2706 let padded = if zero_pad && width > digits.len() {
2711 let pad = "0".repeat(width - digits.len());
2712 format!("{prefix}{pad}{digits}")
2713 } else {
2714 pad_string(&format!("{prefix}{digits}"), width, left_align)
2715 };
2716 result.push_str(&padded);
2717 }
2718 'o' => {
2719 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2720 param_idx += 1;
2721 #[allow(clippy::cast_sign_loss)]
2722 let digits = format!("{:o}", val as u64);
2723 let prefix = if alt_form && val != 0 { "0" } else { "" };
2725 let padded = if zero_pad && width > digits.len() {
2728 let pad = "0".repeat(width - digits.len());
2729 format!("{prefix}{pad}{digits}")
2730 } else {
2731 pad_string(&format!("{prefix}{digits}"), width, left_align)
2732 };
2733 result.push_str(&padded);
2734 }
2735 'c' => {
2736 let param = params.get(param_idx);
2737 param_idx += 1;
2738 let text = match param {
2743 Some(SqliteValue::Null) | None => String::new(),
2744 Some(v) => v.to_text(),
2745 };
2746 if let Some(c) = text.chars().next() {
2747 result.push(c);
2748 }
2749 }
2750 _ => {
2751 result.push('%');
2753 result.push(spec);
2754 }
2755 }
2756 let _ = (left_align, show_sign, space_sign, zero_pad);
2758 }
2759 Ok(result)
2760}
2761
2762fn format_integer(
2763 val: i64,
2764 width: usize,
2765 left_align: bool,
2766 show_sign: bool,
2767 space_sign: bool,
2768 zero_pad: bool,
2769) -> String {
2770 let sign = if val < 0 {
2771 "-".to_owned()
2772 } else if show_sign {
2773 "+".to_owned()
2774 } else if space_sign {
2775 " ".to_owned()
2776 } else {
2777 String::new()
2778 };
2779 let digits = format!("{}", val.unsigned_abs());
2780 let body = format!("{sign}{digits}");
2781 if body.len() >= width {
2782 return body;
2783 }
2784 let pad = width - body.len();
2785 if left_align {
2786 format!("{body}{}", " ".repeat(pad))
2787 } else if zero_pad {
2788 format!("{sign}{}{digits}", "0".repeat(pad))
2789 } else {
2790 format!("{}{body}", " ".repeat(pad))
2791 }
2792}
2793
2794fn nonfinite_float_str(
2798 val: f64,
2799 width: usize,
2800 left_align: bool,
2801 show_sign: bool,
2802 space_sign: bool,
2803) -> Option<String> {
2804 let body = if val.is_nan() {
2805 "NaN".to_owned()
2806 } else if val.is_infinite() {
2807 let sign = if val < 0.0 {
2808 "-"
2809 } else if show_sign {
2810 "+"
2811 } else if space_sign {
2812 " "
2813 } else {
2814 ""
2815 };
2816 format!("{sign}Inf")
2817 } else {
2818 return None;
2819 };
2820 Some(pad_string(&body, width, left_align))
2821}
2822
2823fn finish_float_padding(
2827 body: &str,
2828 width: usize,
2829 left_align: bool,
2830 show_sign: bool,
2831 space_sign: bool,
2832 zero_pad: bool,
2833) -> String {
2834 let (sign, digits) = if let Some(rest) = body.strip_prefix('-') {
2835 ("-", rest)
2836 } else if show_sign {
2837 ("+", body)
2838 } else if space_sign {
2839 (" ", body)
2840 } else {
2841 ("", body)
2842 };
2843 let full_len = sign.len() + digits.len();
2844 if full_len >= width {
2845 return format!("{sign}{digits}");
2846 }
2847 let pad = width - full_len;
2848 if left_align {
2849 format!("{sign}{digits}{}", " ".repeat(pad))
2850 } else if zero_pad {
2851 format!("{sign}{}{digits}", "0".repeat(pad))
2852 } else {
2853 format!("{}{sign}{digits}", " ".repeat(pad))
2854 }
2855}
2856
2857fn format_float_f(
2858 val: f64,
2859 prec: usize,
2860 width: usize,
2861 left_align: bool,
2862 show_sign: bool,
2863 space_sign: bool,
2864 zero_pad: bool,
2865) -> String {
2866 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign) {
2867 return s;
2868 }
2869 let sign = if val.is_sign_negative() {
2871 "-".to_owned()
2872 } else if show_sign {
2873 "+".to_owned()
2874 } else if space_sign {
2875 " ".to_owned()
2876 } else {
2877 String::new()
2878 };
2879 let digits = format!("{:.prec$}", val.abs());
2880 let body = format!("{sign}{digits}");
2881 if body.len() >= width {
2882 return body;
2883 }
2884 let pad = width - body.len();
2885 if left_align {
2886 format!("{body}{}", " ".repeat(pad))
2887 } else if zero_pad {
2888 format!("{sign}{}{digits}", "0".repeat(pad))
2889 } else {
2890 format!("{}{body}", " ".repeat(pad))
2891 }
2892}
2893
2894fn pad_string(s: &str, width: usize, left_align: bool) -> String {
2895 if s.len() >= width {
2896 return s.to_owned();
2897 }
2898 let pad = width - s.len();
2899 if left_align {
2900 format!("{s}{}", " ".repeat(pad))
2901 } else {
2902 format!("{}{s}", " ".repeat(pad))
2903 }
2904}
2905
2906fn normalize_exponent(s: &str) -> String {
2909 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
2910 (&s[..pos], 'e', &s[pos + 1..])
2911 } else if let Some(pos) = s.find('E') {
2912 (&s[..pos], 'E', &s[pos + 1..])
2913 } else {
2914 return s.to_owned();
2915 };
2916 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
2917 ("-", rest)
2918 } else if let Some(rest) = exp_part.strip_prefix('+') {
2919 ("+", rest)
2920 } else {
2921 ("+", exp_part)
2922 };
2923 let padded = if digits.len() < 2 {
2924 format!("0{digits}")
2925 } else {
2926 digits.to_owned()
2927 };
2928 format!("{prefix}{e_char}{sign}{padded}")
2929}
2930
2931fn format_float_g(val: f64, sig: usize, upper: bool) -> String {
2933 if !val.is_finite() {
2934 return format!("{val}");
2935 }
2936 let val = if val == 0.0 { 0.0 } else { val };
2939 let e_str = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2940 let exp: i32 = e_str
2941 .rsplit_once('e')
2942 .and_then(|(_, e)| e.parse().ok())
2943 .unwrap_or(0);
2944 #[allow(clippy::cast_possible_wrap)]
2945 let formatted = if exp < -4 || exp >= sig as i32 {
2946 let s = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2947 let s = if upper { s.replace('e', "E") } else { s };
2948 let trimmed = if s.contains('.') {
2950 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
2951 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
2952 format!("{mantissa}{}", &s[e_pos..])
2953 } else {
2954 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2955 }
2956 } else {
2957 s
2958 };
2959 normalize_exponent(&trimmed)
2960 } else {
2961 let decimal_places = if exp >= 0 {
2962 sig.saturating_sub((exp + 1) as usize)
2963 } else {
2964 sig + exp.unsigned_abs() as usize - 1
2965 };
2966 let s = format!("{val:.decimal_places$}");
2967 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2968 };
2969 formatted
2970}
2971
2972#[cfg(test)]
2973#[allow(clippy::too_many_lines)]
2974mod tests {
2975 use super::*;
2976
2977 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
2978 f.invoke(&[v])
2979 }
2980
2981 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
2982 f.invoke(&[a, b])
2983 }
2984
2985 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
2986 let function = registry
2987 .find_scalar(name, arity)
2988 .expect("known scalar name with bad arity returns erroring scalar");
2989 let args = vec![SqliteValue::Null; arity.max(0) as usize];
2990 let err = function
2991 .invoke(&args)
2992 .expect_err("wrong arity should return function error");
2993 let expected = format!("wrong number of arguments to function {name}()");
2994 assert!(
2995 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
2996 "expected {expected:?}, got {err:?}"
2997 );
2998 }
2999
3000 #[test]
3001 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
3002 let original = get_change_tracking_state();
3003 let expected = ChangeTrackingState {
3004 last_insert_rowid: 17,
3005 last_changes: 23,
3006 total_changes: 42,
3007 };
3008
3009 set_change_tracking_state(expected);
3010 assert_eq!(get_change_tracking_state(), expected);
3011
3012 set_change_tracking_state(original);
3013 }
3014
3015 #[test]
3018 fn test_abs_positive() {
3019 assert_eq!(
3020 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
3021 SqliteValue::Integer(42)
3022 );
3023 }
3024
3025 #[test]
3026 fn test_abs_negative() {
3027 assert_eq!(
3028 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
3029 SqliteValue::Integer(42)
3030 );
3031 }
3032
3033 #[test]
3034 fn test_abs_null() {
3035 assert_eq!(
3036 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
3037 SqliteValue::Null
3038 );
3039 }
3040
3041 #[test]
3042 fn test_abs_min_i64_overflow() {
3043 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
3044 assert!(matches!(err, FrankenError::IntegerOverflow));
3045 }
3046
3047 #[test]
3048 fn test_abs_string_coercion() {
3049 assert_eq!(
3050 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
3051 SqliteValue::Float(7.5)
3052 );
3053 }
3054
3055 #[test]
3056 fn test_abs_whitespace_padded_text() {
3057 assert_eq!(
3059 invoke1(
3060 &AbsFunc,
3061 SqliteValue::Text(SmallText::from_string(" 42 "))
3062 )
3063 .unwrap(),
3064 SqliteValue::Float(42.0)
3065 );
3066 assert_eq!(
3067 invoke1(
3068 &AbsFunc,
3069 SqliteValue::Text(SmallText::from_string(" -7.5 "))
3070 )
3071 .unwrap(),
3072 SqliteValue::Float(7.5)
3073 );
3074 assert_eq!(
3075 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3076 SqliteValue::Float(0.0)
3077 );
3078 }
3079
3080 #[test]
3081 #[allow(clippy::approx_constant)]
3082 fn test_abs_float() {
3083 assert_eq!(
3084 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
3085 SqliteValue::Float(3.14)
3086 );
3087 }
3088
3089 #[test]
3092 fn test_char_basic() {
3093 let f = CharFunc;
3094 let result = f
3095 .invoke(&[
3096 SqliteValue::Integer(72),
3097 SqliteValue::Integer(101),
3098 SqliteValue::Integer(108),
3099 SqliteValue::Integer(108),
3100 SqliteValue::Integer(111),
3101 ])
3102 .unwrap();
3103 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
3104 }
3105
3106 #[test]
3107 fn test_char_null_skipped() {
3108 let f = CharFunc;
3109 let result = f
3111 .invoke(&[
3112 SqliteValue::Integer(65),
3113 SqliteValue::Null,
3114 SqliteValue::Integer(66),
3115 ])
3116 .unwrap();
3117 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
3118 }
3119
3120 #[test]
3121 fn test_char_invalid_scalar_values_use_replacement_character() {
3122 let f = CharFunc;
3123 let result = f
3124 .invoke(&[
3125 SqliteValue::Integer(-1),
3126 SqliteValue::Integer(65),
3127 SqliteValue::Integer(1_114_112),
3128 ])
3129 .unwrap();
3130 assert_eq!(
3131 result,
3132 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
3133 );
3134 }
3135
3136 #[test]
3139 fn test_coalesce_first_non_null() {
3140 let f = CoalesceFunc;
3141 let result = f
3142 .invoke(&[
3143 SqliteValue::Null,
3144 SqliteValue::Null,
3145 SqliteValue::Integer(3),
3146 SqliteValue::Integer(4),
3147 ])
3148 .unwrap();
3149 assert_eq!(result, SqliteValue::Integer(3));
3150 }
3151
3152 #[test]
3155 fn test_concat_null_as_empty() {
3156 let f = ConcatFunc;
3157 let result = f
3158 .invoke(&[
3159 SqliteValue::Null,
3160 SqliteValue::Text(SmallText::from_string("hello")),
3161 SqliteValue::Null,
3162 ])
3163 .unwrap();
3164 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
3165 }
3166
3167 #[test]
3168 #[ignore = "perf-only benchmark"]
3169 fn perf_concat_text_args() {
3170 use std::hint::black_box;
3171 use std::time::Instant;
3172
3173 const TEXT_ARGS: usize = 24;
3174 const INVOCATIONS: usize = 50_000;
3175 const REPEATS: usize = 5;
3176
3177 let f = ConcatFunc;
3178 let mut args = Vec::with_capacity(TEXT_ARGS);
3179 for _ in 0..TEXT_ARGS {
3180 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3181 }
3182
3183 let mut best_ns = u128::MAX;
3184 let mut result_len = 0usize;
3185 for _ in 0..REPEATS {
3186 let started = Instant::now();
3187 for _ in 0..INVOCATIONS {
3188 let result = black_box(
3189 f.invoke(black_box(args.as_slice()))
3190 .expect("concat benchmark invocation must succeed"),
3191 );
3192 result_len = match result {
3193 SqliteValue::Text(text) => text.len(),
3194 SqliteValue::Null
3195 | SqliteValue::Integer(_)
3196 | SqliteValue::Float(_)
3197 | SqliteValue::Blob(_) => 0,
3198 };
3199 }
3200 best_ns = best_ns.min(started.elapsed().as_nanos());
3201 }
3202
3203 println!(
3204 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3205 );
3206 }
3207
3208 #[test]
3211 fn test_concat_ws_null_skipped() {
3212 let f = ConcatWsFunc;
3213 let result = f
3214 .invoke(&[
3215 SqliteValue::Text(SmallText::from_string(",")),
3216 SqliteValue::Text(SmallText::from_string("a")),
3217 SqliteValue::Null,
3218 SqliteValue::Text(SmallText::from_string("b")),
3219 ])
3220 .unwrap();
3221 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
3222 }
3223
3224 #[test]
3225 fn test_concat_ws_empty_string_is_not_skipped() {
3226 let f = ConcatWsFunc;
3227 let result = f
3228 .invoke(&[
3229 SqliteValue::Text(SmallText::from_string("|")),
3230 SqliteValue::Text(SmallText::new("")),
3231 SqliteValue::Text(SmallText::from_string("x")),
3232 ])
3233 .unwrap();
3234 assert_eq!(result, SqliteValue::Text(SmallText::from_string("|x")));
3235 }
3236
3237 #[test]
3238 #[ignore = "perf-only benchmark"]
3239 fn perf_concat_ws_text_args() {
3240 use std::hint::black_box;
3241 use std::time::Instant;
3242
3243 const TEXT_ARGS: usize = 24;
3244 const INVOCATIONS: usize = 50_000;
3245 const REPEATS: usize = 5;
3246
3247 let f = ConcatWsFunc;
3248 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
3249 args.push(SqliteValue::Text(SmallText::from_string(",")));
3250 for _ in 0..TEXT_ARGS {
3251 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3252 }
3253
3254 let mut best_ns = u128::MAX;
3255 let mut result_len = 0usize;
3256 for _ in 0..REPEATS {
3257 let started = Instant::now();
3258 for _ in 0..INVOCATIONS {
3259 let result = black_box(
3260 f.invoke(black_box(args.as_slice()))
3261 .expect("concat_ws benchmark invocation must succeed"),
3262 );
3263 result_len = match result {
3264 SqliteValue::Text(text) => text.len(),
3265 SqliteValue::Null
3266 | SqliteValue::Integer(_)
3267 | SqliteValue::Float(_)
3268 | SqliteValue::Blob(_) => 0,
3269 };
3270 }
3271 best_ns = best_ns.min(started.elapsed().as_nanos());
3272 }
3273
3274 println!(
3275 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3276 );
3277 }
3278
3279 #[test]
3282 fn test_hex_blob() {
3283 let result = invoke1(
3284 &HexFunc,
3285 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
3286 )
3287 .unwrap();
3288 assert_eq!(
3289 result,
3290 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
3291 );
3292 }
3293
3294 #[test]
3295 fn test_hex_number_via_text() {
3296 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
3298 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
3299 }
3300
3301 #[test]
3302 #[ignore = "perf-only benchmark"]
3303 fn perf_hex_text_blob_args() {
3304 use std::hint::black_box;
3305 use std::time::Instant;
3306
3307 const BYTES: usize = 24;
3308 const INVOCATIONS: usize = 100_000;
3309 const REPEATS: usize = 5;
3310
3311 let f = HexFunc;
3312 let text_args = [SqliteValue::Text(SmallText::from_string(
3313 "payload payload sentinel",
3314 ))];
3315 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
3316
3317 let mut text_best_ns = u128::MAX;
3318 let mut blob_best_ns = u128::MAX;
3319 let mut text_result_len = 0usize;
3320 let mut blob_result_len = 0usize;
3321 for _ in 0..REPEATS {
3322 let started = Instant::now();
3323 for _ in 0..INVOCATIONS {
3324 let result = black_box(
3325 f.invoke(black_box(text_args.as_slice()))
3326 .expect("hex text benchmark invocation must succeed"),
3327 );
3328 text_result_len = match result {
3329 SqliteValue::Text(text) => text.len(),
3330 SqliteValue::Null
3331 | SqliteValue::Integer(_)
3332 | SqliteValue::Float(_)
3333 | SqliteValue::Blob(_) => 0,
3334 };
3335 }
3336 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
3337
3338 let started = Instant::now();
3339 for _ in 0..INVOCATIONS {
3340 let result = black_box(
3341 f.invoke(black_box(blob_args.as_slice()))
3342 .expect("hex blob benchmark invocation must succeed"),
3343 );
3344 blob_result_len = match result {
3345 SqliteValue::Text(text) => text.len(),
3346 SqliteValue::Null
3347 | SqliteValue::Integer(_)
3348 | SqliteValue::Float(_)
3349 | SqliteValue::Blob(_) => 0,
3350 };
3351 }
3352 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
3353 }
3354
3355 println!(
3356 "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}"
3357 );
3358 }
3359
3360 #[test]
3363 fn test_iif_true() {
3364 let f = IifFunc;
3365 let result = f
3366 .invoke(&[
3367 SqliteValue::Integer(1),
3368 SqliteValue::Text(SmallText::from_string("yes")),
3369 SqliteValue::Text(SmallText::from_string("no")),
3370 ])
3371 .unwrap();
3372 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3373 }
3374
3375 #[test]
3376 fn test_iif_false() {
3377 let f = IifFunc;
3378 let result = f
3379 .invoke(&[
3380 SqliteValue::Integer(0),
3381 SqliteValue::Text(SmallText::from_string("yes")),
3382 SqliteValue::Text(SmallText::from_string("no")),
3383 ])
3384 .unwrap();
3385 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
3386 }
3387
3388 #[test]
3389 fn test_iif_whitespace_padded_text_truthy() {
3390 let f = IifFunc;
3393 let result = f
3394 .invoke(&[
3395 SqliteValue::Text(SmallText::from_string(" 5 ")),
3396 SqliteValue::Text(SmallText::from_string("yes")),
3397 SqliteValue::Text(SmallText::from_string("no")),
3398 ])
3399 .unwrap();
3400 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3401 }
3402
3403 #[test]
3406 fn test_ifnull_non_null() {
3407 assert_eq!(
3408 invoke2(
3409 &IfnullFunc,
3410 SqliteValue::Integer(5),
3411 SqliteValue::Integer(10)
3412 )
3413 .unwrap(),
3414 SqliteValue::Integer(5)
3415 );
3416 }
3417
3418 #[test]
3419 fn test_ifnull_null() {
3420 assert_eq!(
3421 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
3422 SqliteValue::Integer(10)
3423 );
3424 }
3425
3426 #[test]
3429 fn test_instr_found() {
3430 assert_eq!(
3431 invoke2(
3432 &InstrFunc,
3433 SqliteValue::Text(SmallText::from_string("hello world")),
3434 SqliteValue::Text(SmallText::from_string("world"))
3435 )
3436 .unwrap(),
3437 SqliteValue::Integer(7)
3438 );
3439 }
3440
3441 #[test]
3442 fn test_instr_not_found() {
3443 assert_eq!(
3444 invoke2(
3445 &InstrFunc,
3446 SqliteValue::Text(SmallText::from_string("hello")),
3447 SqliteValue::Text(SmallText::from_string("xyz"))
3448 )
3449 .unwrap(),
3450 SqliteValue::Integer(0)
3451 );
3452 }
3453
3454 #[test]
3455 fn test_instr_empty_needle_returns_one() {
3456 assert_eq!(
3458 invoke2(
3459 &InstrFunc,
3460 SqliteValue::Text(SmallText::from_string("hello")),
3461 SqliteValue::Text(SmallText::new(""))
3462 )
3463 .unwrap(),
3464 SqliteValue::Integer(1)
3465 );
3466 }
3467
3468 #[test]
3469 fn test_instr_empty_haystack_returns_zero() {
3470 assert_eq!(
3471 invoke2(
3472 &InstrFunc,
3473 SqliteValue::Text(SmallText::new("")),
3474 SqliteValue::Text(SmallText::from_string("x"))
3475 )
3476 .unwrap(),
3477 SqliteValue::Integer(0)
3478 );
3479 }
3480
3481 #[test]
3482 fn test_instr_blob_empty_needle_returns_one() {
3483 assert_eq!(
3485 invoke2(
3486 &InstrFunc,
3487 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
3488 SqliteValue::Blob(Arc::from([].as_slice()))
3489 )
3490 .unwrap(),
3491 SqliteValue::Integer(1)
3492 );
3493 }
3494
3495 #[test]
3496 #[ignore = "perf-only benchmark"]
3497 fn perf_instr_text_args() {
3498 use std::hint::black_box;
3499 use std::time::Instant;
3500
3501 const INVOCATIONS: usize = 100_000;
3502 const REPEATS: usize = 5;
3503
3504 let f = InstrFunc;
3505 let args = [
3506 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
3507 SqliteValue::Text(SmallText::from_string("sentinel")),
3508 ];
3509
3510 let mut best_ns = u128::MAX;
3511 let mut result_value = 0i64;
3512 for _ in 0..REPEATS {
3513 let started = Instant::now();
3514 for _ in 0..INVOCATIONS {
3515 let result = black_box(
3516 f.invoke(black_box(args.as_slice()))
3517 .expect("instr benchmark invocation must succeed"),
3518 );
3519 result_value = match result {
3520 SqliteValue::Integer(value) => value,
3521 SqliteValue::Null
3522 | SqliteValue::Float(_)
3523 | SqliteValue::Text(_)
3524 | SqliteValue::Blob(_) => 0,
3525 };
3526 }
3527 best_ns = best_ns.min(started.elapsed().as_nanos());
3528 }
3529
3530 println!(
3531 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
3532 );
3533 }
3534
3535 #[test]
3538 fn test_length_text_chars() {
3539 assert_eq!(
3541 invoke1(
3542 &LengthFunc,
3543 SqliteValue::Text(SmallText::from_string("café"))
3544 )
3545 .unwrap(),
3546 SqliteValue::Integer(4)
3547 );
3548 }
3549
3550 #[test]
3551 fn test_length_text_stops_at_nul() {
3552 assert_eq!(
3553 invoke1(
3554 &LengthFunc,
3555 SqliteValue::Text(SmallText::from_string("A\0B"))
3556 )
3557 .unwrap(),
3558 SqliteValue::Integer(1)
3559 );
3560 assert_eq!(
3561 invoke1(
3562 &LengthFunc,
3563 SqliteValue::Text(SmallText::from_string("\0A"))
3564 )
3565 .unwrap(),
3566 SqliteValue::Integer(0)
3567 );
3568 }
3569
3570 #[test]
3571 fn test_length_blob_bytes() {
3572 assert_eq!(
3573 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
3574 SqliteValue::Integer(2)
3575 );
3576 }
3577
3578 #[test]
3581 fn test_octet_length_multibyte() {
3582 assert_eq!(
3584 invoke1(
3585 &OctetLengthFunc,
3586 SqliteValue::Text(SmallText::from_string("café"))
3587 )
3588 .unwrap(),
3589 SqliteValue::Integer(5)
3590 );
3591 }
3592
3593 #[test]
3596 fn test_lower_ascii() {
3597 assert_eq!(
3598 invoke1(
3599 &LowerFunc,
3600 SqliteValue::Text(SmallText::from_string("HELLO"))
3601 )
3602 .unwrap(),
3603 SqliteValue::Text(SmallText::from_string("hello"))
3604 );
3605 }
3606
3607 #[test]
3608 fn test_upper_ascii() {
3609 assert_eq!(
3610 invoke1(
3611 &UpperFunc,
3612 SqliteValue::Text(SmallText::from_string("hello"))
3613 )
3614 .unwrap(),
3615 SqliteValue::Text(SmallText::from_string("HELLO"))
3616 );
3617 }
3618
3619 #[test]
3622 fn test_trim_default() {
3623 let f = TrimFunc;
3624 assert_eq!(
3625 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
3626 .unwrap(),
3627 SqliteValue::Text(SmallText::from_string("hello"))
3628 );
3629 }
3630
3631 #[test]
3632 fn test_ltrim_default() {
3633 let f = LtrimFunc;
3634 assert_eq!(
3635 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
3636 .unwrap(),
3637 SqliteValue::Text(SmallText::from_string("hello"))
3638 );
3639 }
3640
3641 #[test]
3642 fn test_ltrim_custom() {
3643 let f = LtrimFunc;
3644 assert_eq!(
3645 f.invoke(&[
3646 SqliteValue::Text(SmallText::from_string("xxhello")),
3647 SqliteValue::Text(SmallText::from_string("x")),
3648 ])
3649 .unwrap(),
3650 SqliteValue::Text(SmallText::from_string("hello"))
3651 );
3652 }
3653
3654 #[test]
3655 #[ignore = "perf-only benchmark"]
3656 fn perf_trim_text_args() {
3657 use std::hint::black_box;
3658 use std::time::Instant;
3659
3660 const INVOCATIONS: usize = 100_000;
3661 const REPEATS: usize = 5;
3662
3663 let trim = TrimFunc;
3664 let ltrim = LtrimFunc;
3665 let rtrim = RtrimFunc;
3666 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
3667 let custom_args = [
3668 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
3669 SqliteValue::Text(SmallText::from_string("x")),
3670 ];
3671
3672 let mut trim_best_ns = u128::MAX;
3673 let mut ltrim_best_ns = u128::MAX;
3674 let mut rtrim_best_ns = u128::MAX;
3675 let mut custom_best_ns = u128::MAX;
3676 let mut result_len = 0usize;
3677
3678 for _ in 0..REPEATS {
3679 let started = Instant::now();
3680 for _ in 0..INVOCATIONS {
3681 let result = black_box(
3682 trim.invoke(black_box(default_args.as_slice()))
3683 .expect("trim benchmark invocation must succeed"),
3684 );
3685 result_len = match result {
3686 SqliteValue::Text(text) => text.len(),
3687 SqliteValue::Null
3688 | SqliteValue::Integer(_)
3689 | SqliteValue::Float(_)
3690 | SqliteValue::Blob(_) => 0,
3691 };
3692 }
3693 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
3694
3695 let started = Instant::now();
3696 for _ in 0..INVOCATIONS {
3697 let result = black_box(
3698 ltrim
3699 .invoke(black_box(default_args.as_slice()))
3700 .expect("ltrim benchmark invocation must succeed"),
3701 );
3702 result_len = match result {
3703 SqliteValue::Text(text) => text.len(),
3704 SqliteValue::Null
3705 | SqliteValue::Integer(_)
3706 | SqliteValue::Float(_)
3707 | SqliteValue::Blob(_) => 0,
3708 };
3709 }
3710 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
3711
3712 let started = Instant::now();
3713 for _ in 0..INVOCATIONS {
3714 let result = black_box(
3715 rtrim
3716 .invoke(black_box(default_args.as_slice()))
3717 .expect("rtrim benchmark invocation must succeed"),
3718 );
3719 result_len = match result {
3720 SqliteValue::Text(text) => text.len(),
3721 SqliteValue::Null
3722 | SqliteValue::Integer(_)
3723 | SqliteValue::Float(_)
3724 | SqliteValue::Blob(_) => 0,
3725 };
3726 }
3727 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
3728
3729 let started = Instant::now();
3730 for _ in 0..INVOCATIONS {
3731 let result = black_box(
3732 trim.invoke(black_box(custom_args.as_slice()))
3733 .expect("custom trim benchmark invocation must succeed"),
3734 );
3735 result_len = match result {
3736 SqliteValue::Text(text) => text.len(),
3737 SqliteValue::Null
3738 | SqliteValue::Integer(_)
3739 | SqliteValue::Float(_)
3740 | SqliteValue::Blob(_) => 0,
3741 };
3742 }
3743 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
3744 }
3745
3746 println!(
3747 "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}"
3748 );
3749 }
3750
3751 #[test]
3754 fn test_nullif_equal() {
3755 assert_eq!(
3756 invoke2(
3757 &NullifFunc,
3758 SqliteValue::Integer(5),
3759 SqliteValue::Integer(5)
3760 )
3761 .unwrap(),
3762 SqliteValue::Null
3763 );
3764 }
3765
3766 #[test]
3767 fn test_nullif_different() {
3768 assert_eq!(
3769 invoke2(
3770 &NullifFunc,
3771 SqliteValue::Integer(5),
3772 SqliteValue::Integer(3)
3773 )
3774 .unwrap(),
3775 SqliteValue::Integer(5)
3776 );
3777 }
3778
3779 #[test]
3782 fn test_typeof_each() {
3783 assert_eq!(
3784 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
3785 SqliteValue::Text(SmallText::from_string("null"))
3786 );
3787 assert_eq!(
3788 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
3789 SqliteValue::Text(SmallText::from_string("integer"))
3790 );
3791 assert_eq!(
3792 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
3793 SqliteValue::Text(SmallText::from_string("real"))
3794 );
3795 assert_eq!(
3796 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
3797 SqliteValue::Text(SmallText::from_string("text"))
3798 );
3799 assert_eq!(
3800 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
3801 SqliteValue::Text(SmallText::from_string("blob"))
3802 );
3803 }
3804
3805 #[test]
3808 fn test_subtype_null_returns_zero() {
3809 assert_eq!(
3810 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
3811 SqliteValue::Integer(0)
3812 );
3813 }
3814
3815 #[test]
3818 fn test_replace_basic() {
3819 let f = ReplaceFunc;
3820 assert_eq!(
3821 f.invoke(&[
3822 SqliteValue::Text(SmallText::from_string("hello world")),
3823 SqliteValue::Text(SmallText::from_string("world")),
3824 SqliteValue::Text(SmallText::from_string("earth")),
3825 ])
3826 .unwrap(),
3827 SqliteValue::Text(SmallText::from_string("hello earth"))
3828 );
3829 }
3830
3831 #[test]
3832 fn test_replace_empty_y() {
3833 let f = ReplaceFunc;
3834 assert_eq!(
3835 f.invoke(&[
3836 SqliteValue::Text(SmallText::from_string("hello")),
3837 SqliteValue::Text(SmallText::new("")),
3838 SqliteValue::Text(SmallText::from_string("x")),
3839 ])
3840 .unwrap(),
3841 SqliteValue::Text(SmallText::from_string("hello"))
3842 );
3843 }
3844
3845 #[test]
3846 #[ignore = "perf-only benchmark"]
3847 fn perf_replace_text_args() {
3848 use std::hint::black_box;
3849 use std::time::Instant;
3850
3851 const INVOCATIONS: usize = 100_000;
3852 const REPEATS: usize = 5;
3853
3854 let f = ReplaceFunc;
3855 let args = [
3856 SqliteValue::Text(SmallText::from_string("payload payload payload")),
3857 SqliteValue::Text(SmallText::from_string("zz")),
3858 SqliteValue::Text(SmallText::from_string("replacement")),
3859 ];
3860
3861 let mut best_ns = u128::MAX;
3862 let mut result_len = 0usize;
3863 for _ in 0..REPEATS {
3864 let started = Instant::now();
3865 for _ in 0..INVOCATIONS {
3866 let result = black_box(
3867 f.invoke(black_box(args.as_slice()))
3868 .expect("replace benchmark invocation must succeed"),
3869 );
3870 result_len = match result {
3871 SqliteValue::Text(text) => text.len(),
3872 SqliteValue::Null
3873 | SqliteValue::Integer(_)
3874 | SqliteValue::Float(_)
3875 | SqliteValue::Blob(_) => 0,
3876 };
3877 }
3878 best_ns = best_ns.min(started.elapsed().as_nanos());
3879 }
3880
3881 println!(
3882 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3883 );
3884 }
3885
3886 #[test]
3889 #[allow(clippy::float_cmp)]
3890 fn test_round_half_away() {
3891 assert_eq!(
3893 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
3894 SqliteValue::Float(3.0)
3895 );
3896 assert_eq!(
3897 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
3898 SqliteValue::Float(-3.0)
3899 );
3900 }
3901
3902 #[test]
3903 #[allow(clippy::float_cmp, clippy::approx_constant)]
3904 fn test_round_precision() {
3905 assert_eq!(
3906 RoundFunc
3907 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
3908 .unwrap(),
3909 SqliteValue::Float(3.14)
3910 );
3911 }
3912
3913 #[test]
3914 #[allow(clippy::float_cmp)]
3915 fn test_round_extreme_n_clamped() {
3916 assert_eq!(
3918 RoundFunc
3919 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
3920 .unwrap(),
3921 RoundFunc
3922 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
3923 .unwrap(),
3924 );
3925 assert_eq!(
3927 RoundFunc
3928 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
3929 .unwrap(),
3930 SqliteValue::Float(3.0)
3931 );
3932 let result = RoundFunc
3934 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
3935 .unwrap();
3936 if let SqliteValue::Float(v) = result {
3937 assert!(!v.is_nan(), "round must never return NaN");
3938 }
3939 }
3940
3941 #[test]
3942 #[allow(clippy::float_cmp)]
3943 fn test_round_large_value_no_fractional() {
3944 let big = 9_007_199_254_740_993.0_f64;
3946 assert_eq!(
3947 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
3948 SqliteValue::Float(big)
3949 );
3950 assert_eq!(
3951 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
3952 SqliteValue::Float(-big)
3953 );
3954 }
3955
3956 #[test]
3959 fn test_sign_positive() {
3960 assert_eq!(
3961 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
3962 SqliteValue::Integer(1)
3963 );
3964 }
3965
3966 #[test]
3967 fn test_sign_negative() {
3968 assert_eq!(
3969 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
3970 SqliteValue::Integer(-1)
3971 );
3972 }
3973
3974 #[test]
3975 fn test_sign_zero() {
3976 assert_eq!(
3977 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
3978 SqliteValue::Integer(0)
3979 );
3980 }
3981
3982 #[test]
3983 fn test_sign_null() {
3984 assert_eq!(
3985 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
3986 SqliteValue::Null
3987 );
3988 }
3989
3990 #[test]
3991 fn test_sign_non_numeric() {
3992 assert_eq!(
3994 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3995 SqliteValue::Null
3996 );
3997 }
3998
3999 #[test]
4000 fn test_sign_whitespace_padded_text() {
4001 assert_eq!(
4004 invoke1(
4005 &SignFunc,
4006 SqliteValue::Text(SmallText::from_string(" 5 "))
4007 )
4008 .unwrap(),
4009 SqliteValue::Integer(1)
4010 );
4011 assert_eq!(
4012 invoke1(
4013 &SignFunc,
4014 SqliteValue::Text(SmallText::from_string(" -3.14 "))
4015 )
4016 .unwrap(),
4017 SqliteValue::Integer(-1)
4018 );
4019 }
4020
4021 #[test]
4022 fn test_sign_unicode_space_and_blob_return_null() {
4023 assert_eq!(
4024 invoke1(
4025 &SignFunc,
4026 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
4027 )
4028 .unwrap(),
4029 SqliteValue::Null
4030 );
4031 assert_eq!(
4032 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
4033 SqliteValue::Null
4034 );
4035 }
4036
4037 #[test]
4038 fn test_sign_nan_inf_text_returns_null() {
4039 for s in &[
4042 "NaN",
4043 "nan",
4044 "inf",
4045 "-inf",
4046 "Infinity",
4047 "-Infinity",
4048 "INF",
4049 "+nan",
4050 "+inf",
4051 ] {
4052 assert_eq!(
4053 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
4054 SqliteValue::Null,
4055 "sign('{s}') should be NULL"
4056 );
4057 }
4058 }
4059
4060 #[test]
4061 fn test_sign_numeric_overflow_to_infinity() {
4062 assert_eq!(
4065 invoke1(
4066 &SignFunc,
4067 SqliteValue::Text(SmallText::from_string("1e999"))
4068 )
4069 .unwrap(),
4070 SqliteValue::Integer(1)
4071 );
4072 assert_eq!(
4073 invoke1(
4074 &SignFunc,
4075 SqliteValue::Text(SmallText::from_string("-1e999"))
4076 )
4077 .unwrap(),
4078 SqliteValue::Integer(-1)
4079 );
4080 assert_eq!(
4082 invoke1(
4083 &SignFunc,
4084 SqliteValue::Text(SmallText::from_string("1e-999"))
4085 )
4086 .unwrap(),
4087 SqliteValue::Integer(0)
4088 );
4089 }
4090
4091 #[test]
4092 fn test_sign_float_nan_returns_null() {
4093 assert_eq!(
4095 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
4096 SqliteValue::Null
4097 );
4098 }
4099
4100 #[test]
4103 fn test_scalar_max_null() {
4104 let f = ScalarMaxFunc;
4105 let result = f
4106 .invoke(&[
4107 SqliteValue::Integer(1),
4108 SqliteValue::Null,
4109 SqliteValue::Integer(3),
4110 ])
4111 .unwrap();
4112 assert_eq!(result, SqliteValue::Null);
4113 }
4114
4115 #[test]
4116 fn test_scalar_max_values() {
4117 let f = ScalarMaxFunc;
4118 let result = f
4119 .invoke(&[
4120 SqliteValue::Integer(3),
4121 SqliteValue::Integer(1),
4122 SqliteValue::Integer(2),
4123 ])
4124 .unwrap();
4125 assert_eq!(result, SqliteValue::Integer(3));
4126 }
4127
4128 #[test]
4129 fn test_scalar_min_null() {
4130 let f = ScalarMinFunc;
4131 let result = f
4132 .invoke(&[
4133 SqliteValue::Integer(1),
4134 SqliteValue::Null,
4135 SqliteValue::Integer(3),
4136 ])
4137 .unwrap();
4138 assert_eq!(result, SqliteValue::Null);
4139 }
4140
4141 #[test]
4142 fn test_scalar_min_selects_later_equal_value_while_max_keeps_first() {
4143 let min = ScalarMinFunc;
4144 let max = ScalarMaxFunc;
4145 let numeric = [SqliteValue::Integer(1), SqliteValue::Float(1.0)];
4146 assert!(matches!(
4147 min.invoke(&numeric).unwrap(),
4148 SqliteValue::Float(value) if value == 1.0
4149 ));
4150 assert_eq!(max.invoke(&numeric).unwrap(), SqliteValue::Integer(1));
4151
4152 let text = [
4153 SqliteValue::Text(SmallText::new("a")),
4154 SqliteValue::Text(SmallText::new("A")),
4155 ];
4156 let nocase = crate::collation::NoCaseCollation;
4157 assert_eq!(
4158 min.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4159 SqliteValue::Text(SmallText::new("A"))
4160 );
4161 assert_eq!(
4162 max.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4163 SqliteValue::Text(SmallText::new("a"))
4164 );
4165 }
4166
4167 #[test]
4170 fn test_quote_text() {
4171 assert_eq!(
4172 invoke1(
4173 &QuoteFunc,
4174 SqliteValue::Text(SmallText::from_string("it's"))
4175 )
4176 .unwrap(),
4177 SqliteValue::Text(SmallText::from_string("'it''s'"))
4178 );
4179 }
4180
4181 #[test]
4182 fn test_quote_null() {
4183 assert_eq!(
4184 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
4185 SqliteValue::Text(SmallText::from_string("NULL"))
4186 );
4187 }
4188
4189 #[test]
4190 fn test_quote_blob() {
4191 assert_eq!(
4192 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
4193 SqliteValue::Text(SmallText::from_string("X'AB'"))
4194 );
4195 }
4196
4197 #[test]
4198 fn test_quote_text_truncates_at_first_nul() {
4199 assert_eq!(
4200 invoke1(
4201 &QuoteFunc,
4202 SqliteValue::Text(SmallText::from_string("A\0B"))
4203 )
4204 .unwrap(),
4205 SqliteValue::Text(SmallText::from_string("'A'"))
4206 );
4207 }
4208
4209 #[test]
4210 fn test_unistr_quote_plain_text_matches_quote() {
4211 assert_eq!(
4212 invoke1(
4213 &UnistrQuoteFunc,
4214 SqliteValue::Text(SmallText::from_string("it's"))
4215 )
4216 .unwrap(),
4217 SqliteValue::Text(SmallText::from_string("'it''s'"))
4218 );
4219 }
4220
4221 #[test]
4222 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
4223 assert_eq!(
4224 invoke1(
4225 &UnistrQuoteFunc,
4226 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
4227 )
4228 .unwrap(),
4229 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
4230 );
4231 }
4232
4233 #[test]
4234 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
4235 assert_eq!(
4236 invoke1(
4237 &UnistrQuoteFunc,
4238 SqliteValue::Text(SmallText::from_string("A\0\nB"))
4239 )
4240 .unwrap(),
4241 SqliteValue::Text(SmallText::from_string("'A'"))
4242 );
4243 }
4244
4245 #[test]
4246 fn test_unistr_decodes_backslash_and_unicode_escapes() {
4247 assert_eq!(
4248 invoke1(
4249 &UnistrFunc,
4250 SqliteValue::Text(SmallText::from_string(
4251 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
4252 ))
4253 )
4254 .unwrap(),
4255 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
4256 );
4257 }
4258
4259 #[test]
4260 fn test_unistr_invalid_escape_returns_error() {
4261 for input in [
4262 "\\u12xz",
4263 "\\12xz",
4264 "\\+00xz",
4265 "\\",
4266 "\\x",
4267 "\\U00110000",
4268 "\\D800",
4269 ] {
4270 let err = invoke1(
4271 &UnistrFunc,
4272 SqliteValue::Text(SmallText::from_string(input)),
4273 )
4274 .unwrap_err();
4275 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
4276 }
4277 }
4278
4279 #[test]
4280 #[ignore = "perf-only benchmark"]
4281 fn perf_unistr_text_args() {
4282 use std::hint::black_box;
4283 use std::time::Instant;
4284
4285 const INVOCATIONS: usize = 500_000;
4286 const REPEATS: usize = 7;
4287
4288 let f = UnistrFunc;
4289 let plain_args = [SqliteValue::Text(SmallText::from_string(
4290 "plain unicode payload",
4291 ))];
4292 let escaped_args = [SqliteValue::Text(SmallText::from_string(
4293 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
4294 ))];
4295
4296 let mut plain_best_ns = u128::MAX;
4297 let mut escaped_best_ns = u128::MAX;
4298 let mut checksum = 0usize;
4299 for _ in 0..REPEATS {
4300 let started = Instant::now();
4301 for _ in 0..INVOCATIONS {
4302 let result = black_box(
4303 f.invoke(black_box(plain_args.as_slice()))
4304 .expect("unistr plain benchmark invocation must succeed"),
4305 );
4306 if let SqliteValue::Text(text) = result {
4307 checksum = checksum.wrapping_add(text.len());
4308 }
4309 }
4310 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4311
4312 let started = Instant::now();
4313 for _ in 0..INVOCATIONS {
4314 let result = black_box(
4315 f.invoke(black_box(escaped_args.as_slice()))
4316 .expect("unistr escaped benchmark invocation must succeed"),
4317 );
4318 if let SqliteValue::Text(text) = result {
4319 checksum = checksum.wrapping_add(text.len());
4320 }
4321 }
4322 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
4323 }
4324
4325 println!(
4326 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
4327 );
4328 }
4329
4330 #[test]
4333 fn test_random_range() {
4334 let f = RandomFunc;
4335 let result = f.invoke(&[]).unwrap();
4336 assert!(matches!(result, SqliteValue::Integer(_)));
4337 }
4338
4339 #[test]
4342 fn test_randomblob_length() {
4343 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
4344 match result {
4345 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
4346 other => unreachable!("expected blob, got {other:?}"),
4347 }
4348 }
4349
4350 #[test]
4351 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
4352 for arg in [
4353 SqliteValue::Null,
4354 SqliteValue::Integer(0),
4355 SqliteValue::Integer(-5),
4356 ] {
4357 let result = invoke1(&RandomblobFunc, arg).unwrap();
4358 match result {
4359 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
4360 other => unreachable!("expected one-byte blob, got {other:?}"),
4361 }
4362 }
4363 }
4364
4365 #[test]
4368 fn test_zeroblob_length() {
4369 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
4370 match result {
4371 SqliteValue::Blob(b) => {
4372 assert_eq!(b.len(), 100);
4373 assert!(b.iter().all(|&x| x == 0));
4374 }
4375 other => unreachable!("expected blob, got {other:?}"),
4376 }
4377 }
4378
4379 #[test]
4382 fn test_unhex_valid() {
4383 let result = invoke1(
4384 &UnhexFunc,
4385 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
4386 )
4387 .unwrap();
4388 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
4389 }
4390
4391 #[test]
4392 fn test_unhex_invalid() {
4393 let result = invoke1(
4394 &UnhexFunc,
4395 SqliteValue::Text(SmallText::from_string("ZZZZ")),
4396 )
4397 .unwrap();
4398 assert_eq!(result, SqliteValue::Null);
4399 }
4400
4401 #[test]
4402 fn test_unhex_ignore_chars() {
4403 let f = UnhexFunc;
4404 let result = f
4405 .invoke(&[
4406 SqliteValue::Text(SmallText::from_string("48-65-6C")),
4407 SqliteValue::Text(SmallText::from_string("-")),
4408 ])
4409 .unwrap();
4410 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
4411 }
4412
4413 #[test]
4414 fn test_unhex_ignore_chars_only_between_byte_pairs() {
4415 let f = UnhexFunc;
4416 let result = f
4417 .invoke(&[
4418 SqliteValue::Text(SmallText::from_string("AB CD")),
4419 SqliteValue::Text(SmallText::from_string(" ")),
4420 ])
4421 .unwrap();
4422 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
4423
4424 let result = f
4425 .invoke(&[
4426 SqliteValue::Text(SmallText::from_string("A BCD")),
4427 SqliteValue::Text(SmallText::from_string(" ")),
4428 ])
4429 .unwrap();
4430 assert_eq!(result, SqliteValue::Null);
4431 }
4432
4433 #[test]
4434 fn test_unhex_null_ignore_argument_returns_null() {
4435 let f = UnhexFunc;
4436 let result = f
4437 .invoke(&[
4438 SqliteValue::Text(SmallText::from_string("41")),
4439 SqliteValue::Null,
4440 ])
4441 .unwrap();
4442 assert_eq!(result, SqliteValue::Null);
4443 }
4444
4445 #[test]
4446 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
4447 let f = UnhexFunc;
4448 let result = f
4449 .invoke(&[
4450 SqliteValue::Text(SmallText::from_string("41")),
4451 SqliteValue::Text(SmallText::from_string("4")),
4452 ])
4453 .unwrap();
4454 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
4455 }
4456
4457 #[test]
4458 #[ignore = "perf-only benchmark"]
4459 fn perf_unhex_text_args() {
4460 use std::hint::black_box;
4461 use std::time::Instant;
4462
4463 const INVOCATIONS: usize = 300_000;
4464 const REPEATS: usize = 7;
4465
4466 let f = UnhexFunc;
4467 let plain_args = [SqliteValue::Text(SmallText::from_string(
4468 "48656C6C6F776F726C64",
4469 ))];
4470 let ignore_args = [
4471 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
4472 SqliteValue::Text(SmallText::from_string("-")),
4473 ];
4474 let mut plain_best_ns = u128::MAX;
4475 let mut ignore_best_ns = u128::MAX;
4476 let mut checksum = 0usize;
4477
4478 for _ in 0..REPEATS {
4479 let started = Instant::now();
4480 for _ in 0..INVOCATIONS {
4481 let result = black_box(
4482 f.invoke(black_box(plain_args.as_slice()))
4483 .expect("unhex benchmark invocation must succeed"),
4484 );
4485 if let SqliteValue::Blob(blob) = result {
4486 checksum = checksum.wrapping_add(blob.len());
4487 }
4488 }
4489 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4490
4491 let started = Instant::now();
4492 for _ in 0..INVOCATIONS {
4493 let result = black_box(
4494 f.invoke(black_box(ignore_args.as_slice()))
4495 .expect("unhex ignore benchmark invocation must succeed"),
4496 );
4497 if let SqliteValue::Blob(blob) = result {
4498 checksum = checksum.wrapping_add(blob.len());
4499 }
4500 }
4501 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
4502 }
4503
4504 println!(
4505 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
4506 );
4507 }
4508
4509 #[test]
4512 fn test_unicode_first_char() {
4513 assert_eq!(
4514 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
4515 SqliteValue::Integer(65)
4516 );
4517 }
4518
4519 #[test]
4520 fn test_unicode_text_stops_at_nul() {
4521 assert_eq!(
4522 invoke1(
4523 &UnicodeFunc,
4524 SqliteValue::Text(SmallText::from_string("\0A"))
4525 )
4526 .unwrap(),
4527 SqliteValue::Null
4528 );
4529 assert_eq!(
4530 invoke1(
4531 &UnicodeFunc,
4532 SqliteValue::Text(SmallText::from_string("A\0"))
4533 )
4534 .unwrap(),
4535 SqliteValue::Integer(65)
4536 );
4537 }
4538
4539 #[test]
4540 fn test_unicode_blob_uses_sqlite_utf8_reader() {
4541 let cases: &[(&[u8], SqliteValue)] = &[
4542 (&[0x00, 0x41], SqliteValue::Null),
4543 (&[0x80], SqliteValue::Integer(128)),
4544 (&[0xC2, 0x80], SqliteValue::Integer(128)),
4545 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
4546 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
4547 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
4548 ];
4549
4550 for (bytes, expected) in cases {
4551 assert_eq!(
4552 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
4553 expected.clone()
4554 );
4555 }
4556 }
4557
4558 #[test]
4559 #[ignore = "perf-only benchmark"]
4560 fn perf_unicode_text_arg() {
4561 use std::hint::black_box;
4562 use std::time::Instant;
4563
4564 const INVOCATIONS: usize = 1_000_000;
4565 const REPEATS: usize = 7;
4566
4567 let f = UnicodeFunc;
4568 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
4569 let mut text_best_ns = u128::MAX;
4570 let mut checksum = 0i64;
4571
4572 for _ in 0..REPEATS {
4573 let started = Instant::now();
4574 for _ in 0..INVOCATIONS {
4575 let result = black_box(
4576 f.invoke(black_box(args.as_slice()))
4577 .expect("unicode benchmark invocation must succeed"),
4578 );
4579 if let SqliteValue::Integer(codepoint) = result {
4580 checksum = checksum.wrapping_add(codepoint);
4581 }
4582 }
4583 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4584 }
4585
4586 println!(
4587 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4588 );
4589 }
4590
4591 #[test]
4594 fn test_soundex_basic() {
4595 assert_eq!(
4596 invoke1(
4597 &SoundexFunc,
4598 SqliteValue::Text(SmallText::from_string("Robert"))
4599 )
4600 .unwrap(),
4601 SqliteValue::Text(SmallText::from_string("R163"))
4602 );
4603 }
4604
4605 #[test]
4606 #[ignore = "perf-only benchmark"]
4607 fn perf_soundex_text_arg() {
4608 use std::hint::black_box;
4609 use std::time::Instant;
4610
4611 const INVOCATIONS: usize = 1_000_000;
4612 const REPEATS: usize = 7;
4613
4614 let f = SoundexFunc;
4615 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
4616 let mut text_best_ns = u128::MAX;
4617 let mut checksum = 0usize;
4618
4619 for _ in 0..REPEATS {
4620 let started = Instant::now();
4621 for _ in 0..INVOCATIONS {
4622 let result = black_box(
4623 f.invoke(black_box(args.as_slice()))
4624 .expect("soundex benchmark invocation must succeed"),
4625 );
4626 if let SqliteValue::Text(text) = result {
4627 checksum = checksum.wrapping_add(text.len());
4628 }
4629 }
4630 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4631 }
4632
4633 println!(
4634 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4635 );
4636 }
4637
4638 #[test]
4641 fn test_substr_basic() {
4642 let f = SubstrFunc;
4643 assert_eq!(
4644 f.invoke(&[
4645 SqliteValue::Text(SmallText::from_string("hello")),
4646 SqliteValue::Integer(2),
4647 SqliteValue::Integer(3),
4648 ])
4649 .unwrap(),
4650 SqliteValue::Text(SmallText::from_string("ell"))
4651 );
4652 }
4653
4654 #[test]
4655 fn test_substr_start_zero_quirk() {
4656 let f = SubstrFunc;
4658 let result = f
4659 .invoke(&[
4660 SqliteValue::Text(SmallText::from_string("hello")),
4661 SqliteValue::Integer(0),
4662 SqliteValue::Integer(3),
4663 ])
4664 .unwrap();
4665 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
4666 }
4667
4668 #[test]
4669 fn test_substr_negative_start() {
4670 let f = SubstrFunc;
4672 let result = f
4673 .invoke(&[
4674 SqliteValue::Text(SmallText::from_string("hello")),
4675 SqliteValue::Integer(-2),
4676 ])
4677 .unwrap();
4678 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
4679 }
4680
4681 #[test]
4682 fn test_substr_negative_length() {
4683 let f = SubstrFunc;
4684 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4685 let i = SqliteValue::Integer;
4686 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
4688 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
4690 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
4692 }
4693
4694 #[test]
4695 fn test_substr_negative_start_negative_length() {
4696 let f = SubstrFunc;
4697 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4698 let i = SqliteValue::Integer;
4699 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
4701 }
4702
4703 #[test]
4704 fn test_substr_edge_cases() {
4705 let f = SubstrFunc;
4706 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4707 let i = SqliteValue::Integer;
4708 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
4710 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
4712 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
4714 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
4716 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
4718 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
4720 }
4721
4722 #[test]
4723 fn test_substr_blob_negative_length() {
4724 let f = SubstrFunc;
4725 let i = SqliteValue::Integer;
4726 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
4727 assert_eq!(
4729 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
4730 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
4731 );
4732 }
4733
4734 #[test]
4737 fn test_like_case_insensitive() {
4738 assert_eq!(
4739 invoke2(
4740 &LikeFunc,
4741 SqliteValue::Text(SmallText::from_string("ABC")),
4742 SqliteValue::Text(SmallText::from_string("abc"))
4743 )
4744 .unwrap(),
4745 SqliteValue::Integer(1)
4746 );
4747 }
4748
4749 #[test]
4750 fn test_like_escape() {
4751 let f = LikeFunc;
4752 let result = f
4753 .invoke(&[
4754 SqliteValue::Text(SmallText::from_string("10\\%")),
4755 SqliteValue::Text(SmallText::from_string("10%")),
4756 SqliteValue::Text(SmallText::from_string("\\")),
4757 ])
4758 .unwrap();
4759 assert_eq!(result, SqliteValue::Integer(1));
4760 }
4761
4762 #[test]
4763 fn test_like_escape_rejects_empty_string() {
4764 let err = LikeFunc
4765 .invoke(&[
4766 SqliteValue::Text(SmallText::from_string("a")),
4767 SqliteValue::Text(SmallText::from_string("a")),
4768 SqliteValue::Text(SmallText::new("")),
4769 ])
4770 .unwrap_err();
4771 assert!(
4772 err.to_string()
4773 .contains("ESCAPE expression must be a single character")
4774 );
4775 }
4776
4777 #[test]
4778 fn test_like_escape_rejects_multi_character_string() {
4779 let err = LikeFunc
4780 .invoke(&[
4781 SqliteValue::Text(SmallText::from_string("a")),
4782 SqliteValue::Text(SmallText::from_string("a")),
4783 SqliteValue::Text(SmallText::from_string("xx")),
4784 ])
4785 .unwrap_err();
4786 assert!(
4787 err.to_string()
4788 .contains("ESCAPE expression must be a single character")
4789 );
4790 }
4791
4792 #[test]
4793 fn test_like_percent() {
4794 assert_eq!(
4795 invoke2(
4796 &LikeFunc,
4797 SqliteValue::Text(SmallText::from_string("%ell%")),
4798 SqliteValue::Text(SmallText::from_string("Hello"))
4799 )
4800 .unwrap(),
4801 SqliteValue::Integer(1)
4802 );
4803 }
4804
4805 #[test]
4808 fn test_glob_star() {
4809 assert_eq!(
4810 invoke2(
4811 &GlobFunc,
4812 SqliteValue::Text(SmallText::from_string("*.txt")),
4813 SqliteValue::Text(SmallText::from_string("file.txt"))
4814 )
4815 .unwrap(),
4816 SqliteValue::Integer(1)
4817 );
4818 }
4819
4820 #[test]
4821 fn test_glob_case_sensitive() {
4822 assert_eq!(
4823 invoke2(
4824 &GlobFunc,
4825 SqliteValue::Text(SmallText::from_string("ABC")),
4826 SqliteValue::Text(SmallText::from_string("abc"))
4827 )
4828 .unwrap(),
4829 SqliteValue::Integer(0)
4830 );
4831 }
4832
4833 #[test]
4834 fn test_glob_unterminated_character_class_does_not_match() {
4835 assert_eq!(
4838 invoke2(
4839 &GlobFunc,
4840 SqliteValue::Text(SmallText::from_string("[a")),
4841 SqliteValue::Text(SmallText::from_string("a"))
4842 )
4843 .unwrap(),
4844 SqliteValue::Integer(0)
4845 );
4846 assert_eq!(
4848 invoke2(
4849 &GlobFunc,
4850 SqliteValue::Text(SmallText::from_string("[a]")),
4851 SqliteValue::Text(SmallText::from_string("a"))
4852 )
4853 .unwrap(),
4854 SqliteValue::Integer(1)
4855 );
4856 }
4857
4858 #[test]
4859 fn test_glob_trailing_dash_in_character_class_is_literal() {
4860 let glob = |pattern: &str, text: &str| {
4868 invoke2(
4869 &GlobFunc,
4870 SqliteValue::Text(SmallText::from_string(pattern)),
4871 SqliteValue::Text(SmallText::from_string(text)),
4872 )
4873 .unwrap()
4874 };
4875 assert_eq!(
4877 glob("*[^A-Za-z0-9._:-]*", "peer_abc/123"),
4878 SqliteValue::Integer(1)
4879 );
4880 assert_eq!(
4882 glob("*[^A-Za-z0-9._:-]*", "peer_a.b:c-"),
4883 SqliteValue::Integer(0)
4884 );
4885 assert_eq!(glob("[a-c-]", "-"), SqliteValue::Integer(1));
4887 assert_eq!(glob("[a-c-]", "b"), SqliteValue::Integer(1));
4888 assert_eq!(glob("[a-c-]", "d"), SqliteValue::Integer(0));
4889 assert_eq!(glob("[-a]", "-"), SqliteValue::Integer(1));
4891 assert_eq!(glob("[-a]", "b"), SqliteValue::Integer(0));
4892 }
4893
4894 #[test]
4895 fn test_iif_two_argument_form() {
4896 let f = IifFunc;
4898 assert_eq!(
4899 f.invoke(&[
4900 SqliteValue::Integer(1),
4901 SqliteValue::Text(SmallText::from_string("y")),
4902 ])
4903 .unwrap(),
4904 SqliteValue::Text(SmallText::from_string("y"))
4905 );
4906 assert_eq!(
4907 f.invoke(&[
4908 SqliteValue::Integer(0),
4909 SqliteValue::Text(SmallText::from_string("y")),
4910 ])
4911 .unwrap(),
4912 SqliteValue::Null
4913 );
4914 }
4915
4916 #[test]
4917 fn test_format_g_negative_zero() {
4918 let f = FormatFunc;
4920 assert_eq!(
4921 f.invoke(&[
4922 SqliteValue::Text(SmallText::from_string("%g")),
4923 SqliteValue::Float(-0.0),
4924 ])
4925 .unwrap(),
4926 SqliteValue::Text(SmallText::from_string("0"))
4927 );
4928 }
4929
4930 #[test]
4931 fn test_format_altform2_flag() {
4932 let f = FormatFunc;
4936 assert_eq!(
4937 f.invoke(&[
4938 SqliteValue::Text(SmallText::from_string("%!5s")),
4939 SqliteValue::Text(SmallText::from_string("ab")),
4940 ])
4941 .unwrap(),
4942 SqliteValue::Text(SmallText::from_string(" ab"))
4943 );
4944 assert_eq!(
4945 f.invoke(&[
4946 SqliteValue::Text(SmallText::from_string("%!d")),
4947 SqliteValue::Integer(3),
4948 ])
4949 .unwrap(),
4950 SqliteValue::Text(SmallText::from_string("3"))
4951 );
4952 assert_eq!(
4953 f.invoke(&[
4954 SqliteValue::Text(SmallText::from_string("%!f")),
4955 SqliteValue::Float(0.1),
4956 ])
4957 .unwrap(),
4958 SqliteValue::Text(SmallText::from_string("0.1"))
4959 );
4960 }
4961
4962 #[test]
4965 fn test_format_specifiers() {
4966 let f = FormatFunc;
4967 let result = f
4968 .invoke(&[
4969 SqliteValue::Text(SmallText::from_string("%d %s")),
4970 SqliteValue::Integer(42),
4971 SqliteValue::Text(SmallText::from_string("hello")),
4972 ])
4973 .unwrap();
4974 assert_eq!(
4975 result,
4976 SqliteValue::Text(SmallText::from_string("42 hello"))
4977 );
4978 }
4979
4980 #[test]
4981 fn test_format_n_noop() {
4982 let f = FormatFunc;
4983 let result = f
4985 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
4986 .unwrap();
4987 assert_eq!(
4988 result,
4989 SqliteValue::Text(SmallText::from_string("beforeafter"))
4990 );
4991 }
4992
4993 #[test]
4994 fn test_format_alternate_form_hex_octal() {
4995 let cases: &[(&str, i64, &str)] = &[
4997 ("%#x", 255, "0xff"),
4998 ("%#X", 255, "0XFF"),
4999 ("%#o", 64, "0100"),
5000 ("%#x", 0, "0"), ("%#o", 0, "0"), ("%#5x", 255, " 0xff"), ("%#8x", 255, " 0xff"),
5004 ("%#08x", 255, "0x000000ff"), ("%-#8x", 255, "0xff "), ("%-08x", 255, "000000ff"), ("%#08o", 64, "000000100"),
5008 ("%#x", -1, "0xffffffffffffffff"),
5009 ];
5010 for (fmt, arg, want) in cases {
5011 let f = FormatFunc;
5012 let result = f
5013 .invoke(&[
5014 SqliteValue::Text(SmallText::from_string(*fmt)),
5015 SqliteValue::Integer(*arg),
5016 ])
5017 .unwrap();
5018 assert_eq!(
5019 result,
5020 SqliteValue::Text(SmallText::from_string((*want).to_owned())),
5021 "format({fmt:?}, {arg})"
5022 );
5023 }
5024 }
5025
5026 #[test]
5027 fn test_format_empty_string_is_null() {
5028 let f = FormatFunc;
5032 assert_eq!(
5033 f.invoke(&[SqliteValue::Text(SmallText::from_string(""))])
5034 .unwrap(),
5035 SqliteValue::Null
5036 );
5037 assert_eq!(
5039 f.invoke(&[
5040 SqliteValue::Text(SmallText::from_string("%s")),
5041 SqliteValue::Null,
5042 ])
5043 .unwrap(),
5044 SqliteValue::Text(SmallText::from_string(String::new()))
5045 );
5046 }
5047
5048 #[test]
5051 fn test_sqlite_version_format() {
5052 let result = SqliteVersionFunc.invoke(&[]).unwrap();
5053 match result {
5054 SqliteValue::Text(v) => {
5055 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
5056 }
5057 other => unreachable!("expected text, got {other:?}"),
5058 }
5059 }
5060
5061 #[test]
5062 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
5063 let func = SqliteCompileoptionUsedFunc;
5064 assert_eq!(
5065 invoke1(
5066 &func,
5067 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
5068 )
5069 .unwrap(),
5070 SqliteValue::Integer(1)
5071 );
5072 let expected_icu_enabled = i64::from(cfg!(feature = "ext-icu"));
5073 assert_eq!(
5074 invoke1(
5075 &func,
5076 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
5077 )
5078 .unwrap(),
5079 SqliteValue::Integer(expected_icu_enabled)
5080 );
5081 assert_eq!(
5082 invoke1(
5083 &func,
5084 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
5085 )
5086 .unwrap(),
5087 SqliteValue::Integer(expected_icu_enabled)
5088 );
5089 assert_eq!(
5090 invoke1(
5091 &func,
5092 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
5093 )
5094 .unwrap(),
5095 SqliteValue::Integer(1)
5096 );
5097 assert_eq!(
5098 invoke1(
5099 &func,
5100 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
5101 )
5102 .unwrap(),
5103 SqliteValue::Integer(0)
5104 );
5105 assert_eq!(
5106 invoke1(&func, SqliteValue::Null).unwrap(),
5107 SqliteValue::Null
5108 );
5109 }
5110
5111 #[test]
5112 #[ignore = "perf-only benchmark"]
5113 fn perf_compileoption_used_text_args() {
5114 use std::hint::black_box;
5115 use std::time::Instant;
5116
5117 const INVOCATIONS: usize = 1_000_000;
5118 const REPEATS: usize = 7;
5119
5120 let f = SqliteCompileoptionUsedFunc;
5121 let present_args = [SqliteValue::Text(SmallText::from_string(
5122 "SQLITE_ENABLE_ICU",
5123 ))];
5124 let absent_args = [SqliteValue::Text(SmallText::from_string(
5125 "ENABLE_NOT_PRESENT",
5126 ))];
5127
5128 let mut present_best_ns = u128::MAX;
5129 let mut absent_best_ns = u128::MAX;
5130 let mut checksum = 0i64;
5131 for _ in 0..REPEATS {
5132 let started = Instant::now();
5133 for _ in 0..INVOCATIONS {
5134 let result = black_box(
5135 f.invoke(black_box(present_args.as_slice()))
5136 .expect("compileoption present benchmark invocation must succeed"),
5137 );
5138 if let SqliteValue::Integer(value) = result {
5139 checksum = checksum.wrapping_add(value);
5140 }
5141 }
5142 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
5143
5144 let started = Instant::now();
5145 for _ in 0..INVOCATIONS {
5146 let result = black_box(
5147 f.invoke(black_box(absent_args.as_slice()))
5148 .expect("compileoption absent benchmark invocation must succeed"),
5149 );
5150 if let SqliteValue::Integer(value) = result {
5151 checksum = checksum.wrapping_add(value);
5152 }
5153 }
5154 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
5155 }
5156
5157 println!(
5158 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
5159 );
5160 }
5161
5162 #[test]
5163 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
5164 let func = SqliteCompileoptionGetFunc;
5165 for (index, option) in sqlite_compile_options().iter().enumerate() {
5166 assert_eq!(
5167 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
5168 SqliteValue::Text(SmallText::new(option))
5169 );
5170 }
5171 assert_eq!(
5172 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
5173 SqliteValue::Null
5174 );
5175 assert_eq!(
5176 invoke1(
5177 &func,
5178 SqliteValue::Integer(sqlite_compile_options().len() as i64)
5179 )
5180 .unwrap(),
5181 SqliteValue::Null
5182 );
5183 }
5184
5185 #[test]
5188 fn test_register_builtins_all_present() {
5189 let mut registry = FunctionRegistry::new();
5190 register_builtins(&mut registry);
5191
5192 assert!(registry.find_scalar("abs", 1).is_some());
5194 assert!(registry.find_scalar("typeof", 1).is_some());
5195 assert!(registry.find_scalar("length", 1).is_some());
5196 assert!(registry.find_scalar("lower", 1).is_some());
5197 assert!(registry.find_scalar("upper", 1).is_some());
5198 assert!(registry.find_scalar("hex", 1).is_some());
5199 assert!(registry.find_scalar("coalesce", 3).is_some());
5200 assert!(registry.find_scalar("concat", 2).is_some());
5201 assert!(registry.find_scalar("like", 2).is_some());
5202 assert!(registry.find_scalar("glob", 2).is_some());
5203 assert!(registry.find_scalar("round", 1).is_some());
5204 assert!(registry.find_scalar("substr", 2).is_some());
5205 assert!(registry.find_scalar("substring", 3).is_some());
5206 assert!(registry.find_scalar("sqlite_version", 0).is_some());
5207 assert!(registry.find_scalar("iif", 3).is_some());
5208 assert!(registry.find_scalar("if", 3).is_some());
5209 assert!(registry.find_scalar("format", 1).is_some());
5210 assert!(registry.find_scalar("printf", 1).is_some());
5211 assert!(registry.find_scalar("max", 2).is_some());
5212 assert!(registry.find_scalar("min", 2).is_some());
5213 assert!(registry.find_scalar("sign", 1).is_some());
5214 assert!(registry.find_scalar("random", 0).is_some());
5215
5216 assert!(registry.find_scalar("concat_ws", 3).is_some());
5218 assert!(registry.find_scalar("octet_length", 1).is_some());
5219 assert!(registry.find_scalar("unhex", 1).is_some());
5220 assert!(registry.find_scalar("timediff", 2).is_some());
5221 assert!(registry.find_scalar("unistr", 1).is_some());
5222 assert!(registry.find_scalar("unistr_quote", 1).is_some());
5223
5224 assert!(registry.find_aggregate("median", 1).is_some());
5226 assert!(registry.find_aggregate("percentile", 2).is_some());
5227 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
5228 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
5229
5230 assert!(registry.find_scalar("load_extension", 1).is_none());
5232 assert!(registry.find_scalar("load_extension", 2).is_none());
5233 }
5234
5235 #[test]
5236 fn test_register_builtins_rejects_invalid_variadic_arities() {
5237 let mut registry = FunctionRegistry::new();
5238 register_builtins(&mut registry);
5239
5240 for (name, too_few, valid, too_many) in [
5241 ("coalesce", 1, 2, None),
5242 ("concat", 0, 1, None),
5243 ("concat_ws", 1, 2, None),
5244 ("trim", 0, 1, Some(3)),
5245 ("ltrim", 0, 1, Some(3)),
5246 ("rtrim", 0, 1, Some(3)),
5247 ("round", 0, 1, Some(3)),
5248 ("unhex", 0, 1, Some(3)),
5249 ("substr", 1, 2, Some(4)),
5250 ("substring", 1, 2, Some(4)),
5251 ("max", 0, 1, None),
5252 ("min", 0, 1, None),
5253 ] {
5254 assert_wrong_arg_count(®istry, name, too_few);
5255 assert!(
5256 registry.find_scalar(name, valid).is_some(),
5257 "{name}/{valid} should resolve"
5258 );
5259 if let Some(arity) = too_many {
5260 assert_wrong_arg_count(®istry, name, arity);
5261 }
5262 }
5263
5264 assert!(registry.find_scalar("char", 0).is_some());
5265 assert!(registry.find_scalar("format", 0).is_some());
5266 assert!(registry.find_scalar("printf", 0).is_some());
5267 }
5268
5269 #[test]
5270 fn test_e2e_registry_invoke_through_lookup() {
5271 let mut registry = FunctionRegistry::new();
5272 register_builtins(&mut registry);
5273
5274 let abs = registry.find_scalar("ABS", 1).unwrap();
5276 assert_eq!(
5277 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
5278 SqliteValue::Integer(42)
5279 );
5280
5281 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
5283 assert_eq!(
5284 typeof_fn
5285 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
5286 .unwrap(),
5287 SqliteValue::Text(SmallText::from_string("text"))
5288 );
5289
5290 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
5292 assert_eq!(
5293 coalesce
5294 .invoke(&[
5295 SqliteValue::Null,
5296 SqliteValue::Null,
5297 SqliteValue::Integer(42),
5298 SqliteValue::Integer(99),
5299 ])
5300 .unwrap(),
5301 SqliteValue::Integer(42)
5302 );
5303 }
5304
5305 #[test]
5308 fn test_nondeterministic_functions_flagged() {
5309 assert!(!RandomFunc.is_deterministic());
5312 assert!(!RandomblobFunc.is_deterministic());
5313 assert!(!ChangesFunc.is_deterministic());
5314 assert!(!TotalChangesFunc.is_deterministic());
5315 assert!(!LastInsertRowidFunc.is_deterministic());
5316 assert!(!SqliteVersionFunc.is_deterministic());
5317 assert!(!SqliteSourceIdFunc.is_deterministic());
5318 assert!(!SqliteCompileoptionUsedFunc.is_deterministic());
5319 assert!(!SqliteCompileoptionGetFunc.is_deterministic());
5320 }
5321
5322 #[test]
5323 fn test_deterministic_functions_flagged() {
5324 assert!(AbsFunc.is_deterministic());
5326 assert!(LengthFunc.is_deterministic());
5327 assert!(TypeofFunc.is_deterministic());
5328 assert!(UpperFunc.is_deterministic());
5329 assert!(LowerFunc.is_deterministic());
5330 assert!(HexFunc.is_deterministic());
5331 assert!(CoalesceFunc.is_deterministic());
5332 assert!(IifFunc.is_deterministic());
5333 }
5334
5335 #[test]
5336 fn test_random_produces_different_values() {
5337 let a = RandomFunc.invoke(&[]).unwrap();
5340 let b = RandomFunc.invoke(&[]).unwrap();
5341 assert_ne!(a.as_integer(), b.as_integer());
5344 }
5345
5346 #[test]
5347 fn test_registry_nondeterministic_lookup() {
5348 let mut registry = FunctionRegistry::default();
5349 register_builtins(&mut registry);
5350
5351 let random = registry.find_scalar("random", 0).unwrap();
5353 assert!(!random.is_deterministic());
5354
5355 let changes = registry.find_scalar("changes", 0).unwrap();
5356 assert!(!changes.is_deterministic());
5357
5358 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
5359 assert!(!lir.is_deterministic());
5360
5361 for (name, num_args) in [
5362 ("sqlite_version", 0),
5363 ("sqlite_source_id", 0),
5364 ("sqlite_compileoption_used", 1),
5365 ("sqlite_compileoption_get", 1),
5366 ] {
5367 assert_eq!(
5368 registry.scalar_is_deterministic(name, num_args),
5369 Some(false),
5370 "{name} must publish non-deterministic registry metadata"
5371 );
5372 }
5373
5374 let abs = registry.find_scalar("abs", 1).unwrap();
5376 assert!(abs.is_deterministic());
5377 }
5378
5379 #[test]
5380 fn test_registry_builtin_query_constancy_metadata() {
5381 use crate::{ScalarQueryConstancy, ScalarSchemaSafety};
5382
5383 let mut registry = FunctionRegistry::default();
5384 register_builtins(&mut registry);
5385
5386 for (name, num_args) in [
5387 ("sqlite_version", 0),
5388 ("sqlite_source_id", 0),
5389 ("sqlite_compileoption_used", 1),
5390 ("sqlite_compileoption_get", 1),
5391 ] {
5392 let resolved = registry.resolve_scalar(name, num_args).unwrap();
5393 assert_eq!(resolved.schema_safety(), ScalarSchemaSafety::Never);
5394 assert_eq!(
5395 resolved.query_constancy(),
5396 ScalarQueryConstancy::SlowChanging,
5397 "{name}/{num_args} must match SQLite's slow-changing metadata"
5398 );
5399 }
5400
5401 for (name, num_args) in [
5402 ("date", 0),
5403 ("time", 0),
5404 ("datetime", 0),
5405 ("julianday", 0),
5406 ("unixepoch", 0),
5407 ("strftime", 1),
5408 ("timediff", 2),
5409 ] {
5410 let resolved = registry.resolve_scalar(name, num_args).unwrap();
5411 assert_eq!(
5412 resolved.schema_safety(),
5413 ScalarSchemaSafety::DateTimeConditional
5414 );
5415 assert_eq!(
5416 resolved.query_constancy(),
5417 ScalarQueryConstancy::SlowChanging,
5418 "{name}/{num_args} must be query-constant despite conditional schema safety"
5419 );
5420 }
5421
5422 for (name, num_args) in [
5423 ("random", 0),
5424 ("randomblob", 1),
5425 ("changes", 0),
5426 ("total_changes", 0),
5427 ("last_insert_rowid", 0),
5428 ] {
5429 assert_eq!(
5430 registry
5431 .resolve_scalar(name, num_args)
5432 .unwrap()
5433 .query_constancy(),
5434 ScalarQueryConstancy::Volatile,
5435 "{name}/{num_args} must remain volatile"
5436 );
5437 }
5438
5439 for (name, num_args) in [("abs", 1), ("like", 2), ("like", 3), ("glob", 2)] {
5440 assert_eq!(
5441 registry
5442 .resolve_scalar(name, num_args)
5443 .unwrap()
5444 .query_constancy(),
5445 ScalarQueryConstancy::Constant,
5446 "{name}/{num_args} must remain constant"
5447 );
5448 }
5449
5450 for (name, num_args) in [
5451 ("sqlite_version", 1),
5452 ("sqlite_compileoption_used", 0),
5453 ("like", 1),
5454 ("like", 4),
5455 ("glob", 1),
5456 ] {
5457 assert_eq!(
5458 registry
5459 .resolve_scalar(name, num_args)
5460 .unwrap()
5461 .query_constancy(),
5462 ScalarQueryConstancy::Volatile,
5463 "{name}/{num_args} wrong-arity sentinel must fail closed"
5464 );
5465 }
5466 }
5467}