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 static STATEMENT_NOW: std::cell::Cell<Option<f64>> = const { std::cell::Cell::new(None) };
62}
63
64pub fn reset_statement_now() {
67 STATEMENT_NOW.set(None);
68}
69
70#[must_use]
72pub fn statement_now() -> Option<f64> {
73 STATEMENT_NOW.with(std::cell::Cell::get)
74}
75
76pub fn set_statement_now(now_jdn: f64) {
78 STATEMENT_NOW.set(Some(now_jdn));
79}
80
81pub fn set_case_sensitive_like(case_sensitive: bool) {
84 CASE_SENSITIVE_LIKE.set(case_sensitive);
85}
86
87#[must_use]
89pub fn case_sensitive_like_active() -> bool {
90 CASE_SENSITIVE_LIKE.get()
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct ChangeTrackingState {
96 pub last_insert_rowid: i64,
97 pub last_changes: i64,
98 pub total_changes: i64,
99}
100
101pub fn set_change_tracking_state(state: ChangeTrackingState) {
103 LAST_INSERT_ROWID.set(state.last_insert_rowid);
104 LAST_CHANGES.set(state.last_changes);
105 TOTAL_CHANGES.set(state.total_changes);
106}
107
108#[must_use]
110pub fn get_change_tracking_state() -> ChangeTrackingState {
111 ChangeTrackingState {
112 last_insert_rowid: LAST_INSERT_ROWID.get(),
113 last_changes: LAST_CHANGES.get(),
114 total_changes: TOTAL_CHANGES.get(),
115 }
116}
117
118pub fn set_last_insert_rowid(rowid: i64) {
120 LAST_INSERT_ROWID.set(rowid);
121}
122
123pub fn get_last_insert_rowid() -> i64 {
125 LAST_INSERT_ROWID.get()
126}
127
128pub fn set_last_changes(count: i64) {
132 LAST_CHANGES.set(count);
133 TOTAL_CHANGES.set(TOTAL_CHANGES.get().saturating_add(count));
134}
135
136pub fn get_last_changes() -> i64 {
138 LAST_CHANGES.get()
139}
140
141pub fn get_total_changes() -> i64 {
143 TOTAL_CHANGES.get()
144}
145
146pub fn reset_total_changes() {
148 TOTAL_CHANGES.set(0);
149}
150
151const SQLITE_COMPILE_OPTIONS: &[&str] = &[
152 "COMPILER=rustc",
153 #[cfg(feature = "ext-fts5")]
154 "ENABLE_FTS5",
155 #[cfg(feature = "ext-geopoly")]
156 "ENABLE_GEOPOLY",
157 #[cfg(feature = "ext-icu")]
158 "ENABLE_ICU",
159 #[cfg(feature = "ext-json")]
160 "ENABLE_JSON1",
161 #[cfg(feature = "ext-rtree")]
162 "ENABLE_RTREE",
163 "FRANKENSQLITE",
164 "OMIT_LOAD_EXTENSION",
165 "THREADSAFE=1",
166];
167
168#[must_use]
170pub fn sqlite_compile_options() -> &'static [&'static str] {
171 SQLITE_COMPILE_OPTIONS
172}
173
174fn is_sqlite_compile_option_match(query: &str, option: &str) -> bool {
175 let trimmed = query.trim();
176 let normalized = if trimmed
177 .get(..7)
178 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("SQLITE_"))
179 {
180 &trimmed[7..]
181 } else {
182 trimmed
183 };
184 if normalized.is_empty() {
185 return false;
186 }
187 if option.eq_ignore_ascii_case(normalized) {
188 return true;
189 }
190 option
191 .get(..normalized.len())
192 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(normalized))
193 && option
194 .as_bytes()
195 .get(normalized.len())
196 .is_none_or(|next| !next.is_ascii_alphanumeric() && *next != b'_')
197}
198
199#[must_use]
202pub fn sqlite_compileoption_used(query: &str) -> bool {
203 sqlite_compile_options()
204 .iter()
205 .any(|option| is_sqlite_compile_option_match(query, option))
206}
207
208fn null_propagate(args: &[SqliteValue]) -> Option<SqliteValue> {
212 if args.iter().any(SqliteValue::is_null) {
213 Some(SqliteValue::Null)
214 } else {
215 None
216 }
217}
218
219pub struct AbsFunc;
222
223impl ScalarFunction for AbsFunc {
224 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
225 if args[0].is_null() {
226 return Ok(SqliteValue::Null);
227 }
228 match &args[0] {
229 SqliteValue::Integer(i) => {
230 if *i == i64::MIN {
231 return Err(FrankenError::IntegerOverflow);
232 }
233 Ok(SqliteValue::Integer(i.abs()))
234 }
235 other => {
236 let f = other.to_float();
237 Ok(SqliteValue::Float(if f < 0.0 { -f } else { f }))
240 }
241 }
242 }
243
244 fn num_args(&self) -> i32 {
245 1
246 }
247
248 fn name(&self) -> &str {
249 "abs"
250 }
251}
252
253pub struct CharFunc;
256
257impl ScalarFunction for CharFunc {
258 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
259 let mut result = String::new();
260 for arg in args {
261 let ch = u32::try_from(arg.to_integer())
263 .ok()
264 .and_then(char::from_u32)
265 .unwrap_or(char::REPLACEMENT_CHARACTER);
266 result.push(ch);
267 }
268 Ok(SqliteValue::Text(SmallText::from_string(result)))
269 }
270
271 fn is_deterministic(&self) -> bool {
272 true
273 }
274
275 fn num_args(&self) -> i32 {
276 -1 }
278
279 fn name(&self) -> &str {
280 "char"
281 }
282}
283
284pub struct CoalesceFunc;
287
288impl ScalarFunction for CoalesceFunc {
289 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
290 for arg in args {
294 if !arg.is_null() {
295 return Ok(arg.clone());
296 }
297 }
298 Ok(SqliteValue::Null)
299 }
300
301 fn num_args(&self) -> i32 {
302 -1
303 }
304
305 fn min_args(&self) -> i32 {
306 2
307 }
308
309 fn name(&self) -> &str {
310 "coalesce"
311 }
312}
313
314pub struct ConcatFunc;
317
318impl ScalarFunction for ConcatFunc {
319 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
320 let mut result = String::new();
321 for arg in args {
322 if !arg.is_null() {
324 result.push_str(text_arg(arg).as_ref());
325 }
326 }
327 Ok(SqliteValue::Text(SmallText::from_string(result)))
328 }
329
330 fn num_args(&self) -> i32 {
331 -1
332 }
333
334 fn min_args(&self) -> i32 {
335 1
336 }
337
338 fn name(&self) -> &str {
339 "concat"
340 }
341}
342
343pub struct ConcatWsFunc;
346
347impl ScalarFunction for ConcatWsFunc {
348 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
349 if args.is_empty() {
350 return Ok(SqliteValue::Text(SmallText::new("")));
351 }
352 if args[0].is_null() {
354 return Ok(SqliteValue::Null);
355 }
356 let sep = text_arg(&args[0]);
357 let mut result = String::new();
358 let mut has_part = false;
359 for arg in &args[1..] {
360 if arg.is_null() {
363 continue;
364 }
365 let part = text_arg(arg);
366 if has_part {
367 result.push_str(sep.as_ref());
368 }
369 result.push_str(part.as_ref());
370 has_part = true;
371 }
372 Ok(SqliteValue::Text(SmallText::from_string(result)))
373 }
374
375 fn num_args(&self) -> i32 {
376 -1
377 }
378
379 fn min_args(&self) -> i32 {
380 2
381 }
382
383 fn name(&self) -> &str {
384 "concat_ws"
385 }
386}
387
388pub struct HexFunc;
391
392impl ScalarFunction for HexFunc {
393 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
394 if args[0].is_null() {
398 return Ok(SqliteValue::Text(SmallText::new("")));
399 }
400 let bytes: Cow<'_, [u8]> = match &args[0] {
401 SqliteValue::Blob(b) => Cow::Borrowed(b.as_ref()),
402 SqliteValue::Text(text) => Cow::Borrowed(text.as_bytes_direct()),
403 other => Cow::Owned(other.to_text().into_bytes()),
405 };
406 let mut hex = String::with_capacity(bytes.len() * 2);
407 for b in bytes.as_ref() {
408 let _ = write!(hex, "{b:02X}");
409 }
410 Ok(SqliteValue::Text(SmallText::from_string(hex)))
411 }
412
413 fn num_args(&self) -> i32 {
414 1
415 }
416
417 fn name(&self) -> &str {
418 "hex"
419 }
420}
421
422pub struct IfnullFunc;
425
426impl ScalarFunction for IfnullFunc {
427 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
428 if args[0].is_null() {
429 Ok(args[1].clone())
430 } else {
431 Ok(args[0].clone())
432 }
433 }
434
435 fn num_args(&self) -> i32 {
436 2
437 }
438
439 fn name(&self) -> &str {
440 "ifnull"
441 }
442}
443
444pub struct IifFunc;
447
448impl ScalarFunction for IifFunc {
449 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
450 let cond = &args[0];
451 let is_true = match cond {
454 SqliteValue::Null => false,
455 SqliteValue::Integer(n) => *n != 0,
456 SqliteValue::Float(f) => *f != 0.0,
457 SqliteValue::Text(_) | SqliteValue::Blob(_) => {
458 let i = cond.to_integer();
459 if i != 0 { true } else { cond.to_float() != 0.0 }
460 }
461 };
462 if is_true {
463 Ok(args[1].clone())
464 } else if args.len() >= 3 {
465 Ok(args[2].clone())
466 } else {
467 Ok(SqliteValue::Null)
470 }
471 }
472
473 fn num_args(&self) -> i32 {
474 -1 }
476
477 fn min_args(&self) -> i32 {
478 2
479 }
480
481 fn max_args(&self) -> Option<i32> {
482 Some(3)
483 }
484
485 fn name(&self) -> &str {
486 "iif"
487 }
488}
489
490pub struct InstrFunc;
493
494impl ScalarFunction for InstrFunc {
495 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
496 if let Some(null) = null_propagate(args) {
497 return Ok(null);
498 }
499 match (&args[0], &args[1]) {
500 (SqliteValue::Blob(haystack), SqliteValue::Blob(needle)) => {
501 if needle.is_empty() {
503 return Ok(SqliteValue::Integer(1));
504 }
505 if haystack.is_empty() {
506 return Ok(SqliteValue::Integer(0));
507 }
508 let pos = find_bytes(haystack, needle).map_or(0, |p| p + 1);
509 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
510 }
511 _ => {
512 let haystack = text_arg(&args[0]);
515 let needle = text_arg(&args[1]);
516 let haystack = haystack.as_ref();
517 let needle = needle.as_ref();
518 if needle.is_empty() {
519 return Ok(SqliteValue::Integer(1));
520 }
521 if haystack.is_empty() {
522 return Ok(SqliteValue::Integer(0));
523 }
524 let pos = haystack
525 .find(needle)
526 .map_or(0, |byte_pos| haystack[..byte_pos].chars().count() + 1);
527 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
528 }
529 }
530 }
531
532 fn num_args(&self) -> i32 {
533 2
534 }
535
536 fn name(&self) -> &str {
537 "instr"
538 }
539}
540
541fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
542 if needle.is_empty() {
543 return Some(0);
544 }
545 haystack.windows(needle.len()).position(|w| w == needle)
546}
547
548fn sqlite_text_until_nul(text: &str) -> &str {
549 text.split_once('\0').map_or(text, |(prefix, _)| prefix)
550}
551
552pub struct LengthFunc;
555
556impl ScalarFunction for LengthFunc {
557 #[allow(clippy::cast_possible_wrap)]
558 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
559 if args[0].is_null() {
560 return Ok(SqliteValue::Null);
561 }
562 let len = match &args[0] {
563 SqliteValue::Text(s) => {
564 let text = sqlite_text_until_nul(s.as_str());
565 if text.is_ascii() {
566 text.len()
567 } else {
568 text.chars().count()
569 }
570 }
571 SqliteValue::Blob(b) => b.len(),
572 other => {
573 let text = other.to_text();
575 let text = sqlite_text_until_nul(&text);
576 if text.is_ascii() {
577 text.len()
578 } else {
579 text.chars().count()
580 }
581 }
582 };
583 Ok(SqliteValue::Integer(len as i64))
584 }
585
586 fn num_args(&self) -> i32 {
587 1
588 }
589
590 fn name(&self) -> &str {
591 "length"
592 }
593}
594
595pub struct OctetLengthFunc;
598
599impl ScalarFunction for OctetLengthFunc {
600 #[allow(clippy::cast_possible_wrap)]
601 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
602 if args[0].is_null() {
603 return Ok(SqliteValue::Null);
604 }
605 let len = match &args[0] {
606 SqliteValue::Text(s) => s.len(),
607 SqliteValue::Blob(b) => b.len(),
608 other => other.to_text().len(),
609 };
610 Ok(SqliteValue::Integer(len as i64))
611 }
612
613 fn num_args(&self) -> i32 {
614 1
615 }
616
617 fn name(&self) -> &str {
618 "octet_length"
619 }
620}
621
622pub struct LowerFunc;
625
626impl ScalarFunction for LowerFunc {
627 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
628 if args[0].is_null() {
629 return Ok(SqliteValue::Null);
630 }
631 let lowered = text_arg(&args[0]).as_ref().to_ascii_lowercase();
632 Ok(SqliteValue::Text(SmallText::from_string(lowered)))
633 }
634
635 fn num_args(&self) -> i32 {
636 1
637 }
638
639 fn name(&self) -> &str {
640 "lower"
641 }
642}
643
644pub struct UpperFunc;
645
646impl ScalarFunction for UpperFunc {
647 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
648 if args[0].is_null() {
649 return Ok(SqliteValue::Null);
650 }
651 let upper = text_arg(&args[0]).as_ref().to_ascii_uppercase();
652 Ok(SqliteValue::Text(SmallText::from_string(upper)))
653 }
654
655 fn num_args(&self) -> i32 {
656 1
657 }
658
659 fn name(&self) -> &str {
660 "upper"
661 }
662}
663
664pub struct TrimFunc;
667pub struct LtrimFunc;
668pub struct RtrimFunc;
669
670fn trim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
671 let char_set: Vec<char> = chars.chars().collect();
672 s.trim_matches(|c: char| char_set.contains(&c))
673}
674
675fn ltrim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
676 let char_set: Vec<char> = chars.chars().collect();
677 s.trim_start_matches(|c: char| char_set.contains(&c))
678}
679
680fn rtrim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
681 let char_set: Vec<char> = chars.chars().collect();
682 s.trim_end_matches(|c: char| char_set.contains(&c))
683}
684
685impl ScalarFunction for TrimFunc {
686 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
687 if args[0].is_null() {
688 return Ok(SqliteValue::Null);
689 }
690 let s = text_arg(&args[0]);
691 let chars = if args.len() > 1 && !args[1].is_null() {
692 text_arg(&args[1])
693 } else {
694 Cow::Borrowed(" ")
695 };
696 Ok(SqliteValue::Text(SmallText::new(trim_chars(
697 s.as_ref(),
698 chars.as_ref(),
699 ))))
700 }
701
702 fn num_args(&self) -> i32 {
703 -1 }
705
706 fn min_args(&self) -> i32 {
707 1
708 }
709
710 fn max_args(&self) -> Option<i32> {
711 Some(2)
712 }
713
714 fn name(&self) -> &str {
715 "trim"
716 }
717}
718
719impl ScalarFunction for LtrimFunc {
720 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
721 if args[0].is_null() {
722 return Ok(SqliteValue::Null);
723 }
724 let s = text_arg(&args[0]);
725 let chars = if args.len() > 1 && !args[1].is_null() {
726 text_arg(&args[1])
727 } else {
728 Cow::Borrowed(" ")
729 };
730 Ok(SqliteValue::Text(SmallText::new(ltrim_chars(
731 s.as_ref(),
732 chars.as_ref(),
733 ))))
734 }
735
736 fn num_args(&self) -> i32 {
737 -1
738 }
739
740 fn min_args(&self) -> i32 {
741 1
742 }
743
744 fn max_args(&self) -> Option<i32> {
745 Some(2)
746 }
747
748 fn name(&self) -> &str {
749 "ltrim"
750 }
751}
752
753impl ScalarFunction for RtrimFunc {
754 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
755 if args[0].is_null() {
756 return Ok(SqliteValue::Null);
757 }
758 let s = text_arg(&args[0]);
759 let chars = if args.len() > 1 && !args[1].is_null() {
760 text_arg(&args[1])
761 } else {
762 Cow::Borrowed(" ")
763 };
764 Ok(SqliteValue::Text(SmallText::new(rtrim_chars(
765 s.as_ref(),
766 chars.as_ref(),
767 ))))
768 }
769
770 fn num_args(&self) -> i32 {
771 -1
772 }
773
774 fn min_args(&self) -> i32 {
775 1
776 }
777
778 fn max_args(&self) -> Option<i32> {
779 Some(2)
780 }
781
782 fn name(&self) -> &str {
783 "rtrim"
784 }
785}
786
787pub struct NullifFunc;
790
791impl ScalarFunction for NullifFunc {
792 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
793 self.invoke_with_collation(args, None)
794 }
795
796 fn consumes_argument_collation(&self) -> bool {
797 true
798 }
799
800 fn invoke_with_collation(
801 &self,
802 args: &[SqliteValue],
803 collation: Option<&dyn crate::collation::CollationFunction>,
804 ) -> Result<SqliteValue> {
805 let equal = match (&args[0], &args[1], collation) {
806 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
807 collation.compare(left.as_bytes(), right.as_bytes()) == std::cmp::Ordering::Equal
808 }
809 _ => args[0] == args[1],
810 };
811 if equal {
812 Ok(SqliteValue::Null)
813 } else {
814 Ok(args[0].clone())
815 }
816 }
817
818 fn num_args(&self) -> i32 {
819 2
820 }
821
822 fn name(&self) -> &str {
823 "nullif"
824 }
825}
826
827pub struct TypeofFunc;
830
831impl ScalarFunction for TypeofFunc {
832 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
833 let type_name = match &args[0] {
834 SqliteValue::Null => "null",
835 SqliteValue::Integer(_) => "integer",
836 SqliteValue::Float(_) => "real",
837 SqliteValue::Text(_) => "text",
838 SqliteValue::Blob(_) => "blob",
839 };
840 Ok(SqliteValue::Text(SmallText::new(type_name)))
841 }
842
843 fn num_args(&self) -> i32 {
844 1
845 }
846
847 fn name(&self) -> &str {
848 "typeof"
849 }
850}
851
852pub struct SubtypeFunc;
855
856impl ScalarFunction for SubtypeFunc {
857 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
858 Ok(SqliteValue::Integer(0))
861 }
862
863 fn num_args(&self) -> i32 {
864 1
865 }
866
867 fn name(&self) -> &str {
868 "subtype"
869 }
870}
871
872pub struct ReplaceFunc;
875
876impl ScalarFunction for ReplaceFunc {
877 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
878 if let Some(null) = null_propagate(args) {
879 return Ok(null);
880 }
881 let x = text_arg(&args[0]);
882 let y = text_arg(&args[1]);
883 let z = text_arg(&args[2]);
884 if y.is_empty() {
885 return Ok(SqliteValue::Text(SmallText::from_string(x)));
886 }
887
888 if z.len() > y.len() {
890 let occurrences = x.matches(y.as_ref()).count();
891 let final_len = x.len() + occurrences * (z.len() - y.len());
892 if final_len > 1_000_000_000 {
893 return Err(FrankenError::TooBig);
894 }
895 }
896
897 Ok(SqliteValue::Text(SmallText::from_string(
898 x.replace(y.as_ref(), z.as_ref()),
899 )))
900 }
901
902 fn num_args(&self) -> i32 {
903 3
904 }
905
906 fn name(&self) -> &str {
907 "replace"
908 }
909}
910
911pub struct RoundFunc;
914
915impl ScalarFunction for RoundFunc {
916 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
917 if args[0].is_null() {
918 return Ok(SqliteValue::Null);
919 }
920 if args.len() > 1 && args[1].is_null() {
923 return Ok(SqliteValue::Null);
924 }
925 let x = args[0].to_float();
926 let n = if args.len() > 1 {
928 args[1].to_integer().clamp(0, 30)
929 } else {
930 0
931 };
932 if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
934 return Ok(SqliteValue::Float(x));
935 }
936 #[allow(clippy::cast_possible_truncation)]
942 let rounded = {
943 let prec = (n as usize) + 15;
944 let full = format!("{x:.prec$}");
945 let dot = full.find('.').unwrap_or(full.len());
946 let rd_idx = dot + 1 + n as usize;
947 if rd_idx >= full.len() {
948 format!("{x:.prec$}", prec = n as usize)
949 .parse::<f64>()
950 .unwrap_or(x)
951 } else {
952 let rd = full.as_bytes()[rd_idx] - b'0';
953 if rd != 5 || !full[rd_idx + 1..].bytes().all(|b| b == b'0') {
954 format!("{x:.prec$}", prec = n as usize)
956 .parse::<f64>()
957 .unwrap_or(x)
958 } else {
959 let mut trunc = full.as_bytes()[..rd_idx].to_vec();
962 if trunc.last() == Some(&b'.') {
964 trunc.pop();
965 }
966 let start = usize::from(trunc.first() == Some(&b'-'));
967 let mut carry = true;
968 for b in trunc[start..].iter_mut().rev() {
969 if *b == b'.' {
970 continue;
971 }
972 if carry {
973 if *b == b'9' {
974 *b = b'0';
975 } else {
976 *b += 1;
977 carry = false;
978 break;
979 }
980 }
981 }
982 if carry {
983 trunc.insert(start, b'1');
984 }
985 String::from_utf8(trunc)
986 .ok()
987 .and_then(|s| s.parse::<f64>().ok())
988 .unwrap_or(x)
989 }
990 }
991 };
992 Ok(SqliteValue::Float(rounded))
993 }
994
995 fn num_args(&self) -> i32 {
996 -1 }
998
999 fn min_args(&self) -> i32 {
1000 1
1001 }
1002
1003 fn max_args(&self) -> Option<i32> {
1004 Some(2)
1005 }
1006
1007 fn name(&self) -> &str {
1008 "round"
1009 }
1010}
1011
1012pub struct SignFunc;
1015
1016impl ScalarFunction for SignFunc {
1017 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1018 if args[0].is_null() {
1019 return Ok(SqliteValue::Null);
1020 }
1021 match &args[0] {
1022 SqliteValue::Null => Ok(SqliteValue::Null),
1023 SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
1024 SqliteValue::Float(f) => {
1025 if f.is_nan() {
1026 Ok(SqliteValue::Null)
1027 } else if *f > 0.0 {
1028 Ok(SqliteValue::Integer(1))
1029 } else if *f < 0.0 {
1030 Ok(SqliteValue::Integer(-1))
1031 } else {
1032 Ok(SqliteValue::Integer(0))
1033 }
1034 }
1035 SqliteValue::Text(s) => {
1036 let trimmed = s.trim_matches(|ch: char| ch.is_ascii_whitespace());
1038 if trimmed.is_empty() {
1039 return Ok(SqliteValue::Null);
1040 }
1041
1042 let stripped = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
1048 if stripped.eq_ignore_ascii_case("nan")
1049 || stripped.eq_ignore_ascii_case("inf")
1050 || stripped.eq_ignore_ascii_case("infinity")
1051 {
1052 return Ok(SqliteValue::Null);
1053 }
1054
1055 if let Ok(f) = trimmed.parse::<f64>() {
1058 if f > 0.0 {
1060 Ok(SqliteValue::Integer(1))
1061 } else if f < 0.0 {
1062 Ok(SqliteValue::Integer(-1))
1063 } else {
1064 Ok(SqliteValue::Integer(0))
1065 }
1066 } else if let Ok(i) = trimmed.parse::<i64>() {
1067 Ok(SqliteValue::Integer(i.signum()))
1069 } else {
1070 Ok(SqliteValue::Null)
1071 }
1072 }
1073 SqliteValue::Blob(_) => Ok(SqliteValue::Null),
1074 }
1075 }
1076
1077 fn num_args(&self) -> i32 {
1078 1
1079 }
1080
1081 fn name(&self) -> &str {
1082 "sign"
1083 }
1084}
1085
1086pub struct RandomFunc;
1089
1090impl ScalarFunction for RandomFunc {
1091 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1092 let val = simple_random_i64();
1095 Ok(SqliteValue::Integer(val))
1096 }
1097
1098 fn is_deterministic(&self) -> bool {
1099 false
1100 }
1101
1102 fn num_args(&self) -> i32 {
1103 0
1104 }
1105
1106 fn name(&self) -> &str {
1107 "random"
1108 }
1109}
1110
1111fn simple_random_i64() -> i64 {
1113 use std::sync::atomic::{AtomicU64, Ordering};
1118
1119 static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
1120 let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
1121 x ^= x >> 30;
1122 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1123 x ^= x >> 27;
1124 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
1125 x ^= x >> 31;
1126 x as i64
1127}
1128
1129pub struct RandomblobFunc;
1132
1133impl ScalarFunction for RandomblobFunc {
1134 #[allow(clippy::cast_sign_loss)]
1135 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1136 let n_i64 = if args[0].is_null() {
1140 1
1141 } else {
1142 args[0].to_integer().max(1)
1143 };
1144 if n_i64 > 1_000_000_000 {
1145 return Err(FrankenError::TooBig);
1146 }
1147 let n = n_i64 as usize;
1148 let mut buf = vec![0u8; n];
1149 let mut i = 0;
1150 while i < n {
1151 let rnd = simple_random_i64().to_ne_bytes();
1152 let to_copy = (n - i).min(8);
1153 buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
1154 i += to_copy;
1155 }
1156 Ok(SqliteValue::Blob(Arc::from(buf.as_slice())))
1157 }
1158
1159 fn is_deterministic(&self) -> bool {
1160 false
1161 }
1162
1163 fn num_args(&self) -> i32 {
1164 1
1165 }
1166
1167 fn name(&self) -> &str {
1168 "randomblob"
1169 }
1170}
1171
1172pub struct ZeroblobFunc;
1175
1176impl ScalarFunction for ZeroblobFunc {
1177 #[allow(clippy::cast_sign_loss)]
1178 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1179 if args[0].is_null() {
1181 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1182 }
1183 let n_i64 = args[0].to_integer().max(0);
1184 if n_i64 > 1_000_000_000 {
1185 return Err(FrankenError::TooBig);
1186 }
1187 let n = n_i64 as usize;
1188 Ok(SqliteValue::Blob(Arc::from(vec![0u8; n].as_slice())))
1189 }
1190
1191 fn num_args(&self) -> i32 {
1192 1
1193 }
1194
1195 fn name(&self) -> &str {
1196 "zeroblob"
1197 }
1198}
1199
1200pub struct QuoteFunc;
1203
1204impl ScalarFunction for QuoteFunc {
1205 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1206 let result = quote_sql_value(&args[0], false);
1207 Ok(SqliteValue::Text(SmallText::from_string(result)))
1208 }
1209
1210 fn num_args(&self) -> i32 {
1211 1
1212 }
1213
1214 fn name(&self) -> &str {
1215 "quote"
1216 }
1217}
1218
1219pub struct UnistrQuoteFunc;
1222
1223impl ScalarFunction for UnistrQuoteFunc {
1224 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1225 let result = quote_sql_value(&args[0], true);
1226 Ok(SqliteValue::Text(SmallText::from_string(result)))
1227 }
1228
1229 fn num_args(&self) -> i32 {
1230 1
1231 }
1232
1233 fn name(&self) -> &str {
1234 "unistr_quote"
1235 }
1236}
1237
1238fn quote_sql_value(value: &SqliteValue, use_unistr_quote: bool) -> String {
1239 match value {
1240 SqliteValue::Null => "NULL".to_owned(),
1241 SqliteValue::Integer(i) => i.to_string(),
1242 SqliteValue::Float(f) => format_sqlite_float(*f),
1243 SqliteValue::Text(s) => quote_sql_text_literal(s.as_str(), use_unistr_quote),
1244 SqliteValue::Blob(b) => {
1245 let mut hex = String::with_capacity(3 + b.len() * 2);
1246 hex.push_str("X'");
1247 for byte in b.iter() {
1248 let _ = write!(hex, "{byte:02X}");
1249 }
1250 hex.push('\'');
1251 hex
1252 }
1253 }
1254}
1255
1256fn quote_sql_text_literal(text: &str, use_unistr_quote: bool) -> String {
1257 let text = sqlite_text_until_nul(text);
1258 if use_unistr_quote && text.chars().any(is_unistr_control_char) {
1259 return unistr_quote_sql_text_literal(text);
1260 }
1261
1262 let mut quoted = String::with_capacity(text.len() + 2);
1263 quoted.push('\'');
1264 append_sql_string_literal_body(&mut quoted, text);
1265 quoted.push('\'');
1266 quoted
1267}
1268
1269fn unistr_quote_sql_text_literal(text: &str) -> String {
1270 let mut quoted = String::with_capacity(text.len() + 12);
1271 quoted.push_str("unistr('");
1272 for ch in text.chars() {
1273 match ch {
1274 '\'' => quoted.push_str("''"),
1275 '\\' => quoted.push_str("\\\\"),
1276 _ if is_unistr_control_char(ch) => {
1277 let _ = write!(quoted, "\\u{:04x}", ch as u32);
1278 }
1279 _ => quoted.push(ch),
1280 }
1281 }
1282 quoted.push_str("')");
1283 quoted
1284}
1285
1286fn append_sql_string_literal_body(out: &mut String, text: &str) {
1287 for ch in text.chars() {
1288 if ch == '\'' {
1289 out.push_str("''");
1290 } else {
1291 out.push(ch);
1292 }
1293 }
1294}
1295
1296fn is_unistr_control_char(ch: char) -> bool {
1297 matches!(ch, '\u{0001}'..='\u{001F}')
1298}
1299
1300pub struct UnhexFunc;
1303
1304impl ScalarFunction for UnhexFunc {
1305 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1306 if args[0].is_null() {
1307 return Ok(SqliteValue::Null);
1308 }
1309 if args.len() > 1 && args[1].is_null() {
1310 return Ok(SqliteValue::Null);
1311 }
1312 let input = text_arg(&args[0]);
1313 let ignore_chars: Vec<char> = if args.len() > 1 {
1314 text_arg(&args[1])
1315 .chars()
1316 .filter(|&c| hex_digit(c).is_none())
1317 .collect()
1318 } else {
1319 Vec::new()
1320 };
1321
1322 let mut bytes = Vec::with_capacity(input.len() / 2);
1323 let mut hi_nibble = None;
1324 for c in input.as_ref().chars() {
1325 if ignore_chars.contains(&c) {
1326 if hi_nibble.is_some() {
1327 return Ok(SqliteValue::Null);
1328 }
1329 continue;
1330 }
1331 let digit = match hex_digit(c) {
1332 Some(v) => v,
1333 None => return Ok(SqliteValue::Null),
1334 };
1335 if let Some(hi) = hi_nibble.take() {
1336 bytes.push(hi << 4 | digit);
1337 } else {
1338 hi_nibble = Some(digit);
1339 }
1340 }
1341 if hi_nibble.is_some() {
1342 return Ok(SqliteValue::Null);
1343 }
1344 Ok(SqliteValue::Blob(Arc::from(bytes.as_slice())))
1345 }
1346
1347 fn num_args(&self) -> i32 {
1348 -1 }
1350
1351 fn min_args(&self) -> i32 {
1352 1
1353 }
1354
1355 fn max_args(&self) -> Option<i32> {
1356 Some(2)
1357 }
1358
1359 fn name(&self) -> &str {
1360 "unhex"
1361 }
1362}
1363
1364fn hex_digit(c: char) -> Option<u8> {
1365 match c {
1366 '0'..='9' => Some(c as u8 - b'0'),
1367 'a'..='f' => Some(c as u8 - b'a' + 10),
1368 'A'..='F' => Some(c as u8 - b'A' + 10),
1369 _ => None,
1370 }
1371}
1372
1373pub struct UnicodeFunc;
1376
1377impl ScalarFunction for UnicodeFunc {
1378 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1379 if args[0].is_null() {
1380 return Ok(SqliteValue::Null);
1381 }
1382 if let SqliteValue::Blob(bytes) = &args[0] {
1383 return Ok(
1384 sqlite_blob_first_codepoint(bytes).map_or(SqliteValue::Null, SqliteValue::Integer)
1385 );
1386 }
1387 let s = text_arg(&args[0]);
1388 match sqlite_text_until_nul(s.as_ref()).chars().next() {
1389 Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
1390 None => Ok(SqliteValue::Null),
1391 }
1392 }
1393
1394 fn num_args(&self) -> i32 {
1395 1
1396 }
1397
1398 fn name(&self) -> &str {
1399 "unicode"
1400 }
1401}
1402
1403fn sqlite_blob_first_codepoint(bytes: &[u8]) -> Option<i64> {
1404 let first = *bytes.first()?;
1405 if first == 0 {
1406 return None;
1407 }
1408 let mut codepoint = match first {
1409 0x00..=0xBF => u32::from(first),
1410 0xC0..=0xDF => u32::from(first & 0x1F),
1411 0xE0..=0xEF => u32::from(first & 0x0F),
1412 0xF0..=0xF7 => u32::from(first & 0x07),
1413 _ => 0xFFFD,
1414 };
1415
1416 if first >= 0xC0 && first <= 0xF7 {
1417 for byte in bytes
1418 .iter()
1419 .copied()
1420 .skip(1)
1421 .take_while(|byte| byte & 0xC0 == 0x80)
1422 {
1423 codepoint = codepoint
1424 .wrapping_shl(6)
1425 .wrapping_add(u32::from(byte & 0x3F));
1426 }
1427 if codepoint < 0x80
1428 || (codepoint & 0xFFFF_F800) == 0xD800
1429 || (codepoint & 0xFFFF_FFFE) == 0xFFFE
1430 {
1431 codepoint = 0xFFFD;
1432 }
1433 }
1434
1435 Some(i64::from(codepoint))
1436}
1437
1438pub struct SubstrFunc;
1441
1442impl ScalarFunction for SubstrFunc {
1443 #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
1444 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1445 if args[0].is_null() || args[1].is_null() {
1446 return Ok(SqliteValue::Null);
1447 }
1448 let is_blob = matches!(&args[0], SqliteValue::Blob(_));
1449 if is_blob {
1450 return self.invoke_blob(args);
1451 }
1452
1453 let text = text_arg(&args[0]);
1454 let s = text.as_ref();
1455 let ascii_fast_path = s.is_ascii();
1456 let len = if ascii_fast_path {
1457 s.len() as i64
1458 } else {
1459 s.chars().count() as i64
1460 };
1461 let has_length = args.len() > 2 && !args[2].is_null();
1462
1463 let mut p1 = args[1].to_integer();
1464 let mut p2 = if has_length {
1465 args[2].to_integer()
1466 } else {
1467 1_000_000_000
1468 };
1469
1470 let neg_p2 = p2 < 0;
1474 if neg_p2 {
1475 p2 = p2.saturating_neg();
1476 }
1477
1478 if p1 < 0 {
1480 p1 = p1.saturating_add(len);
1481 if p1 < 0 {
1482 p2 = p2.saturating_add(p1);
1483 p1 = 0;
1484 }
1485 } else if p1 > 0 {
1486 p1 -= 1;
1487 } else if p2 > 0 {
1488 p2 -= 1; }
1490
1491 if neg_p2 {
1493 p1 = p1.saturating_sub(p2);
1494 if p1 < 0 {
1495 p2 = p2.saturating_add(p1);
1496 p1 = 0;
1497 }
1498 }
1499
1500 if p1.saturating_add(p2) > len {
1501 p2 = len.saturating_sub(p1);
1502 }
1503 if p2 <= 0 {
1504 return Ok(SqliteValue::Text(SmallText::new("")));
1505 }
1506
1507 if ascii_fast_path {
1508 let start = p1 as usize;
1509 let end = (p1 + p2) as usize;
1510 return Ok(SqliteValue::Text(SmallText::new(&s[start..end])));
1511 }
1512
1513 let chars: Vec<char> = s.chars().collect();
1514 let result: String = chars[p1 as usize..(p1 + p2) as usize].iter().collect();
1515 Ok(SqliteValue::Text(SmallText::from_string(result)))
1516 }
1517
1518 fn num_args(&self) -> i32 {
1519 -1 }
1521
1522 fn min_args(&self) -> i32 {
1523 2
1524 }
1525
1526 fn max_args(&self) -> Option<i32> {
1527 Some(3)
1528 }
1529
1530 fn name(&self) -> &str {
1531 "substr"
1532 }
1533}
1534
1535impl SubstrFunc {
1536 #[allow(
1537 clippy::unused_self,
1538 clippy::cast_sign_loss,
1539 clippy::cast_possible_wrap
1540 )]
1541 fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1542 let blob = match &args[0] {
1543 SqliteValue::Blob(b) => b,
1544 _ => return Ok(SqliteValue::Null),
1545 };
1546 let len = blob.len() as i64;
1547 let has_length = args.len() > 2 && !args[2].is_null();
1548
1549 let mut p1 = args[1].to_integer();
1550 let mut p2 = if has_length {
1551 args[2].to_integer()
1552 } else {
1553 1_000_000_000
1554 };
1555
1556 let neg_p2 = p2 < 0;
1557 if neg_p2 {
1558 p2 = p2.saturating_neg();
1559 }
1560
1561 if p1 < 0 {
1562 p1 = p1.saturating_add(len);
1563 if p1 < 0 {
1564 p2 = p2.saturating_add(p1);
1565 p1 = 0;
1566 }
1567 } else if p1 > 0 {
1568 p1 -= 1;
1569 } else if p2 > 0 {
1570 p2 -= 1;
1571 }
1572
1573 if neg_p2 {
1574 p1 = p1.saturating_sub(p2);
1575 if p1 < 0 {
1576 p2 = p2.saturating_add(p1);
1577 p1 = 0;
1578 }
1579 }
1580
1581 if p1.saturating_add(p2) > len {
1582 p2 = len.saturating_sub(p1);
1583 }
1584 if p2 <= 0 {
1585 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1586 }
1587
1588 Ok(SqliteValue::Blob(Arc::from(
1589 &blob[p1 as usize..(p1 + p2) as usize],
1590 )))
1591 }
1592}
1593
1594pub struct SoundexFunc;
1597
1598impl ScalarFunction for SoundexFunc {
1599 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1600 if args[0].is_null() {
1601 return Ok(SqliteValue::Text(SmallText::new("?000")));
1603 }
1604 let s = text_arg(&args[0]);
1605 let code = soundex(s.as_ref());
1606 let text = std::str::from_utf8(&code).expect("Soundex output must be ASCII");
1607 Ok(SqliteValue::Text(SmallText::new(text)))
1608 }
1609
1610 fn num_args(&self) -> i32 {
1611 1
1612 }
1613
1614 fn name(&self) -> &str {
1615 "soundex"
1616 }
1617}
1618
1619fn soundex(s: &str) -> [u8; 4] {
1620 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1621 let first = match chars.next() {
1622 Some(c) => c.to_ascii_uppercase(),
1623 None => return *b"?000",
1624 };
1625
1626 let code = |c: char| -> Option<u8> {
1627 match c.to_ascii_uppercase() {
1628 'B' | 'F' | 'P' | 'V' => Some(b'1'),
1629 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some(b'2'),
1630 'D' | 'T' => Some(b'3'),
1631 'L' => Some(b'4'),
1632 'M' | 'N' => Some(b'5'),
1633 'R' => Some(b'6'),
1634 _ => None, }
1636 };
1637
1638 let mut result = *b"0000";
1639 result[0] = first as u8;
1640 let mut result_len = 1;
1641 let mut last_code = code(first);
1642
1643 for c in chars {
1644 if result_len >= result.len() {
1645 break;
1646 }
1647 let current = code(c);
1648 if let Some(digit) = current
1649 && current != last_code
1650 {
1651 result[result_len] = digit;
1652 result_len += 1;
1653 }
1654 last_code = current;
1655 }
1656
1657 result
1658}
1659
1660pub struct ScalarMaxFunc;
1663
1664impl ScalarFunction for ScalarMaxFunc {
1665 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1666 self.invoke_with_collation(args, None)
1667 }
1668
1669 fn consumes_argument_collation(&self) -> bool {
1670 true
1671 }
1672
1673 fn invoke_with_collation(
1674 &self,
1675 args: &[SqliteValue],
1676 collation: Option<&dyn crate::collation::CollationFunction>,
1677 ) -> Result<SqliteValue> {
1678 if let Some(null) = null_propagate(args) {
1680 return Ok(null);
1681 }
1682 let mut max = &args[0];
1683 for arg in &args[1..] {
1684 let ordering = match (arg, max, collation) {
1685 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1686 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1687 }
1688 _ => arg.partial_cmp(max),
1689 };
1690 if ordering == Some(std::cmp::Ordering::Greater) {
1691 max = arg;
1692 }
1693 }
1694 Ok(max.clone())
1695 }
1696
1697 fn num_args(&self) -> i32 {
1698 -1
1699 }
1700
1701 fn min_args(&self) -> i32 {
1702 1
1703 }
1704
1705 fn name(&self) -> &str {
1706 "max"
1707 }
1708}
1709
1710pub struct ScalarMinFunc;
1713
1714impl ScalarFunction for ScalarMinFunc {
1715 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1716 self.invoke_with_collation(args, None)
1717 }
1718
1719 fn consumes_argument_collation(&self) -> bool {
1720 true
1721 }
1722
1723 fn invoke_with_collation(
1724 &self,
1725 args: &[SqliteValue],
1726 collation: Option<&dyn crate::collation::CollationFunction>,
1727 ) -> Result<SqliteValue> {
1728 if let Some(null) = null_propagate(args) {
1730 return Ok(null);
1731 }
1732 let mut min = &args[0];
1733 for arg in &args[1..] {
1734 let ordering = match (arg, min, collation) {
1735 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1736 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1737 }
1738 _ => arg.partial_cmp(min),
1739 };
1740 if matches!(
1744 ordering,
1745 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
1746 ) {
1747 min = arg;
1748 }
1749 }
1750 Ok(min.clone())
1751 }
1752
1753 fn num_args(&self) -> i32 {
1754 -1
1755 }
1756
1757 fn min_args(&self) -> i32 {
1758 1
1759 }
1760
1761 fn name(&self) -> &str {
1762 "min"
1763 }
1764}
1765
1766pub struct LikelihoodFunc;
1769
1770impl ScalarFunction for LikelihoodFunc {
1771 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1772 Ok(args[0].clone())
1774 }
1775
1776 fn num_args(&self) -> i32 {
1777 2
1778 }
1779
1780 fn name(&self) -> &str {
1781 "likelihood"
1782 }
1783}
1784
1785pub struct LikelyFunc;
1786
1787impl ScalarFunction for LikelyFunc {
1788 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1789 Ok(args[0].clone())
1790 }
1791
1792 fn num_args(&self) -> i32 {
1793 1
1794 }
1795
1796 fn name(&self) -> &str {
1797 "likely"
1798 }
1799}
1800
1801pub struct UnlikelyFunc;
1802
1803impl ScalarFunction for UnlikelyFunc {
1804 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1805 Ok(args[0].clone())
1806 }
1807
1808 fn num_args(&self) -> i32 {
1809 1
1810 }
1811
1812 fn name(&self) -> &str {
1813 "unlikely"
1814 }
1815}
1816
1817pub struct SqliteVersionFunc;
1820
1821impl ScalarFunction for SqliteVersionFunc {
1822 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1823 Ok(SqliteValue::Text(SmallText::new(
1824 fsqlite_types::FRANKENSQLITE_SQLITE_VERSION,
1825 )))
1826 }
1827
1828 fn is_deterministic(&self) -> bool {
1829 false
1830 }
1831
1832 fn num_args(&self) -> i32 {
1833 0
1834 }
1835
1836 fn name(&self) -> &str {
1837 "sqlite_version"
1838 }
1839}
1840
1841pub struct SqliteSourceIdFunc;
1844
1845impl ScalarFunction for SqliteSourceIdFunc {
1846 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1847 Ok(SqliteValue::Text(SmallText::new(
1848 fsqlite_types::FRANKENSQLITE_SOURCE_ID,
1849 )))
1850 }
1851
1852 fn is_deterministic(&self) -> bool {
1853 false
1854 }
1855
1856 fn num_args(&self) -> i32 {
1857 0
1858 }
1859
1860 fn name(&self) -> &str {
1861 "sqlite_source_id"
1862 }
1863}
1864
1865pub struct SqliteCompileoptionUsedFunc;
1868
1869impl ScalarFunction for SqliteCompileoptionUsedFunc {
1870 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1871 if args[0].is_null() {
1872 return Ok(SqliteValue::Null);
1873 }
1874 let query = text_arg(&args[0]);
1875 Ok(SqliteValue::Integer(i64::from(sqlite_compileoption_used(
1876 query.as_ref(),
1877 ))))
1878 }
1879
1880 fn is_deterministic(&self) -> bool {
1881 false
1882 }
1883
1884 fn num_args(&self) -> i32 {
1885 1
1886 }
1887
1888 fn name(&self) -> &str {
1889 "sqlite_compileoption_used"
1890 }
1891}
1892
1893pub struct SqliteCompileoptionGetFunc;
1896
1897impl ScalarFunction for SqliteCompileoptionGetFunc {
1898 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1899 if args[0].is_null() {
1900 return Ok(SqliteValue::Null);
1901 }
1902 let n = args[0].to_integer();
1903 #[allow(clippy::cast_sign_loss)]
1904 match sqlite_compile_options().get(n as usize) {
1905 Some(opt) => Ok(SqliteValue::Text(SmallText::new(opt))),
1906 None => Ok(SqliteValue::Null),
1907 }
1908 }
1909
1910 fn is_deterministic(&self) -> bool {
1911 false
1912 }
1913
1914 fn num_args(&self) -> i32 {
1915 1
1916 }
1917
1918 fn name(&self) -> &str {
1919 "sqlite_compileoption_get"
1920 }
1921}
1922
1923pub struct LikeFunc;
1926
1927impl ScalarFunction for LikeFunc {
1928 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1929 if let Some(null) = null_propagate(args) {
1930 return Ok(null);
1931 }
1932 let pattern = text_arg(&args[0]);
1933 let string = text_arg(&args[1]);
1934 let escape = if args.len() > 2 && !args[2].is_null() {
1935 Some(single_char_escape(text_arg(&args[2]).as_ref())?)
1936 } else {
1937 None
1938 };
1939 let matched = like_match(pattern.as_ref(), string.as_ref(), escape);
1940 Ok(SqliteValue::Integer(i64::from(matched)))
1941 }
1942
1943 fn num_args(&self) -> i32 {
1944 -1 }
1946
1947 fn min_args(&self) -> i32 {
1948 2
1949 }
1950
1951 fn max_args(&self) -> Option<i32> {
1952 Some(3)
1953 }
1954
1955 fn name(&self) -> &str {
1956 "like"
1957 }
1958}
1959
1960#[cfg(test)]
1961mod like_func_pragma_tests {
1962 use super::{LikeFunc, case_sensitive_like_active, set_case_sensitive_like};
1963 use crate::ScalarFunction;
1964 use fsqlite_types::SqliteValue;
1965
1966 fn like(pattern: &str, text: &str) -> i64 {
1967 match LikeFunc
1968 .invoke(&[
1969 SqliteValue::Text(pattern.into()),
1970 SqliteValue::Text(text.into()),
1971 ])
1972 .unwrap()
1973 {
1974 SqliteValue::Integer(n) => n,
1975 other => panic!("expected integer, got {other:?}"),
1976 }
1977 }
1978
1979 #[test]
1980 fn like_honors_case_sensitive_like_thread_local() {
1981 set_case_sensitive_like(false);
1983 assert_eq!(like("a", "A"), 1);
1984 assert_eq!(like("A%", "apple"), 1);
1985 set_case_sensitive_like(true);
1987 assert!(case_sensitive_like_active());
1988 assert_eq!(like("a", "A"), 0);
1989 assert_eq!(like("A%", "apple"), 0);
1990 assert_eq!(like("A%", "Apple"), 1);
1991 set_case_sensitive_like(false);
1993 }
1994}
1995
1996fn single_char_escape(escape: &str) -> Result<char> {
1997 let mut chars = escape.chars();
1998 match (chars.next(), chars.next()) {
1999 (Some(ch), None) => Ok(ch),
2000 _ => Err(FrankenError::function_error(
2001 "ESCAPE expression must be a single character",
2002 )),
2003 }
2004}
2005
2006fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
2010 sql_like_cased(pattern, string, escape, case_sensitive_like_active())
2011}
2012
2013pub struct GlobFunc;
2016
2017impl ScalarFunction for GlobFunc {
2018 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2019 if let Some(null) = null_propagate(args) {
2020 return Ok(null);
2021 }
2022 let pattern = text_arg(&args[0]);
2023 let string = text_arg(&args[1]);
2024 let matched = glob_match(pattern.as_ref(), string.as_ref());
2025 Ok(SqliteValue::Integer(i64::from(matched)))
2026 }
2027
2028 fn num_args(&self) -> i32 {
2029 2
2030 }
2031
2032 fn name(&self) -> &str {
2033 "glob"
2034 }
2035}
2036
2037fn glob_match(pattern: &str, string: &str) -> bool {
2039 let pat: Vec<char> = pattern.chars().collect();
2040 let txt: Vec<char> = string.chars().collect();
2041 glob_match_inner(&pat, &txt, 0, 0)
2042}
2043
2044fn text_arg(value: &SqliteValue) -> Cow<'_, str> {
2045 match value.as_text_str() {
2046 Some(text) => Cow::Borrowed(text),
2047 None => Cow::Owned(value.to_text()),
2048 }
2049}
2050
2051fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
2052 while pi < pat.len() {
2053 match pat[pi] {
2054 '*' => {
2055 while pi < pat.len() && pat[pi] == '*' {
2056 pi += 1;
2057 }
2058 if pi >= pat.len() {
2059 return true;
2060 }
2061 for start in ti..=txt.len() {
2062 if glob_match_inner(pat, txt, pi, start) {
2063 return true;
2064 }
2065 }
2066 return false;
2067 }
2068 '?' => {
2069 if ti >= txt.len() {
2070 return false;
2071 }
2072 pi += 1;
2073 ti += 1;
2074 }
2075 '[' => {
2076 if ti >= txt.len() {
2077 return false;
2078 }
2079 pi += 1;
2080 let negate = pi < pat.len() && pat[pi] == '^';
2081 if negate {
2082 pi += 1;
2083 }
2084 let mut found = false;
2085 let mut first = true;
2086 while pi < pat.len() && (first || pat[pi] != ']') {
2087 first = false;
2088 if pi + 2 < pat.len() && pat[pi + 1] == '-' && pat[pi + 2] != ']' {
2095 let lo = pat[pi];
2096 let hi = pat[pi + 2];
2097 if txt[ti] >= lo && txt[ti] <= hi {
2098 found = true;
2099 }
2100 pi += 3;
2101 } else {
2102 if txt[ti] == pat[pi] {
2103 found = true;
2104 }
2105 pi += 1;
2106 }
2107 }
2108 if pi < pat.len() && pat[pi] == ']' {
2109 pi += 1;
2110 } else {
2111 return false;
2115 }
2116 if found == negate {
2117 return false;
2118 }
2119 ti += 1;
2120 }
2121 c => {
2122 if ti >= txt.len() || txt[ti] != c {
2123 return false;
2124 }
2125 pi += 1;
2126 ti += 1;
2127 }
2128 }
2129 }
2130 ti >= txt.len()
2131}
2132
2133pub struct UnistrFunc;
2136
2137const INVALID_UNISTR_ESCAPE: &str = "invalid Unicode escape";
2138
2139fn decode_unistr_escape(chars: &mut std::str::Chars<'_>, digits: usize) -> Result<char> {
2140 let mut lookahead = chars.clone();
2141 let mut codepoint = 0u32;
2142 for _ in 0..digits {
2143 let Some(ch) = lookahead.next() else {
2144 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2145 };
2146 let Some(digit) = hex_digit(ch) else {
2147 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2148 };
2149 codepoint = (codepoint << 4) | u32::from(digit);
2150 }
2151 for _ in 0..digits {
2152 let _digit = chars.next();
2153 }
2154 char::from_u32(codepoint).ok_or_else(|| FrankenError::function_error(INVALID_UNISTR_ESCAPE))
2155}
2156
2157impl ScalarFunction for UnistrFunc {
2158 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2159 if args[0].is_null() {
2160 return Ok(SqliteValue::Null);
2161 }
2162 let input = text_arg(&args[0]);
2163 let mut result = String::with_capacity(input.len());
2164 let mut chars = input.as_ref().chars();
2165 while let Some(ch) = chars.next() {
2166 if ch == '\\' {
2167 if chars.as_str().starts_with('\\') {
2169 let _ = chars.next();
2170 result.push('\\');
2171 continue;
2172 }
2173 let digits = if chars.as_str().starts_with('+') {
2174 let _plus = chars.next();
2176 6
2177 } else if chars.as_str().starts_with('u') {
2178 let _marker = chars.next();
2180 4
2181 } else if chars.as_str().starts_with('U') {
2182 let _marker = chars.next();
2184 8
2185 } else {
2186 4
2188 };
2189 result.push(decode_unistr_escape(&mut chars, digits)?);
2190 continue;
2191 }
2192 result.push(ch);
2193 }
2194 Ok(SqliteValue::Text(SmallText::from_string(result)))
2195 }
2196
2197 fn num_args(&self) -> i32 {
2198 1
2199 }
2200
2201 fn name(&self) -> &str {
2202 "unistr"
2203 }
2204}
2205
2206pub struct ChangesFunc;
2211
2212impl ScalarFunction for ChangesFunc {
2213 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2214 Ok(SqliteValue::Integer(LAST_CHANGES.get()))
2215 }
2216
2217 fn is_deterministic(&self) -> bool {
2218 false
2219 }
2220
2221 fn num_args(&self) -> i32 {
2222 0
2223 }
2224
2225 fn name(&self) -> &str {
2226 "changes"
2227 }
2228}
2229
2230pub struct TotalChangesFunc;
2231
2232impl ScalarFunction for TotalChangesFunc {
2233 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2234 Ok(SqliteValue::Integer(TOTAL_CHANGES.get()))
2235 }
2236
2237 fn is_deterministic(&self) -> bool {
2238 false
2239 }
2240
2241 fn num_args(&self) -> i32 {
2242 0
2243 }
2244
2245 fn name(&self) -> &str {
2246 "total_changes"
2247 }
2248}
2249
2250pub struct LastInsertRowidFunc;
2251
2252impl ScalarFunction for LastInsertRowidFunc {
2253 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2254 Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
2255 }
2256
2257 fn is_deterministic(&self) -> bool {
2258 false
2259 }
2260
2261 fn num_args(&self) -> i32 {
2262 0
2263 }
2264
2265 fn name(&self) -> &str {
2266 "last_insert_rowid"
2267 }
2268}
2269
2270#[allow(clippy::too_many_lines)]
2274pub fn register_builtins(registry: &mut FunctionRegistry) {
2275 registry.register_scalar(AbsFunc);
2277 registry.register_scalar(SignFunc);
2278 registry.register_scalar(RoundFunc);
2279 registry.register_scalar(RandomFunc);
2280 registry.register_scalar(RandomblobFunc);
2281 registry.register_scalar(ZeroblobFunc);
2282
2283 registry.register_scalar(LowerFunc);
2285 registry.register_scalar(UpperFunc);
2286 registry.register_scalar(LengthFunc);
2287 registry.register_scalar(OctetLengthFunc);
2288 registry.register_scalar(TrimFunc);
2289 registry.register_scalar(LtrimFunc);
2290 registry.register_scalar(RtrimFunc);
2291 registry.register_scalar(ReplaceFunc);
2292 registry.register_scalar(SubstrFunc);
2293 registry.register_scalar(InstrFunc);
2294 registry.register_scalar(CharFunc);
2295 registry.register_scalar(UnicodeFunc);
2296 registry.register_scalar(UnistrFunc);
2297 registry.register_scalar(HexFunc);
2298 registry.register_scalar(UnhexFunc);
2299 registry.register_scalar(QuoteFunc);
2300 registry.register_scalar(UnistrQuoteFunc);
2301 registry.register_scalar(SoundexFunc);
2302
2303 registry.register_scalar(TypeofFunc);
2305 registry.register_scalar(SubtypeFunc);
2306
2307 registry.register_scalar(CoalesceFunc);
2309 registry.register_scalar(IfnullFunc);
2310 registry.register_scalar(NullifFunc);
2311 registry.register_scalar(IifFunc);
2312
2313 registry.register_scalar(ConcatFunc);
2315 registry.register_scalar(ConcatWsFunc);
2316 registry.register_scalar(ScalarMaxFunc);
2317 registry.register_scalar(ScalarMinFunc);
2318
2319 registry.register_scalar(LikelihoodFunc);
2321 registry.register_scalar(LikelyFunc);
2322 registry.register_scalar(UnlikelyFunc);
2323
2324 registry.register_scalar(LikeFunc);
2326 registry.register_scalar(GlobFunc);
2327
2328 registry.register_slow_changing_scalar(SqliteVersionFunc);
2330 registry.register_slow_changing_scalar(SqliteSourceIdFunc);
2331 registry.register_slow_changing_scalar(SqliteCompileoptionUsedFunc);
2332 registry.register_slow_changing_scalar(SqliteCompileoptionGetFunc);
2333
2334 registry.register_scalar(ChangesFunc);
2336 registry.register_scalar(TotalChangesFunc);
2337 registry.register_scalar(LastInsertRowidFunc);
2338
2339 struct IfFunc;
2342 impl ScalarFunction for IfFunc {
2343 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2344 IifFunc.invoke(args)
2345 }
2346
2347 fn num_args(&self) -> i32 {
2348 -1 }
2350
2351 fn min_args(&self) -> i32 {
2352 2
2353 }
2354
2355 fn max_args(&self) -> Option<i32> {
2356 Some(3)
2357 }
2358
2359 fn name(&self) -> &str {
2360 "if"
2361 }
2362 }
2363 registry.register_scalar(IfFunc);
2364
2365 struct SubstringFunc;
2367 impl ScalarFunction for SubstringFunc {
2368 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2369 SubstrFunc.invoke(args)
2370 }
2371
2372 fn num_args(&self) -> i32 {
2373 -1
2374 }
2375
2376 fn min_args(&self) -> i32 {
2377 2
2378 }
2379
2380 fn max_args(&self) -> Option<i32> {
2381 Some(3)
2382 }
2383
2384 fn name(&self) -> &str {
2385 "substring"
2386 }
2387 }
2388 registry.register_scalar(SubstringFunc);
2389
2390 struct PrintfFunc;
2392 impl ScalarFunction for PrintfFunc {
2393 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2394 FormatFunc.invoke(args)
2395 }
2396
2397 fn num_args(&self) -> i32 {
2398 -1
2399 }
2400
2401 fn name(&self) -> &str {
2402 "printf"
2403 }
2404 }
2405 registry.register_scalar(FormatFunc);
2406 registry.register_scalar(PrintfFunc);
2407
2408 register_math_builtins(registry);
2410
2411 register_datetime_builtins(registry);
2413
2414 register_aggregate_builtins(registry);
2416}
2417
2418pub struct FormatFunc;
2421
2422impl ScalarFunction for FormatFunc {
2423 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2424 if args.is_empty() || args[0].is_null() {
2425 return Ok(SqliteValue::Null);
2426 }
2427 let fmt_str = args[0].to_text();
2428 if fmt_str.is_empty() {
2434 return Ok(SqliteValue::Null);
2435 }
2436 let params = &args[1..];
2437 let result = sqlite_format(&fmt_str, params)?;
2438 Ok(SqliteValue::Text(SmallText::from_string(result)))
2439 }
2440
2441 fn num_args(&self) -> i32 {
2442 -1
2443 }
2444
2445 fn name(&self) -> &str {
2446 "format"
2447 }
2448}
2449
2450fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<String> {
2453 let mut result = String::new();
2454 let chars: Vec<char> = fmt.chars().collect();
2455 let mut i = 0;
2456 let mut param_idx = 0;
2457
2458 while i < chars.len() {
2459 if chars[i] != '%' {
2460 result.push(chars[i]);
2461 i += 1;
2462 continue;
2463 }
2464 i += 1;
2465 if i >= chars.len() {
2466 break;
2467 }
2468
2469 let mut left_align = false;
2471 let mut show_sign = false;
2472 let mut space_sign = false;
2473 let mut zero_pad = false;
2474 let mut alt_form = false;
2475 let mut alt_form2 = false;
2476 loop {
2477 if i >= chars.len() {
2478 break;
2479 }
2480 match chars[i] {
2481 '-' => left_align = true,
2482 '+' => show_sign = true,
2483 ' ' => space_sign = true,
2484 '0' => zero_pad = true,
2485 '#' => alt_form = true,
2486 '!' => alt_form2 = true,
2489 _ => break,
2490 }
2491 i += 1;
2492 }
2493
2494 let mut width = 0usize;
2498 if i < chars.len() && chars[i] == '*' {
2499 i += 1;
2500 let w = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2501 param_idx += 1;
2502 if w < 0 {
2503 left_align = true;
2504 width = usize::try_from(w.unsigned_abs())
2505 .unwrap_or(0)
2506 .min(100_000_000);
2507 } else {
2508 width = usize::try_from(w).unwrap_or(0).min(100_000_000);
2509 }
2510 } else {
2511 while i < chars.len() && chars[i].is_ascii_digit() {
2512 width = width
2513 .saturating_mul(10)
2514 .saturating_add(chars[i] as usize - '0' as usize)
2515 .min(100_000_000); i += 1;
2517 }
2518 }
2519
2520 let mut precision = None;
2524 if i < chars.len() && chars[i] == '.' {
2525 i += 1;
2526 if i < chars.len() && chars[i] == '*' {
2527 i += 1;
2528 let p = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2529 param_idx += 1;
2530 if p >= 0 {
2531 precision = Some(usize::try_from(p).unwrap_or(0).min(100_000_000));
2532 }
2533 } else {
2534 let mut prec = 0usize;
2535 while i < chars.len() && chars[i].is_ascii_digit() {
2536 prec = prec
2537 .saturating_mul(10)
2538 .saturating_add(chars[i] as usize - '0' as usize)
2539 .min(100_000_000); i += 1;
2541 }
2542 precision = Some(prec);
2543 }
2544 }
2545
2546 if i >= chars.len() {
2547 break;
2548 }
2549
2550 let spec = chars[i];
2551 i += 1;
2552
2553 match spec {
2554 '%' => result.push('%'),
2555 'n' => {} 'd' | 'i' => {
2557 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2558 param_idx += 1;
2559 let formatted =
2560 format_integer(val, width, left_align, show_sign, space_sign, zero_pad);
2561 result.push_str(&formatted);
2562 }
2563 'u' => {
2564 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2567 param_idx += 1;
2568 #[allow(clippy::cast_sign_loss)]
2569 let digits = (val as u64).to_string();
2570 let padded = if zero_pad && width > digits.len() {
2571 format!("{}{}", "0".repeat(width - digits.len()), digits)
2572 } else {
2573 pad_string(&digits, width, left_align)
2574 };
2575 result.push_str(&padded);
2576 }
2577 'f' => {
2578 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2579 param_idx += 1;
2580 let val = if val == 0.0 { 0.0 } else { val };
2584 let formatted = if alt_form2 && val.is_finite() {
2585 finish_float_padding(
2589 &format!("{val:?}"),
2590 width,
2591 left_align,
2592 show_sign,
2593 space_sign,
2594 zero_pad,
2595 )
2596 } else {
2597 let prec = precision.unwrap_or(6);
2598 format_float_f(
2599 val, prec, width, left_align, show_sign, space_sign, zero_pad,
2600 )
2601 };
2602 result.push_str(&formatted);
2603 }
2604 'e' | 'E' => {
2605 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2606 param_idx += 1;
2607 let val = if val == 0.0 { 0.0 } else { val };
2609 let prec = precision.unwrap_or(6);
2610 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2611 {
2612 result.push_str(&s);
2613 } else {
2614 let raw = if spec == 'e' {
2615 format!("{val:.prec$e}")
2616 } else {
2617 format!("{val:.prec$E}")
2618 };
2619 let formatted = normalize_exponent(&raw);
2621 result.push_str(&finish_float_padding(
2622 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2623 ));
2624 }
2625 }
2626 'g' | 'G' => {
2627 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2628 param_idx += 1;
2629 let val = if val == 0.0 { 0.0 } else { val };
2632 let prec = precision.unwrap_or(6);
2633 let sig = prec.max(1);
2634 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2635 {
2636 result.push_str(&s);
2637 } else if alt_form2 {
2638 let mut s = format!("{val:?}");
2641 if spec == 'G' {
2642 s = s.to_uppercase();
2643 }
2644 result.push_str(&finish_float_padding(
2645 &s, width, left_align, show_sign, space_sign, zero_pad,
2646 ));
2647 } else {
2648 let formatted = format_float_g(val, sig, spec == 'G');
2649 result.push_str(&finish_float_padding(
2650 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2651 ));
2652 }
2653 }
2654 's' | 'z' => {
2655 let param = params.get(param_idx);
2656 param_idx += 1;
2657 let val = match param {
2658 Some(SqliteValue::Null) | None => String::new(),
2660 Some(v) => v.to_text(),
2661 };
2662 let truncated = if let Some(prec) = precision {
2667 if val.len() > prec {
2668 let mut end = prec;
2669 while end > 0 && !val.is_char_boundary(end) {
2670 end -= 1;
2671 }
2672 val[..end].to_owned()
2673 } else {
2674 val
2675 }
2676 } else {
2677 val
2678 };
2679 result.push_str(&pad_string(&truncated, width, left_align));
2680 }
2681 'q' => {
2682 let param = params.get(param_idx);
2684 param_idx += 1;
2685 match param {
2686 Some(SqliteValue::Null) | None => {
2688 result.push_str("(NULL)");
2689 }
2690 Some(v) => {
2691 let val = v.to_text();
2692 let escaped = val.replace('\'', "''");
2693 result.push_str(&escaped);
2694 }
2695 }
2696 }
2697 'Q' => {
2698 let param = params.get(param_idx);
2700 param_idx += 1;
2701 match param {
2702 Some(SqliteValue::Null) | None => result.push_str("NULL"),
2703 Some(v) => {
2704 let val = v.to_text();
2705 let escaped = val.replace('\'', "''");
2706 result.push('\'');
2707 result.push_str(&escaped);
2708 result.push('\'');
2709 }
2710 }
2711 }
2712 'w' => {
2713 let param = params.get(param_idx);
2717 param_idx += 1;
2718 if matches!(param, Some(SqliteValue::Null) | None) {
2719 } else {
2721 let val = param.map(SqliteValue::to_text).unwrap_or_default();
2722 let escaped = val.replace('"', "\"\"");
2723 result.push_str(&escaped);
2724 }
2725 }
2726 'x' | 'X' => {
2727 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2728 param_idx += 1;
2729 #[allow(clippy::cast_sign_loss)]
2730 let digits = if spec == 'x' {
2731 format!("{:x}", val as u64)
2732 } else {
2733 format!("{:X}", val as u64)
2734 };
2735 let prefix = if alt_form && val != 0 {
2737 if spec == 'x' { "0x" } else { "0X" }
2738 } else {
2739 ""
2740 };
2741 let padded = if zero_pad && width > digits.len() {
2746 let pad = "0".repeat(width - digits.len());
2747 format!("{prefix}{pad}{digits}")
2748 } else {
2749 pad_string(&format!("{prefix}{digits}"), width, left_align)
2750 };
2751 result.push_str(&padded);
2752 }
2753 'o' => {
2754 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2755 param_idx += 1;
2756 #[allow(clippy::cast_sign_loss)]
2757 let digits = format!("{:o}", val as u64);
2758 let prefix = if alt_form && val != 0 { "0" } else { "" };
2760 let padded = if zero_pad && width > digits.len() {
2763 let pad = "0".repeat(width - digits.len());
2764 format!("{prefix}{pad}{digits}")
2765 } else {
2766 pad_string(&format!("{prefix}{digits}"), width, left_align)
2767 };
2768 result.push_str(&padded);
2769 }
2770 'c' => {
2771 let param = params.get(param_idx);
2772 param_idx += 1;
2773 let text = match param {
2778 Some(SqliteValue::Null) | None => String::new(),
2779 Some(v) => v.to_text(),
2780 };
2781 if let Some(c) = text.chars().next() {
2782 result.push(c);
2783 }
2784 }
2785 _ => {
2786 result.push('%');
2788 result.push(spec);
2789 }
2790 }
2791 let _ = (left_align, show_sign, space_sign, zero_pad);
2793 }
2794 Ok(result)
2795}
2796
2797fn format_integer(
2798 val: i64,
2799 width: usize,
2800 left_align: bool,
2801 show_sign: bool,
2802 space_sign: bool,
2803 zero_pad: bool,
2804) -> String {
2805 let sign = if val < 0 {
2806 "-".to_owned()
2807 } else if show_sign {
2808 "+".to_owned()
2809 } else if space_sign {
2810 " ".to_owned()
2811 } else {
2812 String::new()
2813 };
2814 let digits = format!("{}", val.unsigned_abs());
2815 let body = format!("{sign}{digits}");
2816 if body.len() >= width {
2817 return body;
2818 }
2819 let pad = width - body.len();
2820 if left_align {
2821 format!("{body}{}", " ".repeat(pad))
2822 } else if zero_pad {
2823 format!("{sign}{}{digits}", "0".repeat(pad))
2824 } else {
2825 format!("{}{body}", " ".repeat(pad))
2826 }
2827}
2828
2829fn nonfinite_float_str(
2833 val: f64,
2834 width: usize,
2835 left_align: bool,
2836 show_sign: bool,
2837 space_sign: bool,
2838) -> Option<String> {
2839 let body = if val.is_nan() {
2840 "NaN".to_owned()
2841 } else if val.is_infinite() {
2842 let sign = if val < 0.0 {
2843 "-"
2844 } else if show_sign {
2845 "+"
2846 } else if space_sign {
2847 " "
2848 } else {
2849 ""
2850 };
2851 format!("{sign}Inf")
2852 } else {
2853 return None;
2854 };
2855 Some(pad_string(&body, width, left_align))
2856}
2857
2858fn finish_float_padding(
2862 body: &str,
2863 width: usize,
2864 left_align: bool,
2865 show_sign: bool,
2866 space_sign: bool,
2867 zero_pad: bool,
2868) -> String {
2869 let (sign, digits) = if let Some(rest) = body.strip_prefix('-') {
2870 ("-", rest)
2871 } else if show_sign {
2872 ("+", body)
2873 } else if space_sign {
2874 (" ", body)
2875 } else {
2876 ("", body)
2877 };
2878 let full_len = sign.len() + digits.len();
2879 if full_len >= width {
2880 return format!("{sign}{digits}");
2881 }
2882 let pad = width - full_len;
2883 if left_align {
2884 format!("{sign}{digits}{}", " ".repeat(pad))
2885 } else if zero_pad {
2886 format!("{sign}{}{digits}", "0".repeat(pad))
2887 } else {
2888 format!("{}{sign}{digits}", " ".repeat(pad))
2889 }
2890}
2891
2892fn format_float_f(
2893 val: f64,
2894 prec: usize,
2895 width: usize,
2896 left_align: bool,
2897 show_sign: bool,
2898 space_sign: bool,
2899 zero_pad: bool,
2900) -> String {
2901 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign) {
2902 return s;
2903 }
2904 let sign = if val.is_sign_negative() {
2908 "-".to_owned()
2909 } else if show_sign {
2910 "+".to_owned()
2911 } else if space_sign {
2912 " ".to_owned()
2913 } else {
2914 String::new()
2915 };
2916 let digits = format!("{:.prec$}", val.abs());
2917 let body = format!("{sign}{digits}");
2918 if body.len() >= width {
2919 return body;
2920 }
2921 let pad = width - body.len();
2922 if left_align {
2923 format!("{body}{}", " ".repeat(pad))
2924 } else if zero_pad {
2925 format!("{sign}{}{digits}", "0".repeat(pad))
2926 } else {
2927 format!("{}{body}", " ".repeat(pad))
2928 }
2929}
2930
2931fn pad_string(s: &str, width: usize, left_align: bool) -> String {
2932 if s.len() >= width {
2933 return s.to_owned();
2934 }
2935 let pad = width - s.len();
2936 if left_align {
2937 format!("{s}{}", " ".repeat(pad))
2938 } else {
2939 format!("{}{s}", " ".repeat(pad))
2940 }
2941}
2942
2943fn normalize_exponent(s: &str) -> String {
2946 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
2947 (&s[..pos], 'e', &s[pos + 1..])
2948 } else if let Some(pos) = s.find('E') {
2949 (&s[..pos], 'E', &s[pos + 1..])
2950 } else {
2951 return s.to_owned();
2952 };
2953 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
2954 ("-", rest)
2955 } else if let Some(rest) = exp_part.strip_prefix('+') {
2956 ("+", rest)
2957 } else {
2958 ("+", exp_part)
2959 };
2960 let padded = if digits.len() < 2 {
2961 format!("0{digits}")
2962 } else {
2963 digits.to_owned()
2964 };
2965 format!("{prefix}{e_char}{sign}{padded}")
2966}
2967
2968fn format_float_g(val: f64, sig: usize, upper: bool) -> String {
2970 if !val.is_finite() {
2971 return format!("{val}");
2972 }
2973 let val = if val == 0.0 { 0.0 } else { val };
2976 let e_str = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2977 let exp: i32 = e_str
2978 .rsplit_once('e')
2979 .and_then(|(_, e)| e.parse().ok())
2980 .unwrap_or(0);
2981 #[allow(clippy::cast_possible_wrap)]
2982 let formatted = if exp < -4 || exp >= sig as i32 {
2983 let s = format!("{val:.prec$e}", prec = sig.saturating_sub(1));
2984 let s = if upper { s.replace('e', "E") } else { s };
2985 let trimmed = if s.contains('.') {
2987 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
2988 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
2989 format!("{mantissa}{}", &s[e_pos..])
2990 } else {
2991 s.trim_end_matches('0').trim_end_matches('.').to_owned()
2992 }
2993 } else {
2994 s
2995 };
2996 normalize_exponent(&trimmed)
2997 } else {
2998 let decimal_places = if exp >= 0 {
2999 sig.saturating_sub((exp + 1) as usize)
3000 } else {
3001 sig + exp.unsigned_abs() as usize - 1
3002 };
3003 let s = format!("{val:.decimal_places$}");
3004 s.trim_end_matches('0').trim_end_matches('.').to_owned()
3005 };
3006 formatted
3007}
3008
3009#[cfg(test)]
3010#[allow(clippy::too_many_lines)]
3011mod tests {
3012 use super::*;
3013
3014 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
3015 f.invoke(&[v])
3016 }
3017
3018 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
3019 f.invoke(&[a, b])
3020 }
3021
3022 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
3023 let function = registry
3024 .find_scalar(name, arity)
3025 .expect("known scalar name with bad arity returns erroring scalar");
3026 let args = vec![SqliteValue::Null; arity.max(0) as usize];
3027 let err = function
3028 .invoke(&args)
3029 .expect_err("wrong arity should return function error");
3030 let expected = format!("wrong number of arguments to function {name}()");
3031 assert!(
3032 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
3033 "expected {expected:?}, got {err:?}"
3034 );
3035 }
3036
3037 #[test]
3038 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
3039 let original = get_change_tracking_state();
3040 let expected = ChangeTrackingState {
3041 last_insert_rowid: 17,
3042 last_changes: 23,
3043 total_changes: 42,
3044 };
3045
3046 set_change_tracking_state(expected);
3047 assert_eq!(get_change_tracking_state(), expected);
3048
3049 set_change_tracking_state(original);
3050 }
3051
3052 #[test]
3055 fn test_abs_positive() {
3056 assert_eq!(
3057 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
3058 SqliteValue::Integer(42)
3059 );
3060 }
3061
3062 #[test]
3063 fn test_abs_negative() {
3064 assert_eq!(
3065 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
3066 SqliteValue::Integer(42)
3067 );
3068 }
3069
3070 #[test]
3071 fn test_abs_null() {
3072 assert_eq!(
3073 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
3074 SqliteValue::Null
3075 );
3076 }
3077
3078 #[test]
3079 fn test_abs_min_i64_overflow() {
3080 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
3081 assert!(matches!(err, FrankenError::IntegerOverflow));
3082 }
3083
3084 #[test]
3085 fn test_abs_string_coercion() {
3086 assert_eq!(
3087 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
3088 SqliteValue::Float(7.5)
3089 );
3090 }
3091
3092 #[test]
3093 fn test_abs_whitespace_padded_text() {
3094 assert_eq!(
3096 invoke1(
3097 &AbsFunc,
3098 SqliteValue::Text(SmallText::from_string(" 42 "))
3099 )
3100 .unwrap(),
3101 SqliteValue::Float(42.0)
3102 );
3103 assert_eq!(
3104 invoke1(
3105 &AbsFunc,
3106 SqliteValue::Text(SmallText::from_string(" -7.5 "))
3107 )
3108 .unwrap(),
3109 SqliteValue::Float(7.5)
3110 );
3111 assert_eq!(
3112 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3113 SqliteValue::Float(0.0)
3114 );
3115 }
3116
3117 #[test]
3118 #[allow(clippy::approx_constant)]
3119 fn test_abs_float() {
3120 assert_eq!(
3121 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
3122 SqliteValue::Float(3.14)
3123 );
3124 }
3125
3126 #[test]
3129 fn test_char_basic() {
3130 let f = CharFunc;
3131 let result = f
3132 .invoke(&[
3133 SqliteValue::Integer(72),
3134 SqliteValue::Integer(101),
3135 SqliteValue::Integer(108),
3136 SqliteValue::Integer(108),
3137 SqliteValue::Integer(111),
3138 ])
3139 .unwrap();
3140 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
3141 }
3142
3143 #[test]
3144 fn test_char_null_skipped() {
3145 let f = CharFunc;
3146 let result = f
3148 .invoke(&[
3149 SqliteValue::Integer(65),
3150 SqliteValue::Null,
3151 SqliteValue::Integer(66),
3152 ])
3153 .unwrap();
3154 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
3155 }
3156
3157 #[test]
3158 fn test_char_invalid_scalar_values_use_replacement_character() {
3159 let f = CharFunc;
3160 let result = f
3161 .invoke(&[
3162 SqliteValue::Integer(-1),
3163 SqliteValue::Integer(65),
3164 SqliteValue::Integer(1_114_112),
3165 ])
3166 .unwrap();
3167 assert_eq!(
3168 result,
3169 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
3170 );
3171 }
3172
3173 #[test]
3176 fn test_coalesce_first_non_null() {
3177 let f = CoalesceFunc;
3178 let result = f
3179 .invoke(&[
3180 SqliteValue::Null,
3181 SqliteValue::Null,
3182 SqliteValue::Integer(3),
3183 SqliteValue::Integer(4),
3184 ])
3185 .unwrap();
3186 assert_eq!(result, SqliteValue::Integer(3));
3187 }
3188
3189 #[test]
3192 fn test_concat_null_as_empty() {
3193 let f = ConcatFunc;
3194 let result = f
3195 .invoke(&[
3196 SqliteValue::Null,
3197 SqliteValue::Text(SmallText::from_string("hello")),
3198 SqliteValue::Null,
3199 ])
3200 .unwrap();
3201 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
3202 }
3203
3204 #[test]
3205 #[ignore = "perf-only benchmark"]
3206 fn perf_concat_text_args() {
3207 use std::hint::black_box;
3208 use std::time::Instant;
3209
3210 const TEXT_ARGS: usize = 24;
3211 const INVOCATIONS: usize = 50_000;
3212 const REPEATS: usize = 5;
3213
3214 let f = ConcatFunc;
3215 let mut args = Vec::with_capacity(TEXT_ARGS);
3216 for _ in 0..TEXT_ARGS {
3217 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3218 }
3219
3220 let mut best_ns = u128::MAX;
3221 let mut result_len = 0usize;
3222 for _ in 0..REPEATS {
3223 let started = Instant::now();
3224 for _ in 0..INVOCATIONS {
3225 let result = black_box(
3226 f.invoke(black_box(args.as_slice()))
3227 .expect("concat benchmark invocation must succeed"),
3228 );
3229 result_len = match result {
3230 SqliteValue::Text(text) => text.len(),
3231 SqliteValue::Null
3232 | SqliteValue::Integer(_)
3233 | SqliteValue::Float(_)
3234 | SqliteValue::Blob(_) => 0,
3235 };
3236 }
3237 best_ns = best_ns.min(started.elapsed().as_nanos());
3238 }
3239
3240 println!(
3241 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3242 );
3243 }
3244
3245 #[test]
3248 fn test_concat_ws_null_skipped() {
3249 let f = ConcatWsFunc;
3250 let result = f
3251 .invoke(&[
3252 SqliteValue::Text(SmallText::from_string(",")),
3253 SqliteValue::Text(SmallText::from_string("a")),
3254 SqliteValue::Null,
3255 SqliteValue::Text(SmallText::from_string("b")),
3256 ])
3257 .unwrap();
3258 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
3259 }
3260
3261 #[test]
3262 fn test_concat_ws_empty_string_is_not_skipped() {
3263 let f = ConcatWsFunc;
3264 let result = f
3265 .invoke(&[
3266 SqliteValue::Text(SmallText::from_string("|")),
3267 SqliteValue::Text(SmallText::new("")),
3268 SqliteValue::Text(SmallText::from_string("x")),
3269 ])
3270 .unwrap();
3271 assert_eq!(result, SqliteValue::Text(SmallText::from_string("|x")));
3272 }
3273
3274 #[test]
3275 #[ignore = "perf-only benchmark"]
3276 fn perf_concat_ws_text_args() {
3277 use std::hint::black_box;
3278 use std::time::Instant;
3279
3280 const TEXT_ARGS: usize = 24;
3281 const INVOCATIONS: usize = 50_000;
3282 const REPEATS: usize = 5;
3283
3284 let f = ConcatWsFunc;
3285 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
3286 args.push(SqliteValue::Text(SmallText::from_string(",")));
3287 for _ in 0..TEXT_ARGS {
3288 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3289 }
3290
3291 let mut best_ns = u128::MAX;
3292 let mut result_len = 0usize;
3293 for _ in 0..REPEATS {
3294 let started = Instant::now();
3295 for _ in 0..INVOCATIONS {
3296 let result = black_box(
3297 f.invoke(black_box(args.as_slice()))
3298 .expect("concat_ws benchmark invocation must succeed"),
3299 );
3300 result_len = match result {
3301 SqliteValue::Text(text) => text.len(),
3302 SqliteValue::Null
3303 | SqliteValue::Integer(_)
3304 | SqliteValue::Float(_)
3305 | SqliteValue::Blob(_) => 0,
3306 };
3307 }
3308 best_ns = best_ns.min(started.elapsed().as_nanos());
3309 }
3310
3311 println!(
3312 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3313 );
3314 }
3315
3316 #[test]
3319 fn test_hex_blob() {
3320 let result = invoke1(
3321 &HexFunc,
3322 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
3323 )
3324 .unwrap();
3325 assert_eq!(
3326 result,
3327 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
3328 );
3329 }
3330
3331 #[test]
3332 fn test_hex_number_via_text() {
3333 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
3335 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
3336 }
3337
3338 #[test]
3339 #[ignore = "perf-only benchmark"]
3340 fn perf_hex_text_blob_args() {
3341 use std::hint::black_box;
3342 use std::time::Instant;
3343
3344 const BYTES: usize = 24;
3345 const INVOCATIONS: usize = 100_000;
3346 const REPEATS: usize = 5;
3347
3348 let f = HexFunc;
3349 let text_args = [SqliteValue::Text(SmallText::from_string(
3350 "payload payload sentinel",
3351 ))];
3352 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
3353
3354 let mut text_best_ns = u128::MAX;
3355 let mut blob_best_ns = u128::MAX;
3356 let mut text_result_len = 0usize;
3357 let mut blob_result_len = 0usize;
3358 for _ in 0..REPEATS {
3359 let started = Instant::now();
3360 for _ in 0..INVOCATIONS {
3361 let result = black_box(
3362 f.invoke(black_box(text_args.as_slice()))
3363 .expect("hex text benchmark invocation must succeed"),
3364 );
3365 text_result_len = match result {
3366 SqliteValue::Text(text) => text.len(),
3367 SqliteValue::Null
3368 | SqliteValue::Integer(_)
3369 | SqliteValue::Float(_)
3370 | SqliteValue::Blob(_) => 0,
3371 };
3372 }
3373 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
3374
3375 let started = Instant::now();
3376 for _ in 0..INVOCATIONS {
3377 let result = black_box(
3378 f.invoke(black_box(blob_args.as_slice()))
3379 .expect("hex blob benchmark invocation must succeed"),
3380 );
3381 blob_result_len = match result {
3382 SqliteValue::Text(text) => text.len(),
3383 SqliteValue::Null
3384 | SqliteValue::Integer(_)
3385 | SqliteValue::Float(_)
3386 | SqliteValue::Blob(_) => 0,
3387 };
3388 }
3389 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
3390 }
3391
3392 println!(
3393 "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}"
3394 );
3395 }
3396
3397 #[test]
3400 fn test_iif_true() {
3401 let f = IifFunc;
3402 let result = f
3403 .invoke(&[
3404 SqliteValue::Integer(1),
3405 SqliteValue::Text(SmallText::from_string("yes")),
3406 SqliteValue::Text(SmallText::from_string("no")),
3407 ])
3408 .unwrap();
3409 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3410 }
3411
3412 #[test]
3413 fn test_iif_false() {
3414 let f = IifFunc;
3415 let result = f
3416 .invoke(&[
3417 SqliteValue::Integer(0),
3418 SqliteValue::Text(SmallText::from_string("yes")),
3419 SqliteValue::Text(SmallText::from_string("no")),
3420 ])
3421 .unwrap();
3422 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
3423 }
3424
3425 #[test]
3426 fn test_iif_whitespace_padded_text_truthy() {
3427 let f = IifFunc;
3430 let result = f
3431 .invoke(&[
3432 SqliteValue::Text(SmallText::from_string(" 5 ")),
3433 SqliteValue::Text(SmallText::from_string("yes")),
3434 SqliteValue::Text(SmallText::from_string("no")),
3435 ])
3436 .unwrap();
3437 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3438 }
3439
3440 #[test]
3443 fn test_ifnull_non_null() {
3444 assert_eq!(
3445 invoke2(
3446 &IfnullFunc,
3447 SqliteValue::Integer(5),
3448 SqliteValue::Integer(10)
3449 )
3450 .unwrap(),
3451 SqliteValue::Integer(5)
3452 );
3453 }
3454
3455 #[test]
3456 fn test_ifnull_null() {
3457 assert_eq!(
3458 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
3459 SqliteValue::Integer(10)
3460 );
3461 }
3462
3463 #[test]
3466 fn test_instr_found() {
3467 assert_eq!(
3468 invoke2(
3469 &InstrFunc,
3470 SqliteValue::Text(SmallText::from_string("hello world")),
3471 SqliteValue::Text(SmallText::from_string("world"))
3472 )
3473 .unwrap(),
3474 SqliteValue::Integer(7)
3475 );
3476 }
3477
3478 #[test]
3479 fn test_instr_not_found() {
3480 assert_eq!(
3481 invoke2(
3482 &InstrFunc,
3483 SqliteValue::Text(SmallText::from_string("hello")),
3484 SqliteValue::Text(SmallText::from_string("xyz"))
3485 )
3486 .unwrap(),
3487 SqliteValue::Integer(0)
3488 );
3489 }
3490
3491 #[test]
3492 fn test_instr_empty_needle_returns_one() {
3493 assert_eq!(
3495 invoke2(
3496 &InstrFunc,
3497 SqliteValue::Text(SmallText::from_string("hello")),
3498 SqliteValue::Text(SmallText::new(""))
3499 )
3500 .unwrap(),
3501 SqliteValue::Integer(1)
3502 );
3503 }
3504
3505 #[test]
3506 fn test_instr_empty_haystack_returns_zero() {
3507 assert_eq!(
3508 invoke2(
3509 &InstrFunc,
3510 SqliteValue::Text(SmallText::new("")),
3511 SqliteValue::Text(SmallText::from_string("x"))
3512 )
3513 .unwrap(),
3514 SqliteValue::Integer(0)
3515 );
3516 }
3517
3518 #[test]
3519 fn test_instr_blob_empty_needle_returns_one() {
3520 assert_eq!(
3522 invoke2(
3523 &InstrFunc,
3524 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
3525 SqliteValue::Blob(Arc::from([].as_slice()))
3526 )
3527 .unwrap(),
3528 SqliteValue::Integer(1)
3529 );
3530 }
3531
3532 #[test]
3533 #[ignore = "perf-only benchmark"]
3534 fn perf_instr_text_args() {
3535 use std::hint::black_box;
3536 use std::time::Instant;
3537
3538 const INVOCATIONS: usize = 100_000;
3539 const REPEATS: usize = 5;
3540
3541 let f = InstrFunc;
3542 let args = [
3543 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
3544 SqliteValue::Text(SmallText::from_string("sentinel")),
3545 ];
3546
3547 let mut best_ns = u128::MAX;
3548 let mut result_value = 0i64;
3549 for _ in 0..REPEATS {
3550 let started = Instant::now();
3551 for _ in 0..INVOCATIONS {
3552 let result = black_box(
3553 f.invoke(black_box(args.as_slice()))
3554 .expect("instr benchmark invocation must succeed"),
3555 );
3556 result_value = match result {
3557 SqliteValue::Integer(value) => value,
3558 SqliteValue::Null
3559 | SqliteValue::Float(_)
3560 | SqliteValue::Text(_)
3561 | SqliteValue::Blob(_) => 0,
3562 };
3563 }
3564 best_ns = best_ns.min(started.elapsed().as_nanos());
3565 }
3566
3567 println!(
3568 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
3569 );
3570 }
3571
3572 #[test]
3575 fn test_length_text_chars() {
3576 assert_eq!(
3578 invoke1(
3579 &LengthFunc,
3580 SqliteValue::Text(SmallText::from_string("café"))
3581 )
3582 .unwrap(),
3583 SqliteValue::Integer(4)
3584 );
3585 }
3586
3587 #[test]
3588 fn test_length_text_stops_at_nul() {
3589 assert_eq!(
3590 invoke1(
3591 &LengthFunc,
3592 SqliteValue::Text(SmallText::from_string("A\0B"))
3593 )
3594 .unwrap(),
3595 SqliteValue::Integer(1)
3596 );
3597 assert_eq!(
3598 invoke1(
3599 &LengthFunc,
3600 SqliteValue::Text(SmallText::from_string("\0A"))
3601 )
3602 .unwrap(),
3603 SqliteValue::Integer(0)
3604 );
3605 }
3606
3607 #[test]
3608 fn test_length_blob_bytes() {
3609 assert_eq!(
3610 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
3611 SqliteValue::Integer(2)
3612 );
3613 }
3614
3615 #[test]
3618 fn test_octet_length_multibyte() {
3619 assert_eq!(
3621 invoke1(
3622 &OctetLengthFunc,
3623 SqliteValue::Text(SmallText::from_string("café"))
3624 )
3625 .unwrap(),
3626 SqliteValue::Integer(5)
3627 );
3628 }
3629
3630 #[test]
3633 fn test_lower_ascii() {
3634 assert_eq!(
3635 invoke1(
3636 &LowerFunc,
3637 SqliteValue::Text(SmallText::from_string("HELLO"))
3638 )
3639 .unwrap(),
3640 SqliteValue::Text(SmallText::from_string("hello"))
3641 );
3642 }
3643
3644 #[test]
3645 fn test_upper_ascii() {
3646 assert_eq!(
3647 invoke1(
3648 &UpperFunc,
3649 SqliteValue::Text(SmallText::from_string("hello"))
3650 )
3651 .unwrap(),
3652 SqliteValue::Text(SmallText::from_string("HELLO"))
3653 );
3654 }
3655
3656 #[test]
3659 fn test_trim_default() {
3660 let f = TrimFunc;
3661 assert_eq!(
3662 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
3663 .unwrap(),
3664 SqliteValue::Text(SmallText::from_string("hello"))
3665 );
3666 }
3667
3668 #[test]
3669 fn test_ltrim_default() {
3670 let f = LtrimFunc;
3671 assert_eq!(
3672 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
3673 .unwrap(),
3674 SqliteValue::Text(SmallText::from_string("hello"))
3675 );
3676 }
3677
3678 #[test]
3679 fn test_ltrim_custom() {
3680 let f = LtrimFunc;
3681 assert_eq!(
3682 f.invoke(&[
3683 SqliteValue::Text(SmallText::from_string("xxhello")),
3684 SqliteValue::Text(SmallText::from_string("x")),
3685 ])
3686 .unwrap(),
3687 SqliteValue::Text(SmallText::from_string("hello"))
3688 );
3689 }
3690
3691 #[test]
3692 #[ignore = "perf-only benchmark"]
3693 fn perf_trim_text_args() {
3694 use std::hint::black_box;
3695 use std::time::Instant;
3696
3697 const INVOCATIONS: usize = 100_000;
3698 const REPEATS: usize = 5;
3699
3700 let trim = TrimFunc;
3701 let ltrim = LtrimFunc;
3702 let rtrim = RtrimFunc;
3703 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
3704 let custom_args = [
3705 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
3706 SqliteValue::Text(SmallText::from_string("x")),
3707 ];
3708
3709 let mut trim_best_ns = u128::MAX;
3710 let mut ltrim_best_ns = u128::MAX;
3711 let mut rtrim_best_ns = u128::MAX;
3712 let mut custom_best_ns = u128::MAX;
3713 let mut result_len = 0usize;
3714
3715 for _ in 0..REPEATS {
3716 let started = Instant::now();
3717 for _ in 0..INVOCATIONS {
3718 let result = black_box(
3719 trim.invoke(black_box(default_args.as_slice()))
3720 .expect("trim benchmark invocation must succeed"),
3721 );
3722 result_len = match result {
3723 SqliteValue::Text(text) => text.len(),
3724 SqliteValue::Null
3725 | SqliteValue::Integer(_)
3726 | SqliteValue::Float(_)
3727 | SqliteValue::Blob(_) => 0,
3728 };
3729 }
3730 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
3731
3732 let started = Instant::now();
3733 for _ in 0..INVOCATIONS {
3734 let result = black_box(
3735 ltrim
3736 .invoke(black_box(default_args.as_slice()))
3737 .expect("ltrim benchmark invocation must succeed"),
3738 );
3739 result_len = match result {
3740 SqliteValue::Text(text) => text.len(),
3741 SqliteValue::Null
3742 | SqliteValue::Integer(_)
3743 | SqliteValue::Float(_)
3744 | SqliteValue::Blob(_) => 0,
3745 };
3746 }
3747 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
3748
3749 let started = Instant::now();
3750 for _ in 0..INVOCATIONS {
3751 let result = black_box(
3752 rtrim
3753 .invoke(black_box(default_args.as_slice()))
3754 .expect("rtrim benchmark invocation must succeed"),
3755 );
3756 result_len = match result {
3757 SqliteValue::Text(text) => text.len(),
3758 SqliteValue::Null
3759 | SqliteValue::Integer(_)
3760 | SqliteValue::Float(_)
3761 | SqliteValue::Blob(_) => 0,
3762 };
3763 }
3764 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
3765
3766 let started = Instant::now();
3767 for _ in 0..INVOCATIONS {
3768 let result = black_box(
3769 trim.invoke(black_box(custom_args.as_slice()))
3770 .expect("custom trim benchmark invocation must succeed"),
3771 );
3772 result_len = match result {
3773 SqliteValue::Text(text) => text.len(),
3774 SqliteValue::Null
3775 | SqliteValue::Integer(_)
3776 | SqliteValue::Float(_)
3777 | SqliteValue::Blob(_) => 0,
3778 };
3779 }
3780 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
3781 }
3782
3783 println!(
3784 "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}"
3785 );
3786 }
3787
3788 #[test]
3791 fn test_nullif_equal() {
3792 assert_eq!(
3793 invoke2(
3794 &NullifFunc,
3795 SqliteValue::Integer(5),
3796 SqliteValue::Integer(5)
3797 )
3798 .unwrap(),
3799 SqliteValue::Null
3800 );
3801 }
3802
3803 #[test]
3804 fn test_nullif_different() {
3805 assert_eq!(
3806 invoke2(
3807 &NullifFunc,
3808 SqliteValue::Integer(5),
3809 SqliteValue::Integer(3)
3810 )
3811 .unwrap(),
3812 SqliteValue::Integer(5)
3813 );
3814 }
3815
3816 #[test]
3819 fn test_typeof_each() {
3820 assert_eq!(
3821 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
3822 SqliteValue::Text(SmallText::from_string("null"))
3823 );
3824 assert_eq!(
3825 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
3826 SqliteValue::Text(SmallText::from_string("integer"))
3827 );
3828 assert_eq!(
3829 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
3830 SqliteValue::Text(SmallText::from_string("real"))
3831 );
3832 assert_eq!(
3833 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
3834 SqliteValue::Text(SmallText::from_string("text"))
3835 );
3836 assert_eq!(
3837 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
3838 SqliteValue::Text(SmallText::from_string("blob"))
3839 );
3840 }
3841
3842 #[test]
3845 fn test_subtype_null_returns_zero() {
3846 assert_eq!(
3847 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
3848 SqliteValue::Integer(0)
3849 );
3850 }
3851
3852 #[test]
3855 fn test_replace_basic() {
3856 let f = ReplaceFunc;
3857 assert_eq!(
3858 f.invoke(&[
3859 SqliteValue::Text(SmallText::from_string("hello world")),
3860 SqliteValue::Text(SmallText::from_string("world")),
3861 SqliteValue::Text(SmallText::from_string("earth")),
3862 ])
3863 .unwrap(),
3864 SqliteValue::Text(SmallText::from_string("hello earth"))
3865 );
3866 }
3867
3868 #[test]
3869 fn test_replace_empty_y() {
3870 let f = ReplaceFunc;
3871 assert_eq!(
3872 f.invoke(&[
3873 SqliteValue::Text(SmallText::from_string("hello")),
3874 SqliteValue::Text(SmallText::new("")),
3875 SqliteValue::Text(SmallText::from_string("x")),
3876 ])
3877 .unwrap(),
3878 SqliteValue::Text(SmallText::from_string("hello"))
3879 );
3880 }
3881
3882 #[test]
3883 #[ignore = "perf-only benchmark"]
3884 fn perf_replace_text_args() {
3885 use std::hint::black_box;
3886 use std::time::Instant;
3887
3888 const INVOCATIONS: usize = 100_000;
3889 const REPEATS: usize = 5;
3890
3891 let f = ReplaceFunc;
3892 let args = [
3893 SqliteValue::Text(SmallText::from_string("payload payload payload")),
3894 SqliteValue::Text(SmallText::from_string("zz")),
3895 SqliteValue::Text(SmallText::from_string("replacement")),
3896 ];
3897
3898 let mut best_ns = u128::MAX;
3899 let mut result_len = 0usize;
3900 for _ in 0..REPEATS {
3901 let started = Instant::now();
3902 for _ in 0..INVOCATIONS {
3903 let result = black_box(
3904 f.invoke(black_box(args.as_slice()))
3905 .expect("replace benchmark invocation must succeed"),
3906 );
3907 result_len = match result {
3908 SqliteValue::Text(text) => text.len(),
3909 SqliteValue::Null
3910 | SqliteValue::Integer(_)
3911 | SqliteValue::Float(_)
3912 | SqliteValue::Blob(_) => 0,
3913 };
3914 }
3915 best_ns = best_ns.min(started.elapsed().as_nanos());
3916 }
3917
3918 println!(
3919 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3920 );
3921 }
3922
3923 #[test]
3926 #[allow(clippy::float_cmp)]
3927 fn test_round_half_away() {
3928 assert_eq!(
3930 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
3931 SqliteValue::Float(3.0)
3932 );
3933 assert_eq!(
3934 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
3935 SqliteValue::Float(-3.0)
3936 );
3937 }
3938
3939 #[test]
3940 #[allow(clippy::float_cmp, clippy::approx_constant)]
3941 fn test_round_precision() {
3942 assert_eq!(
3943 RoundFunc
3944 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
3945 .unwrap(),
3946 SqliteValue::Float(3.14)
3947 );
3948 }
3949
3950 #[test]
3951 #[allow(clippy::float_cmp)]
3952 fn test_round_extreme_n_clamped() {
3953 assert_eq!(
3955 RoundFunc
3956 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
3957 .unwrap(),
3958 RoundFunc
3959 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
3960 .unwrap(),
3961 );
3962 assert_eq!(
3964 RoundFunc
3965 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
3966 .unwrap(),
3967 SqliteValue::Float(3.0)
3968 );
3969 let result = RoundFunc
3971 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
3972 .unwrap();
3973 if let SqliteValue::Float(v) = result {
3974 assert!(!v.is_nan(), "round must never return NaN");
3975 }
3976 }
3977
3978 #[test]
3979 #[allow(clippy::float_cmp)]
3980 fn test_round_large_value_no_fractional() {
3981 let big = 9_007_199_254_740_993.0_f64;
3983 assert_eq!(
3984 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
3985 SqliteValue::Float(big)
3986 );
3987 assert_eq!(
3988 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
3989 SqliteValue::Float(-big)
3990 );
3991 }
3992
3993 #[test]
3996 fn test_sign_positive() {
3997 assert_eq!(
3998 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
3999 SqliteValue::Integer(1)
4000 );
4001 }
4002
4003 #[test]
4004 fn test_sign_negative() {
4005 assert_eq!(
4006 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
4007 SqliteValue::Integer(-1)
4008 );
4009 }
4010
4011 #[test]
4012 fn test_sign_zero() {
4013 assert_eq!(
4014 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
4015 SqliteValue::Integer(0)
4016 );
4017 }
4018
4019 #[test]
4020 fn test_sign_null() {
4021 assert_eq!(
4022 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
4023 SqliteValue::Null
4024 );
4025 }
4026
4027 #[test]
4028 fn test_sign_non_numeric() {
4029 assert_eq!(
4031 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
4032 SqliteValue::Null
4033 );
4034 }
4035
4036 #[test]
4037 fn test_sign_whitespace_padded_text() {
4038 assert_eq!(
4041 invoke1(
4042 &SignFunc,
4043 SqliteValue::Text(SmallText::from_string(" 5 "))
4044 )
4045 .unwrap(),
4046 SqliteValue::Integer(1)
4047 );
4048 assert_eq!(
4049 invoke1(
4050 &SignFunc,
4051 SqliteValue::Text(SmallText::from_string(" -3.14 "))
4052 )
4053 .unwrap(),
4054 SqliteValue::Integer(-1)
4055 );
4056 }
4057
4058 #[test]
4059 fn test_sign_unicode_space_and_blob_return_null() {
4060 assert_eq!(
4061 invoke1(
4062 &SignFunc,
4063 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
4064 )
4065 .unwrap(),
4066 SqliteValue::Null
4067 );
4068 assert_eq!(
4069 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
4070 SqliteValue::Null
4071 );
4072 }
4073
4074 #[test]
4075 fn test_sign_nan_inf_text_returns_null() {
4076 for s in &[
4079 "NaN",
4080 "nan",
4081 "inf",
4082 "-inf",
4083 "Infinity",
4084 "-Infinity",
4085 "INF",
4086 "+nan",
4087 "+inf",
4088 ] {
4089 assert_eq!(
4090 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
4091 SqliteValue::Null,
4092 "sign('{s}') should be NULL"
4093 );
4094 }
4095 }
4096
4097 #[test]
4098 fn test_sign_numeric_overflow_to_infinity() {
4099 assert_eq!(
4102 invoke1(
4103 &SignFunc,
4104 SqliteValue::Text(SmallText::from_string("1e999"))
4105 )
4106 .unwrap(),
4107 SqliteValue::Integer(1)
4108 );
4109 assert_eq!(
4110 invoke1(
4111 &SignFunc,
4112 SqliteValue::Text(SmallText::from_string("-1e999"))
4113 )
4114 .unwrap(),
4115 SqliteValue::Integer(-1)
4116 );
4117 assert_eq!(
4119 invoke1(
4120 &SignFunc,
4121 SqliteValue::Text(SmallText::from_string("1e-999"))
4122 )
4123 .unwrap(),
4124 SqliteValue::Integer(0)
4125 );
4126 }
4127
4128 #[test]
4129 fn test_sign_float_nan_returns_null() {
4130 assert_eq!(
4132 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
4133 SqliteValue::Null
4134 );
4135 }
4136
4137 #[test]
4140 fn test_scalar_max_null() {
4141 let f = ScalarMaxFunc;
4142 let result = f
4143 .invoke(&[
4144 SqliteValue::Integer(1),
4145 SqliteValue::Null,
4146 SqliteValue::Integer(3),
4147 ])
4148 .unwrap();
4149 assert_eq!(result, SqliteValue::Null);
4150 }
4151
4152 #[test]
4153 fn test_scalar_max_values() {
4154 let f = ScalarMaxFunc;
4155 let result = f
4156 .invoke(&[
4157 SqliteValue::Integer(3),
4158 SqliteValue::Integer(1),
4159 SqliteValue::Integer(2),
4160 ])
4161 .unwrap();
4162 assert_eq!(result, SqliteValue::Integer(3));
4163 }
4164
4165 #[test]
4166 fn test_scalar_min_null() {
4167 let f = ScalarMinFunc;
4168 let result = f
4169 .invoke(&[
4170 SqliteValue::Integer(1),
4171 SqliteValue::Null,
4172 SqliteValue::Integer(3),
4173 ])
4174 .unwrap();
4175 assert_eq!(result, SqliteValue::Null);
4176 }
4177
4178 #[test]
4179 fn test_scalar_min_selects_later_equal_value_while_max_keeps_first() {
4180 let min = ScalarMinFunc;
4181 let max = ScalarMaxFunc;
4182 let numeric = [SqliteValue::Integer(1), SqliteValue::Float(1.0)];
4183 assert!(matches!(
4184 min.invoke(&numeric).unwrap(),
4185 SqliteValue::Float(value) if value == 1.0
4186 ));
4187 assert_eq!(max.invoke(&numeric).unwrap(), SqliteValue::Integer(1));
4188
4189 let text = [
4190 SqliteValue::Text(SmallText::new("a")),
4191 SqliteValue::Text(SmallText::new("A")),
4192 ];
4193 let nocase = crate::collation::NoCaseCollation;
4194 assert_eq!(
4195 min.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4196 SqliteValue::Text(SmallText::new("A"))
4197 );
4198 assert_eq!(
4199 max.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4200 SqliteValue::Text(SmallText::new("a"))
4201 );
4202 }
4203
4204 #[test]
4207 fn test_quote_text() {
4208 assert_eq!(
4209 invoke1(
4210 &QuoteFunc,
4211 SqliteValue::Text(SmallText::from_string("it's"))
4212 )
4213 .unwrap(),
4214 SqliteValue::Text(SmallText::from_string("'it''s'"))
4215 );
4216 }
4217
4218 #[test]
4219 fn test_quote_null() {
4220 assert_eq!(
4221 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
4222 SqliteValue::Text(SmallText::from_string("NULL"))
4223 );
4224 }
4225
4226 #[test]
4227 fn test_quote_blob() {
4228 assert_eq!(
4229 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
4230 SqliteValue::Text(SmallText::from_string("X'AB'"))
4231 );
4232 }
4233
4234 #[test]
4235 fn test_quote_text_truncates_at_first_nul() {
4236 assert_eq!(
4237 invoke1(
4238 &QuoteFunc,
4239 SqliteValue::Text(SmallText::from_string("A\0B"))
4240 )
4241 .unwrap(),
4242 SqliteValue::Text(SmallText::from_string("'A'"))
4243 );
4244 }
4245
4246 #[test]
4247 fn test_unistr_quote_plain_text_matches_quote() {
4248 assert_eq!(
4249 invoke1(
4250 &UnistrQuoteFunc,
4251 SqliteValue::Text(SmallText::from_string("it's"))
4252 )
4253 .unwrap(),
4254 SqliteValue::Text(SmallText::from_string("'it''s'"))
4255 );
4256 }
4257
4258 #[test]
4259 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
4260 assert_eq!(
4261 invoke1(
4262 &UnistrQuoteFunc,
4263 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
4264 )
4265 .unwrap(),
4266 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
4267 );
4268 }
4269
4270 #[test]
4271 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
4272 assert_eq!(
4273 invoke1(
4274 &UnistrQuoteFunc,
4275 SqliteValue::Text(SmallText::from_string("A\0\nB"))
4276 )
4277 .unwrap(),
4278 SqliteValue::Text(SmallText::from_string("'A'"))
4279 );
4280 }
4281
4282 #[test]
4283 fn test_unistr_decodes_backslash_and_unicode_escapes() {
4284 assert_eq!(
4285 invoke1(
4286 &UnistrFunc,
4287 SqliteValue::Text(SmallText::from_string(
4288 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
4289 ))
4290 )
4291 .unwrap(),
4292 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
4293 );
4294 }
4295
4296 #[test]
4297 fn test_unistr_invalid_escape_returns_error() {
4298 for input in [
4299 "\\u12xz",
4300 "\\12xz",
4301 "\\+00xz",
4302 "\\",
4303 "\\x",
4304 "\\U00110000",
4305 "\\D800",
4306 ] {
4307 let err = invoke1(
4308 &UnistrFunc,
4309 SqliteValue::Text(SmallText::from_string(input)),
4310 )
4311 .unwrap_err();
4312 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
4313 }
4314 }
4315
4316 #[test]
4317 #[ignore = "perf-only benchmark"]
4318 fn perf_unistr_text_args() {
4319 use std::hint::black_box;
4320 use std::time::Instant;
4321
4322 const INVOCATIONS: usize = 500_000;
4323 const REPEATS: usize = 7;
4324
4325 let f = UnistrFunc;
4326 let plain_args = [SqliteValue::Text(SmallText::from_string(
4327 "plain unicode payload",
4328 ))];
4329 let escaped_args = [SqliteValue::Text(SmallText::from_string(
4330 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
4331 ))];
4332
4333 let mut plain_best_ns = u128::MAX;
4334 let mut escaped_best_ns = u128::MAX;
4335 let mut checksum = 0usize;
4336 for _ in 0..REPEATS {
4337 let started = Instant::now();
4338 for _ in 0..INVOCATIONS {
4339 let result = black_box(
4340 f.invoke(black_box(plain_args.as_slice()))
4341 .expect("unistr plain benchmark invocation must succeed"),
4342 );
4343 if let SqliteValue::Text(text) = result {
4344 checksum = checksum.wrapping_add(text.len());
4345 }
4346 }
4347 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4348
4349 let started = Instant::now();
4350 for _ in 0..INVOCATIONS {
4351 let result = black_box(
4352 f.invoke(black_box(escaped_args.as_slice()))
4353 .expect("unistr escaped benchmark invocation must succeed"),
4354 );
4355 if let SqliteValue::Text(text) = result {
4356 checksum = checksum.wrapping_add(text.len());
4357 }
4358 }
4359 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
4360 }
4361
4362 println!(
4363 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
4364 );
4365 }
4366
4367 #[test]
4370 fn test_random_range() {
4371 let f = RandomFunc;
4372 let result = f.invoke(&[]).unwrap();
4373 assert!(matches!(result, SqliteValue::Integer(_)));
4374 }
4375
4376 #[test]
4379 fn test_randomblob_length() {
4380 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
4381 match result {
4382 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
4383 other => unreachable!("expected blob, got {other:?}"),
4384 }
4385 }
4386
4387 #[test]
4388 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
4389 for arg in [
4390 SqliteValue::Null,
4391 SqliteValue::Integer(0),
4392 SqliteValue::Integer(-5),
4393 ] {
4394 let result = invoke1(&RandomblobFunc, arg).unwrap();
4395 match result {
4396 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
4397 other => unreachable!("expected one-byte blob, got {other:?}"),
4398 }
4399 }
4400 }
4401
4402 #[test]
4405 fn test_zeroblob_length() {
4406 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
4407 match result {
4408 SqliteValue::Blob(b) => {
4409 assert_eq!(b.len(), 100);
4410 assert!(b.iter().all(|&x| x == 0));
4411 }
4412 other => unreachable!("expected blob, got {other:?}"),
4413 }
4414 }
4415
4416 #[test]
4419 fn test_unhex_valid() {
4420 let result = invoke1(
4421 &UnhexFunc,
4422 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
4423 )
4424 .unwrap();
4425 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
4426 }
4427
4428 #[test]
4429 fn test_unhex_invalid() {
4430 let result = invoke1(
4431 &UnhexFunc,
4432 SqliteValue::Text(SmallText::from_string("ZZZZ")),
4433 )
4434 .unwrap();
4435 assert_eq!(result, SqliteValue::Null);
4436 }
4437
4438 #[test]
4439 fn test_unhex_ignore_chars() {
4440 let f = UnhexFunc;
4441 let result = f
4442 .invoke(&[
4443 SqliteValue::Text(SmallText::from_string("48-65-6C")),
4444 SqliteValue::Text(SmallText::from_string("-")),
4445 ])
4446 .unwrap();
4447 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
4448 }
4449
4450 #[test]
4451 fn test_unhex_ignore_chars_only_between_byte_pairs() {
4452 let f = UnhexFunc;
4453 let result = f
4454 .invoke(&[
4455 SqliteValue::Text(SmallText::from_string("AB CD")),
4456 SqliteValue::Text(SmallText::from_string(" ")),
4457 ])
4458 .unwrap();
4459 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
4460
4461 let result = f
4462 .invoke(&[
4463 SqliteValue::Text(SmallText::from_string("A BCD")),
4464 SqliteValue::Text(SmallText::from_string(" ")),
4465 ])
4466 .unwrap();
4467 assert_eq!(result, SqliteValue::Null);
4468 }
4469
4470 #[test]
4471 fn test_unhex_null_ignore_argument_returns_null() {
4472 let f = UnhexFunc;
4473 let result = f
4474 .invoke(&[
4475 SqliteValue::Text(SmallText::from_string("41")),
4476 SqliteValue::Null,
4477 ])
4478 .unwrap();
4479 assert_eq!(result, SqliteValue::Null);
4480 }
4481
4482 #[test]
4483 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
4484 let f = UnhexFunc;
4485 let result = f
4486 .invoke(&[
4487 SqliteValue::Text(SmallText::from_string("41")),
4488 SqliteValue::Text(SmallText::from_string("4")),
4489 ])
4490 .unwrap();
4491 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
4492 }
4493
4494 #[test]
4495 #[ignore = "perf-only benchmark"]
4496 fn perf_unhex_text_args() {
4497 use std::hint::black_box;
4498 use std::time::Instant;
4499
4500 const INVOCATIONS: usize = 300_000;
4501 const REPEATS: usize = 7;
4502
4503 let f = UnhexFunc;
4504 let plain_args = [SqliteValue::Text(SmallText::from_string(
4505 "48656C6C6F776F726C64",
4506 ))];
4507 let ignore_args = [
4508 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
4509 SqliteValue::Text(SmallText::from_string("-")),
4510 ];
4511 let mut plain_best_ns = u128::MAX;
4512 let mut ignore_best_ns = u128::MAX;
4513 let mut checksum = 0usize;
4514
4515 for _ in 0..REPEATS {
4516 let started = Instant::now();
4517 for _ in 0..INVOCATIONS {
4518 let result = black_box(
4519 f.invoke(black_box(plain_args.as_slice()))
4520 .expect("unhex benchmark invocation must succeed"),
4521 );
4522 if let SqliteValue::Blob(blob) = result {
4523 checksum = checksum.wrapping_add(blob.len());
4524 }
4525 }
4526 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4527
4528 let started = Instant::now();
4529 for _ in 0..INVOCATIONS {
4530 let result = black_box(
4531 f.invoke(black_box(ignore_args.as_slice()))
4532 .expect("unhex ignore benchmark invocation must succeed"),
4533 );
4534 if let SqliteValue::Blob(blob) = result {
4535 checksum = checksum.wrapping_add(blob.len());
4536 }
4537 }
4538 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
4539 }
4540
4541 println!(
4542 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
4543 );
4544 }
4545
4546 #[test]
4549 fn test_unicode_first_char() {
4550 assert_eq!(
4551 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
4552 SqliteValue::Integer(65)
4553 );
4554 }
4555
4556 #[test]
4557 fn test_unicode_text_stops_at_nul() {
4558 assert_eq!(
4559 invoke1(
4560 &UnicodeFunc,
4561 SqliteValue::Text(SmallText::from_string("\0A"))
4562 )
4563 .unwrap(),
4564 SqliteValue::Null
4565 );
4566 assert_eq!(
4567 invoke1(
4568 &UnicodeFunc,
4569 SqliteValue::Text(SmallText::from_string("A\0"))
4570 )
4571 .unwrap(),
4572 SqliteValue::Integer(65)
4573 );
4574 }
4575
4576 #[test]
4577 fn test_unicode_blob_uses_sqlite_utf8_reader() {
4578 let cases: &[(&[u8], SqliteValue)] = &[
4579 (&[0x00, 0x41], SqliteValue::Null),
4580 (&[0x80], SqliteValue::Integer(128)),
4581 (&[0xC2, 0x80], SqliteValue::Integer(128)),
4582 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
4583 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
4584 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
4585 ];
4586
4587 for (bytes, expected) in cases {
4588 assert_eq!(
4589 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
4590 expected.clone()
4591 );
4592 }
4593 }
4594
4595 #[test]
4596 #[ignore = "perf-only benchmark"]
4597 fn perf_unicode_text_arg() {
4598 use std::hint::black_box;
4599 use std::time::Instant;
4600
4601 const INVOCATIONS: usize = 1_000_000;
4602 const REPEATS: usize = 7;
4603
4604 let f = UnicodeFunc;
4605 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
4606 let mut text_best_ns = u128::MAX;
4607 let mut checksum = 0i64;
4608
4609 for _ in 0..REPEATS {
4610 let started = Instant::now();
4611 for _ in 0..INVOCATIONS {
4612 let result = black_box(
4613 f.invoke(black_box(args.as_slice()))
4614 .expect("unicode benchmark invocation must succeed"),
4615 );
4616 if let SqliteValue::Integer(codepoint) = result {
4617 checksum = checksum.wrapping_add(codepoint);
4618 }
4619 }
4620 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4621 }
4622
4623 println!(
4624 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4625 );
4626 }
4627
4628 #[test]
4631 fn test_soundex_basic() {
4632 assert_eq!(
4633 invoke1(
4634 &SoundexFunc,
4635 SqliteValue::Text(SmallText::from_string("Robert"))
4636 )
4637 .unwrap(),
4638 SqliteValue::Text(SmallText::from_string("R163"))
4639 );
4640 }
4641
4642 #[test]
4643 #[ignore = "perf-only benchmark"]
4644 fn perf_soundex_text_arg() {
4645 use std::hint::black_box;
4646 use std::time::Instant;
4647
4648 const INVOCATIONS: usize = 1_000_000;
4649 const REPEATS: usize = 7;
4650
4651 let f = SoundexFunc;
4652 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
4653 let mut text_best_ns = u128::MAX;
4654 let mut checksum = 0usize;
4655
4656 for _ in 0..REPEATS {
4657 let started = Instant::now();
4658 for _ in 0..INVOCATIONS {
4659 let result = black_box(
4660 f.invoke(black_box(args.as_slice()))
4661 .expect("soundex benchmark invocation must succeed"),
4662 );
4663 if let SqliteValue::Text(text) = result {
4664 checksum = checksum.wrapping_add(text.len());
4665 }
4666 }
4667 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4668 }
4669
4670 println!(
4671 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
4672 );
4673 }
4674
4675 #[test]
4678 fn test_substr_basic() {
4679 let f = SubstrFunc;
4680 assert_eq!(
4681 f.invoke(&[
4682 SqliteValue::Text(SmallText::from_string("hello")),
4683 SqliteValue::Integer(2),
4684 SqliteValue::Integer(3),
4685 ])
4686 .unwrap(),
4687 SqliteValue::Text(SmallText::from_string("ell"))
4688 );
4689 }
4690
4691 #[test]
4692 fn test_substr_start_zero_quirk() {
4693 let f = SubstrFunc;
4695 let result = f
4696 .invoke(&[
4697 SqliteValue::Text(SmallText::from_string("hello")),
4698 SqliteValue::Integer(0),
4699 SqliteValue::Integer(3),
4700 ])
4701 .unwrap();
4702 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
4703 }
4704
4705 #[test]
4706 fn test_substr_negative_start() {
4707 let f = SubstrFunc;
4709 let result = f
4710 .invoke(&[
4711 SqliteValue::Text(SmallText::from_string("hello")),
4712 SqliteValue::Integer(-2),
4713 ])
4714 .unwrap();
4715 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
4716 }
4717
4718 #[test]
4719 fn test_substr_negative_length() {
4720 let f = SubstrFunc;
4721 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4722 let i = SqliteValue::Integer;
4723 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
4725 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
4727 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
4729 }
4730
4731 #[test]
4732 fn test_substr_negative_start_negative_length() {
4733 let f = SubstrFunc;
4734 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4735 let i = SqliteValue::Integer;
4736 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
4738 }
4739
4740 #[test]
4741 fn test_substr_edge_cases() {
4742 let f = SubstrFunc;
4743 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
4744 let i = SqliteValue::Integer;
4745 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
4747 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
4749 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
4751 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
4753 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
4755 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
4757 }
4758
4759 #[test]
4760 fn test_substr_blob_negative_length() {
4761 let f = SubstrFunc;
4762 let i = SqliteValue::Integer;
4763 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
4764 assert_eq!(
4766 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
4767 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
4768 );
4769 }
4770
4771 #[test]
4774 fn test_like_case_insensitive() {
4775 assert_eq!(
4776 invoke2(
4777 &LikeFunc,
4778 SqliteValue::Text(SmallText::from_string("ABC")),
4779 SqliteValue::Text(SmallText::from_string("abc"))
4780 )
4781 .unwrap(),
4782 SqliteValue::Integer(1)
4783 );
4784 }
4785
4786 #[test]
4787 fn test_like_escape() {
4788 let f = LikeFunc;
4789 let result = f
4790 .invoke(&[
4791 SqliteValue::Text(SmallText::from_string("10\\%")),
4792 SqliteValue::Text(SmallText::from_string("10%")),
4793 SqliteValue::Text(SmallText::from_string("\\")),
4794 ])
4795 .unwrap();
4796 assert_eq!(result, SqliteValue::Integer(1));
4797 }
4798
4799 #[test]
4800 fn test_like_escape_rejects_empty_string() {
4801 let err = LikeFunc
4802 .invoke(&[
4803 SqliteValue::Text(SmallText::from_string("a")),
4804 SqliteValue::Text(SmallText::from_string("a")),
4805 SqliteValue::Text(SmallText::new("")),
4806 ])
4807 .unwrap_err();
4808 assert!(
4809 err.to_string()
4810 .contains("ESCAPE expression must be a single character")
4811 );
4812 }
4813
4814 #[test]
4815 fn test_like_escape_rejects_multi_character_string() {
4816 let err = LikeFunc
4817 .invoke(&[
4818 SqliteValue::Text(SmallText::from_string("a")),
4819 SqliteValue::Text(SmallText::from_string("a")),
4820 SqliteValue::Text(SmallText::from_string("xx")),
4821 ])
4822 .unwrap_err();
4823 assert!(
4824 err.to_string()
4825 .contains("ESCAPE expression must be a single character")
4826 );
4827 }
4828
4829 #[test]
4830 fn test_like_percent() {
4831 assert_eq!(
4832 invoke2(
4833 &LikeFunc,
4834 SqliteValue::Text(SmallText::from_string("%ell%")),
4835 SqliteValue::Text(SmallText::from_string("Hello"))
4836 )
4837 .unwrap(),
4838 SqliteValue::Integer(1)
4839 );
4840 }
4841
4842 #[test]
4845 fn test_glob_star() {
4846 assert_eq!(
4847 invoke2(
4848 &GlobFunc,
4849 SqliteValue::Text(SmallText::from_string("*.txt")),
4850 SqliteValue::Text(SmallText::from_string("file.txt"))
4851 )
4852 .unwrap(),
4853 SqliteValue::Integer(1)
4854 );
4855 }
4856
4857 #[test]
4858 fn test_glob_case_sensitive() {
4859 assert_eq!(
4860 invoke2(
4861 &GlobFunc,
4862 SqliteValue::Text(SmallText::from_string("ABC")),
4863 SqliteValue::Text(SmallText::from_string("abc"))
4864 )
4865 .unwrap(),
4866 SqliteValue::Integer(0)
4867 );
4868 }
4869
4870 #[test]
4871 fn test_glob_unterminated_character_class_does_not_match() {
4872 assert_eq!(
4875 invoke2(
4876 &GlobFunc,
4877 SqliteValue::Text(SmallText::from_string("[a")),
4878 SqliteValue::Text(SmallText::from_string("a"))
4879 )
4880 .unwrap(),
4881 SqliteValue::Integer(0)
4882 );
4883 assert_eq!(
4885 invoke2(
4886 &GlobFunc,
4887 SqliteValue::Text(SmallText::from_string("[a]")),
4888 SqliteValue::Text(SmallText::from_string("a"))
4889 )
4890 .unwrap(),
4891 SqliteValue::Integer(1)
4892 );
4893 }
4894
4895 #[test]
4896 fn test_glob_trailing_dash_in_character_class_is_literal() {
4897 let glob = |pattern: &str, text: &str| {
4905 invoke2(
4906 &GlobFunc,
4907 SqliteValue::Text(SmallText::from_string(pattern)),
4908 SqliteValue::Text(SmallText::from_string(text)),
4909 )
4910 .unwrap()
4911 };
4912 assert_eq!(
4914 glob("*[^A-Za-z0-9._:-]*", "peer_abc/123"),
4915 SqliteValue::Integer(1)
4916 );
4917 assert_eq!(
4919 glob("*[^A-Za-z0-9._:-]*", "peer_a.b:c-"),
4920 SqliteValue::Integer(0)
4921 );
4922 assert_eq!(glob("[a-c-]", "-"), SqliteValue::Integer(1));
4924 assert_eq!(glob("[a-c-]", "b"), SqliteValue::Integer(1));
4925 assert_eq!(glob("[a-c-]", "d"), SqliteValue::Integer(0));
4926 assert_eq!(glob("[-a]", "-"), SqliteValue::Integer(1));
4928 assert_eq!(glob("[-a]", "b"), SqliteValue::Integer(0));
4929 }
4930
4931 #[test]
4932 fn test_iif_two_argument_form() {
4933 let f = IifFunc;
4935 assert_eq!(
4936 f.invoke(&[
4937 SqliteValue::Integer(1),
4938 SqliteValue::Text(SmallText::from_string("y")),
4939 ])
4940 .unwrap(),
4941 SqliteValue::Text(SmallText::from_string("y"))
4942 );
4943 assert_eq!(
4944 f.invoke(&[
4945 SqliteValue::Integer(0),
4946 SqliteValue::Text(SmallText::from_string("y")),
4947 ])
4948 .unwrap(),
4949 SqliteValue::Null
4950 );
4951 }
4952
4953 #[test]
4954 fn test_format_g_negative_zero() {
4955 let f = FormatFunc;
4957 assert_eq!(
4958 f.invoke(&[
4959 SqliteValue::Text(SmallText::from_string("%g")),
4960 SqliteValue::Float(-0.0),
4961 ])
4962 .unwrap(),
4963 SqliteValue::Text(SmallText::from_string("0"))
4964 );
4965 }
4966
4967 #[test]
4968 fn test_format_signed_zero_all_specs() {
4969 let f = FormatFunc;
4973 let fmt = |spec: &str, v: f64| -> String {
4974 match f
4975 .invoke(&[
4976 SqliteValue::Text(SmallText::from_string(spec)),
4977 SqliteValue::Float(v),
4978 ])
4979 .unwrap()
4980 {
4981 SqliteValue::Text(s) => s.as_str().to_owned(),
4982 other => panic!("expected text, got {other:?}"),
4983 }
4984 };
4985 assert_eq!(fmt("%f", -0.0), "0.000000");
4987 assert_eq!(fmt("%e", -0.0), "0.000000e+00");
4988 assert_eq!(fmt("%E", -0.0), "0.000000E+00");
4989 assert_eq!(fmt("%G", -0.0), "0");
4990 assert_eq!(fmt("%+g", -0.0), "+0");
4992 assert_eq!(fmt("% g", -0.0), " 0");
4993 assert_eq!(fmt("%+f", -0.0), "+0.000000");
4994 assert_eq!(fmt("%8.2f", -0.0), " 0.00");
4996 assert_eq!(fmt("%!g", -0.0), "0.0");
4998 assert_eq!(fmt("%g", -1e-320 * 1e-10), "0");
5000 assert_eq!(fmt("%g", -1.5), "-1.5");
5002 assert_eq!(fmt("%f", -2.25), "-2.250000");
5003 assert_eq!(fmt("%+g", -1.5), "-1.5");
5004 }
5005
5006 #[test]
5007 fn test_format_altform2_flag() {
5008 let f = FormatFunc;
5012 assert_eq!(
5013 f.invoke(&[
5014 SqliteValue::Text(SmallText::from_string("%!5s")),
5015 SqliteValue::Text(SmallText::from_string("ab")),
5016 ])
5017 .unwrap(),
5018 SqliteValue::Text(SmallText::from_string(" ab"))
5019 );
5020 assert_eq!(
5021 f.invoke(&[
5022 SqliteValue::Text(SmallText::from_string("%!d")),
5023 SqliteValue::Integer(3),
5024 ])
5025 .unwrap(),
5026 SqliteValue::Text(SmallText::from_string("3"))
5027 );
5028 assert_eq!(
5029 f.invoke(&[
5030 SqliteValue::Text(SmallText::from_string("%!f")),
5031 SqliteValue::Float(0.1),
5032 ])
5033 .unwrap(),
5034 SqliteValue::Text(SmallText::from_string("0.1"))
5035 );
5036 }
5037
5038 #[test]
5041 fn test_format_specifiers() {
5042 let f = FormatFunc;
5043 let result = f
5044 .invoke(&[
5045 SqliteValue::Text(SmallText::from_string("%d %s")),
5046 SqliteValue::Integer(42),
5047 SqliteValue::Text(SmallText::from_string("hello")),
5048 ])
5049 .unwrap();
5050 assert_eq!(
5051 result,
5052 SqliteValue::Text(SmallText::from_string("42 hello"))
5053 );
5054 }
5055
5056 #[test]
5057 fn test_format_n_noop() {
5058 let f = FormatFunc;
5059 let result = f
5061 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
5062 .unwrap();
5063 assert_eq!(
5064 result,
5065 SqliteValue::Text(SmallText::from_string("beforeafter"))
5066 );
5067 }
5068
5069 #[test]
5070 fn test_format_alternate_form_hex_octal() {
5071 let cases: &[(&str, i64, &str)] = &[
5073 ("%#x", 255, "0xff"),
5074 ("%#X", 255, "0XFF"),
5075 ("%#o", 64, "0100"),
5076 ("%#x", 0, "0"), ("%#o", 0, "0"), ("%#5x", 255, " 0xff"), ("%#8x", 255, " 0xff"),
5080 ("%#08x", 255, "0x000000ff"), ("%-#8x", 255, "0xff "), ("%-08x", 255, "000000ff"), ("%#08o", 64, "000000100"),
5084 ("%#x", -1, "0xffffffffffffffff"),
5085 ];
5086 for (fmt, arg, want) in cases {
5087 let f = FormatFunc;
5088 let result = f
5089 .invoke(&[
5090 SqliteValue::Text(SmallText::from_string(*fmt)),
5091 SqliteValue::Integer(*arg),
5092 ])
5093 .unwrap();
5094 assert_eq!(
5095 result,
5096 SqliteValue::Text(SmallText::from_string((*want).to_owned())),
5097 "format({fmt:?}, {arg})"
5098 );
5099 }
5100 }
5101
5102 #[test]
5103 fn test_format_empty_string_is_null() {
5104 let f = FormatFunc;
5108 assert_eq!(
5109 f.invoke(&[SqliteValue::Text(SmallText::from_string(""))])
5110 .unwrap(),
5111 SqliteValue::Null
5112 );
5113 assert_eq!(
5115 f.invoke(&[
5116 SqliteValue::Text(SmallText::from_string("%s")),
5117 SqliteValue::Null,
5118 ])
5119 .unwrap(),
5120 SqliteValue::Text(SmallText::from_string(String::new()))
5121 );
5122 }
5123
5124 #[test]
5127 fn test_sqlite_version_format() {
5128 let result = SqliteVersionFunc.invoke(&[]).unwrap();
5129 match result {
5130 SqliteValue::Text(v) => {
5131 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
5132 }
5133 other => unreachable!("expected text, got {other:?}"),
5134 }
5135 }
5136
5137 #[test]
5138 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
5139 let func = SqliteCompileoptionUsedFunc;
5140 assert_eq!(
5141 invoke1(
5142 &func,
5143 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
5144 )
5145 .unwrap(),
5146 SqliteValue::Integer(1)
5147 );
5148 let expected_icu_enabled = i64::from(cfg!(feature = "ext-icu"));
5149 assert_eq!(
5150 invoke1(
5151 &func,
5152 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
5153 )
5154 .unwrap(),
5155 SqliteValue::Integer(expected_icu_enabled)
5156 );
5157 assert_eq!(
5158 invoke1(
5159 &func,
5160 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
5161 )
5162 .unwrap(),
5163 SqliteValue::Integer(expected_icu_enabled)
5164 );
5165 assert_eq!(
5166 invoke1(
5167 &func,
5168 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
5169 )
5170 .unwrap(),
5171 SqliteValue::Integer(1)
5172 );
5173 assert_eq!(
5174 invoke1(
5175 &func,
5176 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
5177 )
5178 .unwrap(),
5179 SqliteValue::Integer(0)
5180 );
5181 assert_eq!(
5182 invoke1(&func, SqliteValue::Null).unwrap(),
5183 SqliteValue::Null
5184 );
5185 }
5186
5187 #[test]
5188 #[ignore = "perf-only benchmark"]
5189 fn perf_compileoption_used_text_args() {
5190 use std::hint::black_box;
5191 use std::time::Instant;
5192
5193 const INVOCATIONS: usize = 1_000_000;
5194 const REPEATS: usize = 7;
5195
5196 let f = SqliteCompileoptionUsedFunc;
5197 let present_args = [SqliteValue::Text(SmallText::from_string(
5198 "SQLITE_ENABLE_ICU",
5199 ))];
5200 let absent_args = [SqliteValue::Text(SmallText::from_string(
5201 "ENABLE_NOT_PRESENT",
5202 ))];
5203
5204 let mut present_best_ns = u128::MAX;
5205 let mut absent_best_ns = u128::MAX;
5206 let mut checksum = 0i64;
5207 for _ in 0..REPEATS {
5208 let started = Instant::now();
5209 for _ in 0..INVOCATIONS {
5210 let result = black_box(
5211 f.invoke(black_box(present_args.as_slice()))
5212 .expect("compileoption present benchmark invocation must succeed"),
5213 );
5214 if let SqliteValue::Integer(value) = result {
5215 checksum = checksum.wrapping_add(value);
5216 }
5217 }
5218 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
5219
5220 let started = Instant::now();
5221 for _ in 0..INVOCATIONS {
5222 let result = black_box(
5223 f.invoke(black_box(absent_args.as_slice()))
5224 .expect("compileoption absent benchmark invocation must succeed"),
5225 );
5226 if let SqliteValue::Integer(value) = result {
5227 checksum = checksum.wrapping_add(value);
5228 }
5229 }
5230 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
5231 }
5232
5233 println!(
5234 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
5235 );
5236 }
5237
5238 #[test]
5239 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
5240 let func = SqliteCompileoptionGetFunc;
5241 for (index, option) in sqlite_compile_options().iter().enumerate() {
5242 assert_eq!(
5243 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
5244 SqliteValue::Text(SmallText::new(option))
5245 );
5246 }
5247 assert_eq!(
5248 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
5249 SqliteValue::Null
5250 );
5251 assert_eq!(
5252 invoke1(
5253 &func,
5254 SqliteValue::Integer(sqlite_compile_options().len() as i64)
5255 )
5256 .unwrap(),
5257 SqliteValue::Null
5258 );
5259 }
5260
5261 #[test]
5264 fn test_register_builtins_all_present() {
5265 let mut registry = FunctionRegistry::new();
5266 register_builtins(&mut registry);
5267
5268 assert!(registry.find_scalar("abs", 1).is_some());
5270 assert!(registry.find_scalar("typeof", 1).is_some());
5271 assert!(registry.find_scalar("length", 1).is_some());
5272 assert!(registry.find_scalar("lower", 1).is_some());
5273 assert!(registry.find_scalar("upper", 1).is_some());
5274 assert!(registry.find_scalar("hex", 1).is_some());
5275 assert!(registry.find_scalar("coalesce", 3).is_some());
5276 assert!(registry.find_scalar("concat", 2).is_some());
5277 assert!(registry.find_scalar("like", 2).is_some());
5278 assert!(registry.find_scalar("glob", 2).is_some());
5279 assert!(registry.find_scalar("round", 1).is_some());
5280 assert!(registry.find_scalar("substr", 2).is_some());
5281 assert!(registry.find_scalar("substring", 3).is_some());
5282 assert!(registry.find_scalar("sqlite_version", 0).is_some());
5283 assert!(registry.find_scalar("iif", 3).is_some());
5284 assert!(registry.find_scalar("if", 3).is_some());
5285 assert!(registry.find_scalar("format", 1).is_some());
5286 assert!(registry.find_scalar("printf", 1).is_some());
5287 assert!(registry.find_scalar("max", 2).is_some());
5288 assert!(registry.find_scalar("min", 2).is_some());
5289 assert!(registry.find_scalar("sign", 1).is_some());
5290 assert!(registry.find_scalar("random", 0).is_some());
5291
5292 assert!(registry.find_scalar("concat_ws", 3).is_some());
5294 assert!(registry.find_scalar("octet_length", 1).is_some());
5295 assert!(registry.find_scalar("unhex", 1).is_some());
5296 assert!(registry.find_scalar("timediff", 2).is_some());
5297 assert!(registry.find_scalar("unistr", 1).is_some());
5298 assert!(registry.find_scalar("unistr_quote", 1).is_some());
5299
5300 assert!(registry.find_aggregate("median", 1).is_some());
5302 assert!(registry.find_aggregate("percentile", 2).is_some());
5303 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
5304 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
5305
5306 assert!(registry.find_scalar("load_extension", 1).is_none());
5308 assert!(registry.find_scalar("load_extension", 2).is_none());
5309 }
5310
5311 #[test]
5312 fn test_register_builtins_rejects_invalid_variadic_arities() {
5313 let mut registry = FunctionRegistry::new();
5314 register_builtins(&mut registry);
5315
5316 for (name, too_few, valid, too_many) in [
5317 ("coalesce", 1, 2, None),
5318 ("concat", 0, 1, None),
5319 ("concat_ws", 1, 2, None),
5320 ("trim", 0, 1, Some(3)),
5321 ("ltrim", 0, 1, Some(3)),
5322 ("rtrim", 0, 1, Some(3)),
5323 ("round", 0, 1, Some(3)),
5324 ("unhex", 0, 1, Some(3)),
5325 ("substr", 1, 2, Some(4)),
5326 ("substring", 1, 2, Some(4)),
5327 ("max", 0, 1, None),
5328 ("min", 0, 1, None),
5329 ] {
5330 assert_wrong_arg_count(®istry, name, too_few);
5331 assert!(
5332 registry.find_scalar(name, valid).is_some(),
5333 "{name}/{valid} should resolve"
5334 );
5335 if let Some(arity) = too_many {
5336 assert_wrong_arg_count(®istry, name, arity);
5337 }
5338 }
5339
5340 assert!(registry.find_scalar("char", 0).is_some());
5341 assert!(registry.find_scalar("format", 0).is_some());
5342 assert!(registry.find_scalar("printf", 0).is_some());
5343 }
5344
5345 #[test]
5346 fn test_e2e_registry_invoke_through_lookup() {
5347 let mut registry = FunctionRegistry::new();
5348 register_builtins(&mut registry);
5349
5350 let abs = registry.find_scalar("ABS", 1).unwrap();
5352 assert_eq!(
5353 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
5354 SqliteValue::Integer(42)
5355 );
5356
5357 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
5359 assert_eq!(
5360 typeof_fn
5361 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
5362 .unwrap(),
5363 SqliteValue::Text(SmallText::from_string("text"))
5364 );
5365
5366 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
5368 assert_eq!(
5369 coalesce
5370 .invoke(&[
5371 SqliteValue::Null,
5372 SqliteValue::Null,
5373 SqliteValue::Integer(42),
5374 SqliteValue::Integer(99),
5375 ])
5376 .unwrap(),
5377 SqliteValue::Integer(42)
5378 );
5379 }
5380
5381 #[test]
5384 fn test_nondeterministic_functions_flagged() {
5385 assert!(!RandomFunc.is_deterministic());
5388 assert!(!RandomblobFunc.is_deterministic());
5389 assert!(!ChangesFunc.is_deterministic());
5390 assert!(!TotalChangesFunc.is_deterministic());
5391 assert!(!LastInsertRowidFunc.is_deterministic());
5392 assert!(!SqliteVersionFunc.is_deterministic());
5393 assert!(!SqliteSourceIdFunc.is_deterministic());
5394 assert!(!SqliteCompileoptionUsedFunc.is_deterministic());
5395 assert!(!SqliteCompileoptionGetFunc.is_deterministic());
5396 }
5397
5398 #[test]
5399 fn test_deterministic_functions_flagged() {
5400 assert!(AbsFunc.is_deterministic());
5402 assert!(LengthFunc.is_deterministic());
5403 assert!(TypeofFunc.is_deterministic());
5404 assert!(UpperFunc.is_deterministic());
5405 assert!(LowerFunc.is_deterministic());
5406 assert!(HexFunc.is_deterministic());
5407 assert!(CoalesceFunc.is_deterministic());
5408 assert!(IifFunc.is_deterministic());
5409 }
5410
5411 #[test]
5412 fn test_random_produces_different_values() {
5413 let a = RandomFunc.invoke(&[]).unwrap();
5416 let b = RandomFunc.invoke(&[]).unwrap();
5417 assert_ne!(a.as_integer(), b.as_integer());
5420 }
5421
5422 #[test]
5423 fn test_registry_nondeterministic_lookup() {
5424 let mut registry = FunctionRegistry::default();
5425 register_builtins(&mut registry);
5426
5427 let random = registry.find_scalar("random", 0).unwrap();
5429 assert!(!random.is_deterministic());
5430
5431 let changes = registry.find_scalar("changes", 0).unwrap();
5432 assert!(!changes.is_deterministic());
5433
5434 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
5435 assert!(!lir.is_deterministic());
5436
5437 for (name, num_args) in [
5438 ("sqlite_version", 0),
5439 ("sqlite_source_id", 0),
5440 ("sqlite_compileoption_used", 1),
5441 ("sqlite_compileoption_get", 1),
5442 ] {
5443 assert_eq!(
5444 registry.scalar_is_deterministic(name, num_args),
5445 Some(false),
5446 "{name} must publish non-deterministic registry metadata"
5447 );
5448 }
5449
5450 let abs = registry.find_scalar("abs", 1).unwrap();
5452 assert!(abs.is_deterministic());
5453 }
5454
5455 #[test]
5456 fn test_registry_builtin_query_constancy_metadata() {
5457 use crate::{ScalarQueryConstancy, ScalarSchemaSafety};
5458
5459 let mut registry = FunctionRegistry::default();
5460 register_builtins(&mut registry);
5461
5462 for (name, num_args) in [
5463 ("sqlite_version", 0),
5464 ("sqlite_source_id", 0),
5465 ("sqlite_compileoption_used", 1),
5466 ("sqlite_compileoption_get", 1),
5467 ] {
5468 let resolved = registry.resolve_scalar(name, num_args).unwrap();
5469 assert_eq!(resolved.schema_safety(), ScalarSchemaSafety::Never);
5470 assert_eq!(
5471 resolved.query_constancy(),
5472 ScalarQueryConstancy::SlowChanging,
5473 "{name}/{num_args} must match SQLite's slow-changing metadata"
5474 );
5475 }
5476
5477 for (name, num_args) in [
5478 ("date", 0),
5479 ("time", 0),
5480 ("datetime", 0),
5481 ("julianday", 0),
5482 ("unixepoch", 0),
5483 ("strftime", 1),
5484 ("timediff", 2),
5485 ] {
5486 let resolved = registry.resolve_scalar(name, num_args).unwrap();
5487 assert_eq!(
5488 resolved.schema_safety(),
5489 ScalarSchemaSafety::DateTimeConditional
5490 );
5491 assert_eq!(
5492 resolved.query_constancy(),
5493 ScalarQueryConstancy::SlowChanging,
5494 "{name}/{num_args} must be query-constant despite conditional schema safety"
5495 );
5496 }
5497
5498 for (name, num_args) in [
5499 ("random", 0),
5500 ("randomblob", 1),
5501 ("changes", 0),
5502 ("total_changes", 0),
5503 ("last_insert_rowid", 0),
5504 ] {
5505 assert_eq!(
5506 registry
5507 .resolve_scalar(name, num_args)
5508 .unwrap()
5509 .query_constancy(),
5510 ScalarQueryConstancy::Volatile,
5511 "{name}/{num_args} must remain volatile"
5512 );
5513 }
5514
5515 for (name, num_args) in [("abs", 1), ("like", 2), ("like", 3), ("glob", 2)] {
5516 assert_eq!(
5517 registry
5518 .resolve_scalar(name, num_args)
5519 .unwrap()
5520 .query_constancy(),
5521 ScalarQueryConstancy::Constant,
5522 "{name}/{num_args} must remain constant"
5523 );
5524 }
5525
5526 for (name, num_args) in [
5527 ("sqlite_version", 1),
5528 ("sqlite_compileoption_used", 0),
5529 ("like", 1),
5530 ("like", 4),
5531 ("glob", 1),
5532 ] {
5533 assert_eq!(
5534 registry
5535 .resolve_scalar(name, num_args)
5536 .unwrap()
5537 .query_constancy(),
5538 ScalarQueryConstancy::Volatile,
5539 "{name}/{num_args} wrong-arity sentinel must fail closed"
5540 );
5541 }
5542 }
5543}