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, sqlite_float_altform2_digits};
32use fsqlite_types::{SmallText, SqliteValue, TextEncoding};
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 static STATEMENT_TEXT_ENCODING: std::cell::Cell<TextEncoding> =
72 const { std::cell::Cell::new(TextEncoding::Utf8) };
73}
74
75pub fn reset_statement_now() {
78 STATEMENT_NOW.set(None);
79}
80
81#[must_use]
83pub fn statement_now() -> Option<f64> {
84 STATEMENT_NOW.with(std::cell::Cell::get)
85}
86
87pub fn set_statement_now(now_jdn: f64) {
89 STATEMENT_NOW.set(Some(now_jdn));
90}
91
92pub fn set_case_sensitive_like(case_sensitive: bool) {
95 CASE_SENSITIVE_LIKE.set(case_sensitive);
96}
97
98#[must_use]
100pub fn case_sensitive_like_active() -> bool {
101 CASE_SENSITIVE_LIKE.get()
102}
103
104pub fn set_statement_text_encoding(encoding: TextEncoding) {
109 STATEMENT_TEXT_ENCODING.set(encoding);
110}
111
112#[must_use]
115pub fn statement_text_encoding() -> TextEncoding {
116 STATEMENT_TEXT_ENCODING.get()
117}
118
119#[must_use]
124fn text_octet_length(text: &str, encoding: TextEncoding) -> usize {
125 match encoding {
126 TextEncoding::Utf8 => text.len(),
127 TextEncoding::Utf16le | TextEncoding::Utf16be => 2 * text.encode_utf16().count(),
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub struct ChangeTrackingState {
134 pub last_insert_rowid: i64,
135 pub last_changes: i64,
136 pub total_changes: i64,
137}
138
139pub fn set_change_tracking_state(state: ChangeTrackingState) {
141 LAST_INSERT_ROWID.set(state.last_insert_rowid);
142 LAST_CHANGES.set(state.last_changes);
143 TOTAL_CHANGES.set(state.total_changes);
144}
145
146#[must_use]
148pub fn get_change_tracking_state() -> ChangeTrackingState {
149 ChangeTrackingState {
150 last_insert_rowid: LAST_INSERT_ROWID.get(),
151 last_changes: LAST_CHANGES.get(),
152 total_changes: TOTAL_CHANGES.get(),
153 }
154}
155
156pub fn set_last_insert_rowid(rowid: i64) {
158 LAST_INSERT_ROWID.set(rowid);
159}
160
161pub fn get_last_insert_rowid() -> i64 {
163 LAST_INSERT_ROWID.get()
164}
165
166pub fn set_last_changes(count: i64) {
170 LAST_CHANGES.set(count);
171 TOTAL_CHANGES.set(TOTAL_CHANGES.get().saturating_add(count));
172}
173
174pub fn get_last_changes() -> i64 {
176 LAST_CHANGES.get()
177}
178
179pub fn get_total_changes() -> i64 {
181 TOTAL_CHANGES.get()
182}
183
184pub fn reset_total_changes() {
186 TOTAL_CHANGES.set(0);
187}
188
189const SQLITE_COMPILE_OPTIONS: &[&str] = &[
190 "COMPILER=rustc",
191 #[cfg(feature = "ext-fts5")]
192 "ENABLE_FTS5",
193 #[cfg(feature = "ext-geopoly")]
194 "ENABLE_GEOPOLY",
195 #[cfg(feature = "ext-icu")]
196 "ENABLE_ICU",
197 #[cfg(feature = "ext-json")]
198 "ENABLE_JSON1",
199 #[cfg(feature = "ext-rtree")]
200 "ENABLE_RTREE",
201 "FRANKENSQLITE",
202 "OMIT_LOAD_EXTENSION",
203 "THREADSAFE=1",
204];
205
206#[must_use]
208pub fn sqlite_compile_options() -> &'static [&'static str] {
209 SQLITE_COMPILE_OPTIONS
210}
211
212fn is_sqlite_compile_option_match(query: &str, option: &str) -> bool {
213 let trimmed = query.trim();
214 let normalized = if trimmed
215 .get(..7)
216 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("SQLITE_"))
217 {
218 &trimmed[7..]
219 } else {
220 trimmed
221 };
222 if normalized.is_empty() {
223 return false;
224 }
225 if option.eq_ignore_ascii_case(normalized) {
226 return true;
227 }
228 option
229 .get(..normalized.len())
230 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(normalized))
231 && option
232 .as_bytes()
233 .get(normalized.len())
234 .is_none_or(|next| !next.is_ascii_alphanumeric() && *next != b'_')
235}
236
237#[must_use]
240pub fn sqlite_compileoption_used(query: &str) -> bool {
241 sqlite_compile_options()
242 .iter()
243 .any(|option| is_sqlite_compile_option_match(query, option))
244}
245
246fn null_propagate(args: &[SqliteValue]) -> Option<SqliteValue> {
250 if args.iter().any(SqliteValue::is_null) {
251 Some(SqliteValue::Null)
252 } else {
253 None
254 }
255}
256
257pub struct AbsFunc;
260
261impl ScalarFunction for AbsFunc {
262 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
263 if args[0].is_null() {
264 return Ok(SqliteValue::Null);
265 }
266 match &args[0] {
267 SqliteValue::Integer(i) => {
268 if *i == i64::MIN {
269 return Err(FrankenError::IntegerOverflow);
270 }
271 Ok(SqliteValue::Integer(i.abs()))
272 }
273 other => {
274 let f = other.to_float();
275 Ok(SqliteValue::Float(if f < 0.0 { -f } else { f }))
278 }
279 }
280 }
281
282 fn num_args(&self) -> i32 {
283 1
284 }
285
286 fn name(&self) -> &str {
287 "abs"
288 }
289}
290
291pub struct CharFunc;
294
295impl ScalarFunction for CharFunc {
296 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
297 let mut result = String::new();
298 for arg in args {
299 let ch = u32::try_from(arg.to_integer())
301 .ok()
302 .and_then(char::from_u32)
303 .unwrap_or(char::REPLACEMENT_CHARACTER);
304 result.push(ch);
305 }
306 Ok(SqliteValue::Text(SmallText::from_string(result)))
307 }
308
309 fn is_deterministic(&self) -> bool {
310 true
311 }
312
313 fn num_args(&self) -> i32 {
314 -1 }
316
317 fn name(&self) -> &str {
318 "char"
319 }
320}
321
322pub struct CoalesceFunc;
325
326impl ScalarFunction for CoalesceFunc {
327 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
328 for arg in args {
332 if !arg.is_null() {
333 return Ok(arg.clone());
334 }
335 }
336 Ok(SqliteValue::Null)
337 }
338
339 fn num_args(&self) -> i32 {
340 -1
341 }
342
343 fn min_args(&self) -> i32 {
344 2
345 }
346
347 fn name(&self) -> &str {
348 "coalesce"
349 }
350}
351
352pub struct ConcatFunc;
355
356impl ScalarFunction for ConcatFunc {
357 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
358 let mut result = String::new();
359 for arg in args {
360 if !arg.is_null() {
362 result.push_str(text_arg(arg).as_ref());
363 }
364 }
365 Ok(SqliteValue::Text(SmallText::from_string(result)))
366 }
367
368 fn num_args(&self) -> i32 {
369 -1
370 }
371
372 fn min_args(&self) -> i32 {
373 1
374 }
375
376 fn name(&self) -> &str {
377 "concat"
378 }
379}
380
381pub struct ConcatWsFunc;
384
385impl ScalarFunction for ConcatWsFunc {
386 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
387 if args.is_empty() {
388 return Ok(SqliteValue::Text(SmallText::new("")));
389 }
390 if args[0].is_null() {
392 return Ok(SqliteValue::Null);
393 }
394 let sep = text_arg(&args[0]);
395 let mut result = String::new();
396 let mut has_part = false;
397 for arg in &args[1..] {
398 if arg.is_null() {
401 continue;
402 }
403 let part = text_arg(arg);
404 if has_part {
405 result.push_str(sep.as_ref());
406 }
407 result.push_str(part.as_ref());
408 has_part = true;
409 }
410 Ok(SqliteValue::Text(SmallText::from_string(result)))
411 }
412
413 fn num_args(&self) -> i32 {
414 -1
415 }
416
417 fn min_args(&self) -> i32 {
418 2
419 }
420
421 fn name(&self) -> &str {
422 "concat_ws"
423 }
424}
425
426pub struct HexFunc;
429
430impl ScalarFunction for HexFunc {
431 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
432 if args[0].is_null() {
436 return Ok(SqliteValue::Text(SmallText::new("")));
437 }
438 let bytes: Cow<'_, [u8]> = match &args[0] {
439 SqliteValue::Blob(b) => Cow::Borrowed(b.as_ref()),
440 SqliteValue::Text(text) => Cow::Borrowed(text.as_bytes_direct()),
441 other => Cow::Owned(other.to_text().into_bytes()),
443 };
444 let mut hex = String::with_capacity(bytes.len() * 2);
445 for b in bytes.as_ref() {
446 let _ = write!(hex, "{b:02X}");
447 }
448 Ok(SqliteValue::Text(SmallText::from_string(hex)))
449 }
450
451 fn num_args(&self) -> i32 {
452 1
453 }
454
455 fn name(&self) -> &str {
456 "hex"
457 }
458}
459
460pub struct IfnullFunc;
463
464impl ScalarFunction for IfnullFunc {
465 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
466 if args[0].is_null() {
467 Ok(args[1].clone())
468 } else {
469 Ok(args[0].clone())
470 }
471 }
472
473 fn num_args(&self) -> i32 {
474 2
475 }
476
477 fn name(&self) -> &str {
478 "ifnull"
479 }
480}
481
482pub struct IifFunc;
485
486impl ScalarFunction for IifFunc {
487 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
488 let cond = &args[0];
489 let is_true = match cond {
492 SqliteValue::Null => false,
493 SqliteValue::Integer(n) => *n != 0,
494 SqliteValue::Float(f) => *f != 0.0,
495 SqliteValue::Text(_) | SqliteValue::Blob(_) => {
496 let i = cond.to_integer();
497 if i != 0 { true } else { cond.to_float() != 0.0 }
498 }
499 };
500 if is_true {
501 Ok(args[1].clone())
502 } else if args.len() >= 3 {
503 Ok(args[2].clone())
504 } else {
505 Ok(SqliteValue::Null)
508 }
509 }
510
511 fn num_args(&self) -> i32 {
512 -1 }
514
515 fn min_args(&self) -> i32 {
516 2
517 }
518
519 fn max_args(&self) -> Option<i32> {
520 Some(3)
521 }
522
523 fn name(&self) -> &str {
524 "iif"
525 }
526}
527
528pub struct InstrFunc;
531
532impl ScalarFunction for InstrFunc {
533 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
534 if let Some(null) = null_propagate(args) {
535 return Ok(null);
536 }
537 match (&args[0], &args[1]) {
538 (SqliteValue::Blob(haystack), SqliteValue::Blob(needle)) => {
539 if needle.is_empty() {
541 return Ok(SqliteValue::Integer(1));
542 }
543 if haystack.is_empty() {
544 return Ok(SqliteValue::Integer(0));
545 }
546 let pos = find_bytes(haystack, needle).map_or(0, |p| p + 1);
547 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
548 }
549 _ => {
550 let haystack = text_arg(&args[0]);
553 let needle = text_arg(&args[1]);
554 let haystack = haystack.as_ref();
555 let needle = needle.as_ref();
556 if needle.is_empty() {
557 return Ok(SqliteValue::Integer(1));
558 }
559 if haystack.is_empty() {
560 return Ok(SqliteValue::Integer(0));
561 }
562 let pos = haystack
563 .find(needle)
564 .map_or(0, |byte_pos| haystack[..byte_pos].chars().count() + 1);
565 Ok(SqliteValue::Integer(i64::try_from(pos).unwrap_or(0)))
566 }
567 }
568 }
569
570 fn num_args(&self) -> i32 {
571 2
572 }
573
574 fn name(&self) -> &str {
575 "instr"
576 }
577}
578
579fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
580 if needle.is_empty() {
581 return Some(0);
582 }
583 haystack.windows(needle.len()).position(|w| w == needle)
584}
585
586fn sqlite_text_until_nul(text: &str) -> &str {
587 text.split_once('\0').map_or(text, |(prefix, _)| prefix)
588}
589
590pub struct LengthFunc;
593
594impl ScalarFunction for LengthFunc {
595 #[allow(clippy::cast_possible_wrap)]
596 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
597 if args[0].is_null() {
598 return Ok(SqliteValue::Null);
599 }
600 let len = match &args[0] {
601 SqliteValue::Text(s) => {
602 let text = sqlite_text_until_nul(s.as_str());
603 if text.is_ascii() {
604 text.len()
605 } else {
606 text.chars().count()
607 }
608 }
609 SqliteValue::Blob(b) => b.len(),
610 other => {
611 let text = other.to_text();
613 let text = sqlite_text_until_nul(&text);
614 if text.is_ascii() {
615 text.len()
616 } else {
617 text.chars().count()
618 }
619 }
620 };
621 Ok(SqliteValue::Integer(len as i64))
622 }
623
624 fn num_args(&self) -> i32 {
625 1
626 }
627
628 fn name(&self) -> &str {
629 "length"
630 }
631}
632
633pub struct OctetLengthFunc;
636
637impl ScalarFunction for OctetLengthFunc {
638 #[allow(clippy::cast_possible_wrap)]
639 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
640 if args[0].is_null() {
641 return Ok(SqliteValue::Null);
642 }
643 let encoding = statement_text_encoding();
648 let len = match &args[0] {
649 SqliteValue::Text(s) => text_octet_length(s.as_str(), encoding),
650 SqliteValue::Blob(b) => b.len(),
651 other => text_octet_length(&other.to_text(), encoding),
652 };
653 Ok(SqliteValue::Integer(len as i64))
654 }
655
656 fn num_args(&self) -> i32 {
657 1
658 }
659
660 fn name(&self) -> &str {
661 "octet_length"
662 }
663}
664
665pub struct LowerFunc;
668
669impl ScalarFunction for LowerFunc {
670 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
671 if args[0].is_null() {
672 return Ok(SqliteValue::Null);
673 }
674 let lowered = text_arg(&args[0]).as_ref().to_ascii_lowercase();
675 Ok(SqliteValue::Text(SmallText::from_string(lowered)))
676 }
677
678 fn num_args(&self) -> i32 {
679 1
680 }
681
682 fn name(&self) -> &str {
683 "lower"
684 }
685}
686
687pub struct UpperFunc;
688
689impl ScalarFunction for UpperFunc {
690 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
691 if args[0].is_null() {
692 return Ok(SqliteValue::Null);
693 }
694 let upper = text_arg(&args[0]).as_ref().to_ascii_uppercase();
695 Ok(SqliteValue::Text(SmallText::from_string(upper)))
696 }
697
698 fn num_args(&self) -> i32 {
699 1
700 }
701
702 fn name(&self) -> &str {
703 "upper"
704 }
705}
706
707pub struct TrimFunc;
710pub struct LtrimFunc;
711pub struct RtrimFunc;
712
713fn trim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
714 let char_set: Vec<char> = chars.chars().collect();
715 s.trim_matches(|c: char| char_set.contains(&c))
716}
717
718fn ltrim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
719 let char_set: Vec<char> = chars.chars().collect();
720 s.trim_start_matches(|c: char| char_set.contains(&c))
721}
722
723fn rtrim_chars<'a>(s: &'a str, chars: &str) -> &'a str {
724 let char_set: Vec<char> = chars.chars().collect();
725 s.trim_end_matches(|c: char| char_set.contains(&c))
726}
727
728impl ScalarFunction for TrimFunc {
729 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
730 if args[0].is_null() {
731 return Ok(SqliteValue::Null);
732 }
733 let s = text_arg(&args[0]);
734 let chars = if args.len() > 1 && !args[1].is_null() {
735 text_arg(&args[1])
736 } else {
737 Cow::Borrowed(" ")
738 };
739 Ok(SqliteValue::Text(SmallText::new(trim_chars(
740 s.as_ref(),
741 chars.as_ref(),
742 ))))
743 }
744
745 fn num_args(&self) -> i32 {
746 -1 }
748
749 fn min_args(&self) -> i32 {
750 1
751 }
752
753 fn max_args(&self) -> Option<i32> {
754 Some(2)
755 }
756
757 fn name(&self) -> &str {
758 "trim"
759 }
760}
761
762impl ScalarFunction for LtrimFunc {
763 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
764 if args[0].is_null() {
765 return Ok(SqliteValue::Null);
766 }
767 let s = text_arg(&args[0]);
768 let chars = if args.len() > 1 && !args[1].is_null() {
769 text_arg(&args[1])
770 } else {
771 Cow::Borrowed(" ")
772 };
773 Ok(SqliteValue::Text(SmallText::new(ltrim_chars(
774 s.as_ref(),
775 chars.as_ref(),
776 ))))
777 }
778
779 fn num_args(&self) -> i32 {
780 -1
781 }
782
783 fn min_args(&self) -> i32 {
784 1
785 }
786
787 fn max_args(&self) -> Option<i32> {
788 Some(2)
789 }
790
791 fn name(&self) -> &str {
792 "ltrim"
793 }
794}
795
796impl ScalarFunction for RtrimFunc {
797 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
798 if args[0].is_null() {
799 return Ok(SqliteValue::Null);
800 }
801 let s = text_arg(&args[0]);
802 let chars = if args.len() > 1 && !args[1].is_null() {
803 text_arg(&args[1])
804 } else {
805 Cow::Borrowed(" ")
806 };
807 Ok(SqliteValue::Text(SmallText::new(rtrim_chars(
808 s.as_ref(),
809 chars.as_ref(),
810 ))))
811 }
812
813 fn num_args(&self) -> i32 {
814 -1
815 }
816
817 fn min_args(&self) -> i32 {
818 1
819 }
820
821 fn max_args(&self) -> Option<i32> {
822 Some(2)
823 }
824
825 fn name(&self) -> &str {
826 "rtrim"
827 }
828}
829
830pub struct NullifFunc;
833
834impl ScalarFunction for NullifFunc {
835 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
836 self.invoke_with_collation(args, None)
837 }
838
839 fn consumes_argument_collation(&self) -> bool {
840 true
841 }
842
843 fn invoke_with_collation(
844 &self,
845 args: &[SqliteValue],
846 collation: Option<&dyn crate::collation::CollationFunction>,
847 ) -> Result<SqliteValue> {
848 let equal = match (&args[0], &args[1], collation) {
849 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
850 collation.compare(left.as_bytes(), right.as_bytes()) == std::cmp::Ordering::Equal
851 }
852 _ => args[0] == args[1],
853 };
854 if equal {
855 Ok(SqliteValue::Null)
856 } else {
857 Ok(args[0].clone())
858 }
859 }
860
861 fn num_args(&self) -> i32 {
862 2
863 }
864
865 fn name(&self) -> &str {
866 "nullif"
867 }
868}
869
870pub struct TypeofFunc;
873
874impl ScalarFunction for TypeofFunc {
875 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
876 let type_name = match &args[0] {
877 SqliteValue::Null => "null",
878 SqliteValue::Integer(_) => "integer",
879 SqliteValue::Float(_) => "real",
880 SqliteValue::Text(_) => "text",
881 SqliteValue::Blob(_) => "blob",
882 };
883 Ok(SqliteValue::Text(SmallText::new(type_name)))
884 }
885
886 fn num_args(&self) -> i32 {
887 1
888 }
889
890 fn name(&self) -> &str {
891 "typeof"
892 }
893}
894
895pub struct SubtypeFunc;
898
899impl ScalarFunction for SubtypeFunc {
900 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
901 Ok(SqliteValue::Integer(0))
904 }
905
906 fn num_args(&self) -> i32 {
907 1
908 }
909
910 fn name(&self) -> &str {
911 "subtype"
912 }
913}
914
915pub struct ReplaceFunc;
918
919impl ScalarFunction for ReplaceFunc {
920 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
921 if let Some(null) = null_propagate(args) {
922 return Ok(null);
923 }
924 let x = text_arg(&args[0]);
925 let y = text_arg(&args[1]);
926 let z = text_arg(&args[2]);
927 if y.is_empty() {
928 return Ok(SqliteValue::Text(SmallText::from_string(x)));
929 }
930
931 if z.len() > y.len() {
933 let occurrences = x.matches(y.as_ref()).count();
934 let final_len = x.len() + occurrences * (z.len() - y.len());
935 if final_len > 1_000_000_000 {
936 return Err(FrankenError::TooBig);
937 }
938 }
939
940 Ok(SqliteValue::Text(SmallText::from_string(
941 x.replace(y.as_ref(), z.as_ref()),
942 )))
943 }
944
945 fn num_args(&self) -> i32 {
946 3
947 }
948
949 fn name(&self) -> &str {
950 "replace"
951 }
952}
953
954pub struct RoundFunc;
957
958impl ScalarFunction for RoundFunc {
959 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
960 if args[0].is_null() {
961 return Ok(SqliteValue::Null);
962 }
963 if args.len() > 1 && args[1].is_null() {
966 return Ok(SqliteValue::Null);
967 }
968 let x = args[0].to_float();
969 let n = if args.len() > 1 {
974 i64::from(args[1].to_integer() as i32).clamp(0, 30)
975 } else {
976 0
977 };
978 if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
980 return Ok(SqliteValue::Float(x));
981 }
982 let rounded = format_fixed_round_half_away(x, n as usize)
987 .parse::<f64>()
988 .unwrap_or(x);
989 Ok(SqliteValue::Float(rounded))
990 }
991
992 fn num_args(&self) -> i32 {
993 -1 }
995
996 fn min_args(&self) -> i32 {
997 1
998 }
999
1000 fn max_args(&self) -> Option<i32> {
1001 Some(2)
1002 }
1003
1004 fn name(&self) -> &str {
1005 "round"
1006 }
1007}
1008
1009pub struct SignFunc;
1012
1013impl ScalarFunction for SignFunc {
1014 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1015 if args[0].is_null() {
1016 return Ok(SqliteValue::Null);
1017 }
1018 match &args[0] {
1019 SqliteValue::Null => Ok(SqliteValue::Null),
1020 SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
1021 SqliteValue::Float(f) => {
1022 if f.is_nan() {
1023 Ok(SqliteValue::Null)
1024 } else if *f > 0.0 {
1025 Ok(SqliteValue::Integer(1))
1026 } else if *f < 0.0 {
1027 Ok(SqliteValue::Integer(-1))
1028 } else {
1029 Ok(SqliteValue::Integer(0))
1030 }
1031 }
1032 SqliteValue::Text(s) => {
1033 let trimmed = s.trim_matches(|ch: char| ch.is_ascii_whitespace());
1035 if trimmed.is_empty() {
1036 return Ok(SqliteValue::Null);
1037 }
1038
1039 let stripped = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
1045 if stripped.eq_ignore_ascii_case("nan")
1046 || stripped.eq_ignore_ascii_case("inf")
1047 || stripped.eq_ignore_ascii_case("infinity")
1048 {
1049 return Ok(SqliteValue::Null);
1050 }
1051
1052 if let Ok(f) = trimmed.parse::<f64>() {
1055 if f > 0.0 {
1057 Ok(SqliteValue::Integer(1))
1058 } else if f < 0.0 {
1059 Ok(SqliteValue::Integer(-1))
1060 } else {
1061 Ok(SqliteValue::Integer(0))
1062 }
1063 } else if let Ok(i) = trimmed.parse::<i64>() {
1064 Ok(SqliteValue::Integer(i.signum()))
1066 } else {
1067 Ok(SqliteValue::Null)
1068 }
1069 }
1070 SqliteValue::Blob(_) => Ok(SqliteValue::Null),
1071 }
1072 }
1073
1074 fn num_args(&self) -> i32 {
1075 1
1076 }
1077
1078 fn name(&self) -> &str {
1079 "sign"
1080 }
1081}
1082
1083pub struct RandomFunc;
1086
1087impl ScalarFunction for RandomFunc {
1088 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1089 let val = simple_random_i64();
1092 Ok(SqliteValue::Integer(val))
1093 }
1094
1095 fn is_deterministic(&self) -> bool {
1096 false
1097 }
1098
1099 fn num_args(&self) -> i32 {
1100 0
1101 }
1102
1103 fn name(&self) -> &str {
1104 "random"
1105 }
1106}
1107
1108fn simple_random_i64() -> i64 {
1110 use std::sync::atomic::{AtomicU64, Ordering};
1115
1116 static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
1117 let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
1118 x ^= x >> 30;
1119 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1120 x ^= x >> 27;
1121 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
1122 x ^= x >> 31;
1123 x as i64
1124}
1125
1126pub struct RandomblobFunc;
1129
1130impl ScalarFunction for RandomblobFunc {
1131 #[allow(clippy::cast_sign_loss)]
1132 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1133 let n_i64 = if args[0].is_null() {
1137 1
1138 } else {
1139 args[0].to_integer().max(1)
1140 };
1141 if n_i64 > 1_000_000_000 {
1142 return Err(FrankenError::TooBig);
1143 }
1144 let n = n_i64 as usize;
1145 let mut buf = vec![0u8; n];
1146 let mut i = 0;
1147 while i < n {
1148 let rnd = simple_random_i64().to_ne_bytes();
1149 let to_copy = (n - i).min(8);
1150 buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
1151 i += to_copy;
1152 }
1153 Ok(SqliteValue::Blob(Arc::from(buf.as_slice())))
1154 }
1155
1156 fn is_deterministic(&self) -> bool {
1157 false
1158 }
1159
1160 fn num_args(&self) -> i32 {
1161 1
1162 }
1163
1164 fn name(&self) -> &str {
1165 "randomblob"
1166 }
1167}
1168
1169pub struct ZeroblobFunc;
1172
1173impl ScalarFunction for ZeroblobFunc {
1174 #[allow(clippy::cast_sign_loss)]
1175 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1176 if args[0].is_null() {
1178 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1179 }
1180 let n_i64 = args[0].to_integer().max(0);
1181 if n_i64 > 1_000_000_000 {
1182 return Err(FrankenError::TooBig);
1183 }
1184 let n = n_i64 as usize;
1185 Ok(SqliteValue::Blob(Arc::from(vec![0u8; n].as_slice())))
1186 }
1187
1188 fn num_args(&self) -> i32 {
1189 1
1190 }
1191
1192 fn name(&self) -> &str {
1193 "zeroblob"
1194 }
1195}
1196
1197pub struct QuoteFunc;
1200
1201impl ScalarFunction for QuoteFunc {
1202 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1203 let result = quote_sql_value(&args[0], false);
1204 Ok(SqliteValue::Text(SmallText::from_string(result)))
1205 }
1206
1207 fn num_args(&self) -> i32 {
1208 1
1209 }
1210
1211 fn name(&self) -> &str {
1212 "quote"
1213 }
1214}
1215
1216pub struct UnistrQuoteFunc;
1219
1220impl ScalarFunction for UnistrQuoteFunc {
1221 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1222 let result = quote_sql_value(&args[0], true);
1223 Ok(SqliteValue::Text(SmallText::from_string(result)))
1224 }
1225
1226 fn num_args(&self) -> i32 {
1227 1
1228 }
1229
1230 fn name(&self) -> &str {
1231 "unistr_quote"
1232 }
1233}
1234
1235fn quote_sql_value(value: &SqliteValue, use_unistr_quote: bool) -> String {
1236 match value {
1237 SqliteValue::Null => "NULL".to_owned(),
1238 SqliteValue::Integer(i) => i.to_string(),
1239 SqliteValue::Float(f) if f.is_infinite() => {
1246 if f.is_sign_positive() {
1247 "9.0e+999".to_owned()
1248 } else {
1249 "-9.0e+999".to_owned()
1250 }
1251 }
1252 SqliteValue::Float(f) => format_sqlite_float(*f),
1253 SqliteValue::Text(s) => quote_sql_text_literal(s.as_str(), use_unistr_quote),
1254 SqliteValue::Blob(b) => {
1255 let mut hex = String::with_capacity(3 + b.len() * 2);
1256 hex.push_str("X'");
1257 for byte in b.iter() {
1258 let _ = write!(hex, "{byte:02X}");
1259 }
1260 hex.push('\'');
1261 hex
1262 }
1263 }
1264}
1265
1266fn quote_sql_text_literal(text: &str, use_unistr_quote: bool) -> String {
1267 let text = sqlite_text_until_nul(text);
1268 if use_unistr_quote && text.chars().any(is_unistr_control_char) {
1269 return unistr_quote_sql_text_literal(text);
1270 }
1271
1272 let mut quoted = String::with_capacity(text.len() + 2);
1273 quoted.push('\'');
1274 append_sql_string_literal_body(&mut quoted, text);
1275 quoted.push('\'');
1276 quoted
1277}
1278
1279fn unistr_quote_sql_text_literal(text: &str) -> String {
1280 let mut quoted = String::with_capacity(text.len() + 12);
1281 quoted.push_str("unistr('");
1282 for ch in text.chars() {
1283 match ch {
1284 '\'' => quoted.push_str("''"),
1285 '\\' => quoted.push_str("\\\\"),
1286 _ if is_unistr_control_char(ch) => {
1287 let _ = write!(quoted, "\\u{:04x}", ch as u32);
1288 }
1289 _ => quoted.push(ch),
1290 }
1291 }
1292 quoted.push_str("')");
1293 quoted
1294}
1295
1296fn append_sql_string_literal_body(out: &mut String, text: &str) {
1297 for ch in text.chars() {
1298 if ch == '\'' {
1299 out.push_str("''");
1300 } else {
1301 out.push(ch);
1302 }
1303 }
1304}
1305
1306fn is_unistr_control_char(ch: char) -> bool {
1307 matches!(ch, '\u{0001}'..='\u{001F}')
1308}
1309
1310pub struct UnhexFunc;
1313
1314impl ScalarFunction for UnhexFunc {
1315 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1316 if args[0].is_null() {
1317 return Ok(SqliteValue::Null);
1318 }
1319 if args.len() > 1 && args[1].is_null() {
1320 return Ok(SqliteValue::Null);
1321 }
1322 let input = text_arg(&args[0]);
1323 let ignore_chars: Vec<char> = if args.len() > 1 {
1324 text_arg(&args[1])
1325 .chars()
1326 .filter(|&c| hex_digit(c).is_none())
1327 .collect()
1328 } else {
1329 Vec::new()
1330 };
1331
1332 let mut bytes = Vec::with_capacity(input.len() / 2);
1333 let mut hi_nibble = None;
1334 for c in input.as_ref().chars() {
1335 if ignore_chars.contains(&c) {
1336 if hi_nibble.is_some() {
1337 return Ok(SqliteValue::Null);
1338 }
1339 continue;
1340 }
1341 let digit = match hex_digit(c) {
1342 Some(v) => v,
1343 None => return Ok(SqliteValue::Null),
1344 };
1345 if let Some(hi) = hi_nibble.take() {
1346 bytes.push(hi << 4 | digit);
1347 } else {
1348 hi_nibble = Some(digit);
1349 }
1350 }
1351 if hi_nibble.is_some() {
1352 return Ok(SqliteValue::Null);
1353 }
1354 Ok(SqliteValue::Blob(Arc::from(bytes.as_slice())))
1355 }
1356
1357 fn num_args(&self) -> i32 {
1358 -1 }
1360
1361 fn min_args(&self) -> i32 {
1362 1
1363 }
1364
1365 fn max_args(&self) -> Option<i32> {
1366 Some(2)
1367 }
1368
1369 fn name(&self) -> &str {
1370 "unhex"
1371 }
1372}
1373
1374fn hex_digit(c: char) -> Option<u8> {
1375 match c {
1376 '0'..='9' => Some(c as u8 - b'0'),
1377 'a'..='f' => Some(c as u8 - b'a' + 10),
1378 'A'..='F' => Some(c as u8 - b'A' + 10),
1379 _ => None,
1380 }
1381}
1382
1383pub struct UnicodeFunc;
1386
1387impl ScalarFunction for UnicodeFunc {
1388 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1389 if args[0].is_null() {
1390 return Ok(SqliteValue::Null);
1391 }
1392 if let SqliteValue::Blob(bytes) = &args[0] {
1393 return Ok(
1394 sqlite_blob_first_codepoint(bytes).map_or(SqliteValue::Null, SqliteValue::Integer)
1395 );
1396 }
1397 let s = text_arg(&args[0]);
1398 match sqlite_text_until_nul(s.as_ref()).chars().next() {
1399 Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
1400 None => Ok(SqliteValue::Null),
1401 }
1402 }
1403
1404 fn num_args(&self) -> i32 {
1405 1
1406 }
1407
1408 fn name(&self) -> &str {
1409 "unicode"
1410 }
1411}
1412
1413fn sqlite_blob_first_codepoint(bytes: &[u8]) -> Option<i64> {
1414 let first = *bytes.first()?;
1415 if first == 0 {
1416 return None;
1417 }
1418 let mut codepoint = match first {
1419 0x00..=0xBF => u32::from(first),
1420 0xC0..=0xDF => u32::from(first & 0x1F),
1421 0xE0..=0xEF => u32::from(first & 0x0F),
1422 0xF0..=0xF7 => u32::from(first & 0x07),
1423 _ => 0xFFFD,
1424 };
1425
1426 if first >= 0xC0 && first <= 0xF7 {
1427 for byte in bytes
1428 .iter()
1429 .copied()
1430 .skip(1)
1431 .take_while(|byte| byte & 0xC0 == 0x80)
1432 {
1433 codepoint = codepoint
1434 .wrapping_shl(6)
1435 .wrapping_add(u32::from(byte & 0x3F));
1436 }
1437 if codepoint < 0x80
1438 || (codepoint & 0xFFFF_F800) == 0xD800
1439 || (codepoint & 0xFFFF_FFFE) == 0xFFFE
1440 {
1441 codepoint = 0xFFFD;
1442 }
1443 }
1444
1445 Some(i64::from(codepoint))
1446}
1447
1448pub struct SubstrFunc;
1451
1452impl ScalarFunction for SubstrFunc {
1453 #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
1454 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1455 if args[0].is_null() || args[1].is_null() {
1456 return Ok(SqliteValue::Null);
1457 }
1458 let is_blob = matches!(&args[0], SqliteValue::Blob(_));
1459 if is_blob {
1460 return self.invoke_blob(args);
1461 }
1462
1463 let text = text_arg(&args[0]);
1464 let full = text.as_ref();
1469 let s = full.split_once('\0').map_or(full, |(prefix, _)| prefix);
1470 let ascii_fast_path = s.is_ascii();
1471 let len = if ascii_fast_path {
1472 s.len() as i64
1473 } else {
1474 s.chars().count() as i64
1475 };
1476 let has_length = args.len() > 2 && !args[2].is_null();
1477
1478 let mut p1 = i64::from(args[1].to_integer() as i32);
1483 let mut p2 = if has_length {
1484 i64::from(args[2].to_integer() as i32)
1485 } else {
1486 1_000_000_000
1487 };
1488
1489 let neg_p2 = p2 < 0;
1493 if neg_p2 {
1494 p2 = p2.saturating_neg();
1495 }
1496
1497 if p1 < 0 {
1499 p1 = p1.saturating_add(len);
1500 if p1 < 0 {
1501 p2 = p2.saturating_add(p1);
1502 p1 = 0;
1503 }
1504 } else if p1 > 0 {
1505 p1 -= 1;
1506 } else if p2 > 0 {
1507 p2 -= 1; }
1509
1510 if neg_p2 {
1512 p1 = p1.saturating_sub(p2);
1513 if p1 < 0 {
1514 p2 = p2.saturating_add(p1);
1515 p1 = 0;
1516 }
1517 }
1518
1519 if p1.saturating_add(p2) > len {
1520 p2 = len.saturating_sub(p1);
1521 }
1522 if p2 <= 0 {
1523 return Ok(SqliteValue::Text(SmallText::new("")));
1524 }
1525
1526 if ascii_fast_path {
1527 let start = p1 as usize;
1528 let end = (p1 + p2) as usize;
1529 return Ok(SqliteValue::Text(SmallText::new(&s[start..end])));
1530 }
1531
1532 let chars: Vec<char> = s.chars().collect();
1533 let result: String = chars[p1 as usize..(p1 + p2) as usize].iter().collect();
1534 Ok(SqliteValue::Text(SmallText::from_string(result)))
1535 }
1536
1537 fn num_args(&self) -> i32 {
1538 -1 }
1540
1541 fn min_args(&self) -> i32 {
1542 2
1543 }
1544
1545 fn max_args(&self) -> Option<i32> {
1546 Some(3)
1547 }
1548
1549 fn name(&self) -> &str {
1550 "substr"
1551 }
1552}
1553
1554impl SubstrFunc {
1555 #[allow(
1556 clippy::unused_self,
1557 clippy::cast_sign_loss,
1558 clippy::cast_possible_wrap
1559 )]
1560 fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1561 let blob = match &args[0] {
1562 SqliteValue::Blob(b) => b,
1563 _ => return Ok(SqliteValue::Null),
1564 };
1565 let len = blob.len() as i64;
1566 let has_length = args.len() > 2 && !args[2].is_null();
1567
1568 let mut p1 = i64::from(args[1].to_integer() as i32);
1573 let mut p2 = if has_length {
1574 i64::from(args[2].to_integer() as i32)
1575 } else {
1576 1_000_000_000
1577 };
1578
1579 let neg_p2 = p2 < 0;
1580 if neg_p2 {
1581 p2 = p2.saturating_neg();
1582 }
1583
1584 if p1 < 0 {
1585 p1 = p1.saturating_add(len);
1586 if p1 < 0 {
1587 p2 = p2.saturating_add(p1);
1588 p1 = 0;
1589 }
1590 } else if p1 > 0 {
1591 p1 -= 1;
1592 } else if p2 > 0 {
1593 p2 -= 1;
1594 }
1595
1596 if neg_p2 {
1597 p1 = p1.saturating_sub(p2);
1598 if p1 < 0 {
1599 p2 = p2.saturating_add(p1);
1600 p1 = 0;
1601 }
1602 }
1603
1604 if p1.saturating_add(p2) > len {
1605 p2 = len.saturating_sub(p1);
1606 }
1607 if p2 <= 0 {
1608 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1609 }
1610
1611 Ok(SqliteValue::Blob(Arc::from(
1612 &blob[p1 as usize..(p1 + p2) as usize],
1613 )))
1614 }
1615}
1616
1617pub struct SoundexFunc;
1620
1621impl ScalarFunction for SoundexFunc {
1622 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1623 if args[0].is_null() {
1624 return Ok(SqliteValue::Text(SmallText::new("?000")));
1626 }
1627 let s = text_arg(&args[0]);
1628 let code = soundex(s.as_ref());
1629 let text = std::str::from_utf8(&code).expect("Soundex output must be ASCII");
1630 Ok(SqliteValue::Text(SmallText::new(text)))
1631 }
1632
1633 fn num_args(&self) -> i32 {
1634 1
1635 }
1636
1637 fn name(&self) -> &str {
1638 "soundex"
1639 }
1640}
1641
1642fn soundex(s: &str) -> [u8; 4] {
1643 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1644 let first = match chars.next() {
1645 Some(c) => c.to_ascii_uppercase(),
1646 None => return *b"?000",
1647 };
1648
1649 let code = |c: char| -> Option<u8> {
1650 match c.to_ascii_uppercase() {
1651 'B' | 'F' | 'P' | 'V' => Some(b'1'),
1652 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some(b'2'),
1653 'D' | 'T' => Some(b'3'),
1654 'L' => Some(b'4'),
1655 'M' | 'N' => Some(b'5'),
1656 'R' => Some(b'6'),
1657 _ => None, }
1659 };
1660
1661 let mut result = *b"0000";
1662 result[0] = first as u8;
1663 let mut result_len = 1;
1664 let mut last_code = code(first);
1665
1666 for c in chars {
1667 if result_len >= result.len() {
1668 break;
1669 }
1670 let current = code(c);
1671 if let Some(digit) = current
1672 && current != last_code
1673 {
1674 result[result_len] = digit;
1675 result_len += 1;
1676 }
1677 last_code = current;
1678 }
1679
1680 result
1681}
1682
1683pub struct ScalarMaxFunc;
1686
1687impl ScalarFunction for ScalarMaxFunc {
1688 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1689 self.invoke_with_collation(args, None)
1690 }
1691
1692 fn consumes_argument_collation(&self) -> bool {
1693 true
1694 }
1695
1696 fn invoke_with_collation(
1697 &self,
1698 args: &[SqliteValue],
1699 collation: Option<&dyn crate::collation::CollationFunction>,
1700 ) -> Result<SqliteValue> {
1701 if let Some(null) = null_propagate(args) {
1703 return Ok(null);
1704 }
1705 let mut max = &args[0];
1706 for arg in &args[1..] {
1707 let ordering = match (arg, max, collation) {
1708 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1709 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1710 }
1711 _ => arg.partial_cmp(max),
1712 };
1713 if ordering == Some(std::cmp::Ordering::Greater) {
1714 max = arg;
1715 }
1716 }
1717 Ok(max.clone())
1718 }
1719
1720 fn num_args(&self) -> i32 {
1721 -1
1722 }
1723
1724 fn min_args(&self) -> i32 {
1725 1
1726 }
1727
1728 fn name(&self) -> &str {
1729 "max"
1730 }
1731}
1732
1733pub struct ScalarMinFunc;
1736
1737impl ScalarFunction for ScalarMinFunc {
1738 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1739 self.invoke_with_collation(args, None)
1740 }
1741
1742 fn consumes_argument_collation(&self) -> bool {
1743 true
1744 }
1745
1746 fn invoke_with_collation(
1747 &self,
1748 args: &[SqliteValue],
1749 collation: Option<&dyn crate::collation::CollationFunction>,
1750 ) -> Result<SqliteValue> {
1751 if let Some(null) = null_propagate(args) {
1753 return Ok(null);
1754 }
1755 let mut min = &args[0];
1756 for arg in &args[1..] {
1757 let ordering = match (arg, min, collation) {
1758 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1759 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1760 }
1761 _ => arg.partial_cmp(min),
1762 };
1763 if matches!(
1767 ordering,
1768 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
1769 ) {
1770 min = arg;
1771 }
1772 }
1773 Ok(min.clone())
1774 }
1775
1776 fn num_args(&self) -> i32 {
1777 -1
1778 }
1779
1780 fn min_args(&self) -> i32 {
1781 1
1782 }
1783
1784 fn name(&self) -> &str {
1785 "min"
1786 }
1787}
1788
1789pub struct LikelihoodFunc;
1792
1793impl ScalarFunction for LikelihoodFunc {
1794 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1795 Ok(args[0].clone())
1797 }
1798
1799 fn num_args(&self) -> i32 {
1800 2
1801 }
1802
1803 fn name(&self) -> &str {
1804 "likelihood"
1805 }
1806}
1807
1808pub struct LikelyFunc;
1809
1810impl ScalarFunction for LikelyFunc {
1811 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1812 Ok(args[0].clone())
1813 }
1814
1815 fn num_args(&self) -> i32 {
1816 1
1817 }
1818
1819 fn name(&self) -> &str {
1820 "likely"
1821 }
1822}
1823
1824pub struct UnlikelyFunc;
1825
1826impl ScalarFunction for UnlikelyFunc {
1827 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1828 Ok(args[0].clone())
1829 }
1830
1831 fn num_args(&self) -> i32 {
1832 1
1833 }
1834
1835 fn name(&self) -> &str {
1836 "unlikely"
1837 }
1838}
1839
1840pub struct SqliteVersionFunc;
1843
1844impl ScalarFunction for SqliteVersionFunc {
1845 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1846 Ok(SqliteValue::Text(SmallText::new(
1847 fsqlite_types::FRANKENSQLITE_SQLITE_VERSION,
1848 )))
1849 }
1850
1851 fn is_deterministic(&self) -> bool {
1852 false
1853 }
1854
1855 fn num_args(&self) -> i32 {
1856 0
1857 }
1858
1859 fn name(&self) -> &str {
1860 "sqlite_version"
1861 }
1862}
1863
1864pub struct SqliteSourceIdFunc;
1867
1868impl ScalarFunction for SqliteSourceIdFunc {
1869 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1870 Ok(SqliteValue::Text(SmallText::new(
1871 fsqlite_types::FRANKENSQLITE_SOURCE_ID,
1872 )))
1873 }
1874
1875 fn is_deterministic(&self) -> bool {
1876 false
1877 }
1878
1879 fn num_args(&self) -> i32 {
1880 0
1881 }
1882
1883 fn name(&self) -> &str {
1884 "sqlite_source_id"
1885 }
1886}
1887
1888pub struct SqliteCompileoptionUsedFunc;
1891
1892impl ScalarFunction for SqliteCompileoptionUsedFunc {
1893 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1894 if args[0].is_null() {
1895 return Ok(SqliteValue::Null);
1896 }
1897 let query = text_arg(&args[0]);
1898 Ok(SqliteValue::Integer(i64::from(sqlite_compileoption_used(
1899 query.as_ref(),
1900 ))))
1901 }
1902
1903 fn is_deterministic(&self) -> bool {
1904 false
1905 }
1906
1907 fn num_args(&self) -> i32 {
1908 1
1909 }
1910
1911 fn name(&self) -> &str {
1912 "sqlite_compileoption_used"
1913 }
1914}
1915
1916pub struct SqliteCompileoptionGetFunc;
1919
1920impl ScalarFunction for SqliteCompileoptionGetFunc {
1921 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1922 if args[0].is_null() {
1923 return Ok(SqliteValue::Null);
1924 }
1925 let n = args[0].to_integer();
1926 #[allow(clippy::cast_sign_loss)]
1927 match sqlite_compile_options().get(n as usize) {
1928 Some(opt) => Ok(SqliteValue::Text(SmallText::new(opt))),
1929 None => Ok(SqliteValue::Null),
1930 }
1931 }
1932
1933 fn is_deterministic(&self) -> bool {
1934 false
1935 }
1936
1937 fn num_args(&self) -> i32 {
1938 1
1939 }
1940
1941 fn name(&self) -> &str {
1942 "sqlite_compileoption_get"
1943 }
1944}
1945
1946pub struct LikeFunc;
1949
1950impl ScalarFunction for LikeFunc {
1951 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1952 if let Some(null) = null_propagate(args) {
1953 return Ok(null);
1954 }
1955 let pattern = text_arg(&args[0]);
1956 let string = text_arg(&args[1]);
1957 let escape = if args.len() > 2 && !args[2].is_null() {
1958 Some(single_char_escape(text_arg(&args[2]).as_ref())?)
1959 } else {
1960 None
1961 };
1962 let matched = like_match(pattern.as_ref(), string.as_ref(), escape);
1963 Ok(SqliteValue::Integer(i64::from(matched)))
1964 }
1965
1966 fn num_args(&self) -> i32 {
1967 -1 }
1969
1970 fn min_args(&self) -> i32 {
1971 2
1972 }
1973
1974 fn max_args(&self) -> Option<i32> {
1975 Some(3)
1976 }
1977
1978 fn name(&self) -> &str {
1979 "like"
1980 }
1981}
1982
1983#[cfg(test)]
1984mod like_func_pragma_tests {
1985 use super::{LikeFunc, case_sensitive_like_active, set_case_sensitive_like};
1986 use crate::ScalarFunction;
1987 use fsqlite_types::SqliteValue;
1988
1989 fn like(pattern: &str, text: &str) -> i64 {
1990 match LikeFunc
1991 .invoke(&[
1992 SqliteValue::Text(pattern.into()),
1993 SqliteValue::Text(text.into()),
1994 ])
1995 .unwrap()
1996 {
1997 SqliteValue::Integer(n) => n,
1998 other => panic!("expected integer, got {other:?}"),
1999 }
2000 }
2001
2002 #[test]
2003 fn like_honors_case_sensitive_like_thread_local() {
2004 set_case_sensitive_like(false);
2006 assert_eq!(like("a", "A"), 1);
2007 assert_eq!(like("A%", "apple"), 1);
2008 set_case_sensitive_like(true);
2010 assert!(case_sensitive_like_active());
2011 assert_eq!(like("a", "A"), 0);
2012 assert_eq!(like("A%", "apple"), 0);
2013 assert_eq!(like("A%", "Apple"), 1);
2014 set_case_sensitive_like(false);
2016 }
2017}
2018
2019fn single_char_escape(escape: &str) -> Result<char> {
2020 let mut chars = escape.chars();
2021 match (chars.next(), chars.next()) {
2022 (Some(ch), None) => Ok(ch),
2023 _ => Err(FrankenError::function_error(
2024 "ESCAPE expression must be a single character",
2025 )),
2026 }
2027}
2028
2029fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
2033 sql_like_cased(pattern, string, escape, case_sensitive_like_active())
2034}
2035
2036pub struct GlobFunc;
2039
2040impl ScalarFunction for GlobFunc {
2041 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2042 if let Some(null) = null_propagate(args) {
2043 return Ok(null);
2044 }
2045 let pattern = text_arg(&args[0]);
2046 let string = text_arg(&args[1]);
2047 let matched = glob_match(pattern.as_ref(), string.as_ref());
2048 Ok(SqliteValue::Integer(i64::from(matched)))
2049 }
2050
2051 fn num_args(&self) -> i32 {
2052 2
2053 }
2054
2055 fn name(&self) -> &str {
2056 "glob"
2057 }
2058}
2059
2060fn glob_match(pattern: &str, string: &str) -> bool {
2062 let pat: Vec<char> = pattern.chars().collect();
2063 let txt: Vec<char> = string.chars().collect();
2064 glob_match_inner(&pat, &txt, 0, 0)
2065}
2066
2067fn text_arg(value: &SqliteValue) -> Cow<'_, str> {
2068 match value.as_text_str() {
2069 Some(text) => Cow::Borrowed(text),
2070 None => Cow::Owned(value.to_text()),
2071 }
2072}
2073
2074fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
2075 while pi < pat.len() {
2076 match pat[pi] {
2077 '*' => {
2078 while pi < pat.len() && pat[pi] == '*' {
2079 pi += 1;
2080 }
2081 if pi >= pat.len() {
2082 return true;
2083 }
2084 for start in ti..=txt.len() {
2085 if glob_match_inner(pat, txt, pi, start) {
2086 return true;
2087 }
2088 }
2089 return false;
2090 }
2091 '?' => {
2092 if ti >= txt.len() {
2093 return false;
2094 }
2095 pi += 1;
2096 ti += 1;
2097 }
2098 '[' => {
2099 if ti >= txt.len() {
2100 return false;
2101 }
2102 pi += 1;
2103 let negate = pi < pat.len() && pat[pi] == '^';
2104 if negate {
2105 pi += 1;
2106 }
2107 let mut found = false;
2108 let mut first = true;
2109 while pi < pat.len() && (first || pat[pi] != ']') {
2110 first = false;
2111 if pi + 2 < pat.len() && pat[pi + 1] == '-' && pat[pi + 2] != ']' {
2118 let lo = pat[pi];
2119 let hi = pat[pi + 2];
2120 if txt[ti] == lo || (txt[ti] >= lo && txt[ti] <= hi) {
2129 found = true;
2130 }
2131 pi += 3;
2132 } else {
2133 if txt[ti] == pat[pi] {
2134 found = true;
2135 }
2136 pi += 1;
2137 }
2138 }
2139 if pi < pat.len() && pat[pi] == ']' {
2140 pi += 1;
2141 } else {
2142 return false;
2146 }
2147 if found == negate {
2148 return false;
2149 }
2150 ti += 1;
2151 }
2152 c => {
2153 if ti >= txt.len() || txt[ti] != c {
2154 return false;
2155 }
2156 pi += 1;
2157 ti += 1;
2158 }
2159 }
2160 }
2161 ti >= txt.len()
2162}
2163
2164pub struct UnistrFunc;
2167
2168const INVALID_UNISTR_ESCAPE: &str = "invalid Unicode escape";
2169
2170fn decode_unistr_escape(chars: &mut std::str::Chars<'_>, digits: usize) -> Result<char> {
2171 let mut lookahead = chars.clone();
2172 let mut codepoint = 0u32;
2173 for _ in 0..digits {
2174 let Some(ch) = lookahead.next() else {
2175 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2176 };
2177 let Some(digit) = hex_digit(ch) else {
2178 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2179 };
2180 codepoint = (codepoint << 4) | u32::from(digit);
2181 }
2182 for _ in 0..digits {
2183 let _digit = chars.next();
2184 }
2185 char::from_u32(codepoint).ok_or_else(|| FrankenError::function_error(INVALID_UNISTR_ESCAPE))
2186}
2187
2188impl ScalarFunction for UnistrFunc {
2189 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2190 if args[0].is_null() {
2191 return Ok(SqliteValue::Null);
2192 }
2193 let input = text_arg(&args[0]);
2194 let mut result = String::with_capacity(input.len());
2195 let mut chars = input.as_ref().chars();
2196 while let Some(ch) = chars.next() {
2197 if ch == '\\' {
2198 if chars.as_str().starts_with('\\') {
2200 let _ = chars.next();
2201 result.push('\\');
2202 continue;
2203 }
2204 let digits = if chars.as_str().starts_with('+') {
2205 let _plus = chars.next();
2207 6
2208 } else if chars.as_str().starts_with('u') {
2209 let _marker = chars.next();
2211 4
2212 } else if chars.as_str().starts_with('U') {
2213 let _marker = chars.next();
2215 8
2216 } else {
2217 4
2219 };
2220 result.push(decode_unistr_escape(&mut chars, digits)?);
2221 continue;
2222 }
2223 result.push(ch);
2224 }
2225 Ok(SqliteValue::Text(SmallText::from_string(result)))
2226 }
2227
2228 fn num_args(&self) -> i32 {
2229 1
2230 }
2231
2232 fn name(&self) -> &str {
2233 "unistr"
2234 }
2235}
2236
2237pub struct ChangesFunc;
2242
2243impl ScalarFunction for ChangesFunc {
2244 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2245 Ok(SqliteValue::Integer(LAST_CHANGES.get()))
2246 }
2247
2248 fn is_deterministic(&self) -> bool {
2249 false
2250 }
2251
2252 fn num_args(&self) -> i32 {
2253 0
2254 }
2255
2256 fn name(&self) -> &str {
2257 "changes"
2258 }
2259}
2260
2261pub struct TotalChangesFunc;
2262
2263impl ScalarFunction for TotalChangesFunc {
2264 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2265 Ok(SqliteValue::Integer(TOTAL_CHANGES.get()))
2266 }
2267
2268 fn is_deterministic(&self) -> bool {
2269 false
2270 }
2271
2272 fn num_args(&self) -> i32 {
2273 0
2274 }
2275
2276 fn name(&self) -> &str {
2277 "total_changes"
2278 }
2279}
2280
2281pub struct LastInsertRowidFunc;
2282
2283impl ScalarFunction for LastInsertRowidFunc {
2284 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2285 Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
2286 }
2287
2288 fn is_deterministic(&self) -> bool {
2289 false
2290 }
2291
2292 fn num_args(&self) -> i32 {
2293 0
2294 }
2295
2296 fn name(&self) -> &str {
2297 "last_insert_rowid"
2298 }
2299}
2300
2301#[allow(clippy::too_many_lines)]
2305pub fn register_builtins(registry: &mut FunctionRegistry) {
2306 registry.register_scalar(AbsFunc);
2308 registry.register_scalar(SignFunc);
2309 registry.register_scalar(RoundFunc);
2310 registry.register_scalar(RandomFunc);
2311 registry.register_scalar(RandomblobFunc);
2312 registry.register_scalar(ZeroblobFunc);
2313
2314 registry.register_scalar(LowerFunc);
2316 registry.register_scalar(UpperFunc);
2317 registry.register_scalar(LengthFunc);
2318 registry.register_scalar(OctetLengthFunc);
2319 registry.register_scalar(TrimFunc);
2320 registry.register_scalar(LtrimFunc);
2321 registry.register_scalar(RtrimFunc);
2322 registry.register_scalar(ReplaceFunc);
2323 registry.register_scalar(SubstrFunc);
2324 registry.register_scalar(InstrFunc);
2325 registry.register_scalar(CharFunc);
2326 registry.register_scalar(UnicodeFunc);
2327 registry.register_scalar(UnistrFunc);
2328 registry.register_scalar(HexFunc);
2329 registry.register_scalar(UnhexFunc);
2330 registry.register_scalar(QuoteFunc);
2331 registry.register_scalar(UnistrQuoteFunc);
2332 registry.register_scalar(SoundexFunc);
2333
2334 registry.register_scalar(TypeofFunc);
2336 registry.register_scalar(SubtypeFunc);
2337
2338 registry.register_scalar(CoalesceFunc);
2340 registry.register_scalar(IfnullFunc);
2341 registry.register_scalar(NullifFunc);
2342 registry.register_scalar(IifFunc);
2343
2344 registry.register_scalar(ConcatFunc);
2346 registry.register_scalar(ConcatWsFunc);
2347 registry.register_scalar(ScalarMaxFunc);
2348 registry.register_scalar(ScalarMinFunc);
2349
2350 registry.register_scalar(LikelihoodFunc);
2352 registry.register_scalar(LikelyFunc);
2353 registry.register_scalar(UnlikelyFunc);
2354
2355 registry.register_scalar(LikeFunc);
2357 registry.register_scalar(GlobFunc);
2358
2359 registry.register_slow_changing_scalar(SqliteVersionFunc);
2361 registry.register_slow_changing_scalar(SqliteSourceIdFunc);
2362 registry.register_slow_changing_scalar(SqliteCompileoptionUsedFunc);
2363 registry.register_slow_changing_scalar(SqliteCompileoptionGetFunc);
2364
2365 registry.register_scalar(ChangesFunc);
2367 registry.register_scalar(TotalChangesFunc);
2368 registry.register_scalar(LastInsertRowidFunc);
2369
2370 struct IfFunc;
2373 impl ScalarFunction for IfFunc {
2374 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2375 IifFunc.invoke(args)
2376 }
2377
2378 fn num_args(&self) -> i32 {
2379 -1 }
2381
2382 fn min_args(&self) -> i32 {
2383 2
2384 }
2385
2386 fn max_args(&self) -> Option<i32> {
2387 Some(3)
2388 }
2389
2390 fn name(&self) -> &str {
2391 "if"
2392 }
2393 }
2394 registry.register_scalar(IfFunc);
2395
2396 struct SubstringFunc;
2398 impl ScalarFunction for SubstringFunc {
2399 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2400 SubstrFunc.invoke(args)
2401 }
2402
2403 fn num_args(&self) -> i32 {
2404 -1
2405 }
2406
2407 fn min_args(&self) -> i32 {
2408 2
2409 }
2410
2411 fn max_args(&self) -> Option<i32> {
2412 Some(3)
2413 }
2414
2415 fn name(&self) -> &str {
2416 "substring"
2417 }
2418 }
2419 registry.register_scalar(SubstringFunc);
2420
2421 struct PrintfFunc;
2423 impl ScalarFunction for PrintfFunc {
2424 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2425 FormatFunc.invoke(args)
2426 }
2427
2428 fn num_args(&self) -> i32 {
2429 -1
2430 }
2431
2432 fn name(&self) -> &str {
2433 "printf"
2434 }
2435 }
2436 registry.register_scalar(FormatFunc);
2437 registry.register_scalar(PrintfFunc);
2438
2439 register_math_builtins(registry);
2441
2442 register_datetime_builtins(registry);
2444
2445 register_aggregate_builtins(registry);
2447}
2448
2449pub struct FormatFunc;
2452
2453impl ScalarFunction for FormatFunc {
2454 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2455 if args.is_empty() || args[0].is_null() {
2456 return Ok(SqliteValue::Null);
2457 }
2458 let fmt_str = args[0].to_text();
2459 if fmt_str.is_empty() {
2465 return Ok(SqliteValue::Null);
2466 }
2467 let params = &args[1..];
2468 let Some(result) = sqlite_format(&fmt_str, params)? else {
2469 return Ok(SqliteValue::Null);
2471 };
2472 Ok(SqliteValue::Text(SmallText::from_string(result)))
2473 }
2474
2475 fn num_args(&self) -> i32 {
2476 -1
2477 }
2478
2479 fn name(&self) -> &str {
2480 "format"
2481 }
2482}
2483
2484const PRINTF_MAX_LENGTH: usize = 1_000_000_000;
2489const PRINTF_FLOAT_PRECISION_CAP: usize = 100_000_000;
2492
2493fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<Option<String>> {
2499 let mut result = String::new();
2500 let chars: Vec<char> = fmt.chars().collect();
2501 let mut i = 0;
2502 let mut param_idx = 0;
2503
2504 while i < chars.len() {
2505 if chars[i] != '%' {
2506 result.push(chars[i]);
2507 i += 1;
2508 continue;
2509 }
2510 i += 1;
2511 if i >= chars.len() {
2512 result.push('%');
2516 break;
2517 }
2518
2519 let mut left_align = false;
2521 let mut show_sign = false;
2522 let mut space_sign = false;
2523 let mut zero_pad = false;
2524 let mut alt_form = false;
2525 let mut alt_form2 = false;
2526 let mut comma_group = false;
2527 loop {
2528 if i >= chars.len() {
2529 break;
2530 }
2531 match chars[i] {
2532 '-' => left_align = true,
2533 '+' => show_sign = true,
2534 ' ' => space_sign = true,
2535 '0' => zero_pad = true,
2536 '#' => alt_form = true,
2537 '!' => alt_form2 = true,
2540 ',' => comma_group = true,
2545 _ => break,
2546 }
2547 i += 1;
2548 }
2549
2550 let width: usize;
2554 if i < chars.len() && chars[i] == '*' {
2555 i += 1;
2556 let w = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2557 param_idx += 1;
2558 let w32 = w as i32;
2565 if w32 < 0 {
2566 left_align = true;
2567 width = if w32 >= -2_147_483_647 {
2568 (-w32) as usize
2569 } else {
2570 0
2571 };
2572 } else {
2573 width = w32 as usize;
2574 }
2575 } else {
2576 width = parse_printf_field(&chars, &mut i);
2582 }
2583
2584 let mut precision = None;
2588 if i < chars.len() && chars[i] == '.' {
2589 i += 1;
2590 if i < chars.len() && chars[i] == '*' {
2591 i += 1;
2592 let p = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2593 param_idx += 1;
2594 let p32 = p as i32;
2602 precision = if p32 == i32::MIN {
2603 None
2604 } else {
2605 Some(usize::try_from(p32.unsigned_abs()).unwrap_or(usize::MAX))
2610 };
2611 } else {
2612 precision = Some(parse_printf_field(&chars, &mut i));
2616 }
2617 }
2618
2619 if i >= chars.len() {
2620 if result.is_empty() {
2629 return Ok(None);
2630 }
2631 break;
2632 }
2633
2634 let spec = chars[i];
2635 i += 1;
2636
2637 if width >= PRINTF_MAX_LENGTH {
2645 return Ok(None);
2646 }
2647 match spec {
2648 'f' | 'e' | 'E' | 'g' | 'G' => {
2649 if let Some(p) = precision.as_mut() {
2650 *p = (*p).min(PRINTF_FLOAT_PRECISION_CAP);
2651 }
2652 }
2653 'd' | 'i' | 'u' | 'x' | 'X' | 'o' | 'c' | 'p' | 'r'
2654 if precision.is_some_and(|p| p >= PRINTF_MAX_LENGTH) =>
2655 {
2656 return Ok(None);
2657 }
2658 _ => {}
2659 }
2660
2661 match spec {
2662 '%' => result.push_str(&pad_string("%", width, left_align)),
2667 'n' => {} 'd' | 'i' => {
2669 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2670 param_idx += 1;
2671 let formatted = format_integer(
2672 val,
2673 width,
2674 left_align,
2675 show_sign,
2676 space_sign,
2677 zero_pad,
2678 comma_group,
2679 precision,
2680 );
2681 result.push_str(&formatted);
2682 }
2683 'u' => {
2684 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2687 param_idx += 1;
2688 #[allow(clippy::cast_sign_loss)]
2689 let digits = apply_int_precision(&(val as u64).to_string(), precision);
2690 let padded = if comma_group {
2691 let base = if zero_pad && width > digits.len() {
2694 format!("{}{}", "0".repeat(width - digits.len()), digits)
2695 } else {
2696 digits
2697 };
2698 let grouped = group_thousands(&base);
2699 if zero_pad {
2700 grouped
2701 } else {
2702 pad_string(&grouped, width, left_align)
2703 }
2704 } else if zero_pad && width > digits.len() {
2705 format!("{}{}", "0".repeat(width - digits.len()), digits)
2706 } else {
2707 pad_string(&digits, width, left_align)
2708 };
2709 result.push_str(&padded);
2710 }
2711 'f' => {
2712 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2713 param_idx += 1;
2714 let val = if val == 0.0 { 0.0 } else { val };
2718 let formatted = if let Some(s) =
2719 nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2720 {
2721 s
2722 } else {
2723 let prec = precision.unwrap_or(6);
2729 let mut mag = if alt_form2 {
2739 if val == 0.0 {
2744 altform2_trim_float("0")
2745 } else {
2746 let (cap_digits, sci_exp) = sqlite_float_altform2_digits(val);
2747 let want =
2749 i64::from(sci_exp) + i64::try_from(prec).unwrap_or(i64::MAX) + 1;
2750 let cap = i64::try_from(cap_digits.len()).unwrap_or(i64::MAX);
2751 if want >= cap {
2752 altform2_render_fixed(&cap_digits, sci_exp, prec)
2753 } else {
2754 altform2_trim_float(&format_fixed_round_half_away(val.abs(), prec))
2755 }
2756 }
2757 } else {
2758 round_positional_to_sig(
2759 &format_fixed_round_half_away(val.abs(), prec),
2760 FLOAT_SIG_DIGITS,
2761 )
2762 };
2763 if alt_form && !mag.contains('.') {
2766 mag.push('.');
2767 }
2768 if comma_group {
2769 mag = group_float_integer_part(&mag);
2770 }
2771 let body = if val.is_sign_negative() {
2772 format!("-{mag}")
2773 } else {
2774 mag
2775 };
2776 finish_float_padding(&body, width, left_align, show_sign, space_sign, zero_pad)
2777 };
2778 result.push_str(&formatted);
2779 }
2780 'e' | 'E' => {
2781 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2782 param_idx += 1;
2783 let val = if val == 0.0 { 0.0 } else { val };
2785 let prec = precision.unwrap_or(6);
2786 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2787 {
2788 result.push_str(&s);
2789 } else if alt_form2 {
2790 let (digits, exp) = altform2_sig_digits(val, prec + 1);
2797 let mut formatted = altform2_render_exp(&digits, exp, spec == 'E');
2798 if val.is_sign_negative() {
2799 formatted = format!("-{formatted}");
2800 }
2801 result.push_str(&finish_float_padding(
2802 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2803 ));
2804 } else {
2805 let mant_prec = prec.min(FLOAT_SIG_DIGITS - 1);
2812 let raw = format_sci_round_half_away(val, mant_prec, spec == 'E');
2813 let mut formatted = normalize_exponent(&raw);
2814 if prec > mant_prec
2815 && let Some(e_pos) = formatted.find(['e', 'E'])
2816 {
2817 let (mant, exp_part) = formatted.split_at(e_pos);
2818 formatted = format!("{mant}{}{exp_part}", "0".repeat(prec - mant_prec));
2819 }
2820 if alt_form && let Some(e_pos) = formatted.find(['e', 'E']) {
2823 let (mantissa, exp_part) = formatted.split_at(e_pos);
2824 if !mantissa.contains('.') {
2825 formatted = format!("{mantissa}.{exp_part}");
2826 }
2827 }
2828 result.push_str(&finish_float_padding(
2829 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2830 ));
2831 }
2832 }
2833 'g' | 'G' => {
2834 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2835 param_idx += 1;
2836 let val = if val == 0.0 { 0.0 } else { val };
2839 let prec = precision.unwrap_or(6);
2840 let sig = prec.max(1);
2841 let max_sig = FLOAT_SIG_DIGITS;
2846 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2847 {
2848 result.push_str(&s);
2849 } else if alt_form2 {
2850 let (digits, exp) = altform2_sig_digits(val, sig);
2859 let use_exp_form =
2860 exp < -4 || i64::from(exp) >= i64::try_from(sig).unwrap_or(i64::MAX);
2861 let mut alt = if use_exp_form {
2862 altform2_render_exp(&digits, exp, spec == 'G')
2863 } else {
2864 altform2_render_fixed(&digits, exp, usize::MAX)
2865 };
2866 if val.is_sign_negative() {
2867 alt = format!("-{alt}");
2868 }
2869 result.push_str(&finish_float_padding(
2870 &alt, width, left_align, show_sign, space_sign, zero_pad,
2871 ));
2872 } else {
2873 let mut formatted = format_float_g(val, sig, spec == 'G', alt_form, max_sig);
2874 if comma_group && !formatted.contains(['e', 'E']) {
2879 formatted = group_signed_decimal_integer_part(&formatted);
2880 }
2881 result.push_str(&finish_float_padding(
2882 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2883 ));
2884 }
2885 }
2886 's' | 'z' => {
2887 let param = params.get(param_idx);
2888 param_idx += 1;
2889 let val = match param {
2890 Some(SqliteValue::Null) | None => String::new(),
2892 Some(v) => v.to_text(),
2893 };
2894 let truncated = if let Some(prec) = precision {
2899 if val.len() > prec {
2900 let mut end = prec;
2901 while end > 0 && !val.is_char_boundary(end) {
2902 end -= 1;
2903 }
2904 val[..end].to_owned()
2905 } else {
2906 val
2907 }
2908 } else {
2909 val
2910 };
2911 result.push_str(&pad_string(&truncated, width, left_align));
2912 }
2913 'q' => {
2914 let param = params.get(param_idx);
2918 param_idx += 1;
2919 let escaped = match param {
2921 Some(SqliteValue::Null) | None => "(NULL)".to_owned(),
2923 Some(v) => {
2924 let text = v.to_text();
2925 truncate_str_precision(&text, precision).replace('\'', "''")
2926 }
2927 };
2928 result.push_str(&pad_string(&escaped, width, left_align));
2929 }
2930 'Q' => {
2931 let param = params.get(param_idx);
2935 param_idx += 1;
2936 let rendered = match param {
2937 Some(SqliteValue::Null) | None => "NULL".to_owned(),
2938 Some(v) => {
2939 let text = v.to_text();
2940 format!(
2941 "'{}'",
2942 truncate_str_precision(&text, precision).replace('\'', "''")
2943 )
2944 }
2945 };
2946 result.push_str(&pad_string(&rendered, width, left_align));
2947 }
2948 'w' => {
2949 let param = params.get(param_idx);
2954 param_idx += 1;
2955 let rendered = match param {
2956 Some(SqliteValue::Null) | None => "(NULL)".to_owned(),
2957 Some(v) => {
2958 let text = v.to_text();
2959 truncate_str_precision(&text, precision).replace('"', "\"\"")
2960 }
2961 };
2962 result.push_str(&pad_string(&rendered, width, left_align));
2963 }
2964 'x' | 'X' | 'p' => {
2968 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2969 param_idx += 1;
2970 #[allow(clippy::cast_sign_loss)]
2971 let digits = apply_int_precision(
2972 &if spec == 'x' {
2973 format!("{:x}", val as u64)
2974 } else {
2975 format!("{:X}", val as u64)
2976 },
2977 precision,
2978 );
2979 let prefix = if alt_form && val != 0 {
2984 if spec == 'X' { "0X" } else { "0x" }
2985 } else {
2986 ""
2987 };
2988 let padded = if zero_pad && width > digits.len() {
2993 let pad = "0".repeat(width - digits.len());
2994 format!("{prefix}{pad}{digits}")
2995 } else {
2996 pad_string(&format!("{prefix}{digits}"), width, left_align)
2997 };
2998 result.push_str(&padded);
2999 }
3000 'o' => {
3001 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
3002 param_idx += 1;
3003 #[allow(clippy::cast_sign_loss)]
3004 let digits = apply_int_precision(&format!("{:o}", val as u64), precision);
3005 let prefix = if alt_form && val != 0 { "0" } else { "" };
3007 let padded = if zero_pad && width > digits.len() {
3010 let pad = "0".repeat(width - digits.len());
3011 format!("{prefix}{pad}{digits}")
3012 } else {
3013 pad_string(&format!("{prefix}{digits}"), width, left_align)
3014 };
3015 result.push_str(&padded);
3016 }
3017 'c' => {
3018 let param = params.get(param_idx);
3019 param_idx += 1;
3020 let text = match param {
3025 Some(SqliteValue::Null) | None => String::new(),
3026 Some(v) => v.to_text(),
3027 };
3028 let content = match text.chars().next() {
3038 Some(c) => c.to_string().repeat(precision.map_or(1, |p| p.max(1))),
3039 None => String::new(),
3040 };
3041 let pad = width.saturating_sub(content.chars().count());
3042 if !left_align {
3043 for _ in 0..pad {
3044 result.push(' ');
3045 }
3046 }
3047 result.push_str(&content);
3048 if left_align {
3049 for _ in 0..pad {
3050 result.push(' ');
3051 }
3052 }
3053 }
3054 'r' => {
3055 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
3061 param_idx += 1;
3062 let abs = val.unsigned_abs();
3063 let suffix = match (abs % 10, abs % 100) {
3064 (1, r) if r != 11 => "st",
3065 (2, r) if r != 12 => "nd",
3066 (3, r) if r != 13 => "rd",
3067 _ => "th",
3068 };
3069 let body = format!("{val}{suffix}");
3070 result.push_str(&pad_string(&body, width, left_align));
3071 }
3072 _ => {
3073 return Ok(None);
3080 }
3081 }
3082 let _ = (left_align, show_sign, space_sign, zero_pad);
3084
3085 if result.len() >= PRINTF_MAX_LENGTH {
3088 return Ok(None);
3089 }
3090 }
3091 Ok(Some(result))
3092}
3093
3094fn parse_printf_field(chars: &[char], i: &mut usize) -> usize {
3100 let mut acc: u32 = 0;
3101 while *i < chars.len() && chars[*i].is_ascii_digit() {
3102 acc = acc
3103 .wrapping_mul(10)
3104 .wrapping_add(chars[*i] as u32 - '0' as u32);
3105 *i += 1;
3106 }
3107 (acc & 0x7FFF_FFFF) as usize
3108}
3109
3110fn truncate_str_precision(val: &str, precision: Option<usize>) -> &str {
3114 match precision {
3115 Some(prec) if val.len() > prec => {
3116 let mut end = prec;
3117 while end > 0 && !val.is_char_boundary(end) {
3118 end -= 1;
3119 }
3120 &val[..end]
3121 }
3122 _ => val,
3123 }
3124}
3125
3126#[allow(clippy::too_many_arguments)]
3130fn format_integer(
3131 val: i64,
3132 width: usize,
3133 left_align: bool,
3134 show_sign: bool,
3135 space_sign: bool,
3136 zero_pad: bool,
3137 comma_group: bool,
3138 precision: Option<usize>,
3139) -> String {
3140 let sign = if val < 0 {
3141 "-".to_owned()
3142 } else if show_sign {
3143 "+".to_owned()
3144 } else if space_sign {
3145 " ".to_owned()
3146 } else {
3147 String::new()
3148 };
3149 let digits = apply_int_precision(&format!("{}", val.unsigned_abs()), precision);
3150 if comma_group {
3151 let padded_digits = if zero_pad && width > sign.len() + digits.len() {
3156 format!("{}{digits}", "0".repeat(width - sign.len() - digits.len()))
3157 } else {
3158 digits
3159 };
3160 let body = format!("{sign}{}", group_thousands(&padded_digits));
3161 if zero_pad || body.len() >= width {
3162 return body;
3163 }
3164 let pad = width - body.len();
3165 return if left_align {
3166 format!("{body}{}", " ".repeat(pad))
3167 } else {
3168 format!("{}{body}", " ".repeat(pad))
3169 };
3170 }
3171 let body = format!("{sign}{digits}");
3172 if body.len() >= width {
3173 return body;
3174 }
3175 let pad = width - body.len();
3176 if zero_pad {
3180 format!("{sign}{}{digits}", "0".repeat(pad))
3181 } else if left_align {
3182 format!("{body}{}", " ".repeat(pad))
3183 } else {
3184 format!("{}{body}", " ".repeat(pad))
3185 }
3186}
3187
3188fn group_thousands(digits: &str) -> String {
3192 if digits.len() <= 3 || !digits.bytes().all(|b| b.is_ascii_digit()) {
3193 return digits.to_owned();
3194 }
3195 let lead = digits.len() % 3;
3196 let mut out = String::with_capacity(digits.len() + digits.len() / 3);
3197 if lead > 0 {
3198 out.push_str(&digits[..lead]);
3199 }
3200 let mut idx = lead;
3201 while idx < digits.len() {
3202 if !out.is_empty() {
3203 out.push(',');
3204 }
3205 out.push_str(&digits[idx..idx + 3]);
3206 idx += 3;
3207 }
3208 out
3209}
3210
3211fn altform2_trim_float(mag: &str) -> String {
3216 if mag.contains('.') {
3217 let trimmed = mag.trim_end_matches('0');
3218 if trimmed.ends_with('.') {
3219 format!("{trimmed}0")
3220 } else {
3221 trimmed.to_owned()
3222 }
3223 } else {
3224 format!("{mag}.0")
3225 }
3226}
3227
3228fn group_float_integer_part(mag: &str) -> String {
3231 if let Some(dot) = mag.find('.') {
3232 format!("{}{}", group_thousands(&mag[..dot]), &mag[dot..])
3233 } else {
3234 group_thousands(mag)
3235 }
3236}
3237
3238fn group_signed_decimal_integer_part(s: &str) -> String {
3241 if let Some(rest) = s.strip_prefix('-') {
3242 format!("-{}", group_float_integer_part(rest))
3243 } else {
3244 group_float_integer_part(s)
3245 }
3246}
3247
3248fn apply_int_precision(digits: &str, precision: Option<usize>) -> String {
3254 match precision {
3255 Some(p) if digits.len() < p => {
3256 format!("{}{digits}", "0".repeat(p - digits.len()))
3257 }
3258 _ => digits.to_owned(),
3259 }
3260}
3261
3262fn altform2_sig_digits(val: f64, want: usize) -> (Vec<u8>, i32) {
3272 let (cap_digits, sci_exp) = sqlite_float_altform2_digits(val);
3273 if want >= cap_digits.len() {
3274 return (cap_digits, sci_exp);
3275 }
3276 let mut digits = cap_digits[..want].to_vec();
3277 let mut exp = sci_exp;
3278 if cap_digits[want] >= b'5' {
3281 let mut idx = want;
3282 loop {
3283 if idx == 0 {
3284 digits.fill(b'0');
3286 digits[0] = b'1';
3287 exp += 1;
3288 break;
3289 }
3290 idx -= 1;
3291 if digits[idx] == b'9' {
3292 digits[idx] = b'0';
3293 } else {
3294 digits[idx] += 1;
3295 break;
3296 }
3297 }
3298 }
3299 (digits, exp)
3300}
3301
3302fn altform2_render_exp(digits: &[u8], exp: i32, upper: bool) -> String {
3308 let e_char = if upper { 'E' } else { 'e' };
3309 let mut mant = String::with_capacity(digits.len() + 2);
3310 mant.push(char::from(digits[0]));
3311 mant.push('.');
3312 if digits.len() > 1 {
3313 mant.extend(digits[1..].iter().map(|&b| char::from(b)));
3314 } else {
3315 mant.push('0');
3316 }
3317 let mant = altform2_trim_float(&mant);
3318 let sign = if exp < 0 { '-' } else { '+' };
3319 format!("{mant}{e_char}{sign}{:02}", exp.unsigned_abs())
3320}
3321
3322fn altform2_render_fixed(digits: &[u8], exp: i32, max_frac: usize) -> String {
3329 let n = digits.len();
3330 let mut out = String::new();
3331 if exp >= 0 {
3332 let int_len = usize::try_from(exp).unwrap_or(0) + 1;
3333 if int_len >= n {
3334 out.extend(digits.iter().map(|&b| char::from(b)));
3336 out.push_str(&"0".repeat(int_len - n));
3337 } else {
3338 out.extend(digits[..int_len].iter().map(|&b| char::from(b)));
3339 out.push('.');
3340 let frac = &digits[int_len..];
3341 let take = frac.len().min(max_frac);
3342 out.extend(frac[..take].iter().map(|&b| char::from(b)));
3343 }
3344 } else {
3345 out.push_str("0.");
3346 let lead_zeros = usize::try_from(-exp - 1).unwrap_or(0);
3347 let take_zeros = lead_zeros.min(max_frac);
3348 out.push_str(&"0".repeat(take_zeros));
3349 let remaining = max_frac.saturating_sub(take_zeros);
3350 let take = n.min(remaining);
3351 out.extend(digits[..take].iter().map(|&b| char::from(b)));
3352 }
3353 altform2_trim_float(&out)
3354}
3355
3356fn nonfinite_float_str(
3360 val: f64,
3361 width: usize,
3362 left_align: bool,
3363 show_sign: bool,
3364 space_sign: bool,
3365) -> Option<String> {
3366 let body = if val.is_nan() {
3367 "NaN".to_owned()
3368 } else if val.is_infinite() {
3369 let sign = if val < 0.0 {
3370 "-"
3371 } else if show_sign {
3372 "+"
3373 } else if space_sign {
3374 " "
3375 } else {
3376 ""
3377 };
3378 format!("{sign}Inf")
3379 } else {
3380 return None;
3381 };
3382 Some(pad_string(&body, width, left_align))
3383}
3384
3385fn finish_float_padding(
3389 body: &str,
3390 width: usize,
3391 left_align: bool,
3392 show_sign: bool,
3393 space_sign: bool,
3394 zero_pad: bool,
3395) -> String {
3396 let (sign, digits) = if let Some(rest) = body.strip_prefix('-') {
3397 ("-", rest)
3398 } else if show_sign {
3399 ("+", body)
3400 } else if space_sign {
3401 (" ", body)
3402 } else {
3403 ("", body)
3404 };
3405 let full_len = sign.len() + digits.len();
3406 if full_len >= width {
3407 return format!("{sign}{digits}");
3408 }
3409 let pad = width - full_len;
3410 if left_align {
3411 format!("{sign}{digits}{}", " ".repeat(pad))
3412 } else if zero_pad {
3413 format!("{sign}{}{digits}", "0".repeat(pad))
3414 } else {
3415 format!("{}{sign}{digits}", " ".repeat(pad))
3416 }
3417}
3418
3419fn pad_string(s: &str, width: usize, left_align: bool) -> String {
3420 if s.len() >= width {
3421 return s.to_owned();
3422 }
3423 let pad = width - s.len();
3424 if left_align {
3425 format!("{s}{}", " ".repeat(pad))
3426 } else {
3427 format!("{}{s}", " ".repeat(pad))
3428 }
3429}
3430
3431fn normalize_exponent(s: &str) -> String {
3434 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
3435 (&s[..pos], 'e', &s[pos + 1..])
3436 } else if let Some(pos) = s.find('E') {
3437 (&s[..pos], 'E', &s[pos + 1..])
3438 } else {
3439 return s.to_owned();
3440 };
3441 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
3442 ("-", rest)
3443 } else if let Some(rest) = exp_part.strip_prefix('+') {
3444 ("+", rest)
3445 } else {
3446 ("+", exp_part)
3447 };
3448 let padded = if digits.len() < 2 {
3449 format!("0{digits}")
3450 } else {
3451 digits.to_owned()
3452 };
3453 format!("{prefix}{e_char}{sign}{padded}")
3454}
3455
3456const MAX_F64_FRACTIONAL_DIGITS: usize = 1074;
3461
3462fn increment_decimal_digits(digits: &mut Vec<u8>) {
3467 let mut carry = true;
3468 for b in digits.iter_mut().rev() {
3469 if *b == b'.' {
3470 continue;
3471 }
3472 if carry {
3473 if *b == b'9' {
3474 *b = b'0';
3475 } else {
3476 *b += 1;
3477 carry = false;
3478 break;
3479 }
3480 }
3481 }
3482 if carry {
3483 digits.insert(0, b'1');
3484 }
3485}
3486
3487fn is_exact_decimal_tie(mag: f64, prec: usize) -> bool {
3498 fn looks_like_tie(mag: f64, prec: usize, guard: usize) -> bool {
3499 let full = format!("{mag:.guard$}");
3500 let Some(dot) = full.find('.') else {
3501 return false;
3502 };
3503 let rd_idx = dot + 1 + prec;
3504 let bytes = full.as_bytes();
3505 rd_idx < bytes.len()
3506 && bytes[rd_idx] == b'5'
3507 && full[rd_idx + 1..].bytes().all(|b| b == b'0')
3508 }
3509 looks_like_tie(mag, prec, prec + 18)
3510 && looks_like_tie(mag, prec, prec + MAX_F64_FRACTIONAL_DIGITS)
3511}
3512
3513fn is_exact_sci_tie(mag: f64, prec: usize) -> bool {
3517 fn looks_like_tie(mag: f64, prec: usize, guard: usize) -> bool {
3518 let s = format!("{mag:.guard$e}");
3519 let Some((mant, _)) = s.split_once('e') else {
3520 return false;
3521 };
3522 let Some(dot) = mant.find('.') else {
3523 return false;
3524 };
3525 let rd_idx = dot + 1 + prec;
3526 let bytes = mant.as_bytes();
3527 rd_idx < bytes.len()
3528 && bytes[rd_idx] == b'5'
3529 && mant[rd_idx + 1..].bytes().all(|b| b == b'0')
3530 }
3531 looks_like_tie(mag, prec, prec + 18)
3532 && looks_like_tie(mag, prec, prec + MAX_F64_FRACTIONAL_DIGITS)
3533}
3534
3535fn format_fixed_round_half_away(val: f64, prec: usize) -> String {
3541 const MAX_FMT_PREC: usize = 1024;
3549 if prec > MAX_FMT_PREC {
3550 let mut s = format_fixed_round_half_away(val, MAX_FMT_PREC);
3551 s.extend(core::iter::repeat_n('0', prec - MAX_FMT_PREC));
3552 return s;
3553 }
3554 let base = format!("{val:.prec$}");
3555 let mag = val.abs();
3556 if !is_exact_decimal_tie(mag, prec) {
3557 return base;
3558 }
3559 let src = format!("{mag:.p$}", p = prec + 2);
3562 let dot = src.find('.').unwrap_or(src.len());
3563 let rd_idx = dot + 1 + prec;
3564 let mut digits = src.as_bytes()[..rd_idx].to_vec();
3565 if digits.last() == Some(&b'.') {
3566 digits.pop();
3567 }
3568 increment_decimal_digits(&mut digits);
3569 let Ok(body) = String::from_utf8(digits) else {
3570 return base;
3571 };
3572 if val.is_sign_negative() {
3573 format!("-{body}")
3574 } else {
3575 body
3576 }
3577}
3578
3579fn format_sci_round_half_away(val: f64, prec: usize, upper: bool) -> String {
3587 let base = if upper {
3588 format!("{val:.prec$E}")
3589 } else {
3590 format!("{val:.prec$e}")
3591 };
3592 let mag = val.abs();
3593 if mag == 0.0 || !is_exact_sci_tie(mag, prec) {
3594 return base;
3595 }
3596 let e_char = if upper { 'E' } else { 'e' };
3597 let src = format!("{mag:.p$e}", p = prec + 2);
3598 let Some((mant, exp_str)) = src.split_once('e') else {
3599 return base;
3600 };
3601 let mut exp: i64 = exp_str.parse().unwrap_or(0);
3602 let dot = mant.find('.').unwrap_or(mant.len());
3603 let rd_idx = dot + 1 + prec;
3604 let mut digits = mant.as_bytes()[..rd_idx].to_vec();
3605 if digits.last() == Some(&b'.') {
3606 digits.pop();
3607 }
3608 increment_decimal_digits(&mut digits);
3609 let Ok(mut mantissa) = String::from_utf8(digits) else {
3610 return base;
3611 };
3612 let int_len = mantissa.find('.').unwrap_or(mantissa.len());
3615 if int_len == 2 {
3616 mantissa = if prec > 0 {
3617 format!("1.{}", "0".repeat(prec))
3618 } else {
3619 "1".to_owned()
3620 };
3621 exp += 1;
3622 }
3623 let sign = if val.is_sign_negative() { "-" } else { "" };
3624 format!("{sign}{mantissa}{e_char}{exp}")
3625}
3626
3627pub(crate) fn format_float_g(
3629 val: f64,
3630 sig: usize,
3631 upper: bool,
3632 alt_form: bool,
3633 max_sig: usize,
3634) -> String {
3635 if !val.is_finite() {
3636 return format!("{val}");
3637 }
3638 let val = if val == 0.0 { 0.0 } else { val };
3641 let sig_digits = sig.min(max_sig).max(1);
3646 let sci = format_sci_round_half_away(val, sig_digits.saturating_sub(1), false);
3651 let exp: i32 = sci
3652 .rsplit_once('e')
3653 .and_then(|(_, e)| e.parse().ok())
3654 .unwrap_or(0);
3655 #[allow(clippy::cast_possible_wrap)]
3656 let formatted = if exp < -4 || exp >= sig as i32 {
3657 let s = if upper { sci.replace('e', "E") } else { sci };
3658 let trimmed = if alt_form {
3662 if let Some(e_pos) = s.find(['e', 'E']) {
3663 let (mantissa, exp_part) = s.split_at(e_pos);
3664 if mantissa.contains('.') {
3665 s.clone()
3666 } else {
3667 format!("{mantissa}.{exp_part}")
3668 }
3669 } else if s.contains('.') {
3670 s.clone()
3671 } else {
3672 format!("{s}.")
3673 }
3674 } else if s.contains('.') {
3675 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
3676 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
3677 format!("{mantissa}{}", &s[e_pos..])
3678 } else {
3679 s.trim_end_matches('0').trim_end_matches('.').to_owned()
3680 }
3681 } else {
3682 s
3683 };
3684 normalize_exponent(&trimmed)
3685 } else {
3686 let decimal_places = if exp >= 0 {
3687 sig_digits.saturating_sub((exp + 1) as usize)
3688 } else {
3689 sig_digits + exp.unsigned_abs() as usize - 1
3690 };
3691 let s = format_fixed_round_half_away(val, decimal_places);
3692 if alt_form {
3694 if s.contains('.') { s } else { format!("{s}.") }
3695 }
3696 else if s.contains('.') {
3702 s.trim_end_matches('0').trim_end_matches('.').to_owned()
3703 } else {
3704 s
3705 }
3706 };
3707 formatted
3708}
3709
3710const FLOAT_SIG_DIGITS: usize = 16;
3714
3715fn round_positional_to_sig(s: &str, max_sig: usize) -> String {
3721 let bytes = s.as_bytes();
3722 let mut sig = 0usize;
3723 let mut started = false;
3724 let mut cut = None;
3725 for (i, &b) in bytes.iter().enumerate() {
3726 if b.is_ascii_digit() && (b != b'0' || started) {
3727 started = true;
3728 sig += 1;
3729 if sig == max_sig {
3730 cut = Some(i);
3731 break;
3732 }
3733 }
3734 }
3735 let Some(cut) = cut else { return s.to_owned() };
3736 if bytes[cut + 1..].iter().all(|b| !b.is_ascii_digit()) {
3738 return s.to_owned();
3739 }
3740 let round_up = bytes[cut + 1..]
3741 .iter()
3742 .find(|b| b.is_ascii_digit())
3743 .is_some_and(|&b| b >= b'5');
3744 let mut kept: Vec<u8> = bytes[..=cut].to_vec();
3745 if round_up {
3746 increment_decimal_digits(&mut kept);
3747 }
3748 let mut tail = String::new();
3749 for &b in &bytes[cut + 1..] {
3750 tail.push(if b == b'.' { '.' } else { '0' });
3751 }
3752 format!("{}{tail}", String::from_utf8_lossy(&kept))
3753}
3754
3755#[cfg(test)]
3756#[allow(clippy::too_many_lines)]
3757mod tests {
3758 use super::*;
3759
3760 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
3761 f.invoke(&[v])
3762 }
3763
3764 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
3765 f.invoke(&[a, b])
3766 }
3767
3768 #[test]
3769 fn test_string_fn_oracle_edges_2026_08() {
3770 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
3774 let int = SqliteValue::Integer;
3775 let run = |f: &dyn ScalarFunction, args: &[SqliteValue]| -> SqliteValue {
3776 f.invoke(args).unwrap()
3777 };
3778
3779 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(0)]), t("abcdef"));
3781 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(0), int(3)]), t("ab"));
3782 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(-2)]), t("ef"));
3784 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(-2), int(1)]), t("e"));
3785 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(2), int(-1)]), t("a"));
3787 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(2), int(-10)]), t("a"));
3788 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(-3), int(2)]), t("de"));
3789 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(-10), int(3)]), t(""));
3791 assert_eq!(run(&SubstrFunc, &[t("abcdef"), int(10)]), t(""));
3792 assert_eq!(
3793 run(&SubstrFunc, &[t("abcdef"), int(3), int(100)]),
3794 t("cdef")
3795 );
3796 assert_eq!(run(&SubstrFunc, &[t("héllo"), int(2), int(2)]), t("él"));
3798
3799 assert_eq!(run(&InstrFunc, &[t("abcabc"), t("bc")]), int(2));
3801 assert_eq!(run(&InstrFunc, &[t("abc"), t("")]), int(1)); assert_eq!(run(&InstrFunc, &[t(""), t("x")]), int(0));
3803 assert_eq!(run(&ReplaceFunc, &[t("aaa"), t("a"), t("bb")]), t("bbbbbb"));
3804 assert_eq!(run(&ReplaceFunc, &[t("abc"), t(""), t("x")]), t("abc")); assert_eq!(run(&TrimFunc, &[t("xxabcxx"), t("x")]), t("abc"));
3806 assert_eq!(run(&TrimFunc, &[t(" abc ")]), t("abc"));
3807 }
3808
3809 #[test]
3810 fn test_substr_i64_extreme_bd_t3tbx() {
3811 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
3815 let int = SqliteValue::Integer;
3816 let run = |args: &[SqliteValue]| -> SqliteValue { SubstrFunc.invoke(args).unwrap() };
3817
3818 assert_eq!(run(&[t("abcdef"), int(i64::MAX)]), t("f"));
3819 assert_eq!(run(&[t("abcdef"), int(i64::MAX), int(5)]), t("f"));
3820 assert_eq!(run(&[t("abcdef"), int(i64::MIN)]), t("abcdef"));
3821 assert_eq!(run(&[t("abcdef"), int(1), int(i64::MAX)]), t(""));
3822 assert_eq!(run(&[t("abcdef"), int(3), int(i64::MIN)]), t(""));
3823 assert_eq!(run(&[t("abcdef"), int(i64::MIN), int(i64::MAX)]), t(""));
3824 assert_eq!(run(&[t("abcdef"), int(i64::MAX), int(-1)]), t("e"));
3825 }
3826
3827 #[test]
3828 fn test_round_ndigits_i32_bd_bv61c() {
3829 let run = |x: f64, n: i64| -> SqliteValue {
3833 RoundFunc
3834 .invoke(&[SqliteValue::Float(x), SqliteValue::Integer(n)])
3835 .unwrap()
3836 };
3837 assert_eq!(run(1.23456, 4_294_967_298), SqliteValue::Float(1.23));
3838 assert_eq!(run(1.23456, -4_294_967_295), SqliteValue::Float(1.2));
3839 assert_eq!(run(1.23456, 2), SqliteValue::Float(1.23)); }
3841
3842 #[test]
3843 fn test_quote_infinity_bd_nk5la() {
3844 let q = |v: SqliteValue| -> String {
3848 match QuoteFunc.invoke(&[v]).unwrap() {
3849 SqliteValue::Text(s) => s.as_str().to_owned(),
3850 other => panic!("expected text, got {other:?}"),
3851 }
3852 };
3853 assert_eq!(q(SqliteValue::Float(f64::INFINITY)), "9.0e+999");
3854 assert_eq!(q(SqliteValue::Float(f64::NEG_INFINITY)), "-9.0e+999");
3855 assert_eq!(q(SqliteValue::Float(1.5)), "1.5"); assert_eq!(q(SqliteValue::Integer(42)), "42");
3857 }
3858
3859 #[test]
3860 fn test_scalar_minmax_and_printf_inf_2026_08() {
3861 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
3865 let int = SqliteValue::Integer;
3866 let flt = SqliteValue::Float;
3867 let blob = |b: &[u8]| SqliteValue::Blob(std::sync::Arc::from(b));
3868
3869 assert_eq!(
3870 ScalarMaxFunc.invoke(&[int(1), t("a"), flt(2.5)]).unwrap(),
3871 t("a")
3872 );
3873 assert_eq!(
3874 ScalarMinFunc.invoke(&[int(1), t("a"), flt(2.5)]).unwrap(),
3875 int(1)
3876 );
3877 assert_eq!(
3879 ScalarMaxFunc
3880 .invoke(&[blob(&[1]), t("z"), int(99)])
3881 .unwrap(),
3882 blob(&[1])
3883 );
3884 assert_eq!(
3886 ScalarMaxFunc.invoke(&[SqliteValue::Null, int(5)]).unwrap(),
3887 SqliteValue::Null
3888 );
3889 assert_eq!(
3890 ScalarMinFunc.invoke(&[SqliteValue::Null, int(5)]).unwrap(),
3891 SqliteValue::Null
3892 );
3893
3894 let f = FormatFunc;
3896 let run = |args: &[SqliteValue]| -> String {
3897 match f.invoke(args).unwrap() {
3898 SqliteValue::Text(s) => s.as_str().to_owned(),
3899 other => panic!("expected text, got {other:?}"),
3900 }
3901 };
3902 assert_eq!(run(&[t("%f"), flt(f64::INFINITY)]), "Inf");
3903 assert_eq!(run(&[t("%+f"), flt(f64::INFINITY)]), "+Inf");
3904 assert_eq!(run(&[t("%e"), flt(f64::NEG_INFINITY)]), "-Inf");
3905 }
3906
3907 #[test]
3908 fn test_hex_unhex_char_unicode_oracle_edges_2026_08() {
3909 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
3914 let int = SqliteValue::Integer;
3915 let blob = |b: &[u8]| SqliteValue::Blob(std::sync::Arc::from(b));
3916 let run = |f: &dyn ScalarFunction, args: &[SqliteValue]| -> SqliteValue {
3917 f.invoke(args).unwrap()
3918 };
3919
3920 assert_eq!(run(&HexFunc, &[t("abc")]), t("616263"));
3922 assert_eq!(run(&HexFunc, &[blob(&[0x00, 0xFF])]), t("00FF"));
3923 assert_eq!(run(&HexFunc, &[int(255)]), t("323535")); assert_eq!(run(&UnhexFunc, &[t("414243")]), blob(b"ABC"));
3926 assert_eq!(run(&UnhexFunc, &[t("4142"), t("")]), blob(b"AB"));
3927 assert_eq!(run(&UnhexFunc, &[t("zz")]), SqliteValue::Null);
3928 assert_eq!(run(&UnhexFunc, &[t("4")]), SqliteValue::Null);
3929 assert_eq!(run(&CharFunc, &[int(65), int(66), int(67)]), t("ABC"));
3931 assert_eq!(run(&CharFunc, &[int(0x1_F600)]), t("😀"));
3932 assert_eq!(run(&CharFunc, &[]), t(""));
3933 assert_eq!(run(&UnicodeFunc, &[t("A")]), int(65));
3935 assert_eq!(run(&UnicodeFunc, &[t("€")]), int(8364));
3936 assert_eq!(run(&UnicodeFunc, &[t("")]), SqliteValue::Null);
3937 assert_eq!(run(&TypeofFunc, &[SqliteValue::Float(1.0)]), t("real"));
3939 }
3940
3941 #[test]
3942 fn test_case_length_trig_oracle_edges_2026_08() {
3943 use crate::math::{Atan2Func, CosFunc, SinFunc};
3944 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
3945 let int = SqliteValue::Integer;
3946 let blob = |b: &[u8]| SqliteValue::Blob(std::sync::Arc::from(b));
3947 let run = |f: &dyn ScalarFunction, args: &[SqliteValue]| -> SqliteValue {
3948 f.invoke(args).unwrap()
3949 };
3950
3951 assert_eq!(run(&UpperFunc, &[t("héllo")]), t("HéLLO"));
3953 assert_eq!(run(&UpperFunc, &[t("ß")]), t("ß"));
3954 assert_eq!(run(&LowerFunc, &[t("HÉLLO")]), t("hÉllo"));
3955 assert_eq!(run(&LengthFunc, &[t("héllo")]), int(5));
3957 assert_eq!(run(&LengthFunc, &[blob(&[0x00, 0xFF])]), int(2));
3958 assert_eq!(run(&LengthFunc, &[int(12345)]), int(5));
3959 assert_eq!(run(&LengthFunc, &[SqliteValue::Null]), SqliteValue::Null);
3960 assert_eq!(
3962 run(&SinFunc, &[SqliteValue::Float(0.0)]),
3963 SqliteValue::Float(0.0)
3964 );
3965 assert_eq!(
3966 run(&CosFunc, &[SqliteValue::Float(0.0)]),
3967 SqliteValue::Float(1.0)
3968 );
3969 assert_eq!(
3970 run(&Atan2Func, &[int(1), int(1)]),
3971 SqliteValue::Float((1.0_f64).atan2(1.0))
3972 );
3973 }
3974
3975 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
3976 let function = registry
3977 .find_scalar(name, arity)
3978 .expect("known scalar name with bad arity returns erroring scalar");
3979 let args = vec![SqliteValue::Null; arity.max(0) as usize];
3980 let err = function
3981 .invoke(&args)
3982 .expect_err("wrong arity should return function error");
3983 let expected = format!("wrong number of arguments to function {name}()");
3984 assert!(
3985 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
3986 "expected {expected:?}, got {err:?}"
3987 );
3988 }
3989
3990 #[test]
3991 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
3992 let original = get_change_tracking_state();
3993 let expected = ChangeTrackingState {
3994 last_insert_rowid: 17,
3995 last_changes: 23,
3996 total_changes: 42,
3997 };
3998
3999 set_change_tracking_state(expected);
4000 assert_eq!(get_change_tracking_state(), expected);
4001
4002 set_change_tracking_state(original);
4003 }
4004
4005 #[test]
4008 fn test_abs_positive() {
4009 assert_eq!(
4010 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
4011 SqliteValue::Integer(42)
4012 );
4013 }
4014
4015 #[test]
4016 fn test_abs_negative() {
4017 assert_eq!(
4018 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
4019 SqliteValue::Integer(42)
4020 );
4021 }
4022
4023 #[test]
4024 fn test_abs_null() {
4025 assert_eq!(
4026 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
4027 SqliteValue::Null
4028 );
4029 }
4030
4031 #[test]
4032 fn test_abs_min_i64_overflow() {
4033 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
4034 assert!(matches!(err, FrankenError::IntegerOverflow));
4035 }
4036
4037 #[test]
4038 fn test_abs_string_coercion() {
4039 assert_eq!(
4040 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
4041 SqliteValue::Float(7.5)
4042 );
4043 }
4044
4045 #[test]
4046 fn test_abs_whitespace_padded_text() {
4047 assert_eq!(
4049 invoke1(
4050 &AbsFunc,
4051 SqliteValue::Text(SmallText::from_string(" 42 "))
4052 )
4053 .unwrap(),
4054 SqliteValue::Float(42.0)
4055 );
4056 assert_eq!(
4057 invoke1(
4058 &AbsFunc,
4059 SqliteValue::Text(SmallText::from_string(" -7.5 "))
4060 )
4061 .unwrap(),
4062 SqliteValue::Float(7.5)
4063 );
4064 assert_eq!(
4065 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
4066 SqliteValue::Float(0.0)
4067 );
4068 }
4069
4070 #[test]
4071 #[allow(clippy::approx_constant)]
4072 fn test_abs_float() {
4073 assert_eq!(
4074 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
4075 SqliteValue::Float(3.14)
4076 );
4077 }
4078
4079 #[test]
4082 fn test_char_basic() {
4083 let f = CharFunc;
4084 let result = f
4085 .invoke(&[
4086 SqliteValue::Integer(72),
4087 SqliteValue::Integer(101),
4088 SqliteValue::Integer(108),
4089 SqliteValue::Integer(108),
4090 SqliteValue::Integer(111),
4091 ])
4092 .unwrap();
4093 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
4094 }
4095
4096 #[test]
4097 fn test_char_null_skipped() {
4098 let f = CharFunc;
4099 let result = f
4101 .invoke(&[
4102 SqliteValue::Integer(65),
4103 SqliteValue::Null,
4104 SqliteValue::Integer(66),
4105 ])
4106 .unwrap();
4107 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
4108 }
4109
4110 #[test]
4111 fn test_char_invalid_scalar_values_use_replacement_character() {
4112 let f = CharFunc;
4113 let result = f
4114 .invoke(&[
4115 SqliteValue::Integer(-1),
4116 SqliteValue::Integer(65),
4117 SqliteValue::Integer(1_114_112),
4118 ])
4119 .unwrap();
4120 assert_eq!(
4121 result,
4122 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
4123 );
4124 }
4125
4126 #[test]
4129 fn test_coalesce_first_non_null() {
4130 let f = CoalesceFunc;
4131 let result = f
4132 .invoke(&[
4133 SqliteValue::Null,
4134 SqliteValue::Null,
4135 SqliteValue::Integer(3),
4136 SqliteValue::Integer(4),
4137 ])
4138 .unwrap();
4139 assert_eq!(result, SqliteValue::Integer(3));
4140 }
4141
4142 #[test]
4145 fn test_concat_null_as_empty() {
4146 let f = ConcatFunc;
4147 let result = f
4148 .invoke(&[
4149 SqliteValue::Null,
4150 SqliteValue::Text(SmallText::from_string("hello")),
4151 SqliteValue::Null,
4152 ])
4153 .unwrap();
4154 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
4155 }
4156
4157 #[test]
4158 #[ignore = "perf-only benchmark"]
4159 fn perf_concat_text_args() {
4160 use std::hint::black_box;
4161 use std::time::Instant;
4162
4163 const TEXT_ARGS: usize = 24;
4164 const INVOCATIONS: usize = 50_000;
4165 const REPEATS: usize = 5;
4166
4167 let f = ConcatFunc;
4168 let mut args = Vec::with_capacity(TEXT_ARGS);
4169 for _ in 0..TEXT_ARGS {
4170 args.push(SqliteValue::Text(SmallText::from_string("payload")));
4171 }
4172
4173 let mut best_ns = u128::MAX;
4174 let mut result_len = 0usize;
4175 for _ in 0..REPEATS {
4176 let started = Instant::now();
4177 for _ in 0..INVOCATIONS {
4178 let result = black_box(
4179 f.invoke(black_box(args.as_slice()))
4180 .expect("concat benchmark invocation must succeed"),
4181 );
4182 result_len = match result {
4183 SqliteValue::Text(text) => text.len(),
4184 SqliteValue::Null
4185 | SqliteValue::Integer(_)
4186 | SqliteValue::Float(_)
4187 | SqliteValue::Blob(_) => 0,
4188 };
4189 }
4190 best_ns = best_ns.min(started.elapsed().as_nanos());
4191 }
4192
4193 println!(
4194 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
4195 );
4196 }
4197
4198 #[test]
4201 fn test_concat_ws_null_skipped() {
4202 let f = ConcatWsFunc;
4203 let result = f
4204 .invoke(&[
4205 SqliteValue::Text(SmallText::from_string(",")),
4206 SqliteValue::Text(SmallText::from_string("a")),
4207 SqliteValue::Null,
4208 SqliteValue::Text(SmallText::from_string("b")),
4209 ])
4210 .unwrap();
4211 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
4212 }
4213
4214 #[test]
4215 fn test_concat_ws_empty_string_is_not_skipped() {
4216 let f = ConcatWsFunc;
4217 let result = f
4218 .invoke(&[
4219 SqliteValue::Text(SmallText::from_string("|")),
4220 SqliteValue::Text(SmallText::new("")),
4221 SqliteValue::Text(SmallText::from_string("x")),
4222 ])
4223 .unwrap();
4224 assert_eq!(result, SqliteValue::Text(SmallText::from_string("|x")));
4225 }
4226
4227 #[test]
4228 #[ignore = "perf-only benchmark"]
4229 fn perf_concat_ws_text_args() {
4230 use std::hint::black_box;
4231 use std::time::Instant;
4232
4233 const TEXT_ARGS: usize = 24;
4234 const INVOCATIONS: usize = 50_000;
4235 const REPEATS: usize = 5;
4236
4237 let f = ConcatWsFunc;
4238 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
4239 args.push(SqliteValue::Text(SmallText::from_string(",")));
4240 for _ in 0..TEXT_ARGS {
4241 args.push(SqliteValue::Text(SmallText::from_string("payload")));
4242 }
4243
4244 let mut best_ns = u128::MAX;
4245 let mut result_len = 0usize;
4246 for _ in 0..REPEATS {
4247 let started = Instant::now();
4248 for _ in 0..INVOCATIONS {
4249 let result = black_box(
4250 f.invoke(black_box(args.as_slice()))
4251 .expect("concat_ws benchmark invocation must succeed"),
4252 );
4253 result_len = match result {
4254 SqliteValue::Text(text) => text.len(),
4255 SqliteValue::Null
4256 | SqliteValue::Integer(_)
4257 | SqliteValue::Float(_)
4258 | SqliteValue::Blob(_) => 0,
4259 };
4260 }
4261 best_ns = best_ns.min(started.elapsed().as_nanos());
4262 }
4263
4264 println!(
4265 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
4266 );
4267 }
4268
4269 #[test]
4272 fn test_hex_blob() {
4273 let result = invoke1(
4274 &HexFunc,
4275 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
4276 )
4277 .unwrap();
4278 assert_eq!(
4279 result,
4280 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
4281 );
4282 }
4283
4284 #[test]
4285 fn test_hex_number_via_text() {
4286 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
4288 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
4289 }
4290
4291 #[test]
4292 #[ignore = "perf-only benchmark"]
4293 fn perf_hex_text_blob_args() {
4294 use std::hint::black_box;
4295 use std::time::Instant;
4296
4297 const BYTES: usize = 24;
4298 const INVOCATIONS: usize = 100_000;
4299 const REPEATS: usize = 5;
4300
4301 let f = HexFunc;
4302 let text_args = [SqliteValue::Text(SmallText::from_string(
4303 "payload payload sentinel",
4304 ))];
4305 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
4306
4307 let mut text_best_ns = u128::MAX;
4308 let mut blob_best_ns = u128::MAX;
4309 let mut text_result_len = 0usize;
4310 let mut blob_result_len = 0usize;
4311 for _ in 0..REPEATS {
4312 let started = Instant::now();
4313 for _ in 0..INVOCATIONS {
4314 let result = black_box(
4315 f.invoke(black_box(text_args.as_slice()))
4316 .expect("hex text benchmark invocation must succeed"),
4317 );
4318 text_result_len = match result {
4319 SqliteValue::Text(text) => text.len(),
4320 SqliteValue::Null
4321 | SqliteValue::Integer(_)
4322 | SqliteValue::Float(_)
4323 | SqliteValue::Blob(_) => 0,
4324 };
4325 }
4326 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
4327
4328 let started = Instant::now();
4329 for _ in 0..INVOCATIONS {
4330 let result = black_box(
4331 f.invoke(black_box(blob_args.as_slice()))
4332 .expect("hex blob benchmark invocation must succeed"),
4333 );
4334 blob_result_len = match result {
4335 SqliteValue::Text(text) => text.len(),
4336 SqliteValue::Null
4337 | SqliteValue::Integer(_)
4338 | SqliteValue::Float(_)
4339 | SqliteValue::Blob(_) => 0,
4340 };
4341 }
4342 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
4343 }
4344
4345 println!(
4346 "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}"
4347 );
4348 }
4349
4350 #[test]
4353 fn test_iif_true() {
4354 let f = IifFunc;
4355 let result = f
4356 .invoke(&[
4357 SqliteValue::Integer(1),
4358 SqliteValue::Text(SmallText::from_string("yes")),
4359 SqliteValue::Text(SmallText::from_string("no")),
4360 ])
4361 .unwrap();
4362 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
4363 }
4364
4365 #[test]
4366 fn test_iif_false() {
4367 let f = IifFunc;
4368 let result = f
4369 .invoke(&[
4370 SqliteValue::Integer(0),
4371 SqliteValue::Text(SmallText::from_string("yes")),
4372 SqliteValue::Text(SmallText::from_string("no")),
4373 ])
4374 .unwrap();
4375 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
4376 }
4377
4378 #[test]
4379 fn test_iif_whitespace_padded_text_truthy() {
4380 let f = IifFunc;
4383 let result = f
4384 .invoke(&[
4385 SqliteValue::Text(SmallText::from_string(" 5 ")),
4386 SqliteValue::Text(SmallText::from_string("yes")),
4387 SqliteValue::Text(SmallText::from_string("no")),
4388 ])
4389 .unwrap();
4390 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
4391 }
4392
4393 #[test]
4396 fn test_ifnull_non_null() {
4397 assert_eq!(
4398 invoke2(
4399 &IfnullFunc,
4400 SqliteValue::Integer(5),
4401 SqliteValue::Integer(10)
4402 )
4403 .unwrap(),
4404 SqliteValue::Integer(5)
4405 );
4406 }
4407
4408 #[test]
4409 fn test_ifnull_null() {
4410 assert_eq!(
4411 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
4412 SqliteValue::Integer(10)
4413 );
4414 }
4415
4416 #[test]
4419 fn test_instr_found() {
4420 assert_eq!(
4421 invoke2(
4422 &InstrFunc,
4423 SqliteValue::Text(SmallText::from_string("hello world")),
4424 SqliteValue::Text(SmallText::from_string("world"))
4425 )
4426 .unwrap(),
4427 SqliteValue::Integer(7)
4428 );
4429 }
4430
4431 #[test]
4432 fn test_instr_not_found() {
4433 assert_eq!(
4434 invoke2(
4435 &InstrFunc,
4436 SqliteValue::Text(SmallText::from_string("hello")),
4437 SqliteValue::Text(SmallText::from_string("xyz"))
4438 )
4439 .unwrap(),
4440 SqliteValue::Integer(0)
4441 );
4442 }
4443
4444 #[test]
4445 fn test_instr_empty_needle_returns_one() {
4446 assert_eq!(
4448 invoke2(
4449 &InstrFunc,
4450 SqliteValue::Text(SmallText::from_string("hello")),
4451 SqliteValue::Text(SmallText::new(""))
4452 )
4453 .unwrap(),
4454 SqliteValue::Integer(1)
4455 );
4456 }
4457
4458 #[test]
4459 fn test_instr_empty_haystack_returns_zero() {
4460 assert_eq!(
4461 invoke2(
4462 &InstrFunc,
4463 SqliteValue::Text(SmallText::new("")),
4464 SqliteValue::Text(SmallText::from_string("x"))
4465 )
4466 .unwrap(),
4467 SqliteValue::Integer(0)
4468 );
4469 }
4470
4471 #[test]
4472 fn test_instr_blob_empty_needle_returns_one() {
4473 assert_eq!(
4475 invoke2(
4476 &InstrFunc,
4477 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
4478 SqliteValue::Blob(Arc::from([].as_slice()))
4479 )
4480 .unwrap(),
4481 SqliteValue::Integer(1)
4482 );
4483 }
4484
4485 #[test]
4486 #[ignore = "perf-only benchmark"]
4487 fn perf_instr_text_args() {
4488 use std::hint::black_box;
4489 use std::time::Instant;
4490
4491 const INVOCATIONS: usize = 100_000;
4492 const REPEATS: usize = 5;
4493
4494 let f = InstrFunc;
4495 let args = [
4496 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
4497 SqliteValue::Text(SmallText::from_string("sentinel")),
4498 ];
4499
4500 let mut best_ns = u128::MAX;
4501 let mut result_value = 0i64;
4502 for _ in 0..REPEATS {
4503 let started = Instant::now();
4504 for _ in 0..INVOCATIONS {
4505 let result = black_box(
4506 f.invoke(black_box(args.as_slice()))
4507 .expect("instr benchmark invocation must succeed"),
4508 );
4509 result_value = match result {
4510 SqliteValue::Integer(value) => value,
4511 SqliteValue::Null
4512 | SqliteValue::Float(_)
4513 | SqliteValue::Text(_)
4514 | SqliteValue::Blob(_) => 0,
4515 };
4516 }
4517 best_ns = best_ns.min(started.elapsed().as_nanos());
4518 }
4519
4520 println!(
4521 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
4522 );
4523 }
4524
4525 #[test]
4528 fn test_length_text_chars() {
4529 assert_eq!(
4531 invoke1(
4532 &LengthFunc,
4533 SqliteValue::Text(SmallText::from_string("café"))
4534 )
4535 .unwrap(),
4536 SqliteValue::Integer(4)
4537 );
4538 }
4539
4540 #[test]
4541 fn test_length_text_stops_at_nul() {
4542 assert_eq!(
4543 invoke1(
4544 &LengthFunc,
4545 SqliteValue::Text(SmallText::from_string("A\0B"))
4546 )
4547 .unwrap(),
4548 SqliteValue::Integer(1)
4549 );
4550 assert_eq!(
4551 invoke1(
4552 &LengthFunc,
4553 SqliteValue::Text(SmallText::from_string("\0A"))
4554 )
4555 .unwrap(),
4556 SqliteValue::Integer(0)
4557 );
4558 }
4559
4560 #[test]
4561 fn test_length_blob_bytes() {
4562 assert_eq!(
4563 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
4564 SqliteValue::Integer(2)
4565 );
4566 }
4567
4568 #[test]
4571 fn test_octet_length_multibyte() {
4572 assert_eq!(
4574 invoke1(
4575 &OctetLengthFunc,
4576 SqliteValue::Text(SmallText::from_string("café"))
4577 )
4578 .unwrap(),
4579 SqliteValue::Integer(5)
4580 );
4581 }
4582
4583 #[test]
4584 fn test_octet_length_honors_statement_text_encoding() {
4585 set_statement_text_encoding(TextEncoding::Utf8);
4587 assert_eq!(
4588 invoke1(
4589 &OctetLengthFunc,
4590 SqliteValue::Text(SmallText::from_string("abc"))
4591 )
4592 .unwrap(),
4593 SqliteValue::Integer(3)
4594 );
4595 assert_eq!(
4596 invoke1(&OctetLengthFunc, SqliteValue::Integer(12345)).unwrap(),
4597 SqliteValue::Integer(5)
4598 );
4599
4600 set_statement_text_encoding(TextEncoding::Utf16le);
4602 assert_eq!(statement_text_encoding(), TextEncoding::Utf16le);
4603 assert_eq!(
4604 invoke1(
4605 &OctetLengthFunc,
4606 SqliteValue::Text(SmallText::from_string("abc"))
4607 )
4608 .unwrap(),
4609 SqliteValue::Integer(6)
4610 );
4611 assert_eq!(
4612 invoke1(&OctetLengthFunc, SqliteValue::Integer(12345)).unwrap(),
4613 SqliteValue::Integer(10)
4614 );
4615 assert_eq!(
4617 invoke1(
4618 &OctetLengthFunc,
4619 SqliteValue::Text(SmallText::from_string("\u{1F600}"))
4620 )
4621 .unwrap(),
4622 SqliteValue::Integer(4)
4623 );
4624
4625 set_statement_text_encoding(TextEncoding::Utf16be);
4627 assert_eq!(
4628 invoke1(
4629 &OctetLengthFunc,
4630 SqliteValue::Text(SmallText::from_string("abc"))
4631 )
4632 .unwrap(),
4633 SqliteValue::Integer(6)
4634 );
4635
4636 assert_eq!(
4638 invoke1(&OctetLengthFunc, SqliteValue::Blob(vec![1, 2, 3].into())).unwrap(),
4639 SqliteValue::Integer(3)
4640 );
4641
4642 set_statement_text_encoding(TextEncoding::Utf8);
4644 }
4645
4646 #[test]
4649 fn test_lower_ascii() {
4650 assert_eq!(
4651 invoke1(
4652 &LowerFunc,
4653 SqliteValue::Text(SmallText::from_string("HELLO"))
4654 )
4655 .unwrap(),
4656 SqliteValue::Text(SmallText::from_string("hello"))
4657 );
4658 }
4659
4660 #[test]
4661 fn test_upper_ascii() {
4662 assert_eq!(
4663 invoke1(
4664 &UpperFunc,
4665 SqliteValue::Text(SmallText::from_string("hello"))
4666 )
4667 .unwrap(),
4668 SqliteValue::Text(SmallText::from_string("HELLO"))
4669 );
4670 }
4671
4672 #[test]
4675 fn test_trim_default() {
4676 let f = TrimFunc;
4677 assert_eq!(
4678 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
4679 .unwrap(),
4680 SqliteValue::Text(SmallText::from_string("hello"))
4681 );
4682 }
4683
4684 #[test]
4685 fn test_ltrim_default() {
4686 let f = LtrimFunc;
4687 assert_eq!(
4688 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
4689 .unwrap(),
4690 SqliteValue::Text(SmallText::from_string("hello"))
4691 );
4692 }
4693
4694 #[test]
4695 fn test_ltrim_custom() {
4696 let f = LtrimFunc;
4697 assert_eq!(
4698 f.invoke(&[
4699 SqliteValue::Text(SmallText::from_string("xxhello")),
4700 SqliteValue::Text(SmallText::from_string("x")),
4701 ])
4702 .unwrap(),
4703 SqliteValue::Text(SmallText::from_string("hello"))
4704 );
4705 }
4706
4707 #[test]
4708 #[ignore = "perf-only benchmark"]
4709 fn perf_trim_text_args() {
4710 use std::hint::black_box;
4711 use std::time::Instant;
4712
4713 const INVOCATIONS: usize = 100_000;
4714 const REPEATS: usize = 5;
4715
4716 let trim = TrimFunc;
4717 let ltrim = LtrimFunc;
4718 let rtrim = RtrimFunc;
4719 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
4720 let custom_args = [
4721 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
4722 SqliteValue::Text(SmallText::from_string("x")),
4723 ];
4724
4725 let mut trim_best_ns = u128::MAX;
4726 let mut ltrim_best_ns = u128::MAX;
4727 let mut rtrim_best_ns = u128::MAX;
4728 let mut custom_best_ns = u128::MAX;
4729 let mut result_len = 0usize;
4730
4731 for _ in 0..REPEATS {
4732 let started = Instant::now();
4733 for _ in 0..INVOCATIONS {
4734 let result = black_box(
4735 trim.invoke(black_box(default_args.as_slice()))
4736 .expect("trim benchmark invocation must succeed"),
4737 );
4738 result_len = match result {
4739 SqliteValue::Text(text) => text.len(),
4740 SqliteValue::Null
4741 | SqliteValue::Integer(_)
4742 | SqliteValue::Float(_)
4743 | SqliteValue::Blob(_) => 0,
4744 };
4745 }
4746 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
4747
4748 let started = Instant::now();
4749 for _ in 0..INVOCATIONS {
4750 let result = black_box(
4751 ltrim
4752 .invoke(black_box(default_args.as_slice()))
4753 .expect("ltrim benchmark invocation must succeed"),
4754 );
4755 result_len = match result {
4756 SqliteValue::Text(text) => text.len(),
4757 SqliteValue::Null
4758 | SqliteValue::Integer(_)
4759 | SqliteValue::Float(_)
4760 | SqliteValue::Blob(_) => 0,
4761 };
4762 }
4763 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
4764
4765 let started = Instant::now();
4766 for _ in 0..INVOCATIONS {
4767 let result = black_box(
4768 rtrim
4769 .invoke(black_box(default_args.as_slice()))
4770 .expect("rtrim benchmark invocation must succeed"),
4771 );
4772 result_len = match result {
4773 SqliteValue::Text(text) => text.len(),
4774 SqliteValue::Null
4775 | SqliteValue::Integer(_)
4776 | SqliteValue::Float(_)
4777 | SqliteValue::Blob(_) => 0,
4778 };
4779 }
4780 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
4781
4782 let started = Instant::now();
4783 for _ in 0..INVOCATIONS {
4784 let result = black_box(
4785 trim.invoke(black_box(custom_args.as_slice()))
4786 .expect("custom trim benchmark invocation must succeed"),
4787 );
4788 result_len = match result {
4789 SqliteValue::Text(text) => text.len(),
4790 SqliteValue::Null
4791 | SqliteValue::Integer(_)
4792 | SqliteValue::Float(_)
4793 | SqliteValue::Blob(_) => 0,
4794 };
4795 }
4796 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
4797 }
4798
4799 println!(
4800 "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}"
4801 );
4802 }
4803
4804 #[test]
4807 fn test_nullif_equal() {
4808 assert_eq!(
4809 invoke2(
4810 &NullifFunc,
4811 SqliteValue::Integer(5),
4812 SqliteValue::Integer(5)
4813 )
4814 .unwrap(),
4815 SqliteValue::Null
4816 );
4817 }
4818
4819 #[test]
4820 fn test_nullif_different() {
4821 assert_eq!(
4822 invoke2(
4823 &NullifFunc,
4824 SqliteValue::Integer(5),
4825 SqliteValue::Integer(3)
4826 )
4827 .unwrap(),
4828 SqliteValue::Integer(5)
4829 );
4830 }
4831
4832 #[test]
4835 fn test_typeof_each() {
4836 assert_eq!(
4837 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
4838 SqliteValue::Text(SmallText::from_string("null"))
4839 );
4840 assert_eq!(
4841 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
4842 SqliteValue::Text(SmallText::from_string("integer"))
4843 );
4844 assert_eq!(
4845 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
4846 SqliteValue::Text(SmallText::from_string("real"))
4847 );
4848 assert_eq!(
4849 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
4850 SqliteValue::Text(SmallText::from_string("text"))
4851 );
4852 assert_eq!(
4853 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
4854 SqliteValue::Text(SmallText::from_string("blob"))
4855 );
4856 }
4857
4858 #[test]
4861 fn test_subtype_null_returns_zero() {
4862 assert_eq!(
4863 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
4864 SqliteValue::Integer(0)
4865 );
4866 }
4867
4868 #[test]
4871 fn test_replace_basic() {
4872 let f = ReplaceFunc;
4873 assert_eq!(
4874 f.invoke(&[
4875 SqliteValue::Text(SmallText::from_string("hello world")),
4876 SqliteValue::Text(SmallText::from_string("world")),
4877 SqliteValue::Text(SmallText::from_string("earth")),
4878 ])
4879 .unwrap(),
4880 SqliteValue::Text(SmallText::from_string("hello earth"))
4881 );
4882 }
4883
4884 #[test]
4885 fn test_replace_empty_y() {
4886 let f = ReplaceFunc;
4887 assert_eq!(
4888 f.invoke(&[
4889 SqliteValue::Text(SmallText::from_string("hello")),
4890 SqliteValue::Text(SmallText::new("")),
4891 SqliteValue::Text(SmallText::from_string("x")),
4892 ])
4893 .unwrap(),
4894 SqliteValue::Text(SmallText::from_string("hello"))
4895 );
4896 }
4897
4898 #[test]
4899 #[ignore = "perf-only benchmark"]
4900 fn perf_replace_text_args() {
4901 use std::hint::black_box;
4902 use std::time::Instant;
4903
4904 const INVOCATIONS: usize = 100_000;
4905 const REPEATS: usize = 5;
4906
4907 let f = ReplaceFunc;
4908 let args = [
4909 SqliteValue::Text(SmallText::from_string("payload payload payload")),
4910 SqliteValue::Text(SmallText::from_string("zz")),
4911 SqliteValue::Text(SmallText::from_string("replacement")),
4912 ];
4913
4914 let mut best_ns = u128::MAX;
4915 let mut result_len = 0usize;
4916 for _ in 0..REPEATS {
4917 let started = Instant::now();
4918 for _ in 0..INVOCATIONS {
4919 let result = black_box(
4920 f.invoke(black_box(args.as_slice()))
4921 .expect("replace benchmark invocation must succeed"),
4922 );
4923 result_len = match result {
4924 SqliteValue::Text(text) => text.len(),
4925 SqliteValue::Null
4926 | SqliteValue::Integer(_)
4927 | SqliteValue::Float(_)
4928 | SqliteValue::Blob(_) => 0,
4929 };
4930 }
4931 best_ns = best_ns.min(started.elapsed().as_nanos());
4932 }
4933
4934 println!(
4935 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
4936 );
4937 }
4938
4939 #[test]
4942 #[allow(clippy::float_cmp)]
4943 fn test_round_half_away() {
4944 assert_eq!(
4946 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
4947 SqliteValue::Float(3.0)
4948 );
4949 assert_eq!(
4950 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
4951 SqliteValue::Float(-3.0)
4952 );
4953 }
4954
4955 #[test]
4956 #[allow(clippy::float_cmp, clippy::approx_constant)]
4957 fn test_round_precision() {
4958 assert_eq!(
4959 RoundFunc
4960 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
4961 .unwrap(),
4962 SqliteValue::Float(3.14)
4963 );
4964 }
4965
4966 #[test]
4967 #[allow(clippy::float_cmp)]
4968 fn test_round_extreme_n_clamped() {
4969 assert_eq!(
4971 RoundFunc
4972 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
4973 .unwrap(),
4974 RoundFunc
4975 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
4976 .unwrap(),
4977 );
4978 assert_eq!(
4980 RoundFunc
4981 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
4982 .unwrap(),
4983 SqliteValue::Float(3.0)
4984 );
4985 let result = RoundFunc
4987 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
4988 .unwrap();
4989 if let SqliteValue::Float(v) = result {
4990 assert!(!v.is_nan(), "round must never return NaN");
4991 }
4992 }
4993
4994 #[test]
4995 #[allow(clippy::float_cmp)]
4996 fn test_round_large_value_no_fractional() {
4997 let big = 9_007_199_254_740_993.0_f64;
4999 assert_eq!(
5000 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
5001 SqliteValue::Float(big)
5002 );
5003 assert_eq!(
5004 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
5005 SqliteValue::Float(-big)
5006 );
5007 }
5008
5009 #[test]
5012 fn test_sign_positive() {
5013 assert_eq!(
5014 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
5015 SqliteValue::Integer(1)
5016 );
5017 }
5018
5019 #[test]
5020 fn test_sign_negative() {
5021 assert_eq!(
5022 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
5023 SqliteValue::Integer(-1)
5024 );
5025 }
5026
5027 #[test]
5028 fn test_sign_zero() {
5029 assert_eq!(
5030 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
5031 SqliteValue::Integer(0)
5032 );
5033 }
5034
5035 #[test]
5036 fn test_sign_null() {
5037 assert_eq!(
5038 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
5039 SqliteValue::Null
5040 );
5041 }
5042
5043 #[test]
5044 fn test_sign_non_numeric() {
5045 assert_eq!(
5047 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
5048 SqliteValue::Null
5049 );
5050 }
5051
5052 #[test]
5053 fn test_sign_whitespace_padded_text() {
5054 assert_eq!(
5057 invoke1(
5058 &SignFunc,
5059 SqliteValue::Text(SmallText::from_string(" 5 "))
5060 )
5061 .unwrap(),
5062 SqliteValue::Integer(1)
5063 );
5064 assert_eq!(
5065 invoke1(
5066 &SignFunc,
5067 SqliteValue::Text(SmallText::from_string(" -3.14 "))
5068 )
5069 .unwrap(),
5070 SqliteValue::Integer(-1)
5071 );
5072 }
5073
5074 #[test]
5075 fn test_sign_unicode_space_and_blob_return_null() {
5076 assert_eq!(
5077 invoke1(
5078 &SignFunc,
5079 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
5080 )
5081 .unwrap(),
5082 SqliteValue::Null
5083 );
5084 assert_eq!(
5085 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
5086 SqliteValue::Null
5087 );
5088 }
5089
5090 #[test]
5091 fn test_sign_nan_inf_text_returns_null() {
5092 for s in &[
5095 "NaN",
5096 "nan",
5097 "inf",
5098 "-inf",
5099 "Infinity",
5100 "-Infinity",
5101 "INF",
5102 "+nan",
5103 "+inf",
5104 ] {
5105 assert_eq!(
5106 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
5107 SqliteValue::Null,
5108 "sign('{s}') should be NULL"
5109 );
5110 }
5111 }
5112
5113 #[test]
5114 fn test_sign_numeric_overflow_to_infinity() {
5115 assert_eq!(
5118 invoke1(
5119 &SignFunc,
5120 SqliteValue::Text(SmallText::from_string("1e999"))
5121 )
5122 .unwrap(),
5123 SqliteValue::Integer(1)
5124 );
5125 assert_eq!(
5126 invoke1(
5127 &SignFunc,
5128 SqliteValue::Text(SmallText::from_string("-1e999"))
5129 )
5130 .unwrap(),
5131 SqliteValue::Integer(-1)
5132 );
5133 assert_eq!(
5135 invoke1(
5136 &SignFunc,
5137 SqliteValue::Text(SmallText::from_string("1e-999"))
5138 )
5139 .unwrap(),
5140 SqliteValue::Integer(0)
5141 );
5142 }
5143
5144 #[test]
5145 fn test_sign_float_nan_returns_null() {
5146 assert_eq!(
5148 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
5149 SqliteValue::Null
5150 );
5151 }
5152
5153 #[test]
5156 fn test_scalar_max_null() {
5157 let f = ScalarMaxFunc;
5158 let result = f
5159 .invoke(&[
5160 SqliteValue::Integer(1),
5161 SqliteValue::Null,
5162 SqliteValue::Integer(3),
5163 ])
5164 .unwrap();
5165 assert_eq!(result, SqliteValue::Null);
5166 }
5167
5168 #[test]
5169 fn test_scalar_max_values() {
5170 let f = ScalarMaxFunc;
5171 let result = f
5172 .invoke(&[
5173 SqliteValue::Integer(3),
5174 SqliteValue::Integer(1),
5175 SqliteValue::Integer(2),
5176 ])
5177 .unwrap();
5178 assert_eq!(result, SqliteValue::Integer(3));
5179 }
5180
5181 #[test]
5182 fn test_scalar_min_null() {
5183 let f = ScalarMinFunc;
5184 let result = f
5185 .invoke(&[
5186 SqliteValue::Integer(1),
5187 SqliteValue::Null,
5188 SqliteValue::Integer(3),
5189 ])
5190 .unwrap();
5191 assert_eq!(result, SqliteValue::Null);
5192 }
5193
5194 #[test]
5195 fn test_scalar_min_selects_later_equal_value_while_max_keeps_first() {
5196 let min = ScalarMinFunc;
5197 let max = ScalarMaxFunc;
5198 let numeric = [SqliteValue::Integer(1), SqliteValue::Float(1.0)];
5199 assert!(matches!(
5200 min.invoke(&numeric).unwrap(),
5201 SqliteValue::Float(value) if value == 1.0
5202 ));
5203 assert_eq!(max.invoke(&numeric).unwrap(), SqliteValue::Integer(1));
5204
5205 let text = [
5206 SqliteValue::Text(SmallText::new("a")),
5207 SqliteValue::Text(SmallText::new("A")),
5208 ];
5209 let nocase = crate::collation::NoCaseCollation;
5210 assert_eq!(
5211 min.invoke_with_collation(&text, Some(&nocase)).unwrap(),
5212 SqliteValue::Text(SmallText::new("A"))
5213 );
5214 assert_eq!(
5215 max.invoke_with_collation(&text, Some(&nocase)).unwrap(),
5216 SqliteValue::Text(SmallText::new("a"))
5217 );
5218 }
5219
5220 #[test]
5223 fn test_quote_text() {
5224 assert_eq!(
5225 invoke1(
5226 &QuoteFunc,
5227 SqliteValue::Text(SmallText::from_string("it's"))
5228 )
5229 .unwrap(),
5230 SqliteValue::Text(SmallText::from_string("'it''s'"))
5231 );
5232 }
5233
5234 #[test]
5235 fn test_quote_null() {
5236 assert_eq!(
5237 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
5238 SqliteValue::Text(SmallText::from_string("NULL"))
5239 );
5240 }
5241
5242 #[test]
5243 fn test_quote_blob() {
5244 assert_eq!(
5245 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
5246 SqliteValue::Text(SmallText::from_string("X'AB'"))
5247 );
5248 }
5249
5250 #[test]
5251 fn test_quote_text_truncates_at_first_nul() {
5252 assert_eq!(
5253 invoke1(
5254 &QuoteFunc,
5255 SqliteValue::Text(SmallText::from_string("A\0B"))
5256 )
5257 .unwrap(),
5258 SqliteValue::Text(SmallText::from_string("'A'"))
5259 );
5260 }
5261
5262 #[test]
5263 fn test_unistr_quote_plain_text_matches_quote() {
5264 assert_eq!(
5265 invoke1(
5266 &UnistrQuoteFunc,
5267 SqliteValue::Text(SmallText::from_string("it's"))
5268 )
5269 .unwrap(),
5270 SqliteValue::Text(SmallText::from_string("'it''s'"))
5271 );
5272 }
5273
5274 #[test]
5275 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
5276 assert_eq!(
5277 invoke1(
5278 &UnistrQuoteFunc,
5279 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
5280 )
5281 .unwrap(),
5282 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
5283 );
5284 }
5285
5286 #[test]
5287 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
5288 assert_eq!(
5289 invoke1(
5290 &UnistrQuoteFunc,
5291 SqliteValue::Text(SmallText::from_string("A\0\nB"))
5292 )
5293 .unwrap(),
5294 SqliteValue::Text(SmallText::from_string("'A'"))
5295 );
5296 }
5297
5298 #[test]
5299 fn test_unistr_decodes_backslash_and_unicode_escapes() {
5300 assert_eq!(
5301 invoke1(
5302 &UnistrFunc,
5303 SqliteValue::Text(SmallText::from_string(
5304 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
5305 ))
5306 )
5307 .unwrap(),
5308 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
5309 );
5310 }
5311
5312 #[test]
5313 fn test_unistr_invalid_escape_returns_error() {
5314 for input in [
5315 "\\u12xz",
5316 "\\12xz",
5317 "\\+00xz",
5318 "\\",
5319 "\\x",
5320 "\\U00110000",
5321 "\\D800",
5322 ] {
5323 let err = invoke1(
5324 &UnistrFunc,
5325 SqliteValue::Text(SmallText::from_string(input)),
5326 )
5327 .unwrap_err();
5328 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
5329 }
5330 }
5331
5332 #[test]
5333 #[ignore = "perf-only benchmark"]
5334 fn perf_unistr_text_args() {
5335 use std::hint::black_box;
5336 use std::time::Instant;
5337
5338 const INVOCATIONS: usize = 500_000;
5339 const REPEATS: usize = 7;
5340
5341 let f = UnistrFunc;
5342 let plain_args = [SqliteValue::Text(SmallText::from_string(
5343 "plain unicode payload",
5344 ))];
5345 let escaped_args = [SqliteValue::Text(SmallText::from_string(
5346 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
5347 ))];
5348
5349 let mut plain_best_ns = u128::MAX;
5350 let mut escaped_best_ns = u128::MAX;
5351 let mut checksum = 0usize;
5352 for _ in 0..REPEATS {
5353 let started = Instant::now();
5354 for _ in 0..INVOCATIONS {
5355 let result = black_box(
5356 f.invoke(black_box(plain_args.as_slice()))
5357 .expect("unistr plain benchmark invocation must succeed"),
5358 );
5359 if let SqliteValue::Text(text) = result {
5360 checksum = checksum.wrapping_add(text.len());
5361 }
5362 }
5363 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
5364
5365 let started = Instant::now();
5366 for _ in 0..INVOCATIONS {
5367 let result = black_box(
5368 f.invoke(black_box(escaped_args.as_slice()))
5369 .expect("unistr escaped benchmark invocation must succeed"),
5370 );
5371 if let SqliteValue::Text(text) = result {
5372 checksum = checksum.wrapping_add(text.len());
5373 }
5374 }
5375 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
5376 }
5377
5378 println!(
5379 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
5380 );
5381 }
5382
5383 #[test]
5386 fn test_random_range() {
5387 let f = RandomFunc;
5388 let result = f.invoke(&[]).unwrap();
5389 assert!(matches!(result, SqliteValue::Integer(_)));
5390 }
5391
5392 #[test]
5395 fn test_randomblob_length() {
5396 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
5397 match result {
5398 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
5399 other => unreachable!("expected blob, got {other:?}"),
5400 }
5401 }
5402
5403 #[test]
5404 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
5405 for arg in [
5406 SqliteValue::Null,
5407 SqliteValue::Integer(0),
5408 SqliteValue::Integer(-5),
5409 ] {
5410 let result = invoke1(&RandomblobFunc, arg).unwrap();
5411 match result {
5412 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
5413 other => unreachable!("expected one-byte blob, got {other:?}"),
5414 }
5415 }
5416 }
5417
5418 #[test]
5421 fn test_zeroblob_length() {
5422 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
5423 match result {
5424 SqliteValue::Blob(b) => {
5425 assert_eq!(b.len(), 100);
5426 assert!(b.iter().all(|&x| x == 0));
5427 }
5428 other => unreachable!("expected blob, got {other:?}"),
5429 }
5430 }
5431
5432 #[test]
5435 fn test_unhex_valid() {
5436 let result = invoke1(
5437 &UnhexFunc,
5438 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
5439 )
5440 .unwrap();
5441 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
5442 }
5443
5444 #[test]
5445 fn test_unhex_invalid() {
5446 let result = invoke1(
5447 &UnhexFunc,
5448 SqliteValue::Text(SmallText::from_string("ZZZZ")),
5449 )
5450 .unwrap();
5451 assert_eq!(result, SqliteValue::Null);
5452 }
5453
5454 #[test]
5455 fn test_unhex_ignore_chars() {
5456 let f = UnhexFunc;
5457 let result = f
5458 .invoke(&[
5459 SqliteValue::Text(SmallText::from_string("48-65-6C")),
5460 SqliteValue::Text(SmallText::from_string("-")),
5461 ])
5462 .unwrap();
5463 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
5464 }
5465
5466 #[test]
5467 fn test_unhex_ignore_chars_only_between_byte_pairs() {
5468 let f = UnhexFunc;
5469 let result = f
5470 .invoke(&[
5471 SqliteValue::Text(SmallText::from_string("AB CD")),
5472 SqliteValue::Text(SmallText::from_string(" ")),
5473 ])
5474 .unwrap();
5475 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
5476
5477 let result = f
5478 .invoke(&[
5479 SqliteValue::Text(SmallText::from_string("A BCD")),
5480 SqliteValue::Text(SmallText::from_string(" ")),
5481 ])
5482 .unwrap();
5483 assert_eq!(result, SqliteValue::Null);
5484 }
5485
5486 #[test]
5487 fn test_unhex_null_ignore_argument_returns_null() {
5488 let f = UnhexFunc;
5489 let result = f
5490 .invoke(&[
5491 SqliteValue::Text(SmallText::from_string("41")),
5492 SqliteValue::Null,
5493 ])
5494 .unwrap();
5495 assert_eq!(result, SqliteValue::Null);
5496 }
5497
5498 #[test]
5499 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
5500 let f = UnhexFunc;
5501 let result = f
5502 .invoke(&[
5503 SqliteValue::Text(SmallText::from_string("41")),
5504 SqliteValue::Text(SmallText::from_string("4")),
5505 ])
5506 .unwrap();
5507 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
5508 }
5509
5510 #[test]
5511 #[ignore = "perf-only benchmark"]
5512 fn perf_unhex_text_args() {
5513 use std::hint::black_box;
5514 use std::time::Instant;
5515
5516 const INVOCATIONS: usize = 300_000;
5517 const REPEATS: usize = 7;
5518
5519 let f = UnhexFunc;
5520 let plain_args = [SqliteValue::Text(SmallText::from_string(
5521 "48656C6C6F776F726C64",
5522 ))];
5523 let ignore_args = [
5524 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
5525 SqliteValue::Text(SmallText::from_string("-")),
5526 ];
5527 let mut plain_best_ns = u128::MAX;
5528 let mut ignore_best_ns = u128::MAX;
5529 let mut checksum = 0usize;
5530
5531 for _ in 0..REPEATS {
5532 let started = Instant::now();
5533 for _ in 0..INVOCATIONS {
5534 let result = black_box(
5535 f.invoke(black_box(plain_args.as_slice()))
5536 .expect("unhex benchmark invocation must succeed"),
5537 );
5538 if let SqliteValue::Blob(blob) = result {
5539 checksum = checksum.wrapping_add(blob.len());
5540 }
5541 }
5542 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
5543
5544 let started = Instant::now();
5545 for _ in 0..INVOCATIONS {
5546 let result = black_box(
5547 f.invoke(black_box(ignore_args.as_slice()))
5548 .expect("unhex ignore benchmark invocation must succeed"),
5549 );
5550 if let SqliteValue::Blob(blob) = result {
5551 checksum = checksum.wrapping_add(blob.len());
5552 }
5553 }
5554 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
5555 }
5556
5557 println!(
5558 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
5559 );
5560 }
5561
5562 #[test]
5565 fn test_unicode_first_char() {
5566 assert_eq!(
5567 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
5568 SqliteValue::Integer(65)
5569 );
5570 }
5571
5572 #[test]
5573 fn test_unicode_text_stops_at_nul() {
5574 assert_eq!(
5575 invoke1(
5576 &UnicodeFunc,
5577 SqliteValue::Text(SmallText::from_string("\0A"))
5578 )
5579 .unwrap(),
5580 SqliteValue::Null
5581 );
5582 assert_eq!(
5583 invoke1(
5584 &UnicodeFunc,
5585 SqliteValue::Text(SmallText::from_string("A\0"))
5586 )
5587 .unwrap(),
5588 SqliteValue::Integer(65)
5589 );
5590 }
5591
5592 #[test]
5593 fn test_unicode_blob_uses_sqlite_utf8_reader() {
5594 let cases: &[(&[u8], SqliteValue)] = &[
5595 (&[0x00, 0x41], SqliteValue::Null),
5596 (&[0x80], SqliteValue::Integer(128)),
5597 (&[0xC2, 0x80], SqliteValue::Integer(128)),
5598 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
5599 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
5600 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
5601 ];
5602
5603 for (bytes, expected) in cases {
5604 assert_eq!(
5605 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
5606 expected.clone()
5607 );
5608 }
5609 }
5610
5611 #[test]
5612 #[ignore = "perf-only benchmark"]
5613 fn perf_unicode_text_arg() {
5614 use std::hint::black_box;
5615 use std::time::Instant;
5616
5617 const INVOCATIONS: usize = 1_000_000;
5618 const REPEATS: usize = 7;
5619
5620 let f = UnicodeFunc;
5621 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
5622 let mut text_best_ns = u128::MAX;
5623 let mut checksum = 0i64;
5624
5625 for _ in 0..REPEATS {
5626 let started = Instant::now();
5627 for _ in 0..INVOCATIONS {
5628 let result = black_box(
5629 f.invoke(black_box(args.as_slice()))
5630 .expect("unicode benchmark invocation must succeed"),
5631 );
5632 if let SqliteValue::Integer(codepoint) = result {
5633 checksum = checksum.wrapping_add(codepoint);
5634 }
5635 }
5636 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
5637 }
5638
5639 println!(
5640 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
5641 );
5642 }
5643
5644 #[test]
5647 fn test_soundex_basic() {
5648 assert_eq!(
5649 invoke1(
5650 &SoundexFunc,
5651 SqliteValue::Text(SmallText::from_string("Robert"))
5652 )
5653 .unwrap(),
5654 SqliteValue::Text(SmallText::from_string("R163"))
5655 );
5656 }
5657
5658 #[test]
5659 #[ignore = "perf-only benchmark"]
5660 fn perf_soundex_text_arg() {
5661 use std::hint::black_box;
5662 use std::time::Instant;
5663
5664 const INVOCATIONS: usize = 1_000_000;
5665 const REPEATS: usize = 7;
5666
5667 let f = SoundexFunc;
5668 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
5669 let mut text_best_ns = u128::MAX;
5670 let mut checksum = 0usize;
5671
5672 for _ in 0..REPEATS {
5673 let started = Instant::now();
5674 for _ in 0..INVOCATIONS {
5675 let result = black_box(
5676 f.invoke(black_box(args.as_slice()))
5677 .expect("soundex benchmark invocation must succeed"),
5678 );
5679 if let SqliteValue::Text(text) = result {
5680 checksum = checksum.wrapping_add(text.len());
5681 }
5682 }
5683 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
5684 }
5685
5686 println!(
5687 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
5688 );
5689 }
5690
5691 #[test]
5694 fn test_substr_basic() {
5695 let f = SubstrFunc;
5696 assert_eq!(
5697 f.invoke(&[
5698 SqliteValue::Text(SmallText::from_string("hello")),
5699 SqliteValue::Integer(2),
5700 SqliteValue::Integer(3),
5701 ])
5702 .unwrap(),
5703 SqliteValue::Text(SmallText::from_string("ell"))
5704 );
5705 }
5706
5707 #[test]
5708 fn test_substr_truncates_at_embedded_nul() {
5709 let f = SubstrFunc;
5713 let s = SqliteValue::Text(SmallText::from_string("a\u{0}bc"));
5714 assert_eq!(
5715 f.invoke(&[s.clone(), SqliteValue::Integer(1), SqliteValue::Integer(4)])
5716 .unwrap(),
5717 SqliteValue::Text(SmallText::from_string("a"))
5718 );
5719 assert_eq!(
5720 f.invoke(&[s, SqliteValue::Integer(3)]).unwrap(),
5721 SqliteValue::Text(SmallText::from_string(""))
5722 );
5723 }
5724
5725 #[test]
5726 fn test_substr_start_zero_quirk() {
5727 let f = SubstrFunc;
5729 let result = f
5730 .invoke(&[
5731 SqliteValue::Text(SmallText::from_string("hello")),
5732 SqliteValue::Integer(0),
5733 SqliteValue::Integer(3),
5734 ])
5735 .unwrap();
5736 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
5737 }
5738
5739 #[test]
5740 fn test_substr_negative_start() {
5741 let f = SubstrFunc;
5743 let result = f
5744 .invoke(&[
5745 SqliteValue::Text(SmallText::from_string("hello")),
5746 SqliteValue::Integer(-2),
5747 ])
5748 .unwrap();
5749 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
5750 }
5751
5752 #[test]
5753 fn test_substr_negative_length() {
5754 let f = SubstrFunc;
5755 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5756 let i = SqliteValue::Integer;
5757 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
5759 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
5761 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
5763 }
5764
5765 #[test]
5766 fn test_substr_negative_start_negative_length() {
5767 let f = SubstrFunc;
5768 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5769 let i = SqliteValue::Integer;
5770 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
5772 }
5773
5774 #[test]
5775 fn test_substr_edge_cases() {
5776 let f = SubstrFunc;
5777 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5778 let i = SqliteValue::Integer;
5779 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
5781 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
5783 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
5785 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
5787 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
5789 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
5791 }
5792
5793 #[test]
5794 fn test_substr_blob_negative_length() {
5795 let f = SubstrFunc;
5796 let i = SqliteValue::Integer;
5797 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
5798 assert_eq!(
5800 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
5801 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
5802 );
5803 }
5804
5805 #[test]
5808 fn test_like_case_insensitive() {
5809 assert_eq!(
5810 invoke2(
5811 &LikeFunc,
5812 SqliteValue::Text(SmallText::from_string("ABC")),
5813 SqliteValue::Text(SmallText::from_string("abc"))
5814 )
5815 .unwrap(),
5816 SqliteValue::Integer(1)
5817 );
5818 }
5819
5820 #[test]
5821 fn test_like_escape() {
5822 let f = LikeFunc;
5823 let result = f
5824 .invoke(&[
5825 SqliteValue::Text(SmallText::from_string("10\\%")),
5826 SqliteValue::Text(SmallText::from_string("10%")),
5827 SqliteValue::Text(SmallText::from_string("\\")),
5828 ])
5829 .unwrap();
5830 assert_eq!(result, SqliteValue::Integer(1));
5831 }
5832
5833 #[test]
5834 fn test_like_escape_rejects_empty_string() {
5835 let err = LikeFunc
5836 .invoke(&[
5837 SqliteValue::Text(SmallText::from_string("a")),
5838 SqliteValue::Text(SmallText::from_string("a")),
5839 SqliteValue::Text(SmallText::new("")),
5840 ])
5841 .unwrap_err();
5842 assert!(
5843 err.to_string()
5844 .contains("ESCAPE expression must be a single character")
5845 );
5846 }
5847
5848 #[test]
5849 fn test_like_escape_rejects_multi_character_string() {
5850 let err = LikeFunc
5851 .invoke(&[
5852 SqliteValue::Text(SmallText::from_string("a")),
5853 SqliteValue::Text(SmallText::from_string("a")),
5854 SqliteValue::Text(SmallText::from_string("xx")),
5855 ])
5856 .unwrap_err();
5857 assert!(
5858 err.to_string()
5859 .contains("ESCAPE expression must be a single character")
5860 );
5861 }
5862
5863 #[test]
5864 fn test_like_percent() {
5865 assert_eq!(
5866 invoke2(
5867 &LikeFunc,
5868 SqliteValue::Text(SmallText::from_string("%ell%")),
5869 SqliteValue::Text(SmallText::from_string("Hello"))
5870 )
5871 .unwrap(),
5872 SqliteValue::Integer(1)
5873 );
5874 }
5875
5876 #[test]
5879 fn test_glob_star() {
5880 assert_eq!(
5881 invoke2(
5882 &GlobFunc,
5883 SqliteValue::Text(SmallText::from_string("*.txt")),
5884 SqliteValue::Text(SmallText::from_string("file.txt"))
5885 )
5886 .unwrap(),
5887 SqliteValue::Integer(1)
5888 );
5889 }
5890
5891 #[test]
5892 fn test_glob_case_sensitive() {
5893 assert_eq!(
5894 invoke2(
5895 &GlobFunc,
5896 SqliteValue::Text(SmallText::from_string("ABC")),
5897 SqliteValue::Text(SmallText::from_string("abc"))
5898 )
5899 .unwrap(),
5900 SqliteValue::Integer(0)
5901 );
5902 }
5903
5904 #[test]
5905 fn test_glob_unterminated_character_class_does_not_match() {
5906 assert_eq!(
5909 invoke2(
5910 &GlobFunc,
5911 SqliteValue::Text(SmallText::from_string("[a")),
5912 SqliteValue::Text(SmallText::from_string("a"))
5913 )
5914 .unwrap(),
5915 SqliteValue::Integer(0)
5916 );
5917 assert_eq!(
5919 invoke2(
5920 &GlobFunc,
5921 SqliteValue::Text(SmallText::from_string("[a]")),
5922 SqliteValue::Text(SmallText::from_string("a"))
5923 )
5924 .unwrap(),
5925 SqliteValue::Integer(1)
5926 );
5927 }
5928
5929 #[test]
5930 fn test_glob_trailing_dash_in_character_class_is_literal() {
5931 let glob = |pattern: &str, text: &str| {
5939 invoke2(
5940 &GlobFunc,
5941 SqliteValue::Text(SmallText::from_string(pattern)),
5942 SqliteValue::Text(SmallText::from_string(text)),
5943 )
5944 .unwrap()
5945 };
5946 assert_eq!(
5948 glob("*[^A-Za-z0-9._:-]*", "peer_abc/123"),
5949 SqliteValue::Integer(1)
5950 );
5951 assert_eq!(
5953 glob("*[^A-Za-z0-9._:-]*", "peer_a.b:c-"),
5954 SqliteValue::Integer(0)
5955 );
5956 assert_eq!(glob("[a-c-]", "-"), SqliteValue::Integer(1));
5958 assert_eq!(glob("[a-c-]", "b"), SqliteValue::Integer(1));
5959 assert_eq!(glob("[a-c-]", "d"), SqliteValue::Integer(0));
5960 assert_eq!(glob("[-a]", "-"), SqliteValue::Integer(1));
5962 assert_eq!(glob("[-a]", "b"), SqliteValue::Integer(0));
5963 }
5964
5965 #[test]
5966 fn test_iif_two_argument_form() {
5967 let f = IifFunc;
5969 assert_eq!(
5970 f.invoke(&[
5971 SqliteValue::Integer(1),
5972 SqliteValue::Text(SmallText::from_string("y")),
5973 ])
5974 .unwrap(),
5975 SqliteValue::Text(SmallText::from_string("y"))
5976 );
5977 assert_eq!(
5978 f.invoke(&[
5979 SqliteValue::Integer(0),
5980 SqliteValue::Text(SmallText::from_string("y")),
5981 ])
5982 .unwrap(),
5983 SqliteValue::Null
5984 );
5985 }
5986
5987 #[test]
5988 fn test_format_g_negative_zero() {
5989 let f = FormatFunc;
5991 assert_eq!(
5992 f.invoke(&[
5993 SqliteValue::Text(SmallText::from_string("%g")),
5994 SqliteValue::Float(-0.0),
5995 ])
5996 .unwrap(),
5997 SqliteValue::Text(SmallText::from_string("0"))
5998 );
5999 }
6000
6001 #[test]
6002 fn test_format_signed_zero_all_specs() {
6003 let f = FormatFunc;
6007 let fmt = |spec: &str, v: f64| -> String {
6008 match f
6009 .invoke(&[
6010 SqliteValue::Text(SmallText::from_string(spec)),
6011 SqliteValue::Float(v),
6012 ])
6013 .unwrap()
6014 {
6015 SqliteValue::Text(s) => s.as_str().to_owned(),
6016 other => panic!("expected text, got {other:?}"),
6017 }
6018 };
6019 assert_eq!(fmt("%f", -0.0), "0.000000");
6021 assert_eq!(fmt("%e", -0.0), "0.000000e+00");
6022 assert_eq!(fmt("%E", -0.0), "0.000000E+00");
6023 assert_eq!(fmt("%G", -0.0), "0");
6024 assert_eq!(fmt("%+g", -0.0), "+0");
6026 assert_eq!(fmt("% g", -0.0), " 0");
6027 assert_eq!(fmt("%+f", -0.0), "+0.000000");
6028 assert_eq!(fmt("%8.2f", -0.0), " 0.00");
6030 assert_eq!(fmt("%!g", -0.0), "0.0");
6032 assert_eq!(fmt("%g", -1e-320 * 1e-10), "0");
6034 assert_eq!(fmt("%g", -1.5), "-1.5");
6036 assert_eq!(fmt("%f", -2.25), "-2.250000");
6037 assert_eq!(fmt("%+g", -1.5), "-1.5");
6038 }
6039
6040 #[test]
6041 fn test_format_g_integer_trailing_zeros() {
6042 let f = FormatFunc;
6047 let fmt = |spec: &str, v: f64| -> String {
6048 match f
6049 .invoke(&[
6050 SqliteValue::Text(SmallText::from_string(spec)),
6051 SqliteValue::Float(v),
6052 ])
6053 .unwrap()
6054 {
6055 SqliteValue::Text(s) => s.as_str().to_owned(),
6056 other => panic!("expected text, got {other:?}"),
6057 }
6058 };
6059 assert_eq!(fmt("%g", 100000.0), "100000");
6061 assert_eq!(fmt("%g", 120000.0), "120000");
6062 assert_eq!(fmt("%g", 250000.0), "250000");
6063 assert_eq!(fmt("%g", 100.0), "100");
6064 assert_eq!(fmt("%g", 999999.0), "999999");
6065 assert_eq!(fmt("%G", 100000.0), "100000");
6066 assert_eq!(fmt("%g", 0.5), "0.5");
6068 assert_eq!(fmt("%g", 1.5), "1.5");
6069 assert_eq!(fmt("%g", 1000000.0), "1e+06");
6071 assert_eq!(fmt("%g", 1234560.0), "1.23456e+06");
6072 assert_eq!(fmt("%G", 1000000.0), "1E+06");
6073 }
6074
6075 #[test]
6076 fn test_format_c_field_width_bd_ul4c0() {
6077 let f = FormatFunc;
6083 let fmt = |spec: &str, v: SqliteValue| -> String {
6084 match f
6085 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
6086 .unwrap()
6087 {
6088 SqliteValue::Text(s) => s.as_str().to_owned(),
6089 other => panic!("expected text, got {other:?}"),
6090 }
6091 };
6092 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6093 assert_eq!(fmt(">%3c<", SqliteValue::Integer(65)), "> 6<");
6095 assert_eq!(fmt(">%-3c<", SqliteValue::Integer(65)), ">6 <");
6096 assert_eq!(fmt(">%5c<", txt("abc")), "> a<");
6098 assert_eq!(fmt(">%-5c<", txt("abc")), ">a <");
6099 assert_eq!(fmt(">%03c<", SqliteValue::Integer(65)), "> 6<");
6101 assert_eq!(fmt(">%3c<", txt("é")), "> é<");
6103 assert_eq!(fmt(">%c<", SqliteValue::Integer(65)), ">6<");
6105 assert_eq!(fmt(">%c<", txt("abc")), ">a<");
6106 }
6107
6108 #[test]
6109 fn test_format_quote_specifiers_field_width_bd_8959m() {
6110 let f = FormatFunc;
6115 let fmt = |spec: &str, v: SqliteValue| -> String {
6116 match f
6117 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
6118 .unwrap()
6119 {
6120 SqliteValue::Text(s) => s.as_str().to_owned(),
6121 other => panic!("expected text, got {other:?}"),
6122 }
6123 };
6124 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6125 assert_eq!(fmt(">%6q<", txt("ab")), "> ab<");
6127 assert_eq!(fmt(">%-6q<", txt("ab")), ">ab <");
6128 assert_eq!(fmt(">%8q<", SqliteValue::Null), "> (NULL)<");
6129 assert_eq!(fmt(">%8q<", txt("a'b")), "> a''b<");
6130 assert_eq!(fmt(">%3q<", txt("abcde")), ">abcde<"); assert_eq!(fmt(">%6Q<", txt("ab")), "> 'ab'<");
6133 assert_eq!(fmt(">%-6Q<", txt("ab")), ">'ab' <");
6134 assert_eq!(fmt(">%6Q<", SqliteValue::Null), "> NULL<");
6135 assert_eq!(fmt(">%6w<", txt("ab")), "> ab<");
6137 assert_eq!(fmt(">%-6w<", txt("ab")), ">ab <");
6138 assert_eq!(fmt(">%4q<", txt("é")), "> é<");
6140 assert_eq!(fmt(">%q<", txt("ab")), ">ab<");
6142 assert_eq!(fmt(">%Q<", txt("ab")), ">'ab'<");
6143 }
6144
6145 #[test]
6146 fn test_format_round_half_away_from_zero_bd_o1tu1() {
6147 let f = FormatFunc;
6155 let fmt = |spec: &str, v: f64| -> String {
6156 match f
6157 .invoke(&[
6158 SqliteValue::Text(SmallText::from_string(spec)),
6159 SqliteValue::Float(v),
6160 ])
6161 .unwrap()
6162 {
6163 SqliteValue::Text(s) => s.as_str().to_owned(),
6164 other => panic!("expected text, got {other:?}"),
6165 }
6166 };
6167 let cases: &[(&str, f64, &str)] = &[
6168 ("%.0f", 2.5, "3"),
6170 ("%.0f", 0.5, "1"),
6171 ("%.0f", -2.5, "-3"),
6172 ("%.0f", 3.5, "4"),
6173 ("%.0f", -0.5, "-1"),
6174 ("%.0f", -3.5, "-4"),
6175 ("%.0f", 1.5, "2"),
6176 ("%.2f", 0.125, "0.13"),
6177 ("%.2f", 0.375, "0.38"),
6178 ("%.2f", 0.625, "0.63"),
6179 ("%.2f", 2.125, "2.13"),
6180 ("%.2f", -0.125, "-0.13"),
6181 ("%.1f", 0.25, "0.3"),
6182 ("%.1f", 0.75, "0.8"),
6183 ("%.1f", 2.25, "2.3"),
6184 ("%.1f", -0.25, "-0.3"),
6185 ("%.1f", 0.05, "0.1"),
6186 ("%.0f", 12.5, "13"),
6187 ("%.2f", 12.5, "12.50"),
6188 ("%.2f", 0.135, "0.14"),
6190 ("%.2f", 0.35, "0.35"),
6191 ("%.2f", 0.15, "0.15"),
6192 ("%.2f", 0.85, "0.85"),
6193 ("%.2f", 0.95, "0.95"),
6194 ("%.2f", 1.005, "1.00"),
6195 ("%.2f", 2.675, "2.67"),
6196 ("%.2f", 0.005, "0.01"),
6197 ("%.2f", 0.015, "0.01"),
6198 ("%.2f", 0.025, "0.03"),
6199 ("%.1f", 0.35, "0.3"),
6200 ("%.1f", 0.15, "0.1"),
6201 ("%.1f", 0.135, "0.1"),
6202 ("%.0f", 2.675, "3"),
6203 ("%.0f", 0.49999, "0"),
6204 ("%+.0f", 2.5, "+3"),
6206 ("%8.0f", 2.5, " 3"),
6207 ("%.0e", 2.5, "3e+00"),
6209 ("%.0e", 9.5, "1e+01"),
6210 ("%.0e", 1.5, "2e+00"),
6211 ("%.0e", 250.0, "3e+02"),
6212 ("%.0e", 0.25, "3e-01"),
6213 ("%.1e", 1.25, "1.3e+00"),
6214 ("%.1e", 12.5, "1.3e+01"),
6215 ("%.0E", 2.5, "3E+00"),
6216 ("%.1e", 0.5, "5.0e-01"),
6218 ("%.1e", 9.95, "9.9e+00"),
6219 ("%.1e", 1.005, "1.0e+00"),
6220 ("%.1e", 2.675, "2.7e+00"),
6221 ("%.1e", 1.35, "1.4e+00"),
6222 ("%.0e", 0.5, "5e-01"),
6223 ("%.0e", 9.95, "1e+01"),
6224 ("%.0e", 1.005, "1e+00"),
6225 ("%.1g", 0.25, "0.3"),
6227 ("%.1g", 2.5, "3"),
6228 ("%.1g", 25.0, "3e+01"),
6229 ("%.2g", 0.125, "0.13"),
6230 ("%.2g", 1.25, "1.3"),
6231 ("%.2g", 12.5, "13"),
6232 ("%.1g", 0.35, "0.3"),
6234 ("%.1g", 0.15, "0.1"),
6235 ("%.1g", 0.45, "0.5"),
6236 ("%.1g", 0.125, "0.1"),
6237 ("%.2g", 0.135, "0.14"),
6238 ("%.2g", 1.005, "1"),
6239 ("%.2g", 2.675, "2.7"),
6240 ];
6241 for (spec, v, want) in cases {
6242 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6243 }
6244 }
6245
6246 #[test]
6247 fn test_round_half_away_near_ties_match_oracle_bd_o1tu1() {
6248 #[allow(clippy::float_cmp)]
6254 fn round1(v: f64) -> f64 {
6255 match RoundFunc
6256 .invoke(&[SqliteValue::Float(v), SqliteValue::Integer(1)])
6257 .unwrap()
6258 {
6259 SqliteValue::Float(x) => x,
6260 other => panic!("expected float, got {other:?}"),
6261 }
6262 }
6263 let cases: &[(f64, f64)] = &[
6264 (0.15, 0.1),
6265 (0.35, 0.3),
6266 (0.85, 0.8),
6267 (0.95, 0.9),
6268 (0.135, 0.1),
6269 (1.005, 1.0),
6270 (2.675, 2.7),
6271 (0.25, 0.3),
6275 (0.45, 0.5),
6276 (2.5, 2.5),
6277 ];
6278 for (v, want) in cases {
6279 #[allow(clippy::float_cmp)]
6280 let got = round1(*v);
6281 assert_eq!(got, *want, "round({v}, 1)");
6282 }
6283 }
6284
6285 #[test]
6286 fn test_format_altform2_flag() {
6287 let f = FormatFunc;
6291 assert_eq!(
6292 f.invoke(&[
6293 SqliteValue::Text(SmallText::from_string("%!5s")),
6294 SqliteValue::Text(SmallText::from_string("ab")),
6295 ])
6296 .unwrap(),
6297 SqliteValue::Text(SmallText::from_string(" ab"))
6298 );
6299 assert_eq!(
6300 f.invoke(&[
6301 SqliteValue::Text(SmallText::from_string("%!d")),
6302 SqliteValue::Integer(3),
6303 ])
6304 .unwrap(),
6305 SqliteValue::Text(SmallText::from_string("3"))
6306 );
6307 assert_eq!(
6308 f.invoke(&[
6309 SqliteValue::Text(SmallText::from_string("%!f")),
6310 SqliteValue::Float(0.1),
6311 ])
6312 .unwrap(),
6313 SqliteValue::Text(SmallText::from_string("0.1"))
6314 );
6315 }
6316
6317 #[test]
6318 fn test_format_altform2_precision_and_width() {
6319 let f = FormatFunc;
6326 let fmt = |spec: &str, v: f64| -> String {
6327 match f
6328 .invoke(&[
6329 SqliteValue::Text(SmallText::from_string(spec)),
6330 SqliteValue::Float(v),
6331 ])
6332 .unwrap()
6333 {
6334 SqliteValue::Text(s) => s.as_str().to_owned(),
6335 other => panic!("expected text, got {other:?}"),
6336 }
6337 };
6338 let cases: &[(&str, f64, &str)] = &[
6339 ("%!f", 0.1, "0.1"),
6340 ("%!5.2f", 3.14159, " 3.14"),
6341 ("%!.3f", 1.5, "1.5"),
6342 ("%!f", 3.14159, "3.14159"),
6343 ("%!f", 5.0, "5.0"),
6344 ("%!f", 5.5, "5.5"),
6345 ("%!.0f", 5.5, "6.0"),
6346 ("%!f", -0.5, "-0.5"),
6347 ("%+!f", 0.5, "+0.5"),
6348 ("%!f", 100.0, "100.0"),
6349 ("%!8.2f", 3.14159, " 3.14"),
6350 ("%!08.3f", 1.5, "000001.5"),
6351 ("%!10.2f", 3.14159, " 3.14"),
6352 ];
6353 for (spec, v, want) in cases {
6354 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6355 }
6356 }
6357
6358 #[test]
6359 fn test_format_comma_grouping_flag() {
6360 let f = FormatFunc;
6368 let fmt = |spec: &str, v: SqliteValue| -> String {
6369 match f
6370 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
6371 .unwrap()
6372 {
6373 SqliteValue::Text(s) => s.as_str().to_owned(),
6374 other => panic!("expected text, got {other:?}"),
6375 }
6376 };
6377 let int_cases: &[(&str, i64, &str)] = &[
6378 ("%,d", 1234567, "1,234,567"),
6379 ("%,d", -1234567, "-1,234,567"),
6380 ("%,d", 123, "123"),
6381 ("%,d", 1000, "1,000"),
6382 ("%,d", 0, "0"),
6383 ("%,d", -100, "-100"),
6384 ("%,d", 1000000, "1,000,000"),
6385 ("%,10d", 1234567, " 1,234,567"),
6386 ("%,08d", 1234, "00,001,234"),
6387 ("%+,d", 1234567, "+1,234,567"),
6388 ("%, d", 1234567, " 1,234,567"),
6389 ("%-,12d", 1234567, "1,234,567 "),
6390 ("%,i", 1234567, "1,234,567"),
6391 ("%,u", 1234567, "1,234,567"),
6392 ("%,x", 1234567, "12d687"),
6393 ];
6394 for (spec, v, want) in int_cases {
6395 assert_eq!(
6396 fmt(spec, SqliteValue::Integer(*v)),
6397 *want,
6398 "spec={spec} v={v}"
6399 );
6400 }
6401 let float_cases: &[(&str, f64, &str)] = &[
6402 ("%,f", 1234567.5, "1,234,567.500000"),
6403 ("%,.2f", 1234567.891, "1,234,567.89"),
6404 ("%,f", -1234.5, "-1,234.500000"),
6405 ("%,e", 1234.5, "1.234500e+03"),
6406 ("%,g", 1234.5, "1,234.5"),
6408 ("%,g", 12.0, "12"),
6409 ("%,g", 1234567.0, "1.23457e+06"),
6410 ("%,g", 1000000.0, "1e+06"),
6411 ("%,.2g", 1234.5, "1.2e+03"),
6412 ];
6413 for (spec, v, want) in float_cases {
6414 assert_eq!(
6415 fmt(spec, SqliteValue::Float(*v)),
6416 *want,
6417 "spec={spec} v={v}"
6418 );
6419 }
6420 }
6421
6422 #[test]
6423 fn test_format_integer_precision() {
6424 let f = FormatFunc;
6429 let fmt = |spec: &str, v: i64| -> String {
6430 match f
6431 .invoke(&[
6432 SqliteValue::Text(SmallText::from_string(spec)),
6433 SqliteValue::Integer(v),
6434 ])
6435 .unwrap()
6436 {
6437 SqliteValue::Text(s) => s.as_str().to_owned(),
6438 other => panic!("expected text, got {other:?}"),
6439 }
6440 };
6441 let cases: &[(&str, i64, &str)] = &[
6442 ("%.3d", 5, "005"),
6443 ("%.3d", -5, "-005"),
6444 ("%.0d", 0, "0"),
6445 ("%.0d", 5, "5"),
6446 ("%5.3d", 42, " 042"),
6447 ("%-5.3d", 42, "042 "),
6448 ("%.3d", 12345, "12345"),
6449 ("%+.3d", 5, "+005"),
6450 ("% .3d", 5, " 005"),
6451 ("%08.3d", 42, "00000042"),
6452 ("%.3i", 9, "009"),
6453 ("%.3u", 7, "007"),
6454 ("%.3x", 10, "00a"),
6455 ("%.3o", 8, "010"),
6456 ];
6457 for (spec, v, want) in cases {
6458 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6459 }
6460 }
6461
6462 #[test]
6463 fn test_format_altform2_exponential() {
6464 let f = FormatFunc;
6468 let fmt = |spec: &str, v: f64| -> String {
6469 match f
6470 .invoke(&[
6471 SqliteValue::Text(SmallText::from_string(spec)),
6472 SqliteValue::Float(v),
6473 ])
6474 .unwrap()
6475 {
6476 SqliteValue::Text(s) => s.as_str().to_owned(),
6477 other => panic!("expected text, got {other:?}"),
6478 }
6479 };
6480 let cases: &[(&str, f64, &str)] = &[
6481 ("%!e", 3.14159, "3.14159e+00"),
6482 ("%!E", 3.14159, "3.14159E+00"),
6483 ("%!e", 5.0, "5.0e+00"),
6484 ("%!.2e", 3.14159, "3.14e+00"),
6485 ("%!.0e", 3.0, "3.0e+00"),
6486 ];
6487 for (spec, v, want) in cases {
6488 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6489 }
6490 }
6491
6492 #[test]
6493 fn test_format_altform2_g_honors_precision() {
6494 let f = FormatFunc;
6500 let fmt = |spec: &str, v: f64| -> String {
6501 match f
6502 .invoke(&[
6503 SqliteValue::Text(SmallText::from_string(spec)),
6504 SqliteValue::Float(v),
6505 ])
6506 .unwrap()
6507 {
6508 SqliteValue::Text(s) => s.as_str().to_owned(),
6509 other => panic!("expected text, got {other:?}"),
6510 }
6511 };
6512 let cases: &[(&str, f64, &str)] = &[
6513 ("%!g", 12345.0, "12345.0"),
6514 ("%!.0g", 12345.0, "1.0e+04"),
6515 ("%!.1g", 12345.0, "1.0e+04"),
6516 ("%!.3g", 12345.0, "1.23e+04"),
6517 ("%!.2g", 0.000123, "0.00012"),
6518 ("%!g", 100.0, "100.0"),
6519 ("%!.0g", 5.0, "5.0"),
6520 ("%!g", 0.1, "0.1"),
6521 ("%!G", 12345.0, "12345.0"),
6522 ("%!.0G", 12345.0, "1.0E+04"),
6523 ];
6524 for (spec, v, want) in cases {
6525 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6526 }
6527 }
6528
6529 #[test]
6530 #[allow(clippy::excessive_precision, clippy::unreadable_literal)]
6531 fn test_format_altform2_high_precision_bd_ixizz() {
6532 let f = FormatFunc;
6539 let fmt = |spec: &str, v: f64| -> String {
6540 match f
6541 .invoke(&[
6542 SqliteValue::Text(SmallText::from_string(spec)),
6543 SqliteValue::Float(v),
6544 ])
6545 .unwrap()
6546 {
6547 SqliteValue::Text(s) => s.as_str().to_owned(),
6548 other => panic!("expected text, got {other:?}"),
6549 }
6550 };
6551 let third = 1.0 / 3.0;
6552 let two_thirds = 2.0 / 3.0;
6553 let seventh = 1.0 / 7.0;
6554 let cases: &[(&str, f64, &str)] = &[
6555 ("%!.40e", two_thirds, "6.66666666666666629e-01"),
6557 ("%!.40e", third, "3.33333333333333314e-01"),
6558 ("%!.40e", 0.1, "1.00000000000000005e-01"),
6559 ("%!.40e", seventh, "1.42857142857142849e-01"),
6560 ("%!.40e", 1e300, "1.000000000000000052e+300"),
6561 ("%!.40E", two_thirds, "6.66666666666666629E-01"),
6562 ("%!.40e", -two_thirds, "-6.66666666666666629e-01"),
6563 ("%!.16e", two_thirds, "6.6666666666666663e-01"),
6565 ("%!.17e", two_thirds, "6.66666666666666629e-01"),
6566 ("%!.40f", third, "0.333333333333333314"),
6568 ("%!.40f", 0.1, "0.100000000000000005"),
6569 ("%!.18f", third, "0.333333333333333314"),
6570 ("%!.40f", -two_thirds, "-0.666666666666666629"),
6571 ("%!.40f", 12345678901234567890.0, "12345678901234567160.0"),
6573 ("%!.17g", two_thirds, "0.66666666666666663"),
6575 ("%!.18g", two_thirds, "0.666666666666666629"),
6576 ("%!.40g", two_thirds, "0.666666666666666629"),
6577 ("%!.40g", -two_thirds, "-0.666666666666666629"),
6578 ];
6579 for (spec, v, want) in cases {
6580 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6581 }
6582 }
6583
6584 #[test]
6585 fn test_format_alt_form_hash_floats() {
6586 let f = FormatFunc;
6590 let fmt = |spec: &str, v: f64| -> String {
6591 match f
6592 .invoke(&[
6593 SqliteValue::Text(SmallText::from_string(spec)),
6594 SqliteValue::Float(v),
6595 ])
6596 .unwrap()
6597 {
6598 SqliteValue::Text(s) => s.as_str().to_owned(),
6599 other => panic!("expected text, got {other:?}"),
6600 }
6601 };
6602 let cases: &[(&str, f64, &str)] = &[
6603 ("%#.0f", 3.0, "3."),
6604 ("%#.2f", 3.5, "3.50"),
6605 ("%#.0f", -3.0, "-3."),
6606 ("%#5.0f", 3.0, " 3."),
6607 ("%#.0f", 0.0, "0."),
6608 ("%#.0e", 3.0, "3.e+00"),
6609 ("%#e", 3.0, "3.000000e+00"),
6610 ("%#.0g", 3.0, "3."),
6611 ("%#g", 3.0, "3.00000"),
6612 ("%#.3g", 3.0, "3.00"),
6613 ("%#g", 100000.0, "100000."),
6614 ("%#g", 0.0001, "0.000100000"),
6615 ("%#.1g", 9.9, "1.e+01"),
6616 ("%#g", 1234567.0, "1.23457e+06"),
6617 ];
6618 for (spec, v, want) in cases {
6619 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6620 }
6621 }
6622
6623 #[test]
6624 fn test_printf_large_float_precision_no_panic() {
6625 let f = FormatFunc;
6630 let run = |args: &[SqliteValue]| -> String {
6631 match f.invoke(args).unwrap() {
6632 SqliteValue::Text(s) => s.as_str().to_owned(),
6633 other => panic!("expected text, got {other:?}"),
6634 }
6635 };
6636 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6637 let real = SqliteValue::Float;
6638
6639 let big = run(&[txt("%.100000f"), real(1.5)]);
6641 assert_eq!(big.len(), 100_002);
6642 assert!(big.starts_with("1.5"));
6643 assert!(big["1.5".len()..].bytes().all(|b| b == b'0'));
6644
6645 let mid = run(&[txt("%.2000f"), real(0.25)]);
6647 assert_eq!(mid.len(), 2002);
6648 assert!(mid.starts_with("0.25"));
6649 assert!(mid["0.25".len()..].bytes().all(|b| b == b'0'));
6650
6651 assert_eq!(run(&[txt("%.10f"), real(1.5)]), "1.5000000000");
6653 assert_eq!(run(&[txt("%.4f"), real(2.0)]), "2.0000");
6654 }
6655
6656 #[test]
6657 fn test_printf_bd_9zzr0_review_fixes() {
6658 let f = FormatFunc;
6660 let run = |args: &[SqliteValue]| -> String {
6661 match f.invoke(args).unwrap() {
6662 SqliteValue::Text(s) => s.as_str().to_owned(),
6663 other => panic!("expected text, got {other:?}"),
6664 }
6665 };
6666 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6667 let int = SqliteValue::Integer;
6668
6669 assert_eq!(run(&[txt("[%.5c]"), txt("A")]), "[AAAAA]");
6671 assert_eq!(run(&[txt("[%c]"), txt("A")]), "[A]");
6672 assert_eq!(run(&[txt("[%3c]"), txt("B")]), "[ B]");
6673 assert_eq!(run(&[txt("[%-05d]"), int(42)]), "[00042]");
6675 assert_eq!(run(&[txt("[%05d]"), int(42)]), "[00042]");
6676 assert_eq!(run(&[txt("[%-5d]"), int(42)]), "[42 ]");
6677 assert_eq!(run(&[txt("[%.*d]"), int(-3), int(42)]), "[042]");
6679 assert_eq!(run(&[txt("[%.*d]"), int(3), int(42)]), "[042]");
6680 assert_eq!(run(&[txt("[%w]"), SqliteValue::Null]), "[(NULL)]");
6682 assert_eq!(run(&[txt("[%10w]"), SqliteValue::Null]), "[ (NULL)]");
6683 assert_eq!(run(&[txt("[%.3q]"), txt("ab'cdef")]), "[ab'']");
6685 assert_eq!(run(&[txt("[%.3Q]"), txt("ab'cdef")]), "['ab''']");
6686 assert_eq!(run(&[txt("[%.3w]"), txt("a\"bcdef")]), "[a\"\"b]");
6687 assert_eq!(run(&[txt("[%.0c]"), txt("A")]), "[A]");
6690 assert_eq!(run(&[txt("[%.1c]"), txt("A")]), "[A]");
6691 assert_eq!(run(&[txt("[%.*d]"), int(-4_294_967_293), int(42)]), "[042]");
6695 assert_eq!(run(&[txt("[%.*d]"), int(-2_147_483_648), int(42)]), "[42]");
6696 assert_eq!(run(&[txt("[%.*d]"), int(-1), int(42)]), "[42]");
6697 }
6698
6699 #[test]
6700 fn test_printf_int_max_width_precision_overflow_bd_mcgdb() {
6701 let f = FormatFunc;
6707 let run = |args: &[SqliteValue]| -> Option<String> {
6709 match f.invoke(args).unwrap() {
6710 SqliteValue::Text(s) => Some(s.as_str().to_owned()),
6711 SqliteValue::Null => None,
6712 other => panic!("expected text or null, got {other:?}"),
6713 }
6714 };
6715 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6716 let int = SqliteValue::Integer;
6717 let some = |s: &str| Some(s.to_owned());
6718
6719 assert_eq!(run(&[txt("%2147483648d"), int(5)]), some("5"));
6722 assert_eq!(run(&[txt("%2147483649d"), int(5)]), some("5")); assert_eq!(run(&[txt("%2147483650d"), int(5)]), some(" 5")); assert_eq!(run(&[txt("%4294967296d"), int(5)]), some("5"));
6726 assert_eq!(run(&[txt("%4294967301d"), int(5)]), some(" 5"));
6727 assert_eq!(run(&[txt("%8589934592d"), int(5)]), some("5"));
6729 assert_eq!(run(&[txt("%8589934597d"), int(5)]), some(" 5"));
6730
6731 assert_eq!(run(&[txt("%1000000000d"), int(5)]), None);
6733 assert_eq!(run(&[txt("%2147483647d"), int(5)]), None); assert_eq!(run(&[txt("%4294967295d"), int(5)]), None); assert_eq!(run(&[txt("%1000000000s"), txt("ab")]), None);
6736
6737 assert_eq!(run(&[txt("%2147483648s"), txt("ab")]), some("ab")); assert_eq!(run(&[txt("%4294967301s"), txt("ab")]), some(" ab")); assert_eq!(run(&[txt("%.2147483648d"), int(5)]), some("5")); assert_eq!(run(&[txt("%.4294967301d"), int(5)]), some("00005")); assert_eq!(run(&[txt("%.1000000000d"), int(5)]), None);
6745 assert_eq!(run(&[txt("%.2147483648c"), txt("A")]), some("A")); assert_eq!(run(&[txt("%.4294967301c"), txt("A")]), some("AAAAA"));
6748 assert_eq!(run(&[txt("%.1000000000c"), txt("A")]), None);
6749
6750 assert_eq!(run(&[txt("%.2147483648f"), int(5)]), some("5"));
6753 assert_eq!(run(&[txt("%.2147483649f"), int(5)]), some("5.0"));
6754 assert_eq!(run(&[txt("%.4294967296f"), int(5)]), some("5"));
6755 assert_eq!(run(&[txt("%.1000000000g"), int(5)]), some("5"));
6757
6758 assert_eq!(run(&[txt("%5d"), int(5)]), some(" 5"));
6760 assert_eq!(run(&[txt("%-5d"), int(5)]), some("5 "));
6761 assert_eq!(run(&[txt("%.3d"), int(5)]), some("005"));
6762 }
6763
6764 #[test]
6765 fn test_printf_incomplete_conversion_edges_bd_ybftw() {
6766 let f = FormatFunc;
6772 let run = |args: &[SqliteValue]| -> Option<String> {
6773 match f.invoke(args).unwrap() {
6774 SqliteValue::Text(s) => Some(s.as_str().to_owned()),
6775 SqliteValue::Null => None,
6776 other => panic!("expected text or null, got {other:?}"),
6777 }
6778 };
6779 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6780 let some = |s: &str| Some(s.to_owned());
6781
6782 assert_eq!(run(&[txt("%")]), some("%"));
6784 assert_eq!(run(&[txt("abc%")]), some("abc%"));
6785 assert_eq!(run(&[txt("x%"), SqliteValue::Integer(9)]), some("x%"));
6787 assert_eq!(run(&[txt("%%")]), some("%"));
6789 assert_eq!(run(&[txt("abc%%def")]), some("abc%def"));
6790
6791 for bad in [
6794 "%5", "%-", "%.", "%+", "%#", "% ", "%05", "%-5", "%.3", "%5.3",
6795 ] {
6796 assert_eq!(run(&[txt(bad)]), None, "printf('{bad}') must be NULL");
6797 }
6798 let int = SqliteValue::Integer;
6801 assert_eq!(run(&[txt("abc%5")]), some("abc"));
6802 assert_eq!(run(&[txt("x%-")]), some("x"));
6803 assert_eq!(run(&[txt(" %5")]), some(" ")); assert_eq!(run(&[txt("%d%5"), int(0)]), some("0"));
6805 assert_eq!(run(&[txt("ab%d%5"), int(0)]), some("ab0"));
6806 }
6807
6808 #[test]
6809 fn test_printf_conversion_flag_edges_2026_08() {
6810 let f = FormatFunc;
6816 let run = |args: &[SqliteValue]| -> String {
6817 match f.invoke(args).unwrap() {
6818 SqliteValue::Text(s) => s.as_str().to_owned(),
6819 other => panic!("expected text, got {other:?}"),
6820 }
6821 };
6822 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6823 let int = SqliteValue::Integer;
6824 let flt = SqliteValue::Float;
6825
6826 assert_eq!(run(&[txt("%#x"), int(255)]), "0xff");
6827 assert_eq!(run(&[txt("%#X"), int(255)]), "0XFF");
6828 assert_eq!(run(&[txt("%#o"), int(8)]), "010");
6829 assert_eq!(run(&[txt("%08x"), int(255)]), "000000ff");
6830 assert_eq!(run(&[txt("%-8x|"), int(255)]), "ff |");
6831 assert_eq!(run(&[txt("%x"), int(-1)]), "ffffffffffffffff"); assert_eq!(run(&[txt("%o"), int(-1)]), "1777777777777777777777");
6833 assert_eq!(run(&[txt("%+d"), int(5)]), "+5");
6834 assert_eq!(run(&[txt("% d"), int(5)]), " 5");
6835 assert_eq!(run(&[txt("%+d"), int(-5)]), "-5");
6836 assert_eq!(run(&[txt("%,d"), int(1_234_567)]), "1,234,567");
6837 assert_eq!(run(&[txt("%5.3d"), int(7)]), " 007");
6838 assert_eq!(run(&[txt("%-+8.3d|"), int(7)]), "+007 |");
6839 assert_eq!(run(&[txt("%c"), int(65)]), "6");
6841 assert_eq!(run(&[txt("%.3c"), int(65)]), "666");
6842 assert_eq!(run(&[txt("%5c|"), int(65)]), " 6|");
6843 assert_eq!(run(&[txt("%#x"), int(0)]), "0"); assert_eq!(run(&[txt("%X"), int(3_735_928_559)]), "DEADBEEF");
6845 assert_eq!(run(&[txt("%,.2f"), flt(1234.5)]), "1,234.50");
6846 assert_eq!(run(&[txt("%+.2e"), flt(1234.5)]), "+1.23e+03");
6847 }
6848
6849 #[test]
6850 fn test_printf_dynamic_width_precision_bd_3fpd4() {
6851 let f = FormatFunc;
6856 let run = |args: &[SqliteValue]| -> Option<String> {
6857 match f.invoke(args).unwrap() {
6858 SqliteValue::Text(s) => Some(s.as_str().to_owned()),
6859 SqliteValue::Null => None,
6860 other => panic!("expected text or null, got {other:?}"),
6861 }
6862 };
6863 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6864 let int = SqliteValue::Integer;
6865 let flt = SqliteValue::Float;
6866 let some = |s: &str| Some(s.to_owned());
6867
6868 assert_eq!(run(&[txt("%*d"), int(5), int(7)]), some(" 7"));
6869 assert_eq!(run(&[txt("%*d"), int(-5), int(7)]), some("7 "));
6870 assert_eq!(run(&[txt("%*d"), int(2_147_483_648), int(7)]), some("7"));
6872 assert_eq!(run(&[txt("%*d"), int(4_294_967_296), int(7)]), some("7"));
6873 assert_eq!(
6874 run(&[txt("%*d"), int(4_294_967_301), int(7)]),
6875 some(" 7")
6876 );
6877 assert_eq!(run(&[txt("%*d"), int(-2_147_483_648), int(7)]), some("7"));
6878 assert_eq!(run(&[txt("%*d"), int(1_000_000_000), int(7)]), None);
6880 assert_eq!(run(&[txt("%*d"), int(-2_147_483_647), int(7)]), None);
6881 assert_eq!(run(&[txt("%.*f"), int(2), flt(3.14159)]), some("3.14"));
6884 assert_eq!(run(&[txt("%.*d"), int(-3), int(42)]), some("042"));
6885 assert_eq!(
6886 run(&[txt("%.*f"), int(2_147_483_648), flt(5.5)]),
6887 some("5.500000")
6888 );
6889 assert_eq!(run(&[txt("%.*d"), int(1_000_000_000), int(7)]), None);
6890 assert_eq!(
6892 run(&[txt("%*.*f"), int(10), int(2), flt(3.14159)]),
6893 some(" 3.14")
6894 );
6895 }
6896
6897 #[test]
6898 #[allow(clippy::excessive_precision)] fn test_format_high_precision_shortest_round_trip() {
6900 let f = FormatFunc;
6905 let fmt = |spec: &str, v: f64| -> String {
6906 match f
6907 .invoke(&[
6908 SqliteValue::Text(SmallText::from_string(spec)),
6909 SqliteValue::Float(v),
6910 ])
6911 .unwrap()
6912 {
6913 SqliteValue::Text(s) => s.as_str().to_owned(),
6914 other => panic!("expected text, got {other:?}"),
6915 }
6916 };
6917 let third = 1.0 / 3.0;
6918 let pi = std::f64::consts::PI;
6919 let cases: &[(&str, f64, &str)] = &[
6920 ("%.20f", 0.1, "0.10000000000000000000"),
6922 ("%.18f", 0.1, "0.100000000000000000"),
6923 ("%.30f", 1.5, "1.500000000000000000000000000000"),
6924 ("%.25f", 1.5, "1.5000000000000000000000000"),
6925 ("%.17f", third, "0.33333333333333330"),
6926 ("%.18f", third, "0.333333333333333300"),
6927 ("%.17g", 0.1, "0.1"),
6928 ("%.25g", 0.1, "0.1"),
6929 ("%.17g", third, "0.3333333333333333"),
6930 ("%.18g", third, "0.3333333333333333"),
6931 ("%.17g", pi, "3.141592653589793"),
6932 ("%.17e", 0.1, "1.00000000000000000e-01"),
6933 ("%.16e", 0.1, "1.0000000000000000e-01"),
6934 ("%.19e", third, "3.3333333333333330000e-01"),
6935 ("%.17e", 2.675, "2.67500000000000000e+00"),
6936 ("%.17g", 1e-20, "9.999999999999999e-21"),
6939 ("%.20g", 1e-20, "9.999999999999999e-21"),
6940 ("%.17e", 1e-20, "9.99999999999999900e-21"),
6941 ("%.30g", 1.0 / 7.0, "0.1428571428571428"),
6942 ("%.2f", 123_456_789_012_345.678, "123456789012345.70"),
6944 ("%.6f", 123_456_789_012_345.678, "123456789012345.700000"),
6945 ("%.17g", 123_456_789_012_345.678, "123456789012345.7"),
6946 ("%f", 6.022e23, "602200000000000000000000.000000"),
6947 ("%.17f", 2.675, "2.67500000000000000"),
6949 ("%.20f", -0.1, "-0.10000000000000000000"),
6950 ("%.17f", -1.0 / 3.0, "-0.33333333333333330"),
6951 ("%!f", 0.1, "0.1"),
6953 ("%!f", 1.5, "1.5"),
6954 ("%.2f", 0.1, "0.10"),
6956 ("%.6f", 0.1, "0.100000"),
6957 ("%.15f", 0.1, "0.100000000000000"),
6958 ("%.1f", 0.15, "0.1"),
6959 ("%.2f", 2.675, "2.67"),
6960 ("%.0f", 2.5, "3"),
6961 ("%g", third, "0.333333"),
6962 ("%.6e", 0.1, "1.000000e-01"),
6963 ("%f", 1.5, "1.500000"),
6964 ];
6965 for (spec, v, want) in cases {
6966 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6967 }
6968 }
6969
6970 #[test]
6973 fn test_format_specifiers() {
6974 let f = FormatFunc;
6975 let result = f
6976 .invoke(&[
6977 SqliteValue::Text(SmallText::from_string("%d %s")),
6978 SqliteValue::Integer(42),
6979 SqliteValue::Text(SmallText::from_string("hello")),
6980 ])
6981 .unwrap();
6982 assert_eq!(
6983 result,
6984 SqliteValue::Text(SmallText::from_string("42 hello"))
6985 );
6986 }
6987
6988 #[test]
6989 fn test_format_n_noop() {
6990 let f = FormatFunc;
6991 let result = f
6993 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
6994 .unwrap();
6995 assert_eq!(
6996 result,
6997 SqliteValue::Text(SmallText::from_string("beforeafter"))
6998 );
6999 }
7000
7001 #[test]
7002 fn test_format_literal_percent_honors_width() {
7003 let f = FormatFunc;
7007 let fmt = |spec: &str| -> String {
7008 match f
7009 .invoke(&[SqliteValue::Text(SmallText::from_string(spec))])
7010 .unwrap()
7011 {
7012 SqliteValue::Text(s) => s.as_str().to_owned(),
7013 other => panic!("expected text, got {other:?}"),
7014 }
7015 };
7016 assert_eq!(fmt("%%"), "%");
7017 assert_eq!(fmt("%5%"), " %");
7018 assert_eq!(fmt("%-5%"), "% ");
7019 assert_eq!(fmt("%05%"), " %");
7020 assert_eq!(fmt("[%3%]"), "[ %]");
7021 }
7022
7023 #[test]
7024 fn test_format_alternate_form_hex_octal() {
7025 let cases: &[(&str, i64, &str)] = &[
7027 ("%#x", 255, "0xff"),
7028 ("%#X", 255, "0XFF"),
7029 ("%#o", 64, "0100"),
7030 ("%#x", 0, "0"), ("%#o", 0, "0"), ("%#5x", 255, " 0xff"), ("%#8x", 255, " 0xff"),
7034 ("%#08x", 255, "0x000000ff"), ("%-#8x", 255, "0xff "), ("%-08x", 255, "000000ff"), ("%#08o", 64, "000000100"),
7038 ("%#x", -1, "0xffffffffffffffff"),
7039 ];
7040 for (fmt, arg, want) in cases {
7041 let f = FormatFunc;
7042 let result = f
7043 .invoke(&[
7044 SqliteValue::Text(SmallText::from_string(*fmt)),
7045 SqliteValue::Integer(*arg),
7046 ])
7047 .unwrap();
7048 assert_eq!(
7049 result,
7050 SqliteValue::Text(SmallText::from_string((*want).to_owned())),
7051 "format({fmt:?}, {arg})"
7052 );
7053 }
7054 }
7055
7056 #[test]
7057 fn test_format_empty_string_is_null() {
7058 let f = FormatFunc;
7062 assert_eq!(
7063 f.invoke(&[SqliteValue::Text(SmallText::from_string(""))])
7064 .unwrap(),
7065 SqliteValue::Null
7066 );
7067 assert_eq!(
7069 f.invoke(&[
7070 SqliteValue::Text(SmallText::from_string("%s")),
7071 SqliteValue::Null,
7072 ])
7073 .unwrap(),
7074 SqliteValue::Text(SmallText::from_string(String::new()))
7075 );
7076 }
7077
7078 #[test]
7081 fn test_sqlite_version_format() {
7082 let result = SqliteVersionFunc.invoke(&[]).unwrap();
7083 match result {
7084 SqliteValue::Text(v) => {
7085 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
7086 }
7087 other => unreachable!("expected text, got {other:?}"),
7088 }
7089 }
7090
7091 #[test]
7092 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
7093 let func = SqliteCompileoptionUsedFunc;
7094 assert_eq!(
7095 invoke1(
7096 &func,
7097 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
7098 )
7099 .unwrap(),
7100 SqliteValue::Integer(1)
7101 );
7102 let expected_icu_enabled = i64::from(cfg!(feature = "ext-icu"));
7103 assert_eq!(
7104 invoke1(
7105 &func,
7106 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
7107 )
7108 .unwrap(),
7109 SqliteValue::Integer(expected_icu_enabled)
7110 );
7111 assert_eq!(
7112 invoke1(
7113 &func,
7114 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
7115 )
7116 .unwrap(),
7117 SqliteValue::Integer(expected_icu_enabled)
7118 );
7119 assert_eq!(
7120 invoke1(
7121 &func,
7122 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
7123 )
7124 .unwrap(),
7125 SqliteValue::Integer(1)
7126 );
7127 assert_eq!(
7128 invoke1(
7129 &func,
7130 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
7131 )
7132 .unwrap(),
7133 SqliteValue::Integer(0)
7134 );
7135 assert_eq!(
7136 invoke1(&func, SqliteValue::Null).unwrap(),
7137 SqliteValue::Null
7138 );
7139 }
7140
7141 #[test]
7142 #[ignore = "perf-only benchmark"]
7143 fn perf_compileoption_used_text_args() {
7144 use std::hint::black_box;
7145 use std::time::Instant;
7146
7147 const INVOCATIONS: usize = 1_000_000;
7148 const REPEATS: usize = 7;
7149
7150 let f = SqliteCompileoptionUsedFunc;
7151 let present_args = [SqliteValue::Text(SmallText::from_string(
7152 "SQLITE_ENABLE_ICU",
7153 ))];
7154 let absent_args = [SqliteValue::Text(SmallText::from_string(
7155 "ENABLE_NOT_PRESENT",
7156 ))];
7157
7158 let mut present_best_ns = u128::MAX;
7159 let mut absent_best_ns = u128::MAX;
7160 let mut checksum = 0i64;
7161 for _ in 0..REPEATS {
7162 let started = Instant::now();
7163 for _ in 0..INVOCATIONS {
7164 let result = black_box(
7165 f.invoke(black_box(present_args.as_slice()))
7166 .expect("compileoption present benchmark invocation must succeed"),
7167 );
7168 if let SqliteValue::Integer(value) = result {
7169 checksum = checksum.wrapping_add(value);
7170 }
7171 }
7172 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
7173
7174 let started = Instant::now();
7175 for _ in 0..INVOCATIONS {
7176 let result = black_box(
7177 f.invoke(black_box(absent_args.as_slice()))
7178 .expect("compileoption absent benchmark invocation must succeed"),
7179 );
7180 if let SqliteValue::Integer(value) = result {
7181 checksum = checksum.wrapping_add(value);
7182 }
7183 }
7184 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
7185 }
7186
7187 println!(
7188 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
7189 );
7190 }
7191
7192 #[test]
7193 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
7194 let func = SqliteCompileoptionGetFunc;
7195 for (index, option) in sqlite_compile_options().iter().enumerate() {
7196 assert_eq!(
7197 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
7198 SqliteValue::Text(SmallText::new(option))
7199 );
7200 }
7201 assert_eq!(
7202 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
7203 SqliteValue::Null
7204 );
7205 assert_eq!(
7206 invoke1(
7207 &func,
7208 SqliteValue::Integer(sqlite_compile_options().len() as i64)
7209 )
7210 .unwrap(),
7211 SqliteValue::Null
7212 );
7213 }
7214
7215 #[test]
7218 fn test_register_builtins_all_present() {
7219 let mut registry = FunctionRegistry::new();
7220 register_builtins(&mut registry);
7221
7222 assert!(registry.find_scalar("abs", 1).is_some());
7224 assert!(registry.find_scalar("typeof", 1).is_some());
7225 assert!(registry.find_scalar("length", 1).is_some());
7226 assert!(registry.find_scalar("lower", 1).is_some());
7227 assert!(registry.find_scalar("upper", 1).is_some());
7228 assert!(registry.find_scalar("hex", 1).is_some());
7229 assert!(registry.find_scalar("coalesce", 3).is_some());
7230 assert!(registry.find_scalar("concat", 2).is_some());
7231 assert!(registry.find_scalar("like", 2).is_some());
7232 assert!(registry.find_scalar("glob", 2).is_some());
7233 assert!(registry.find_scalar("round", 1).is_some());
7234 assert!(registry.find_scalar("substr", 2).is_some());
7235 assert!(registry.find_scalar("substring", 3).is_some());
7236 assert!(registry.find_scalar("sqlite_version", 0).is_some());
7237 assert!(registry.find_scalar("iif", 3).is_some());
7238 assert!(registry.find_scalar("if", 3).is_some());
7239 assert!(registry.find_scalar("format", 1).is_some());
7240 assert!(registry.find_scalar("printf", 1).is_some());
7241 assert!(registry.find_scalar("max", 2).is_some());
7242 assert!(registry.find_scalar("min", 2).is_some());
7243 assert!(registry.find_scalar("sign", 1).is_some());
7244 assert!(registry.find_scalar("random", 0).is_some());
7245
7246 assert!(registry.find_scalar("concat_ws", 3).is_some());
7248 assert!(registry.find_scalar("octet_length", 1).is_some());
7249 assert!(registry.find_scalar("unhex", 1).is_some());
7250 assert!(registry.find_scalar("timediff", 2).is_some());
7251 assert!(registry.find_scalar("unistr", 1).is_some());
7252 assert!(registry.find_scalar("unistr_quote", 1).is_some());
7253
7254 assert!(registry.find_aggregate("median", 1).is_some());
7256 assert!(registry.find_aggregate("percentile", 2).is_some());
7257 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
7258 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
7259
7260 assert!(registry.find_scalar("load_extension", 1).is_none());
7262 assert!(registry.find_scalar("load_extension", 2).is_none());
7263 }
7264
7265 #[test]
7266 fn test_register_builtins_rejects_invalid_variadic_arities() {
7267 let mut registry = FunctionRegistry::new();
7268 register_builtins(&mut registry);
7269
7270 for (name, too_few, valid, too_many) in [
7271 ("coalesce", 1, 2, None),
7272 ("concat", 0, 1, None),
7273 ("concat_ws", 1, 2, None),
7274 ("trim", 0, 1, Some(3)),
7275 ("ltrim", 0, 1, Some(3)),
7276 ("rtrim", 0, 1, Some(3)),
7277 ("round", 0, 1, Some(3)),
7278 ("unhex", 0, 1, Some(3)),
7279 ("substr", 1, 2, Some(4)),
7280 ("substring", 1, 2, Some(4)),
7281 ("max", 0, 1, None),
7282 ("min", 0, 1, None),
7283 ] {
7284 assert_wrong_arg_count(®istry, name, too_few);
7285 assert!(
7286 registry.find_scalar(name, valid).is_some(),
7287 "{name}/{valid} should resolve"
7288 );
7289 if let Some(arity) = too_many {
7290 assert_wrong_arg_count(®istry, name, arity);
7291 }
7292 }
7293
7294 assert!(registry.find_scalar("char", 0).is_some());
7295 assert!(registry.find_scalar("format", 0).is_some());
7296 assert!(registry.find_scalar("printf", 0).is_some());
7297 }
7298
7299 #[test]
7300 fn test_e2e_registry_invoke_through_lookup() {
7301 let mut registry = FunctionRegistry::new();
7302 register_builtins(&mut registry);
7303
7304 let abs = registry.find_scalar("ABS", 1).unwrap();
7306 assert_eq!(
7307 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
7308 SqliteValue::Integer(42)
7309 );
7310
7311 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
7313 assert_eq!(
7314 typeof_fn
7315 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
7316 .unwrap(),
7317 SqliteValue::Text(SmallText::from_string("text"))
7318 );
7319
7320 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
7322 assert_eq!(
7323 coalesce
7324 .invoke(&[
7325 SqliteValue::Null,
7326 SqliteValue::Null,
7327 SqliteValue::Integer(42),
7328 SqliteValue::Integer(99),
7329 ])
7330 .unwrap(),
7331 SqliteValue::Integer(42)
7332 );
7333 }
7334
7335 #[test]
7338 fn test_nondeterministic_functions_flagged() {
7339 assert!(!RandomFunc.is_deterministic());
7342 assert!(!RandomblobFunc.is_deterministic());
7343 assert!(!ChangesFunc.is_deterministic());
7344 assert!(!TotalChangesFunc.is_deterministic());
7345 assert!(!LastInsertRowidFunc.is_deterministic());
7346 assert!(!SqliteVersionFunc.is_deterministic());
7347 assert!(!SqliteSourceIdFunc.is_deterministic());
7348 assert!(!SqliteCompileoptionUsedFunc.is_deterministic());
7349 assert!(!SqliteCompileoptionGetFunc.is_deterministic());
7350 }
7351
7352 #[test]
7353 fn test_deterministic_functions_flagged() {
7354 assert!(AbsFunc.is_deterministic());
7356 assert!(LengthFunc.is_deterministic());
7357 assert!(TypeofFunc.is_deterministic());
7358 assert!(UpperFunc.is_deterministic());
7359 assert!(LowerFunc.is_deterministic());
7360 assert!(HexFunc.is_deterministic());
7361 assert!(CoalesceFunc.is_deterministic());
7362 assert!(IifFunc.is_deterministic());
7363 }
7364
7365 #[test]
7366 fn test_random_produces_different_values() {
7367 let a = RandomFunc.invoke(&[]).unwrap();
7370 let b = RandomFunc.invoke(&[]).unwrap();
7371 assert_ne!(a.as_integer(), b.as_integer());
7374 }
7375
7376 #[test]
7377 fn test_registry_nondeterministic_lookup() {
7378 let mut registry = FunctionRegistry::default();
7379 register_builtins(&mut registry);
7380
7381 let random = registry.find_scalar("random", 0).unwrap();
7383 assert!(!random.is_deterministic());
7384
7385 let changes = registry.find_scalar("changes", 0).unwrap();
7386 assert!(!changes.is_deterministic());
7387
7388 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
7389 assert!(!lir.is_deterministic());
7390
7391 for (name, num_args) in [
7392 ("sqlite_version", 0),
7393 ("sqlite_source_id", 0),
7394 ("sqlite_compileoption_used", 1),
7395 ("sqlite_compileoption_get", 1),
7396 ] {
7397 assert_eq!(
7398 registry.scalar_is_deterministic(name, num_args),
7399 Some(false),
7400 "{name} must publish non-deterministic registry metadata"
7401 );
7402 }
7403
7404 let abs = registry.find_scalar("abs", 1).unwrap();
7406 assert!(abs.is_deterministic());
7407 }
7408
7409 #[test]
7410 fn test_registry_builtin_query_constancy_metadata() {
7411 use crate::{ScalarQueryConstancy, ScalarSchemaSafety};
7412
7413 let mut registry = FunctionRegistry::default();
7414 register_builtins(&mut registry);
7415
7416 for (name, num_args) in [
7417 ("sqlite_version", 0),
7418 ("sqlite_source_id", 0),
7419 ("sqlite_compileoption_used", 1),
7420 ("sqlite_compileoption_get", 1),
7421 ] {
7422 let resolved = registry.resolve_scalar(name, num_args).unwrap();
7423 assert_eq!(resolved.schema_safety(), ScalarSchemaSafety::Never);
7424 assert_eq!(
7425 resolved.query_constancy(),
7426 ScalarQueryConstancy::SlowChanging,
7427 "{name}/{num_args} must match SQLite's slow-changing metadata"
7428 );
7429 }
7430
7431 for (name, num_args) in [
7432 ("date", 0),
7433 ("time", 0),
7434 ("datetime", 0),
7435 ("julianday", 0),
7436 ("unixepoch", 0),
7437 ("strftime", 1),
7438 ("timediff", 2),
7439 ] {
7440 let resolved = registry.resolve_scalar(name, num_args).unwrap();
7441 assert_eq!(
7442 resolved.schema_safety(),
7443 ScalarSchemaSafety::DateTimeConditional
7444 );
7445 assert_eq!(
7446 resolved.query_constancy(),
7447 ScalarQueryConstancy::SlowChanging,
7448 "{name}/{num_args} must be query-constant despite conditional schema safety"
7449 );
7450 }
7451
7452 for (name, num_args) in [
7453 ("random", 0),
7454 ("randomblob", 1),
7455 ("changes", 0),
7456 ("total_changes", 0),
7457 ("last_insert_rowid", 0),
7458 ] {
7459 assert_eq!(
7460 registry
7461 .resolve_scalar(name, num_args)
7462 .unwrap()
7463 .query_constancy(),
7464 ScalarQueryConstancy::Volatile,
7465 "{name}/{num_args} must remain volatile"
7466 );
7467 }
7468
7469 for (name, num_args) in [("abs", 1), ("like", 2), ("like", 3), ("glob", 2)] {
7470 assert_eq!(
7471 registry
7472 .resolve_scalar(name, num_args)
7473 .unwrap()
7474 .query_constancy(),
7475 ScalarQueryConstancy::Constant,
7476 "{name}/{num_args} must remain constant"
7477 );
7478 }
7479
7480 for (name, num_args) in [
7481 ("sqlite_version", 1),
7482 ("sqlite_compileoption_used", 0),
7483 ("like", 1),
7484 ("like", 4),
7485 ("glob", 1),
7486 ] {
7487 assert_eq!(
7488 registry
7489 .resolve_scalar(name, num_args)
7490 .unwrap()
7491 .query_constancy(),
7492 ScalarQueryConstancy::Volatile,
7493 "{name}/{num_args} wrong-arity sentinel must fail closed"
7494 );
7495 }
7496 }
7497}