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 {
971 args[1].to_integer().clamp(0, 30)
972 } else {
973 0
974 };
975 if !(-4_503_599_627_370_496.0..=4_503_599_627_370_496.0).contains(&x) {
977 return Ok(SqliteValue::Float(x));
978 }
979 let rounded = format_fixed_round_half_away(x, n as usize)
984 .parse::<f64>()
985 .unwrap_or(x);
986 Ok(SqliteValue::Float(rounded))
987 }
988
989 fn num_args(&self) -> i32 {
990 -1 }
992
993 fn min_args(&self) -> i32 {
994 1
995 }
996
997 fn max_args(&self) -> Option<i32> {
998 Some(2)
999 }
1000
1001 fn name(&self) -> &str {
1002 "round"
1003 }
1004}
1005
1006pub struct SignFunc;
1009
1010impl ScalarFunction for SignFunc {
1011 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1012 if args[0].is_null() {
1013 return Ok(SqliteValue::Null);
1014 }
1015 match &args[0] {
1016 SqliteValue::Null => Ok(SqliteValue::Null),
1017 SqliteValue::Integer(i) => Ok(SqliteValue::Integer(i.signum())),
1018 SqliteValue::Float(f) => {
1019 if f.is_nan() {
1020 Ok(SqliteValue::Null)
1021 } else if *f > 0.0 {
1022 Ok(SqliteValue::Integer(1))
1023 } else if *f < 0.0 {
1024 Ok(SqliteValue::Integer(-1))
1025 } else {
1026 Ok(SqliteValue::Integer(0))
1027 }
1028 }
1029 SqliteValue::Text(s) => {
1030 let trimmed = s.trim_matches(|ch: char| ch.is_ascii_whitespace());
1032 if trimmed.is_empty() {
1033 return Ok(SqliteValue::Null);
1034 }
1035
1036 let stripped = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed);
1042 if stripped.eq_ignore_ascii_case("nan")
1043 || stripped.eq_ignore_ascii_case("inf")
1044 || stripped.eq_ignore_ascii_case("infinity")
1045 {
1046 return Ok(SqliteValue::Null);
1047 }
1048
1049 if let Ok(f) = trimmed.parse::<f64>() {
1052 if f > 0.0 {
1054 Ok(SqliteValue::Integer(1))
1055 } else if f < 0.0 {
1056 Ok(SqliteValue::Integer(-1))
1057 } else {
1058 Ok(SqliteValue::Integer(0))
1059 }
1060 } else if let Ok(i) = trimmed.parse::<i64>() {
1061 Ok(SqliteValue::Integer(i.signum()))
1063 } else {
1064 Ok(SqliteValue::Null)
1065 }
1066 }
1067 SqliteValue::Blob(_) => Ok(SqliteValue::Null),
1068 }
1069 }
1070
1071 fn num_args(&self) -> i32 {
1072 1
1073 }
1074
1075 fn name(&self) -> &str {
1076 "sign"
1077 }
1078}
1079
1080pub struct RandomFunc;
1083
1084impl ScalarFunction for RandomFunc {
1085 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1086 let val = simple_random_i64();
1089 Ok(SqliteValue::Integer(val))
1090 }
1091
1092 fn is_deterministic(&self) -> bool {
1093 false
1094 }
1095
1096 fn num_args(&self) -> i32 {
1097 0
1098 }
1099
1100 fn name(&self) -> &str {
1101 "random"
1102 }
1103}
1104
1105fn simple_random_i64() -> i64 {
1107 use std::sync::atomic::{AtomicU64, Ordering};
1112
1113 static STATE: AtomicU64 = AtomicU64::new(0xD1B5_4A32_D192_ED03);
1114 let mut x = STATE.fetch_add(0x9E37_79B9_7F4A_7C15, Ordering::Relaxed);
1115 x ^= x >> 30;
1116 x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9);
1117 x ^= x >> 27;
1118 x = x.wrapping_mul(0x94D0_49BB_1331_11EB);
1119 x ^= x >> 31;
1120 x as i64
1121}
1122
1123pub struct RandomblobFunc;
1126
1127impl ScalarFunction for RandomblobFunc {
1128 #[allow(clippy::cast_sign_loss)]
1129 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1130 let n_i64 = if args[0].is_null() {
1134 1
1135 } else {
1136 args[0].to_integer().max(1)
1137 };
1138 if n_i64 > 1_000_000_000 {
1139 return Err(FrankenError::TooBig);
1140 }
1141 let n = n_i64 as usize;
1142 let mut buf = vec![0u8; n];
1143 let mut i = 0;
1144 while i < n {
1145 let rnd = simple_random_i64().to_ne_bytes();
1146 let to_copy = (n - i).min(8);
1147 buf[i..i + to_copy].copy_from_slice(&rnd[..to_copy]);
1148 i += to_copy;
1149 }
1150 Ok(SqliteValue::Blob(Arc::from(buf.as_slice())))
1151 }
1152
1153 fn is_deterministic(&self) -> bool {
1154 false
1155 }
1156
1157 fn num_args(&self) -> i32 {
1158 1
1159 }
1160
1161 fn name(&self) -> &str {
1162 "randomblob"
1163 }
1164}
1165
1166pub struct ZeroblobFunc;
1169
1170impl ScalarFunction for ZeroblobFunc {
1171 #[allow(clippy::cast_sign_loss)]
1172 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1173 if args[0].is_null() {
1175 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1176 }
1177 let n_i64 = args[0].to_integer().max(0);
1178 if n_i64 > 1_000_000_000 {
1179 return Err(FrankenError::TooBig);
1180 }
1181 let n = n_i64 as usize;
1182 Ok(SqliteValue::Blob(Arc::from(vec![0u8; n].as_slice())))
1183 }
1184
1185 fn num_args(&self) -> i32 {
1186 1
1187 }
1188
1189 fn name(&self) -> &str {
1190 "zeroblob"
1191 }
1192}
1193
1194pub struct QuoteFunc;
1197
1198impl ScalarFunction for QuoteFunc {
1199 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1200 let result = quote_sql_value(&args[0], false);
1201 Ok(SqliteValue::Text(SmallText::from_string(result)))
1202 }
1203
1204 fn num_args(&self) -> i32 {
1205 1
1206 }
1207
1208 fn name(&self) -> &str {
1209 "quote"
1210 }
1211}
1212
1213pub struct UnistrQuoteFunc;
1216
1217impl ScalarFunction for UnistrQuoteFunc {
1218 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1219 let result = quote_sql_value(&args[0], true);
1220 Ok(SqliteValue::Text(SmallText::from_string(result)))
1221 }
1222
1223 fn num_args(&self) -> i32 {
1224 1
1225 }
1226
1227 fn name(&self) -> &str {
1228 "unistr_quote"
1229 }
1230}
1231
1232fn quote_sql_value(value: &SqliteValue, use_unistr_quote: bool) -> String {
1233 match value {
1234 SqliteValue::Null => "NULL".to_owned(),
1235 SqliteValue::Integer(i) => i.to_string(),
1236 SqliteValue::Float(f) => format_sqlite_float(*f),
1237 SqliteValue::Text(s) => quote_sql_text_literal(s.as_str(), use_unistr_quote),
1238 SqliteValue::Blob(b) => {
1239 let mut hex = String::with_capacity(3 + b.len() * 2);
1240 hex.push_str("X'");
1241 for byte in b.iter() {
1242 let _ = write!(hex, "{byte:02X}");
1243 }
1244 hex.push('\'');
1245 hex
1246 }
1247 }
1248}
1249
1250fn quote_sql_text_literal(text: &str, use_unistr_quote: bool) -> String {
1251 let text = sqlite_text_until_nul(text);
1252 if use_unistr_quote && text.chars().any(is_unistr_control_char) {
1253 return unistr_quote_sql_text_literal(text);
1254 }
1255
1256 let mut quoted = String::with_capacity(text.len() + 2);
1257 quoted.push('\'');
1258 append_sql_string_literal_body(&mut quoted, text);
1259 quoted.push('\'');
1260 quoted
1261}
1262
1263fn unistr_quote_sql_text_literal(text: &str) -> String {
1264 let mut quoted = String::with_capacity(text.len() + 12);
1265 quoted.push_str("unistr('");
1266 for ch in text.chars() {
1267 match ch {
1268 '\'' => quoted.push_str("''"),
1269 '\\' => quoted.push_str("\\\\"),
1270 _ if is_unistr_control_char(ch) => {
1271 let _ = write!(quoted, "\\u{:04x}", ch as u32);
1272 }
1273 _ => quoted.push(ch),
1274 }
1275 }
1276 quoted.push_str("')");
1277 quoted
1278}
1279
1280fn append_sql_string_literal_body(out: &mut String, text: &str) {
1281 for ch in text.chars() {
1282 if ch == '\'' {
1283 out.push_str("''");
1284 } else {
1285 out.push(ch);
1286 }
1287 }
1288}
1289
1290fn is_unistr_control_char(ch: char) -> bool {
1291 matches!(ch, '\u{0001}'..='\u{001F}')
1292}
1293
1294pub struct UnhexFunc;
1297
1298impl ScalarFunction for UnhexFunc {
1299 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1300 if args[0].is_null() {
1301 return Ok(SqliteValue::Null);
1302 }
1303 if args.len() > 1 && args[1].is_null() {
1304 return Ok(SqliteValue::Null);
1305 }
1306 let input = text_arg(&args[0]);
1307 let ignore_chars: Vec<char> = if args.len() > 1 {
1308 text_arg(&args[1])
1309 .chars()
1310 .filter(|&c| hex_digit(c).is_none())
1311 .collect()
1312 } else {
1313 Vec::new()
1314 };
1315
1316 let mut bytes = Vec::with_capacity(input.len() / 2);
1317 let mut hi_nibble = None;
1318 for c in input.as_ref().chars() {
1319 if ignore_chars.contains(&c) {
1320 if hi_nibble.is_some() {
1321 return Ok(SqliteValue::Null);
1322 }
1323 continue;
1324 }
1325 let digit = match hex_digit(c) {
1326 Some(v) => v,
1327 None => return Ok(SqliteValue::Null),
1328 };
1329 if let Some(hi) = hi_nibble.take() {
1330 bytes.push(hi << 4 | digit);
1331 } else {
1332 hi_nibble = Some(digit);
1333 }
1334 }
1335 if hi_nibble.is_some() {
1336 return Ok(SqliteValue::Null);
1337 }
1338 Ok(SqliteValue::Blob(Arc::from(bytes.as_slice())))
1339 }
1340
1341 fn num_args(&self) -> i32 {
1342 -1 }
1344
1345 fn min_args(&self) -> i32 {
1346 1
1347 }
1348
1349 fn max_args(&self) -> Option<i32> {
1350 Some(2)
1351 }
1352
1353 fn name(&self) -> &str {
1354 "unhex"
1355 }
1356}
1357
1358fn hex_digit(c: char) -> Option<u8> {
1359 match c {
1360 '0'..='9' => Some(c as u8 - b'0'),
1361 'a'..='f' => Some(c as u8 - b'a' + 10),
1362 'A'..='F' => Some(c as u8 - b'A' + 10),
1363 _ => None,
1364 }
1365}
1366
1367pub struct UnicodeFunc;
1370
1371impl ScalarFunction for UnicodeFunc {
1372 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1373 if args[0].is_null() {
1374 return Ok(SqliteValue::Null);
1375 }
1376 if let SqliteValue::Blob(bytes) = &args[0] {
1377 return Ok(
1378 sqlite_blob_first_codepoint(bytes).map_or(SqliteValue::Null, SqliteValue::Integer)
1379 );
1380 }
1381 let s = text_arg(&args[0]);
1382 match sqlite_text_until_nul(s.as_ref()).chars().next() {
1383 Some(c) => Ok(SqliteValue::Integer(i64::from(c as u32))),
1384 None => Ok(SqliteValue::Null),
1385 }
1386 }
1387
1388 fn num_args(&self) -> i32 {
1389 1
1390 }
1391
1392 fn name(&self) -> &str {
1393 "unicode"
1394 }
1395}
1396
1397fn sqlite_blob_first_codepoint(bytes: &[u8]) -> Option<i64> {
1398 let first = *bytes.first()?;
1399 if first == 0 {
1400 return None;
1401 }
1402 let mut codepoint = match first {
1403 0x00..=0xBF => u32::from(first),
1404 0xC0..=0xDF => u32::from(first & 0x1F),
1405 0xE0..=0xEF => u32::from(first & 0x0F),
1406 0xF0..=0xF7 => u32::from(first & 0x07),
1407 _ => 0xFFFD,
1408 };
1409
1410 if first >= 0xC0 && first <= 0xF7 {
1411 for byte in bytes
1412 .iter()
1413 .copied()
1414 .skip(1)
1415 .take_while(|byte| byte & 0xC0 == 0x80)
1416 {
1417 codepoint = codepoint
1418 .wrapping_shl(6)
1419 .wrapping_add(u32::from(byte & 0x3F));
1420 }
1421 if codepoint < 0x80
1422 || (codepoint & 0xFFFF_F800) == 0xD800
1423 || (codepoint & 0xFFFF_FFFE) == 0xFFFE
1424 {
1425 codepoint = 0xFFFD;
1426 }
1427 }
1428
1429 Some(i64::from(codepoint))
1430}
1431
1432pub struct SubstrFunc;
1435
1436impl ScalarFunction for SubstrFunc {
1437 #[allow(clippy::cast_sign_loss, clippy::cast_possible_wrap)]
1438 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1439 if args[0].is_null() || args[1].is_null() {
1440 return Ok(SqliteValue::Null);
1441 }
1442 let is_blob = matches!(&args[0], SqliteValue::Blob(_));
1443 if is_blob {
1444 return self.invoke_blob(args);
1445 }
1446
1447 let text = text_arg(&args[0]);
1448 let full = text.as_ref();
1453 let s = full.split_once('\0').map_or(full, |(prefix, _)| prefix);
1454 let ascii_fast_path = s.is_ascii();
1455 let len = if ascii_fast_path {
1456 s.len() as i64
1457 } else {
1458 s.chars().count() as i64
1459 };
1460 let has_length = args.len() > 2 && !args[2].is_null();
1461
1462 let mut p1 = args[1].to_integer();
1463 let mut p2 = if has_length {
1464 args[2].to_integer()
1465 } else {
1466 1_000_000_000
1467 };
1468
1469 let neg_p2 = p2 < 0;
1473 if neg_p2 {
1474 p2 = p2.saturating_neg();
1475 }
1476
1477 if p1 < 0 {
1479 p1 = p1.saturating_add(len);
1480 if p1 < 0 {
1481 p2 = p2.saturating_add(p1);
1482 p1 = 0;
1483 }
1484 } else if p1 > 0 {
1485 p1 -= 1;
1486 } else if p2 > 0 {
1487 p2 -= 1; }
1489
1490 if neg_p2 {
1492 p1 = p1.saturating_sub(p2);
1493 if p1 < 0 {
1494 p2 = p2.saturating_add(p1);
1495 p1 = 0;
1496 }
1497 }
1498
1499 if p1.saturating_add(p2) > len {
1500 p2 = len.saturating_sub(p1);
1501 }
1502 if p2 <= 0 {
1503 return Ok(SqliteValue::Text(SmallText::new("")));
1504 }
1505
1506 if ascii_fast_path {
1507 let start = p1 as usize;
1508 let end = (p1 + p2) as usize;
1509 return Ok(SqliteValue::Text(SmallText::new(&s[start..end])));
1510 }
1511
1512 let chars: Vec<char> = s.chars().collect();
1513 let result: String = chars[p1 as usize..(p1 + p2) as usize].iter().collect();
1514 Ok(SqliteValue::Text(SmallText::from_string(result)))
1515 }
1516
1517 fn num_args(&self) -> i32 {
1518 -1 }
1520
1521 fn min_args(&self) -> i32 {
1522 2
1523 }
1524
1525 fn max_args(&self) -> Option<i32> {
1526 Some(3)
1527 }
1528
1529 fn name(&self) -> &str {
1530 "substr"
1531 }
1532}
1533
1534impl SubstrFunc {
1535 #[allow(
1536 clippy::unused_self,
1537 clippy::cast_sign_loss,
1538 clippy::cast_possible_wrap
1539 )]
1540 fn invoke_blob(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1541 let blob = match &args[0] {
1542 SqliteValue::Blob(b) => b,
1543 _ => return Ok(SqliteValue::Null),
1544 };
1545 let len = blob.len() as i64;
1546 let has_length = args.len() > 2 && !args[2].is_null();
1547
1548 let mut p1 = args[1].to_integer();
1549 let mut p2 = if has_length {
1550 args[2].to_integer()
1551 } else {
1552 1_000_000_000
1553 };
1554
1555 let neg_p2 = p2 < 0;
1556 if neg_p2 {
1557 p2 = p2.saturating_neg();
1558 }
1559
1560 if p1 < 0 {
1561 p1 = p1.saturating_add(len);
1562 if p1 < 0 {
1563 p2 = p2.saturating_add(p1);
1564 p1 = 0;
1565 }
1566 } else if p1 > 0 {
1567 p1 -= 1;
1568 } else if p2 > 0 {
1569 p2 -= 1;
1570 }
1571
1572 if neg_p2 {
1573 p1 = p1.saturating_sub(p2);
1574 if p1 < 0 {
1575 p2 = p2.saturating_add(p1);
1576 p1 = 0;
1577 }
1578 }
1579
1580 if p1.saturating_add(p2) > len {
1581 p2 = len.saturating_sub(p1);
1582 }
1583 if p2 <= 0 {
1584 return Ok(SqliteValue::Blob(Arc::from([] as [u8; 0])));
1585 }
1586
1587 Ok(SqliteValue::Blob(Arc::from(
1588 &blob[p1 as usize..(p1 + p2) as usize],
1589 )))
1590 }
1591}
1592
1593pub struct SoundexFunc;
1596
1597impl ScalarFunction for SoundexFunc {
1598 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1599 if args[0].is_null() {
1600 return Ok(SqliteValue::Text(SmallText::new("?000")));
1602 }
1603 let s = text_arg(&args[0]);
1604 let code = soundex(s.as_ref());
1605 let text = std::str::from_utf8(&code).expect("Soundex output must be ASCII");
1606 Ok(SqliteValue::Text(SmallText::new(text)))
1607 }
1608
1609 fn num_args(&self) -> i32 {
1610 1
1611 }
1612
1613 fn name(&self) -> &str {
1614 "soundex"
1615 }
1616}
1617
1618fn soundex(s: &str) -> [u8; 4] {
1619 let mut chars = s.chars().filter(|c| c.is_ascii_alphabetic());
1620 let first = match chars.next() {
1621 Some(c) => c.to_ascii_uppercase(),
1622 None => return *b"?000",
1623 };
1624
1625 let code = |c: char| -> Option<u8> {
1626 match c.to_ascii_uppercase() {
1627 'B' | 'F' | 'P' | 'V' => Some(b'1'),
1628 'C' | 'G' | 'J' | 'K' | 'Q' | 'S' | 'X' | 'Z' => Some(b'2'),
1629 'D' | 'T' => Some(b'3'),
1630 'L' => Some(b'4'),
1631 'M' | 'N' => Some(b'5'),
1632 'R' => Some(b'6'),
1633 _ => None, }
1635 };
1636
1637 let mut result = *b"0000";
1638 result[0] = first as u8;
1639 let mut result_len = 1;
1640 let mut last_code = code(first);
1641
1642 for c in chars {
1643 if result_len >= result.len() {
1644 break;
1645 }
1646 let current = code(c);
1647 if let Some(digit) = current
1648 && current != last_code
1649 {
1650 result[result_len] = digit;
1651 result_len += 1;
1652 }
1653 last_code = current;
1654 }
1655
1656 result
1657}
1658
1659pub struct ScalarMaxFunc;
1662
1663impl ScalarFunction for ScalarMaxFunc {
1664 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1665 self.invoke_with_collation(args, None)
1666 }
1667
1668 fn consumes_argument_collation(&self) -> bool {
1669 true
1670 }
1671
1672 fn invoke_with_collation(
1673 &self,
1674 args: &[SqliteValue],
1675 collation: Option<&dyn crate::collation::CollationFunction>,
1676 ) -> Result<SqliteValue> {
1677 if let Some(null) = null_propagate(args) {
1679 return Ok(null);
1680 }
1681 let mut max = &args[0];
1682 for arg in &args[1..] {
1683 let ordering = match (arg, max, collation) {
1684 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1685 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1686 }
1687 _ => arg.partial_cmp(max),
1688 };
1689 if ordering == Some(std::cmp::Ordering::Greater) {
1690 max = arg;
1691 }
1692 }
1693 Ok(max.clone())
1694 }
1695
1696 fn num_args(&self) -> i32 {
1697 -1
1698 }
1699
1700 fn min_args(&self) -> i32 {
1701 1
1702 }
1703
1704 fn name(&self) -> &str {
1705 "max"
1706 }
1707}
1708
1709pub struct ScalarMinFunc;
1712
1713impl ScalarFunction for ScalarMinFunc {
1714 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1715 self.invoke_with_collation(args, None)
1716 }
1717
1718 fn consumes_argument_collation(&self) -> bool {
1719 true
1720 }
1721
1722 fn invoke_with_collation(
1723 &self,
1724 args: &[SqliteValue],
1725 collation: Option<&dyn crate::collation::CollationFunction>,
1726 ) -> Result<SqliteValue> {
1727 if let Some(null) = null_propagate(args) {
1729 return Ok(null);
1730 }
1731 let mut min = &args[0];
1732 for arg in &args[1..] {
1733 let ordering = match (arg, min, collation) {
1734 (SqliteValue::Text(left), SqliteValue::Text(right), Some(collation)) => {
1735 Some(collation.compare(left.as_bytes(), right.as_bytes()))
1736 }
1737 _ => arg.partial_cmp(min),
1738 };
1739 if matches!(
1743 ordering,
1744 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
1745 ) {
1746 min = arg;
1747 }
1748 }
1749 Ok(min.clone())
1750 }
1751
1752 fn num_args(&self) -> i32 {
1753 -1
1754 }
1755
1756 fn min_args(&self) -> i32 {
1757 1
1758 }
1759
1760 fn name(&self) -> &str {
1761 "min"
1762 }
1763}
1764
1765pub struct LikelihoodFunc;
1768
1769impl ScalarFunction for LikelihoodFunc {
1770 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1771 Ok(args[0].clone())
1773 }
1774
1775 fn num_args(&self) -> i32 {
1776 2
1777 }
1778
1779 fn name(&self) -> &str {
1780 "likelihood"
1781 }
1782}
1783
1784pub struct LikelyFunc;
1785
1786impl ScalarFunction for LikelyFunc {
1787 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1788 Ok(args[0].clone())
1789 }
1790
1791 fn num_args(&self) -> i32 {
1792 1
1793 }
1794
1795 fn name(&self) -> &str {
1796 "likely"
1797 }
1798}
1799
1800pub struct UnlikelyFunc;
1801
1802impl ScalarFunction for UnlikelyFunc {
1803 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1804 Ok(args[0].clone())
1805 }
1806
1807 fn num_args(&self) -> i32 {
1808 1
1809 }
1810
1811 fn name(&self) -> &str {
1812 "unlikely"
1813 }
1814}
1815
1816pub struct SqliteVersionFunc;
1819
1820impl ScalarFunction for SqliteVersionFunc {
1821 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1822 Ok(SqliteValue::Text(SmallText::new(
1823 fsqlite_types::FRANKENSQLITE_SQLITE_VERSION,
1824 )))
1825 }
1826
1827 fn is_deterministic(&self) -> bool {
1828 false
1829 }
1830
1831 fn num_args(&self) -> i32 {
1832 0
1833 }
1834
1835 fn name(&self) -> &str {
1836 "sqlite_version"
1837 }
1838}
1839
1840pub struct SqliteSourceIdFunc;
1843
1844impl ScalarFunction for SqliteSourceIdFunc {
1845 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
1846 Ok(SqliteValue::Text(SmallText::new(
1847 fsqlite_types::FRANKENSQLITE_SOURCE_ID,
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_source_id"
1861 }
1862}
1863
1864pub struct SqliteCompileoptionUsedFunc;
1867
1868impl ScalarFunction for SqliteCompileoptionUsedFunc {
1869 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1870 if args[0].is_null() {
1871 return Ok(SqliteValue::Null);
1872 }
1873 let query = text_arg(&args[0]);
1874 Ok(SqliteValue::Integer(i64::from(sqlite_compileoption_used(
1875 query.as_ref(),
1876 ))))
1877 }
1878
1879 fn is_deterministic(&self) -> bool {
1880 false
1881 }
1882
1883 fn num_args(&self) -> i32 {
1884 1
1885 }
1886
1887 fn name(&self) -> &str {
1888 "sqlite_compileoption_used"
1889 }
1890}
1891
1892pub struct SqliteCompileoptionGetFunc;
1895
1896impl ScalarFunction for SqliteCompileoptionGetFunc {
1897 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1898 if args[0].is_null() {
1899 return Ok(SqliteValue::Null);
1900 }
1901 let n = args[0].to_integer();
1902 #[allow(clippy::cast_sign_loss)]
1903 match sqlite_compile_options().get(n as usize) {
1904 Some(opt) => Ok(SqliteValue::Text(SmallText::new(opt))),
1905 None => Ok(SqliteValue::Null),
1906 }
1907 }
1908
1909 fn is_deterministic(&self) -> bool {
1910 false
1911 }
1912
1913 fn num_args(&self) -> i32 {
1914 1
1915 }
1916
1917 fn name(&self) -> &str {
1918 "sqlite_compileoption_get"
1919 }
1920}
1921
1922pub struct LikeFunc;
1925
1926impl ScalarFunction for LikeFunc {
1927 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
1928 if let Some(null) = null_propagate(args) {
1929 return Ok(null);
1930 }
1931 let pattern = text_arg(&args[0]);
1932 let string = text_arg(&args[1]);
1933 let escape = if args.len() > 2 && !args[2].is_null() {
1934 Some(single_char_escape(text_arg(&args[2]).as_ref())?)
1935 } else {
1936 None
1937 };
1938 let matched = like_match(pattern.as_ref(), string.as_ref(), escape);
1939 Ok(SqliteValue::Integer(i64::from(matched)))
1940 }
1941
1942 fn num_args(&self) -> i32 {
1943 -1 }
1945
1946 fn min_args(&self) -> i32 {
1947 2
1948 }
1949
1950 fn max_args(&self) -> Option<i32> {
1951 Some(3)
1952 }
1953
1954 fn name(&self) -> &str {
1955 "like"
1956 }
1957}
1958
1959#[cfg(test)]
1960mod like_func_pragma_tests {
1961 use super::{LikeFunc, case_sensitive_like_active, set_case_sensitive_like};
1962 use crate::ScalarFunction;
1963 use fsqlite_types::SqliteValue;
1964
1965 fn like(pattern: &str, text: &str) -> i64 {
1966 match LikeFunc
1967 .invoke(&[
1968 SqliteValue::Text(pattern.into()),
1969 SqliteValue::Text(text.into()),
1970 ])
1971 .unwrap()
1972 {
1973 SqliteValue::Integer(n) => n,
1974 other => panic!("expected integer, got {other:?}"),
1975 }
1976 }
1977
1978 #[test]
1979 fn like_honors_case_sensitive_like_thread_local() {
1980 set_case_sensitive_like(false);
1982 assert_eq!(like("a", "A"), 1);
1983 assert_eq!(like("A%", "apple"), 1);
1984 set_case_sensitive_like(true);
1986 assert!(case_sensitive_like_active());
1987 assert_eq!(like("a", "A"), 0);
1988 assert_eq!(like("A%", "apple"), 0);
1989 assert_eq!(like("A%", "Apple"), 1);
1990 set_case_sensitive_like(false);
1992 }
1993}
1994
1995fn single_char_escape(escape: &str) -> Result<char> {
1996 let mut chars = escape.chars();
1997 match (chars.next(), chars.next()) {
1998 (Some(ch), None) => Ok(ch),
1999 _ => Err(FrankenError::function_error(
2000 "ESCAPE expression must be a single character",
2001 )),
2002 }
2003}
2004
2005fn like_match(pattern: &str, string: &str, escape: Option<char>) -> bool {
2009 sql_like_cased(pattern, string, escape, case_sensitive_like_active())
2010}
2011
2012pub struct GlobFunc;
2015
2016impl ScalarFunction for GlobFunc {
2017 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2018 if let Some(null) = null_propagate(args) {
2019 return Ok(null);
2020 }
2021 let pattern = text_arg(&args[0]);
2022 let string = text_arg(&args[1]);
2023 let matched = glob_match(pattern.as_ref(), string.as_ref());
2024 Ok(SqliteValue::Integer(i64::from(matched)))
2025 }
2026
2027 fn num_args(&self) -> i32 {
2028 2
2029 }
2030
2031 fn name(&self) -> &str {
2032 "glob"
2033 }
2034}
2035
2036fn glob_match(pattern: &str, string: &str) -> bool {
2038 let pat: Vec<char> = pattern.chars().collect();
2039 let txt: Vec<char> = string.chars().collect();
2040 glob_match_inner(&pat, &txt, 0, 0)
2041}
2042
2043fn text_arg(value: &SqliteValue) -> Cow<'_, str> {
2044 match value.as_text_str() {
2045 Some(text) => Cow::Borrowed(text),
2046 None => Cow::Owned(value.to_text()),
2047 }
2048}
2049
2050fn glob_match_inner(pat: &[char], txt: &[char], mut pi: usize, mut ti: usize) -> bool {
2051 while pi < pat.len() {
2052 match pat[pi] {
2053 '*' => {
2054 while pi < pat.len() && pat[pi] == '*' {
2055 pi += 1;
2056 }
2057 if pi >= pat.len() {
2058 return true;
2059 }
2060 for start in ti..=txt.len() {
2061 if glob_match_inner(pat, txt, pi, start) {
2062 return true;
2063 }
2064 }
2065 return false;
2066 }
2067 '?' => {
2068 if ti >= txt.len() {
2069 return false;
2070 }
2071 pi += 1;
2072 ti += 1;
2073 }
2074 '[' => {
2075 if ti >= txt.len() {
2076 return false;
2077 }
2078 pi += 1;
2079 let negate = pi < pat.len() && pat[pi] == '^';
2080 if negate {
2081 pi += 1;
2082 }
2083 let mut found = false;
2084 let mut first = true;
2085 while pi < pat.len() && (first || pat[pi] != ']') {
2086 first = false;
2087 if pi + 2 < pat.len() && pat[pi + 1] == '-' && pat[pi + 2] != ']' {
2094 let lo = pat[pi];
2095 let hi = pat[pi + 2];
2096 if txt[ti] >= lo && txt[ti] <= hi {
2097 found = true;
2098 }
2099 pi += 3;
2100 } else {
2101 if txt[ti] == pat[pi] {
2102 found = true;
2103 }
2104 pi += 1;
2105 }
2106 }
2107 if pi < pat.len() && pat[pi] == ']' {
2108 pi += 1;
2109 } else {
2110 return false;
2114 }
2115 if found == negate {
2116 return false;
2117 }
2118 ti += 1;
2119 }
2120 c => {
2121 if ti >= txt.len() || txt[ti] != c {
2122 return false;
2123 }
2124 pi += 1;
2125 ti += 1;
2126 }
2127 }
2128 }
2129 ti >= txt.len()
2130}
2131
2132pub struct UnistrFunc;
2135
2136const INVALID_UNISTR_ESCAPE: &str = "invalid Unicode escape";
2137
2138fn decode_unistr_escape(chars: &mut std::str::Chars<'_>, digits: usize) -> Result<char> {
2139 let mut lookahead = chars.clone();
2140 let mut codepoint = 0u32;
2141 for _ in 0..digits {
2142 let Some(ch) = lookahead.next() else {
2143 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2144 };
2145 let Some(digit) = hex_digit(ch) else {
2146 return Err(FrankenError::function_error(INVALID_UNISTR_ESCAPE));
2147 };
2148 codepoint = (codepoint << 4) | u32::from(digit);
2149 }
2150 for _ in 0..digits {
2151 let _digit = chars.next();
2152 }
2153 char::from_u32(codepoint).ok_or_else(|| FrankenError::function_error(INVALID_UNISTR_ESCAPE))
2154}
2155
2156impl ScalarFunction for UnistrFunc {
2157 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2158 if args[0].is_null() {
2159 return Ok(SqliteValue::Null);
2160 }
2161 let input = text_arg(&args[0]);
2162 let mut result = String::with_capacity(input.len());
2163 let mut chars = input.as_ref().chars();
2164 while let Some(ch) = chars.next() {
2165 if ch == '\\' {
2166 if chars.as_str().starts_with('\\') {
2168 let _ = chars.next();
2169 result.push('\\');
2170 continue;
2171 }
2172 let digits = if chars.as_str().starts_with('+') {
2173 let _plus = chars.next();
2175 6
2176 } else if chars.as_str().starts_with('u') {
2177 let _marker = chars.next();
2179 4
2180 } else if chars.as_str().starts_with('U') {
2181 let _marker = chars.next();
2183 8
2184 } else {
2185 4
2187 };
2188 result.push(decode_unistr_escape(&mut chars, digits)?);
2189 continue;
2190 }
2191 result.push(ch);
2192 }
2193 Ok(SqliteValue::Text(SmallText::from_string(result)))
2194 }
2195
2196 fn num_args(&self) -> i32 {
2197 1
2198 }
2199
2200 fn name(&self) -> &str {
2201 "unistr"
2202 }
2203}
2204
2205pub struct ChangesFunc;
2210
2211impl ScalarFunction for ChangesFunc {
2212 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2213 Ok(SqliteValue::Integer(LAST_CHANGES.get()))
2214 }
2215
2216 fn is_deterministic(&self) -> bool {
2217 false
2218 }
2219
2220 fn num_args(&self) -> i32 {
2221 0
2222 }
2223
2224 fn name(&self) -> &str {
2225 "changes"
2226 }
2227}
2228
2229pub struct TotalChangesFunc;
2230
2231impl ScalarFunction for TotalChangesFunc {
2232 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2233 Ok(SqliteValue::Integer(TOTAL_CHANGES.get()))
2234 }
2235
2236 fn is_deterministic(&self) -> bool {
2237 false
2238 }
2239
2240 fn num_args(&self) -> i32 {
2241 0
2242 }
2243
2244 fn name(&self) -> &str {
2245 "total_changes"
2246 }
2247}
2248
2249pub struct LastInsertRowidFunc;
2250
2251impl ScalarFunction for LastInsertRowidFunc {
2252 fn invoke(&self, _args: &[SqliteValue]) -> Result<SqliteValue> {
2253 Ok(SqliteValue::Integer(LAST_INSERT_ROWID.get()))
2254 }
2255
2256 fn is_deterministic(&self) -> bool {
2257 false
2258 }
2259
2260 fn num_args(&self) -> i32 {
2261 0
2262 }
2263
2264 fn name(&self) -> &str {
2265 "last_insert_rowid"
2266 }
2267}
2268
2269#[allow(clippy::too_many_lines)]
2273pub fn register_builtins(registry: &mut FunctionRegistry) {
2274 registry.register_scalar(AbsFunc);
2276 registry.register_scalar(SignFunc);
2277 registry.register_scalar(RoundFunc);
2278 registry.register_scalar(RandomFunc);
2279 registry.register_scalar(RandomblobFunc);
2280 registry.register_scalar(ZeroblobFunc);
2281
2282 registry.register_scalar(LowerFunc);
2284 registry.register_scalar(UpperFunc);
2285 registry.register_scalar(LengthFunc);
2286 registry.register_scalar(OctetLengthFunc);
2287 registry.register_scalar(TrimFunc);
2288 registry.register_scalar(LtrimFunc);
2289 registry.register_scalar(RtrimFunc);
2290 registry.register_scalar(ReplaceFunc);
2291 registry.register_scalar(SubstrFunc);
2292 registry.register_scalar(InstrFunc);
2293 registry.register_scalar(CharFunc);
2294 registry.register_scalar(UnicodeFunc);
2295 registry.register_scalar(UnistrFunc);
2296 registry.register_scalar(HexFunc);
2297 registry.register_scalar(UnhexFunc);
2298 registry.register_scalar(QuoteFunc);
2299 registry.register_scalar(UnistrQuoteFunc);
2300 registry.register_scalar(SoundexFunc);
2301
2302 registry.register_scalar(TypeofFunc);
2304 registry.register_scalar(SubtypeFunc);
2305
2306 registry.register_scalar(CoalesceFunc);
2308 registry.register_scalar(IfnullFunc);
2309 registry.register_scalar(NullifFunc);
2310 registry.register_scalar(IifFunc);
2311
2312 registry.register_scalar(ConcatFunc);
2314 registry.register_scalar(ConcatWsFunc);
2315 registry.register_scalar(ScalarMaxFunc);
2316 registry.register_scalar(ScalarMinFunc);
2317
2318 registry.register_scalar(LikelihoodFunc);
2320 registry.register_scalar(LikelyFunc);
2321 registry.register_scalar(UnlikelyFunc);
2322
2323 registry.register_scalar(LikeFunc);
2325 registry.register_scalar(GlobFunc);
2326
2327 registry.register_slow_changing_scalar(SqliteVersionFunc);
2329 registry.register_slow_changing_scalar(SqliteSourceIdFunc);
2330 registry.register_slow_changing_scalar(SqliteCompileoptionUsedFunc);
2331 registry.register_slow_changing_scalar(SqliteCompileoptionGetFunc);
2332
2333 registry.register_scalar(ChangesFunc);
2335 registry.register_scalar(TotalChangesFunc);
2336 registry.register_scalar(LastInsertRowidFunc);
2337
2338 struct IfFunc;
2341 impl ScalarFunction for IfFunc {
2342 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2343 IifFunc.invoke(args)
2344 }
2345
2346 fn num_args(&self) -> i32 {
2347 -1 }
2349
2350 fn min_args(&self) -> i32 {
2351 2
2352 }
2353
2354 fn max_args(&self) -> Option<i32> {
2355 Some(3)
2356 }
2357
2358 fn name(&self) -> &str {
2359 "if"
2360 }
2361 }
2362 registry.register_scalar(IfFunc);
2363
2364 struct SubstringFunc;
2366 impl ScalarFunction for SubstringFunc {
2367 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2368 SubstrFunc.invoke(args)
2369 }
2370
2371 fn num_args(&self) -> i32 {
2372 -1
2373 }
2374
2375 fn min_args(&self) -> i32 {
2376 2
2377 }
2378
2379 fn max_args(&self) -> Option<i32> {
2380 Some(3)
2381 }
2382
2383 fn name(&self) -> &str {
2384 "substring"
2385 }
2386 }
2387 registry.register_scalar(SubstringFunc);
2388
2389 struct PrintfFunc;
2391 impl ScalarFunction for PrintfFunc {
2392 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2393 FormatFunc.invoke(args)
2394 }
2395
2396 fn num_args(&self) -> i32 {
2397 -1
2398 }
2399
2400 fn name(&self) -> &str {
2401 "printf"
2402 }
2403 }
2404 registry.register_scalar(FormatFunc);
2405 registry.register_scalar(PrintfFunc);
2406
2407 register_math_builtins(registry);
2409
2410 register_datetime_builtins(registry);
2412
2413 register_aggregate_builtins(registry);
2415}
2416
2417pub struct FormatFunc;
2420
2421impl ScalarFunction for FormatFunc {
2422 fn invoke(&self, args: &[SqliteValue]) -> Result<SqliteValue> {
2423 if args.is_empty() || args[0].is_null() {
2424 return Ok(SqliteValue::Null);
2425 }
2426 let fmt_str = args[0].to_text();
2427 if fmt_str.is_empty() {
2433 return Ok(SqliteValue::Null);
2434 }
2435 let params = &args[1..];
2436 let result = sqlite_format(&fmt_str, params)?;
2437 Ok(SqliteValue::Text(SmallText::from_string(result)))
2438 }
2439
2440 fn num_args(&self) -> i32 {
2441 -1
2442 }
2443
2444 fn name(&self) -> &str {
2445 "format"
2446 }
2447}
2448
2449fn sqlite_format(fmt: &str, params: &[SqliteValue]) -> Result<String> {
2452 let mut result = String::new();
2453 let chars: Vec<char> = fmt.chars().collect();
2454 let mut i = 0;
2455 let mut param_idx = 0;
2456
2457 while i < chars.len() {
2458 if chars[i] != '%' {
2459 result.push(chars[i]);
2460 i += 1;
2461 continue;
2462 }
2463 i += 1;
2464 if i >= chars.len() {
2465 break;
2466 }
2467
2468 let mut left_align = false;
2470 let mut show_sign = false;
2471 let mut space_sign = false;
2472 let mut zero_pad = false;
2473 let mut alt_form = false;
2474 let mut alt_form2 = false;
2475 let mut comma_group = false;
2476 loop {
2477 if i >= chars.len() {
2478 break;
2479 }
2480 match chars[i] {
2481 '-' => left_align = true,
2482 '+' => show_sign = true,
2483 ' ' => space_sign = true,
2484 '0' => zero_pad = true,
2485 '#' => alt_form = true,
2486 '!' => alt_form2 = true,
2489 ',' => comma_group = true,
2494 _ => break,
2495 }
2496 i += 1;
2497 }
2498
2499 let mut width = 0usize;
2503 if i < chars.len() && chars[i] == '*' {
2504 i += 1;
2505 let w = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2506 param_idx += 1;
2507 if w < 0 {
2508 left_align = true;
2509 width = usize::try_from(w.unsigned_abs())
2510 .unwrap_or(0)
2511 .min(100_000_000);
2512 } else {
2513 width = usize::try_from(w).unwrap_or(0).min(100_000_000);
2514 }
2515 } else {
2516 while i < chars.len() && chars[i].is_ascii_digit() {
2517 width = width
2518 .saturating_mul(10)
2519 .saturating_add(chars[i] as usize - '0' as usize)
2520 .min(100_000_000); i += 1;
2522 }
2523 }
2524
2525 let mut precision = None;
2529 if i < chars.len() && chars[i] == '.' {
2530 i += 1;
2531 if i < chars.len() && chars[i] == '*' {
2532 i += 1;
2533 let p = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2534 param_idx += 1;
2535 let p32 = p as i32;
2543 precision = if p32 == i32::MIN {
2544 None
2545 } else {
2546 Some(
2547 usize::try_from(p32.unsigned_abs())
2548 .unwrap_or(usize::MAX)
2549 .min(100_000_000),
2550 )
2551 };
2552 } else {
2553 let mut prec = 0usize;
2554 while i < chars.len() && chars[i].is_ascii_digit() {
2555 prec = prec
2556 .saturating_mul(10)
2557 .saturating_add(chars[i] as usize - '0' as usize)
2558 .min(100_000_000); i += 1;
2560 }
2561 precision = Some(prec);
2562 }
2563 }
2564
2565 if i >= chars.len() {
2566 break;
2567 }
2568
2569 let spec = chars[i];
2570 i += 1;
2571
2572 match spec {
2573 '%' => result.push_str(&pad_string("%", width, left_align)),
2578 'n' => {} 'd' | 'i' => {
2580 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2581 param_idx += 1;
2582 let formatted = format_integer(
2583 val,
2584 width,
2585 left_align,
2586 show_sign,
2587 space_sign,
2588 zero_pad,
2589 comma_group,
2590 precision,
2591 );
2592 result.push_str(&formatted);
2593 }
2594 'u' => {
2595 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2598 param_idx += 1;
2599 #[allow(clippy::cast_sign_loss)]
2600 let digits = apply_int_precision(&(val as u64).to_string(), precision);
2601 let padded = if comma_group {
2602 let base = if zero_pad && width > digits.len() {
2605 format!("{}{}", "0".repeat(width - digits.len()), digits)
2606 } else {
2607 digits
2608 };
2609 let grouped = group_thousands(&base);
2610 if zero_pad {
2611 grouped
2612 } else {
2613 pad_string(&grouped, width, left_align)
2614 }
2615 } else if zero_pad && width > digits.len() {
2616 format!("{}{}", "0".repeat(width - digits.len()), digits)
2617 } else {
2618 pad_string(&digits, width, left_align)
2619 };
2620 result.push_str(&padded);
2621 }
2622 'f' => {
2623 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2624 param_idx += 1;
2625 let val = if val == 0.0 { 0.0 } else { val };
2629 let formatted = if let Some(s) =
2630 nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2631 {
2632 s
2633 } else {
2634 let prec = precision.unwrap_or(6);
2640 let mut mag = if alt_form2 {
2650 if val == 0.0 {
2655 altform2_trim_float("0")
2656 } else {
2657 let (cap_digits, sci_exp) = sqlite_float_altform2_digits(val);
2658 let want =
2660 i64::from(sci_exp) + i64::try_from(prec).unwrap_or(i64::MAX) + 1;
2661 let cap = i64::try_from(cap_digits.len()).unwrap_or(i64::MAX);
2662 if want >= cap {
2663 altform2_render_fixed(&cap_digits, sci_exp, prec)
2664 } else {
2665 altform2_trim_float(&format_fixed_round_half_away(val.abs(), prec))
2666 }
2667 }
2668 } else {
2669 round_positional_to_sig(
2670 &format_fixed_round_half_away(val.abs(), prec),
2671 FLOAT_SIG_DIGITS,
2672 )
2673 };
2674 if alt_form && !mag.contains('.') {
2677 mag.push('.');
2678 }
2679 if comma_group {
2680 mag = group_float_integer_part(&mag);
2681 }
2682 let body = if val.is_sign_negative() {
2683 format!("-{mag}")
2684 } else {
2685 mag
2686 };
2687 finish_float_padding(&body, width, left_align, show_sign, space_sign, zero_pad)
2688 };
2689 result.push_str(&formatted);
2690 }
2691 'e' | 'E' => {
2692 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2693 param_idx += 1;
2694 let val = if val == 0.0 { 0.0 } else { val };
2696 let prec = precision.unwrap_or(6);
2697 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2698 {
2699 result.push_str(&s);
2700 } else if alt_form2 {
2701 let (digits, exp) = altform2_sig_digits(val, prec + 1);
2708 let mut formatted = altform2_render_exp(&digits, exp, spec == 'E');
2709 if val.is_sign_negative() {
2710 formatted = format!("-{formatted}");
2711 }
2712 result.push_str(&finish_float_padding(
2713 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2714 ));
2715 } else {
2716 let mant_prec = prec.min(FLOAT_SIG_DIGITS - 1);
2723 let raw = format_sci_round_half_away(val, mant_prec, spec == 'E');
2724 let mut formatted = normalize_exponent(&raw);
2725 if prec > mant_prec
2726 && let Some(e_pos) = formatted.find(['e', 'E'])
2727 {
2728 let (mant, exp_part) = formatted.split_at(e_pos);
2729 formatted = format!("{mant}{}{exp_part}", "0".repeat(prec - mant_prec));
2730 }
2731 if alt_form && let Some(e_pos) = formatted.find(['e', 'E']) {
2734 let (mantissa, exp_part) = formatted.split_at(e_pos);
2735 if !mantissa.contains('.') {
2736 formatted = format!("{mantissa}.{exp_part}");
2737 }
2738 }
2739 result.push_str(&finish_float_padding(
2740 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2741 ));
2742 }
2743 }
2744 'g' | 'G' => {
2745 let val = params.get(param_idx).map_or(0.0, SqliteValue::to_float);
2746 param_idx += 1;
2747 let val = if val == 0.0 { 0.0 } else { val };
2750 let prec = precision.unwrap_or(6);
2751 let sig = prec.max(1);
2752 let max_sig = FLOAT_SIG_DIGITS;
2757 if let Some(s) = nonfinite_float_str(val, width, left_align, show_sign, space_sign)
2758 {
2759 result.push_str(&s);
2760 } else if alt_form2 {
2761 let (digits, exp) = altform2_sig_digits(val, sig);
2770 let use_exp_form =
2771 exp < -4 || i64::from(exp) >= i64::try_from(sig).unwrap_or(i64::MAX);
2772 let mut alt = if use_exp_form {
2773 altform2_render_exp(&digits, exp, spec == 'G')
2774 } else {
2775 altform2_render_fixed(&digits, exp, usize::MAX)
2776 };
2777 if val.is_sign_negative() {
2778 alt = format!("-{alt}");
2779 }
2780 result.push_str(&finish_float_padding(
2781 &alt, width, left_align, show_sign, space_sign, zero_pad,
2782 ));
2783 } else {
2784 let mut formatted = format_float_g(val, sig, spec == 'G', alt_form, max_sig);
2785 if comma_group && !formatted.contains(['e', 'E']) {
2790 formatted = group_signed_decimal_integer_part(&formatted);
2791 }
2792 result.push_str(&finish_float_padding(
2793 &formatted, width, left_align, show_sign, space_sign, zero_pad,
2794 ));
2795 }
2796 }
2797 's' | 'z' => {
2798 let param = params.get(param_idx);
2799 param_idx += 1;
2800 let val = match param {
2801 Some(SqliteValue::Null) | None => String::new(),
2803 Some(v) => v.to_text(),
2804 };
2805 let truncated = if let Some(prec) = precision {
2810 if val.len() > prec {
2811 let mut end = prec;
2812 while end > 0 && !val.is_char_boundary(end) {
2813 end -= 1;
2814 }
2815 val[..end].to_owned()
2816 } else {
2817 val
2818 }
2819 } else {
2820 val
2821 };
2822 result.push_str(&pad_string(&truncated, width, left_align));
2823 }
2824 'q' => {
2825 let param = params.get(param_idx);
2829 param_idx += 1;
2830 let escaped = match param {
2832 Some(SqliteValue::Null) | None => "(NULL)".to_owned(),
2834 Some(v) => {
2835 let text = v.to_text();
2836 truncate_str_precision(&text, precision).replace('\'', "''")
2837 }
2838 };
2839 result.push_str(&pad_string(&escaped, width, left_align));
2840 }
2841 'Q' => {
2842 let param = params.get(param_idx);
2846 param_idx += 1;
2847 let rendered = match param {
2848 Some(SqliteValue::Null) | None => "NULL".to_owned(),
2849 Some(v) => {
2850 let text = v.to_text();
2851 format!(
2852 "'{}'",
2853 truncate_str_precision(&text, precision).replace('\'', "''")
2854 )
2855 }
2856 };
2857 result.push_str(&pad_string(&rendered, width, left_align));
2858 }
2859 'w' => {
2860 let param = params.get(param_idx);
2865 param_idx += 1;
2866 let rendered = match param {
2867 Some(SqliteValue::Null) | None => "(NULL)".to_owned(),
2868 Some(v) => {
2869 let text = v.to_text();
2870 truncate_str_precision(&text, precision).replace('"', "\"\"")
2871 }
2872 };
2873 result.push_str(&pad_string(&rendered, width, left_align));
2874 }
2875 'x' | 'X' => {
2876 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2877 param_idx += 1;
2878 #[allow(clippy::cast_sign_loss)]
2879 let digits = apply_int_precision(
2880 &if spec == 'x' {
2881 format!("{:x}", val as u64)
2882 } else {
2883 format!("{:X}", val as u64)
2884 },
2885 precision,
2886 );
2887 let prefix = if alt_form && val != 0 {
2889 if spec == 'x' { "0x" } else { "0X" }
2890 } else {
2891 ""
2892 };
2893 let padded = if zero_pad && width > digits.len() {
2898 let pad = "0".repeat(width - digits.len());
2899 format!("{prefix}{pad}{digits}")
2900 } else {
2901 pad_string(&format!("{prefix}{digits}"), width, left_align)
2902 };
2903 result.push_str(&padded);
2904 }
2905 'o' => {
2906 let val = params.get(param_idx).map_or(0, SqliteValue::to_integer);
2907 param_idx += 1;
2908 #[allow(clippy::cast_sign_loss)]
2909 let digits = apply_int_precision(&format!("{:o}", val as u64), precision);
2910 let prefix = if alt_form && val != 0 { "0" } else { "" };
2912 let padded = if zero_pad && width > digits.len() {
2915 let pad = "0".repeat(width - digits.len());
2916 format!("{prefix}{pad}{digits}")
2917 } else {
2918 pad_string(&format!("{prefix}{digits}"), width, left_align)
2919 };
2920 result.push_str(&padded);
2921 }
2922 'c' => {
2923 let param = params.get(param_idx);
2924 param_idx += 1;
2925 let text = match param {
2930 Some(SqliteValue::Null) | None => String::new(),
2931 Some(v) => v.to_text(),
2932 };
2933 let content = match text.chars().next() {
2943 Some(c) => c.to_string().repeat(precision.map_or(1, |p| p.max(1))),
2944 None => String::new(),
2945 };
2946 let pad = width.saturating_sub(content.chars().count());
2947 if !left_align {
2948 for _ in 0..pad {
2949 result.push(' ');
2950 }
2951 }
2952 result.push_str(&content);
2953 if left_align {
2954 for _ in 0..pad {
2955 result.push(' ');
2956 }
2957 }
2958 }
2959 _ => {
2960 result.push('%');
2962 result.push(spec);
2963 }
2964 }
2965 let _ = (left_align, show_sign, space_sign, zero_pad);
2967 }
2968 Ok(result)
2969}
2970
2971fn truncate_str_precision(val: &str, precision: Option<usize>) -> &str {
2975 match precision {
2976 Some(prec) if val.len() > prec => {
2977 let mut end = prec;
2978 while end > 0 && !val.is_char_boundary(end) {
2979 end -= 1;
2980 }
2981 &val[..end]
2982 }
2983 _ => val,
2984 }
2985}
2986
2987#[allow(clippy::too_many_arguments)]
2991fn format_integer(
2992 val: i64,
2993 width: usize,
2994 left_align: bool,
2995 show_sign: bool,
2996 space_sign: bool,
2997 zero_pad: bool,
2998 comma_group: bool,
2999 precision: Option<usize>,
3000) -> String {
3001 let sign = if val < 0 {
3002 "-".to_owned()
3003 } else if show_sign {
3004 "+".to_owned()
3005 } else if space_sign {
3006 " ".to_owned()
3007 } else {
3008 String::new()
3009 };
3010 let digits = apply_int_precision(&format!("{}", val.unsigned_abs()), precision);
3011 if comma_group {
3012 let padded_digits = if zero_pad && width > sign.len() + digits.len() {
3017 format!("{}{digits}", "0".repeat(width - sign.len() - digits.len()))
3018 } else {
3019 digits
3020 };
3021 let body = format!("{sign}{}", group_thousands(&padded_digits));
3022 if zero_pad || body.len() >= width {
3023 return body;
3024 }
3025 let pad = width - body.len();
3026 return if left_align {
3027 format!("{body}{}", " ".repeat(pad))
3028 } else {
3029 format!("{}{body}", " ".repeat(pad))
3030 };
3031 }
3032 let body = format!("{sign}{digits}");
3033 if body.len() >= width {
3034 return body;
3035 }
3036 let pad = width - body.len();
3037 if zero_pad {
3041 format!("{sign}{}{digits}", "0".repeat(pad))
3042 } else if left_align {
3043 format!("{body}{}", " ".repeat(pad))
3044 } else {
3045 format!("{}{body}", " ".repeat(pad))
3046 }
3047}
3048
3049fn group_thousands(digits: &str) -> String {
3053 if digits.len() <= 3 || !digits.bytes().all(|b| b.is_ascii_digit()) {
3054 return digits.to_owned();
3055 }
3056 let lead = digits.len() % 3;
3057 let mut out = String::with_capacity(digits.len() + digits.len() / 3);
3058 if lead > 0 {
3059 out.push_str(&digits[..lead]);
3060 }
3061 let mut idx = lead;
3062 while idx < digits.len() {
3063 if !out.is_empty() {
3064 out.push(',');
3065 }
3066 out.push_str(&digits[idx..idx + 3]);
3067 idx += 3;
3068 }
3069 out
3070}
3071
3072fn altform2_trim_float(mag: &str) -> String {
3077 if mag.contains('.') {
3078 let trimmed = mag.trim_end_matches('0');
3079 if trimmed.ends_with('.') {
3080 format!("{trimmed}0")
3081 } else {
3082 trimmed.to_owned()
3083 }
3084 } else {
3085 format!("{mag}.0")
3086 }
3087}
3088
3089fn group_float_integer_part(mag: &str) -> String {
3092 if let Some(dot) = mag.find('.') {
3093 format!("{}{}", group_thousands(&mag[..dot]), &mag[dot..])
3094 } else {
3095 group_thousands(mag)
3096 }
3097}
3098
3099fn group_signed_decimal_integer_part(s: &str) -> String {
3102 if let Some(rest) = s.strip_prefix('-') {
3103 format!("-{}", group_float_integer_part(rest))
3104 } else {
3105 group_float_integer_part(s)
3106 }
3107}
3108
3109fn apply_int_precision(digits: &str, precision: Option<usize>) -> String {
3115 match precision {
3116 Some(p) if digits.len() < p => {
3117 format!("{}{digits}", "0".repeat(p - digits.len()))
3118 }
3119 _ => digits.to_owned(),
3120 }
3121}
3122
3123fn altform2_sig_digits(val: f64, want: usize) -> (Vec<u8>, i32) {
3133 let (cap_digits, sci_exp) = sqlite_float_altform2_digits(val);
3134 if want >= cap_digits.len() {
3135 return (cap_digits, sci_exp);
3136 }
3137 let mut digits = cap_digits[..want].to_vec();
3138 let mut exp = sci_exp;
3139 if cap_digits[want] >= b'5' {
3142 let mut idx = want;
3143 loop {
3144 if idx == 0 {
3145 digits.fill(b'0');
3147 digits[0] = b'1';
3148 exp += 1;
3149 break;
3150 }
3151 idx -= 1;
3152 if digits[idx] == b'9' {
3153 digits[idx] = b'0';
3154 } else {
3155 digits[idx] += 1;
3156 break;
3157 }
3158 }
3159 }
3160 (digits, exp)
3161}
3162
3163fn altform2_render_exp(digits: &[u8], exp: i32, upper: bool) -> String {
3169 let e_char = if upper { 'E' } else { 'e' };
3170 let mut mant = String::with_capacity(digits.len() + 2);
3171 mant.push(char::from(digits[0]));
3172 mant.push('.');
3173 if digits.len() > 1 {
3174 mant.extend(digits[1..].iter().map(|&b| char::from(b)));
3175 } else {
3176 mant.push('0');
3177 }
3178 let mant = altform2_trim_float(&mant);
3179 let sign = if exp < 0 { '-' } else { '+' };
3180 format!("{mant}{e_char}{sign}{:02}", exp.unsigned_abs())
3181}
3182
3183fn altform2_render_fixed(digits: &[u8], exp: i32, max_frac: usize) -> String {
3190 let n = digits.len();
3191 let mut out = String::new();
3192 if exp >= 0 {
3193 let int_len = usize::try_from(exp).unwrap_or(0) + 1;
3194 if int_len >= n {
3195 out.extend(digits.iter().map(|&b| char::from(b)));
3197 out.push_str(&"0".repeat(int_len - n));
3198 } else {
3199 out.extend(digits[..int_len].iter().map(|&b| char::from(b)));
3200 out.push('.');
3201 let frac = &digits[int_len..];
3202 let take = frac.len().min(max_frac);
3203 out.extend(frac[..take].iter().map(|&b| char::from(b)));
3204 }
3205 } else {
3206 out.push_str("0.");
3207 let lead_zeros = usize::try_from(-exp - 1).unwrap_or(0);
3208 let take_zeros = lead_zeros.min(max_frac);
3209 out.push_str(&"0".repeat(take_zeros));
3210 let remaining = max_frac.saturating_sub(take_zeros);
3211 let take = n.min(remaining);
3212 out.extend(digits[..take].iter().map(|&b| char::from(b)));
3213 }
3214 altform2_trim_float(&out)
3215}
3216
3217fn nonfinite_float_str(
3221 val: f64,
3222 width: usize,
3223 left_align: bool,
3224 show_sign: bool,
3225 space_sign: bool,
3226) -> Option<String> {
3227 let body = if val.is_nan() {
3228 "NaN".to_owned()
3229 } else if val.is_infinite() {
3230 let sign = if val < 0.0 {
3231 "-"
3232 } else if show_sign {
3233 "+"
3234 } else if space_sign {
3235 " "
3236 } else {
3237 ""
3238 };
3239 format!("{sign}Inf")
3240 } else {
3241 return None;
3242 };
3243 Some(pad_string(&body, width, left_align))
3244}
3245
3246fn finish_float_padding(
3250 body: &str,
3251 width: usize,
3252 left_align: bool,
3253 show_sign: bool,
3254 space_sign: bool,
3255 zero_pad: bool,
3256) -> String {
3257 let (sign, digits) = if let Some(rest) = body.strip_prefix('-') {
3258 ("-", rest)
3259 } else if show_sign {
3260 ("+", body)
3261 } else if space_sign {
3262 (" ", body)
3263 } else {
3264 ("", body)
3265 };
3266 let full_len = sign.len() + digits.len();
3267 if full_len >= width {
3268 return format!("{sign}{digits}");
3269 }
3270 let pad = width - full_len;
3271 if left_align {
3272 format!("{sign}{digits}{}", " ".repeat(pad))
3273 } else if zero_pad {
3274 format!("{sign}{}{digits}", "0".repeat(pad))
3275 } else {
3276 format!("{}{sign}{digits}", " ".repeat(pad))
3277 }
3278}
3279
3280fn pad_string(s: &str, width: usize, left_align: bool) -> String {
3281 if s.len() >= width {
3282 return s.to_owned();
3283 }
3284 let pad = width - s.len();
3285 if left_align {
3286 format!("{s}{}", " ".repeat(pad))
3287 } else {
3288 format!("{}{s}", " ".repeat(pad))
3289 }
3290}
3291
3292fn normalize_exponent(s: &str) -> String {
3295 let (prefix, e_char, exp_part) = if let Some(pos) = s.find('e') {
3296 (&s[..pos], 'e', &s[pos + 1..])
3297 } else if let Some(pos) = s.find('E') {
3298 (&s[..pos], 'E', &s[pos + 1..])
3299 } else {
3300 return s.to_owned();
3301 };
3302 let (sign, digits) = if let Some(rest) = exp_part.strip_prefix('-') {
3303 ("-", rest)
3304 } else if let Some(rest) = exp_part.strip_prefix('+') {
3305 ("+", rest)
3306 } else {
3307 ("+", exp_part)
3308 };
3309 let padded = if digits.len() < 2 {
3310 format!("0{digits}")
3311 } else {
3312 digits.to_owned()
3313 };
3314 format!("{prefix}{e_char}{sign}{padded}")
3315}
3316
3317const MAX_F64_FRACTIONAL_DIGITS: usize = 1074;
3322
3323fn increment_decimal_digits(digits: &mut Vec<u8>) {
3328 let mut carry = true;
3329 for b in digits.iter_mut().rev() {
3330 if *b == b'.' {
3331 continue;
3332 }
3333 if carry {
3334 if *b == b'9' {
3335 *b = b'0';
3336 } else {
3337 *b += 1;
3338 carry = false;
3339 break;
3340 }
3341 }
3342 }
3343 if carry {
3344 digits.insert(0, b'1');
3345 }
3346}
3347
3348fn is_exact_decimal_tie(mag: f64, prec: usize) -> bool {
3359 fn looks_like_tie(mag: f64, prec: usize, guard: usize) -> bool {
3360 let full = format!("{mag:.guard$}");
3361 let Some(dot) = full.find('.') else {
3362 return false;
3363 };
3364 let rd_idx = dot + 1 + prec;
3365 let bytes = full.as_bytes();
3366 rd_idx < bytes.len()
3367 && bytes[rd_idx] == b'5'
3368 && full[rd_idx + 1..].bytes().all(|b| b == b'0')
3369 }
3370 looks_like_tie(mag, prec, prec + 18)
3371 && looks_like_tie(mag, prec, prec + MAX_F64_FRACTIONAL_DIGITS)
3372}
3373
3374fn is_exact_sci_tie(mag: f64, prec: usize) -> bool {
3378 fn looks_like_tie(mag: f64, prec: usize, guard: usize) -> bool {
3379 let s = format!("{mag:.guard$e}");
3380 let Some((mant, _)) = s.split_once('e') else {
3381 return false;
3382 };
3383 let Some(dot) = mant.find('.') else {
3384 return false;
3385 };
3386 let rd_idx = dot + 1 + prec;
3387 let bytes = mant.as_bytes();
3388 rd_idx < bytes.len()
3389 && bytes[rd_idx] == b'5'
3390 && mant[rd_idx + 1..].bytes().all(|b| b == b'0')
3391 }
3392 looks_like_tie(mag, prec, prec + 18)
3393 && looks_like_tie(mag, prec, prec + MAX_F64_FRACTIONAL_DIGITS)
3394}
3395
3396fn format_fixed_round_half_away(val: f64, prec: usize) -> String {
3402 let base = format!("{val:.prec$}");
3403 let mag = val.abs();
3404 if !is_exact_decimal_tie(mag, prec) {
3405 return base;
3406 }
3407 let src = format!("{mag:.p$}", p = prec + 2);
3410 let dot = src.find('.').unwrap_or(src.len());
3411 let rd_idx = dot + 1 + prec;
3412 let mut digits = src.as_bytes()[..rd_idx].to_vec();
3413 if digits.last() == Some(&b'.') {
3414 digits.pop();
3415 }
3416 increment_decimal_digits(&mut digits);
3417 let Ok(body) = String::from_utf8(digits) else {
3418 return base;
3419 };
3420 if val.is_sign_negative() {
3421 format!("-{body}")
3422 } else {
3423 body
3424 }
3425}
3426
3427fn format_sci_round_half_away(val: f64, prec: usize, upper: bool) -> String {
3435 let base = if upper {
3436 format!("{val:.prec$E}")
3437 } else {
3438 format!("{val:.prec$e}")
3439 };
3440 let mag = val.abs();
3441 if mag == 0.0 || !is_exact_sci_tie(mag, prec) {
3442 return base;
3443 }
3444 let e_char = if upper { 'E' } else { 'e' };
3445 let src = format!("{mag:.p$e}", p = prec + 2);
3446 let Some((mant, exp_str)) = src.split_once('e') else {
3447 return base;
3448 };
3449 let mut exp: i64 = exp_str.parse().unwrap_or(0);
3450 let dot = mant.find('.').unwrap_or(mant.len());
3451 let rd_idx = dot + 1 + prec;
3452 let mut digits = mant.as_bytes()[..rd_idx].to_vec();
3453 if digits.last() == Some(&b'.') {
3454 digits.pop();
3455 }
3456 increment_decimal_digits(&mut digits);
3457 let Ok(mut mantissa) = String::from_utf8(digits) else {
3458 return base;
3459 };
3460 let int_len = mantissa.find('.').unwrap_or(mantissa.len());
3463 if int_len == 2 {
3464 mantissa = if prec > 0 {
3465 format!("1.{}", "0".repeat(prec))
3466 } else {
3467 "1".to_owned()
3468 };
3469 exp += 1;
3470 }
3471 let sign = if val.is_sign_negative() { "-" } else { "" };
3472 format!("{sign}{mantissa}{e_char}{exp}")
3473}
3474
3475fn format_float_g(val: f64, sig: usize, upper: bool, alt_form: bool, max_sig: usize) -> String {
3477 if !val.is_finite() {
3478 return format!("{val}");
3479 }
3480 let val = if val == 0.0 { 0.0 } else { val };
3483 let sig_digits = sig.min(max_sig).max(1);
3488 let sci = format_sci_round_half_away(val, sig_digits.saturating_sub(1), false);
3493 let exp: i32 = sci
3494 .rsplit_once('e')
3495 .and_then(|(_, e)| e.parse().ok())
3496 .unwrap_or(0);
3497 #[allow(clippy::cast_possible_wrap)]
3498 let formatted = if exp < -4 || exp >= sig as i32 {
3499 let s = if upper { sci.replace('e', "E") } else { sci };
3500 let trimmed = if alt_form {
3504 if let Some(e_pos) = s.find(['e', 'E']) {
3505 let (mantissa, exp_part) = s.split_at(e_pos);
3506 if mantissa.contains('.') {
3507 s.clone()
3508 } else {
3509 format!("{mantissa}.{exp_part}")
3510 }
3511 } else if s.contains('.') {
3512 s.clone()
3513 } else {
3514 format!("{s}.")
3515 }
3516 } else if s.contains('.') {
3517 if let Some(e_pos) = s.find('e').or_else(|| s.find('E')) {
3518 let mantissa = s[..e_pos].trim_end_matches('0').trim_end_matches('.');
3519 format!("{mantissa}{}", &s[e_pos..])
3520 } else {
3521 s.trim_end_matches('0').trim_end_matches('.').to_owned()
3522 }
3523 } else {
3524 s
3525 };
3526 normalize_exponent(&trimmed)
3527 } else {
3528 let decimal_places = if exp >= 0 {
3529 sig_digits.saturating_sub((exp + 1) as usize)
3530 } else {
3531 sig_digits + exp.unsigned_abs() as usize - 1
3532 };
3533 let s = format_fixed_round_half_away(val, decimal_places);
3534 if alt_form {
3536 if s.contains('.') { s } else { format!("{s}.") }
3537 }
3538 else if s.contains('.') {
3544 s.trim_end_matches('0').trim_end_matches('.').to_owned()
3545 } else {
3546 s
3547 }
3548 };
3549 formatted
3550}
3551
3552const FLOAT_SIG_DIGITS: usize = 16;
3556
3557fn round_positional_to_sig(s: &str, max_sig: usize) -> String {
3563 let bytes = s.as_bytes();
3564 let mut sig = 0usize;
3565 let mut started = false;
3566 let mut cut = None;
3567 for (i, &b) in bytes.iter().enumerate() {
3568 if b.is_ascii_digit() && (b != b'0' || started) {
3569 started = true;
3570 sig += 1;
3571 if sig == max_sig {
3572 cut = Some(i);
3573 break;
3574 }
3575 }
3576 }
3577 let Some(cut) = cut else { return s.to_owned() };
3578 if bytes[cut + 1..].iter().all(|b| !b.is_ascii_digit()) {
3580 return s.to_owned();
3581 }
3582 let round_up = bytes[cut + 1..]
3583 .iter()
3584 .find(|b| b.is_ascii_digit())
3585 .is_some_and(|&b| b >= b'5');
3586 let mut kept: Vec<u8> = bytes[..=cut].to_vec();
3587 if round_up {
3588 increment_decimal_digits(&mut kept);
3589 }
3590 let mut tail = String::new();
3591 for &b in &bytes[cut + 1..] {
3592 tail.push(if b == b'.' { '.' } else { '0' });
3593 }
3594 format!("{}{tail}", String::from_utf8_lossy(&kept))
3595}
3596
3597#[cfg(test)]
3598#[allow(clippy::too_many_lines)]
3599mod tests {
3600 use super::*;
3601
3602 fn invoke1(f: &dyn ScalarFunction, v: SqliteValue) -> Result<SqliteValue> {
3603 f.invoke(&[v])
3604 }
3605
3606 fn invoke2(f: &dyn ScalarFunction, a: SqliteValue, b: SqliteValue) -> Result<SqliteValue> {
3607 f.invoke(&[a, b])
3608 }
3609
3610 fn assert_wrong_arg_count(registry: &FunctionRegistry, name: &str, arity: i32) {
3611 let function = registry
3612 .find_scalar(name, arity)
3613 .expect("known scalar name with bad arity returns erroring scalar");
3614 let args = vec![SqliteValue::Null; arity.max(0) as usize];
3615 let err = function
3616 .invoke(&args)
3617 .expect_err("wrong arity should return function error");
3618 let expected = format!("wrong number of arguments to function {name}()");
3619 assert!(
3620 matches!(&err, FrankenError::FunctionError(message) if message == &expected),
3621 "expected {expected:?}, got {err:?}"
3622 );
3623 }
3624
3625 #[test]
3626 fn test_get_change_tracking_state_returns_thread_local_snapshot() {
3627 let original = get_change_tracking_state();
3628 let expected = ChangeTrackingState {
3629 last_insert_rowid: 17,
3630 last_changes: 23,
3631 total_changes: 42,
3632 };
3633
3634 set_change_tracking_state(expected);
3635 assert_eq!(get_change_tracking_state(), expected);
3636
3637 set_change_tracking_state(original);
3638 }
3639
3640 #[test]
3643 fn test_abs_positive() {
3644 assert_eq!(
3645 invoke1(&AbsFunc, SqliteValue::Integer(42)).unwrap(),
3646 SqliteValue::Integer(42)
3647 );
3648 }
3649
3650 #[test]
3651 fn test_abs_negative() {
3652 assert_eq!(
3653 invoke1(&AbsFunc, SqliteValue::Integer(-42)).unwrap(),
3654 SqliteValue::Integer(42)
3655 );
3656 }
3657
3658 #[test]
3659 fn test_abs_null() {
3660 assert_eq!(
3661 invoke1(&AbsFunc, SqliteValue::Null).unwrap(),
3662 SqliteValue::Null
3663 );
3664 }
3665
3666 #[test]
3667 fn test_abs_min_i64_overflow() {
3668 let err = invoke1(&AbsFunc, SqliteValue::Integer(i64::MIN)).unwrap_err();
3669 assert!(matches!(err, FrankenError::IntegerOverflow));
3670 }
3671
3672 #[test]
3673 fn test_abs_string_coercion() {
3674 assert_eq!(
3675 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("-7.5"))).unwrap(),
3676 SqliteValue::Float(7.5)
3677 );
3678 }
3679
3680 #[test]
3681 fn test_abs_whitespace_padded_text() {
3682 assert_eq!(
3684 invoke1(
3685 &AbsFunc,
3686 SqliteValue::Text(SmallText::from_string(" 42 "))
3687 )
3688 .unwrap(),
3689 SqliteValue::Float(42.0)
3690 );
3691 assert_eq!(
3692 invoke1(
3693 &AbsFunc,
3694 SqliteValue::Text(SmallText::from_string(" -7.5 "))
3695 )
3696 .unwrap(),
3697 SqliteValue::Float(7.5)
3698 );
3699 assert_eq!(
3700 invoke1(&AbsFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
3701 SqliteValue::Float(0.0)
3702 );
3703 }
3704
3705 #[test]
3706 #[allow(clippy::approx_constant)]
3707 fn test_abs_float() {
3708 assert_eq!(
3709 invoke1(&AbsFunc, SqliteValue::Float(-3.14)).unwrap(),
3710 SqliteValue::Float(3.14)
3711 );
3712 }
3713
3714 #[test]
3717 fn test_char_basic() {
3718 let f = CharFunc;
3719 let result = f
3720 .invoke(&[
3721 SqliteValue::Integer(72),
3722 SqliteValue::Integer(101),
3723 SqliteValue::Integer(108),
3724 SqliteValue::Integer(108),
3725 SqliteValue::Integer(111),
3726 ])
3727 .unwrap();
3728 assert_eq!(result, SqliteValue::Text(SmallText::from_string("Hello")));
3729 }
3730
3731 #[test]
3732 fn test_char_null_skipped() {
3733 let f = CharFunc;
3734 let result = f
3736 .invoke(&[
3737 SqliteValue::Integer(65),
3738 SqliteValue::Null,
3739 SqliteValue::Integer(66),
3740 ])
3741 .unwrap();
3742 assert_eq!(result, SqliteValue::Text(SmallText::from_string("A\0B")));
3743 }
3744
3745 #[test]
3746 fn test_char_invalid_scalar_values_use_replacement_character() {
3747 let f = CharFunc;
3748 let result = f
3749 .invoke(&[
3750 SqliteValue::Integer(-1),
3751 SqliteValue::Integer(65),
3752 SqliteValue::Integer(1_114_112),
3753 ])
3754 .unwrap();
3755 assert_eq!(
3756 result,
3757 SqliteValue::Text(SmallText::from_string("\u{fffd}A\u{fffd}"))
3758 );
3759 }
3760
3761 #[test]
3764 fn test_coalesce_first_non_null() {
3765 let f = CoalesceFunc;
3766 let result = f
3767 .invoke(&[
3768 SqliteValue::Null,
3769 SqliteValue::Null,
3770 SqliteValue::Integer(3),
3771 SqliteValue::Integer(4),
3772 ])
3773 .unwrap();
3774 assert_eq!(result, SqliteValue::Integer(3));
3775 }
3776
3777 #[test]
3780 fn test_concat_null_as_empty() {
3781 let f = ConcatFunc;
3782 let result = f
3783 .invoke(&[
3784 SqliteValue::Null,
3785 SqliteValue::Text(SmallText::from_string("hello")),
3786 SqliteValue::Null,
3787 ])
3788 .unwrap();
3789 assert_eq!(result, SqliteValue::Text(SmallText::from_string("hello")));
3790 }
3791
3792 #[test]
3793 #[ignore = "perf-only benchmark"]
3794 fn perf_concat_text_args() {
3795 use std::hint::black_box;
3796 use std::time::Instant;
3797
3798 const TEXT_ARGS: usize = 24;
3799 const INVOCATIONS: usize = 50_000;
3800 const REPEATS: usize = 5;
3801
3802 let f = ConcatFunc;
3803 let mut args = Vec::with_capacity(TEXT_ARGS);
3804 for _ in 0..TEXT_ARGS {
3805 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3806 }
3807
3808 let mut best_ns = u128::MAX;
3809 let mut result_len = 0usize;
3810 for _ in 0..REPEATS {
3811 let started = Instant::now();
3812 for _ in 0..INVOCATIONS {
3813 let result = black_box(
3814 f.invoke(black_box(args.as_slice()))
3815 .expect("concat benchmark invocation must succeed"),
3816 );
3817 result_len = match result {
3818 SqliteValue::Text(text) => text.len(),
3819 SqliteValue::Null
3820 | SqliteValue::Integer(_)
3821 | SqliteValue::Float(_)
3822 | SqliteValue::Blob(_) => 0,
3823 };
3824 }
3825 best_ns = best_ns.min(started.elapsed().as_nanos());
3826 }
3827
3828 println!(
3829 "concat_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3830 );
3831 }
3832
3833 #[test]
3836 fn test_concat_ws_null_skipped() {
3837 let f = ConcatWsFunc;
3838 let result = f
3839 .invoke(&[
3840 SqliteValue::Text(SmallText::from_string(",")),
3841 SqliteValue::Text(SmallText::from_string("a")),
3842 SqliteValue::Null,
3843 SqliteValue::Text(SmallText::from_string("b")),
3844 ])
3845 .unwrap();
3846 assert_eq!(result, SqliteValue::Text(SmallText::from_string("a,b")));
3847 }
3848
3849 #[test]
3850 fn test_concat_ws_empty_string_is_not_skipped() {
3851 let f = ConcatWsFunc;
3852 let result = f
3853 .invoke(&[
3854 SqliteValue::Text(SmallText::from_string("|")),
3855 SqliteValue::Text(SmallText::new("")),
3856 SqliteValue::Text(SmallText::from_string("x")),
3857 ])
3858 .unwrap();
3859 assert_eq!(result, SqliteValue::Text(SmallText::from_string("|x")));
3860 }
3861
3862 #[test]
3863 #[ignore = "perf-only benchmark"]
3864 fn perf_concat_ws_text_args() {
3865 use std::hint::black_box;
3866 use std::time::Instant;
3867
3868 const TEXT_ARGS: usize = 24;
3869 const INVOCATIONS: usize = 50_000;
3870 const REPEATS: usize = 5;
3871
3872 let f = ConcatWsFunc;
3873 let mut args = Vec::with_capacity(TEXT_ARGS + 1);
3874 args.push(SqliteValue::Text(SmallText::from_string(",")));
3875 for _ in 0..TEXT_ARGS {
3876 args.push(SqliteValue::Text(SmallText::from_string("payload")));
3877 }
3878
3879 let mut best_ns = u128::MAX;
3880 let mut result_len = 0usize;
3881 for _ in 0..REPEATS {
3882 let started = Instant::now();
3883 for _ in 0..INVOCATIONS {
3884 let result = black_box(
3885 f.invoke(black_box(args.as_slice()))
3886 .expect("concat_ws benchmark invocation must succeed"),
3887 );
3888 result_len = match result {
3889 SqliteValue::Text(text) => text.len(),
3890 SqliteValue::Null
3891 | SqliteValue::Integer(_)
3892 | SqliteValue::Float(_)
3893 | SqliteValue::Blob(_) => 0,
3894 };
3895 }
3896 best_ns = best_ns.min(started.elapsed().as_nanos());
3897 }
3898
3899 println!(
3900 "concat_ws_text_args text_args={TEXT_ARGS} invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
3901 );
3902 }
3903
3904 #[test]
3907 fn test_hex_blob() {
3908 let result = invoke1(
3909 &HexFunc,
3910 SqliteValue::Blob(Arc::from([0xDE, 0xAD, 0xBE, 0xEF].as_slice())),
3911 )
3912 .unwrap();
3913 assert_eq!(
3914 result,
3915 SqliteValue::Text(SmallText::from_string("DEADBEEF"))
3916 );
3917 }
3918
3919 #[test]
3920 fn test_hex_number_via_text() {
3921 let result = invoke1(&HexFunc, SqliteValue::Integer(42)).unwrap();
3923 assert_eq!(result, SqliteValue::Text(SmallText::from_string("3432")));
3924 }
3925
3926 #[test]
3927 #[ignore = "perf-only benchmark"]
3928 fn perf_hex_text_blob_args() {
3929 use std::hint::black_box;
3930 use std::time::Instant;
3931
3932 const BYTES: usize = 24;
3933 const INVOCATIONS: usize = 100_000;
3934 const REPEATS: usize = 5;
3935
3936 let f = HexFunc;
3937 let text_args = [SqliteValue::Text(SmallText::from_string(
3938 "payload payload sentinel",
3939 ))];
3940 let blob_args = [SqliteValue::Blob(Arc::from([0xAB; BYTES].as_slice()))];
3941
3942 let mut text_best_ns = u128::MAX;
3943 let mut blob_best_ns = u128::MAX;
3944 let mut text_result_len = 0usize;
3945 let mut blob_result_len = 0usize;
3946 for _ in 0..REPEATS {
3947 let started = Instant::now();
3948 for _ in 0..INVOCATIONS {
3949 let result = black_box(
3950 f.invoke(black_box(text_args.as_slice()))
3951 .expect("hex text benchmark invocation must succeed"),
3952 );
3953 text_result_len = match result {
3954 SqliteValue::Text(text) => text.len(),
3955 SqliteValue::Null
3956 | SqliteValue::Integer(_)
3957 | SqliteValue::Float(_)
3958 | SqliteValue::Blob(_) => 0,
3959 };
3960 }
3961 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
3962
3963 let started = Instant::now();
3964 for _ in 0..INVOCATIONS {
3965 let result = black_box(
3966 f.invoke(black_box(blob_args.as_slice()))
3967 .expect("hex blob benchmark invocation must succeed"),
3968 );
3969 blob_result_len = match result {
3970 SqliteValue::Text(text) => text.len(),
3971 SqliteValue::Null
3972 | SqliteValue::Integer(_)
3973 | SqliteValue::Float(_)
3974 | SqliteValue::Blob(_) => 0,
3975 };
3976 }
3977 blob_best_ns = blob_best_ns.min(started.elapsed().as_nanos());
3978 }
3979
3980 println!(
3981 "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}"
3982 );
3983 }
3984
3985 #[test]
3988 fn test_iif_true() {
3989 let f = IifFunc;
3990 let result = f
3991 .invoke(&[
3992 SqliteValue::Integer(1),
3993 SqliteValue::Text(SmallText::from_string("yes")),
3994 SqliteValue::Text(SmallText::from_string("no")),
3995 ])
3996 .unwrap();
3997 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
3998 }
3999
4000 #[test]
4001 fn test_iif_false() {
4002 let f = IifFunc;
4003 let result = f
4004 .invoke(&[
4005 SqliteValue::Integer(0),
4006 SqliteValue::Text(SmallText::from_string("yes")),
4007 SqliteValue::Text(SmallText::from_string("no")),
4008 ])
4009 .unwrap();
4010 assert_eq!(result, SqliteValue::Text(SmallText::from_string("no")));
4011 }
4012
4013 #[test]
4014 fn test_iif_whitespace_padded_text_truthy() {
4015 let f = IifFunc;
4018 let result = f
4019 .invoke(&[
4020 SqliteValue::Text(SmallText::from_string(" 5 ")),
4021 SqliteValue::Text(SmallText::from_string("yes")),
4022 SqliteValue::Text(SmallText::from_string("no")),
4023 ])
4024 .unwrap();
4025 assert_eq!(result, SqliteValue::Text(SmallText::from_string("yes")));
4026 }
4027
4028 #[test]
4031 fn test_ifnull_non_null() {
4032 assert_eq!(
4033 invoke2(
4034 &IfnullFunc,
4035 SqliteValue::Integer(5),
4036 SqliteValue::Integer(10)
4037 )
4038 .unwrap(),
4039 SqliteValue::Integer(5)
4040 );
4041 }
4042
4043 #[test]
4044 fn test_ifnull_null() {
4045 assert_eq!(
4046 invoke2(&IfnullFunc, SqliteValue::Null, SqliteValue::Integer(10)).unwrap(),
4047 SqliteValue::Integer(10)
4048 );
4049 }
4050
4051 #[test]
4054 fn test_instr_found() {
4055 assert_eq!(
4056 invoke2(
4057 &InstrFunc,
4058 SqliteValue::Text(SmallText::from_string("hello world")),
4059 SqliteValue::Text(SmallText::from_string("world"))
4060 )
4061 .unwrap(),
4062 SqliteValue::Integer(7)
4063 );
4064 }
4065
4066 #[test]
4067 fn test_instr_not_found() {
4068 assert_eq!(
4069 invoke2(
4070 &InstrFunc,
4071 SqliteValue::Text(SmallText::from_string("hello")),
4072 SqliteValue::Text(SmallText::from_string("xyz"))
4073 )
4074 .unwrap(),
4075 SqliteValue::Integer(0)
4076 );
4077 }
4078
4079 #[test]
4080 fn test_instr_empty_needle_returns_one() {
4081 assert_eq!(
4083 invoke2(
4084 &InstrFunc,
4085 SqliteValue::Text(SmallText::from_string("hello")),
4086 SqliteValue::Text(SmallText::new(""))
4087 )
4088 .unwrap(),
4089 SqliteValue::Integer(1)
4090 );
4091 }
4092
4093 #[test]
4094 fn test_instr_empty_haystack_returns_zero() {
4095 assert_eq!(
4096 invoke2(
4097 &InstrFunc,
4098 SqliteValue::Text(SmallText::new("")),
4099 SqliteValue::Text(SmallText::from_string("x"))
4100 )
4101 .unwrap(),
4102 SqliteValue::Integer(0)
4103 );
4104 }
4105
4106 #[test]
4107 fn test_instr_blob_empty_needle_returns_one() {
4108 assert_eq!(
4110 invoke2(
4111 &InstrFunc,
4112 SqliteValue::Blob(Arc::from([1, 2, 3].as_slice())),
4113 SqliteValue::Blob(Arc::from([].as_slice()))
4114 )
4115 .unwrap(),
4116 SqliteValue::Integer(1)
4117 );
4118 }
4119
4120 #[test]
4121 #[ignore = "perf-only benchmark"]
4122 fn perf_instr_text_args() {
4123 use std::hint::black_box;
4124 use std::time::Instant;
4125
4126 const INVOCATIONS: usize = 100_000;
4127 const REPEATS: usize = 5;
4128
4129 let f = InstrFunc;
4130 let args = [
4131 SqliteValue::Text(SmallText::from_string("payload payload sentinel")),
4132 SqliteValue::Text(SmallText::from_string("sentinel")),
4133 ];
4134
4135 let mut best_ns = u128::MAX;
4136 let mut result_value = 0i64;
4137 for _ in 0..REPEATS {
4138 let started = Instant::now();
4139 for _ in 0..INVOCATIONS {
4140 let result = black_box(
4141 f.invoke(black_box(args.as_slice()))
4142 .expect("instr benchmark invocation must succeed"),
4143 );
4144 result_value = match result {
4145 SqliteValue::Integer(value) => value,
4146 SqliteValue::Null
4147 | SqliteValue::Float(_)
4148 | SqliteValue::Text(_)
4149 | SqliteValue::Blob(_) => 0,
4150 };
4151 }
4152 best_ns = best_ns.min(started.elapsed().as_nanos());
4153 }
4154
4155 println!(
4156 "instr_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_value={result_value}"
4157 );
4158 }
4159
4160 #[test]
4163 fn test_length_text_chars() {
4164 assert_eq!(
4166 invoke1(
4167 &LengthFunc,
4168 SqliteValue::Text(SmallText::from_string("café"))
4169 )
4170 .unwrap(),
4171 SqliteValue::Integer(4)
4172 );
4173 }
4174
4175 #[test]
4176 fn test_length_text_stops_at_nul() {
4177 assert_eq!(
4178 invoke1(
4179 &LengthFunc,
4180 SqliteValue::Text(SmallText::from_string("A\0B"))
4181 )
4182 .unwrap(),
4183 SqliteValue::Integer(1)
4184 );
4185 assert_eq!(
4186 invoke1(
4187 &LengthFunc,
4188 SqliteValue::Text(SmallText::from_string("\0A"))
4189 )
4190 .unwrap(),
4191 SqliteValue::Integer(0)
4192 );
4193 }
4194
4195 #[test]
4196 fn test_length_blob_bytes() {
4197 assert_eq!(
4198 invoke1(&LengthFunc, SqliteValue::Blob(Arc::from([1, 2].as_slice()))).unwrap(),
4199 SqliteValue::Integer(2)
4200 );
4201 }
4202
4203 #[test]
4206 fn test_octet_length_multibyte() {
4207 assert_eq!(
4209 invoke1(
4210 &OctetLengthFunc,
4211 SqliteValue::Text(SmallText::from_string("café"))
4212 )
4213 .unwrap(),
4214 SqliteValue::Integer(5)
4215 );
4216 }
4217
4218 #[test]
4219 fn test_octet_length_honors_statement_text_encoding() {
4220 set_statement_text_encoding(TextEncoding::Utf8);
4222 assert_eq!(
4223 invoke1(
4224 &OctetLengthFunc,
4225 SqliteValue::Text(SmallText::from_string("abc"))
4226 )
4227 .unwrap(),
4228 SqliteValue::Integer(3)
4229 );
4230 assert_eq!(
4231 invoke1(&OctetLengthFunc, SqliteValue::Integer(12345)).unwrap(),
4232 SqliteValue::Integer(5)
4233 );
4234
4235 set_statement_text_encoding(TextEncoding::Utf16le);
4237 assert_eq!(statement_text_encoding(), TextEncoding::Utf16le);
4238 assert_eq!(
4239 invoke1(
4240 &OctetLengthFunc,
4241 SqliteValue::Text(SmallText::from_string("abc"))
4242 )
4243 .unwrap(),
4244 SqliteValue::Integer(6)
4245 );
4246 assert_eq!(
4247 invoke1(&OctetLengthFunc, SqliteValue::Integer(12345)).unwrap(),
4248 SqliteValue::Integer(10)
4249 );
4250 assert_eq!(
4252 invoke1(
4253 &OctetLengthFunc,
4254 SqliteValue::Text(SmallText::from_string("\u{1F600}"))
4255 )
4256 .unwrap(),
4257 SqliteValue::Integer(4)
4258 );
4259
4260 set_statement_text_encoding(TextEncoding::Utf16be);
4262 assert_eq!(
4263 invoke1(
4264 &OctetLengthFunc,
4265 SqliteValue::Text(SmallText::from_string("abc"))
4266 )
4267 .unwrap(),
4268 SqliteValue::Integer(6)
4269 );
4270
4271 assert_eq!(
4273 invoke1(&OctetLengthFunc, SqliteValue::Blob(vec![1, 2, 3].into())).unwrap(),
4274 SqliteValue::Integer(3)
4275 );
4276
4277 set_statement_text_encoding(TextEncoding::Utf8);
4279 }
4280
4281 #[test]
4284 fn test_lower_ascii() {
4285 assert_eq!(
4286 invoke1(
4287 &LowerFunc,
4288 SqliteValue::Text(SmallText::from_string("HELLO"))
4289 )
4290 .unwrap(),
4291 SqliteValue::Text(SmallText::from_string("hello"))
4292 );
4293 }
4294
4295 #[test]
4296 fn test_upper_ascii() {
4297 assert_eq!(
4298 invoke1(
4299 &UpperFunc,
4300 SqliteValue::Text(SmallText::from_string("hello"))
4301 )
4302 .unwrap(),
4303 SqliteValue::Text(SmallText::from_string("HELLO"))
4304 );
4305 }
4306
4307 #[test]
4310 fn test_trim_default() {
4311 let f = TrimFunc;
4312 assert_eq!(
4313 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello "))])
4314 .unwrap(),
4315 SqliteValue::Text(SmallText::from_string("hello"))
4316 );
4317 }
4318
4319 #[test]
4320 fn test_ltrim_default() {
4321 let f = LtrimFunc;
4322 assert_eq!(
4323 f.invoke(&[SqliteValue::Text(SmallText::from_string(" hello"))])
4324 .unwrap(),
4325 SqliteValue::Text(SmallText::from_string("hello"))
4326 );
4327 }
4328
4329 #[test]
4330 fn test_ltrim_custom() {
4331 let f = LtrimFunc;
4332 assert_eq!(
4333 f.invoke(&[
4334 SqliteValue::Text(SmallText::from_string("xxhello")),
4335 SqliteValue::Text(SmallText::from_string("x")),
4336 ])
4337 .unwrap(),
4338 SqliteValue::Text(SmallText::from_string("hello"))
4339 );
4340 }
4341
4342 #[test]
4343 #[ignore = "perf-only benchmark"]
4344 fn perf_trim_text_args() {
4345 use std::hint::black_box;
4346 use std::time::Instant;
4347
4348 const INVOCATIONS: usize = 100_000;
4349 const REPEATS: usize = 5;
4350
4351 let trim = TrimFunc;
4352 let ltrim = LtrimFunc;
4353 let rtrim = RtrimFunc;
4354 let default_args = [SqliteValue::Text(SmallText::from_string(" payload "))];
4355 let custom_args = [
4356 SqliteValue::Text(SmallText::from_string("xxxpayloadxxx")),
4357 SqliteValue::Text(SmallText::from_string("x")),
4358 ];
4359
4360 let mut trim_best_ns = u128::MAX;
4361 let mut ltrim_best_ns = u128::MAX;
4362 let mut rtrim_best_ns = u128::MAX;
4363 let mut custom_best_ns = u128::MAX;
4364 let mut result_len = 0usize;
4365
4366 for _ in 0..REPEATS {
4367 let started = Instant::now();
4368 for _ in 0..INVOCATIONS {
4369 let result = black_box(
4370 trim.invoke(black_box(default_args.as_slice()))
4371 .expect("trim benchmark invocation must succeed"),
4372 );
4373 result_len = match result {
4374 SqliteValue::Text(text) => text.len(),
4375 SqliteValue::Null
4376 | SqliteValue::Integer(_)
4377 | SqliteValue::Float(_)
4378 | SqliteValue::Blob(_) => 0,
4379 };
4380 }
4381 trim_best_ns = trim_best_ns.min(started.elapsed().as_nanos());
4382
4383 let started = Instant::now();
4384 for _ in 0..INVOCATIONS {
4385 let result = black_box(
4386 ltrim
4387 .invoke(black_box(default_args.as_slice()))
4388 .expect("ltrim benchmark invocation must succeed"),
4389 );
4390 result_len = match result {
4391 SqliteValue::Text(text) => text.len(),
4392 SqliteValue::Null
4393 | SqliteValue::Integer(_)
4394 | SqliteValue::Float(_)
4395 | SqliteValue::Blob(_) => 0,
4396 };
4397 }
4398 ltrim_best_ns = ltrim_best_ns.min(started.elapsed().as_nanos());
4399
4400 let started = Instant::now();
4401 for _ in 0..INVOCATIONS {
4402 let result = black_box(
4403 rtrim
4404 .invoke(black_box(default_args.as_slice()))
4405 .expect("rtrim benchmark invocation must succeed"),
4406 );
4407 result_len = match result {
4408 SqliteValue::Text(text) => text.len(),
4409 SqliteValue::Null
4410 | SqliteValue::Integer(_)
4411 | SqliteValue::Float(_)
4412 | SqliteValue::Blob(_) => 0,
4413 };
4414 }
4415 rtrim_best_ns = rtrim_best_ns.min(started.elapsed().as_nanos());
4416
4417 let started = Instant::now();
4418 for _ in 0..INVOCATIONS {
4419 let result = black_box(
4420 trim.invoke(black_box(custom_args.as_slice()))
4421 .expect("custom trim benchmark invocation must succeed"),
4422 );
4423 result_len = match result {
4424 SqliteValue::Text(text) => text.len(),
4425 SqliteValue::Null
4426 | SqliteValue::Integer(_)
4427 | SqliteValue::Float(_)
4428 | SqliteValue::Blob(_) => 0,
4429 };
4430 }
4431 custom_best_ns = custom_best_ns.min(started.elapsed().as_nanos());
4432 }
4433
4434 println!(
4435 "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}"
4436 );
4437 }
4438
4439 #[test]
4442 fn test_nullif_equal() {
4443 assert_eq!(
4444 invoke2(
4445 &NullifFunc,
4446 SqliteValue::Integer(5),
4447 SqliteValue::Integer(5)
4448 )
4449 .unwrap(),
4450 SqliteValue::Null
4451 );
4452 }
4453
4454 #[test]
4455 fn test_nullif_different() {
4456 assert_eq!(
4457 invoke2(
4458 &NullifFunc,
4459 SqliteValue::Integer(5),
4460 SqliteValue::Integer(3)
4461 )
4462 .unwrap(),
4463 SqliteValue::Integer(5)
4464 );
4465 }
4466
4467 #[test]
4470 fn test_typeof_each() {
4471 assert_eq!(
4472 invoke1(&TypeofFunc, SqliteValue::Null).unwrap(),
4473 SqliteValue::Text(SmallText::from_string("null"))
4474 );
4475 assert_eq!(
4476 invoke1(&TypeofFunc, SqliteValue::Integer(1)).unwrap(),
4477 SqliteValue::Text(SmallText::from_string("integer"))
4478 );
4479 assert_eq!(
4480 invoke1(&TypeofFunc, SqliteValue::Float(1.0)).unwrap(),
4481 SqliteValue::Text(SmallText::from_string("real"))
4482 );
4483 assert_eq!(
4484 invoke1(&TypeofFunc, SqliteValue::Text(SmallText::from_string("x"))).unwrap(),
4485 SqliteValue::Text(SmallText::from_string("text"))
4486 );
4487 assert_eq!(
4488 invoke1(&TypeofFunc, SqliteValue::Blob(Arc::from([0].as_slice()))).unwrap(),
4489 SqliteValue::Text(SmallText::from_string("blob"))
4490 );
4491 }
4492
4493 #[test]
4496 fn test_subtype_null_returns_zero() {
4497 assert_eq!(
4498 invoke1(&SubtypeFunc, SqliteValue::Null).unwrap(),
4499 SqliteValue::Integer(0)
4500 );
4501 }
4502
4503 #[test]
4506 fn test_replace_basic() {
4507 let f = ReplaceFunc;
4508 assert_eq!(
4509 f.invoke(&[
4510 SqliteValue::Text(SmallText::from_string("hello world")),
4511 SqliteValue::Text(SmallText::from_string("world")),
4512 SqliteValue::Text(SmallText::from_string("earth")),
4513 ])
4514 .unwrap(),
4515 SqliteValue::Text(SmallText::from_string("hello earth"))
4516 );
4517 }
4518
4519 #[test]
4520 fn test_replace_empty_y() {
4521 let f = ReplaceFunc;
4522 assert_eq!(
4523 f.invoke(&[
4524 SqliteValue::Text(SmallText::from_string("hello")),
4525 SqliteValue::Text(SmallText::new("")),
4526 SqliteValue::Text(SmallText::from_string("x")),
4527 ])
4528 .unwrap(),
4529 SqliteValue::Text(SmallText::from_string("hello"))
4530 );
4531 }
4532
4533 #[test]
4534 #[ignore = "perf-only benchmark"]
4535 fn perf_replace_text_args() {
4536 use std::hint::black_box;
4537 use std::time::Instant;
4538
4539 const INVOCATIONS: usize = 100_000;
4540 const REPEATS: usize = 5;
4541
4542 let f = ReplaceFunc;
4543 let args = [
4544 SqliteValue::Text(SmallText::from_string("payload payload payload")),
4545 SqliteValue::Text(SmallText::from_string("zz")),
4546 SqliteValue::Text(SmallText::from_string("replacement")),
4547 ];
4548
4549 let mut best_ns = u128::MAX;
4550 let mut result_len = 0usize;
4551 for _ in 0..REPEATS {
4552 let started = Instant::now();
4553 for _ in 0..INVOCATIONS {
4554 let result = black_box(
4555 f.invoke(black_box(args.as_slice()))
4556 .expect("replace benchmark invocation must succeed"),
4557 );
4558 result_len = match result {
4559 SqliteValue::Text(text) => text.len(),
4560 SqliteValue::Null
4561 | SqliteValue::Integer(_)
4562 | SqliteValue::Float(_)
4563 | SqliteValue::Blob(_) => 0,
4564 };
4565 }
4566 best_ns = best_ns.min(started.elapsed().as_nanos());
4567 }
4568
4569 println!(
4570 "replace_text_args invocations={INVOCATIONS} repeats={REPEATS} best_ns={best_ns} result_len={result_len}"
4571 );
4572 }
4573
4574 #[test]
4577 #[allow(clippy::float_cmp)]
4578 fn test_round_half_away() {
4579 assert_eq!(
4581 RoundFunc.invoke(&[SqliteValue::Float(2.5)]).unwrap(),
4582 SqliteValue::Float(3.0)
4583 );
4584 assert_eq!(
4585 RoundFunc.invoke(&[SqliteValue::Float(-2.5)]).unwrap(),
4586 SqliteValue::Float(-3.0)
4587 );
4588 }
4589
4590 #[test]
4591 #[allow(clippy::float_cmp, clippy::approx_constant)]
4592 fn test_round_precision() {
4593 assert_eq!(
4594 RoundFunc
4595 .invoke(&[SqliteValue::Float(3.14159), SqliteValue::Integer(2)])
4596 .unwrap(),
4597 SqliteValue::Float(3.14)
4598 );
4599 }
4600
4601 #[test]
4602 #[allow(clippy::float_cmp)]
4603 fn test_round_extreme_n_clamped() {
4604 assert_eq!(
4606 RoundFunc
4607 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(400)])
4608 .unwrap(),
4609 RoundFunc
4610 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(30)])
4611 .unwrap(),
4612 );
4613 assert_eq!(
4615 RoundFunc
4616 .invoke(&[SqliteValue::Float(2.5), SqliteValue::Integer(-5)])
4617 .unwrap(),
4618 SqliteValue::Float(3.0)
4619 );
4620 let result = RoundFunc
4622 .invoke(&[SqliteValue::Float(1.5), SqliteValue::Integer(i64::MAX)])
4623 .unwrap();
4624 if let SqliteValue::Float(v) = result {
4625 assert!(!v.is_nan(), "round must never return NaN");
4626 }
4627 }
4628
4629 #[test]
4630 #[allow(clippy::float_cmp)]
4631 fn test_round_large_value_no_fractional() {
4632 let big = 9_007_199_254_740_993.0_f64;
4634 assert_eq!(
4635 RoundFunc.invoke(&[SqliteValue::Float(big)]).unwrap(),
4636 SqliteValue::Float(big)
4637 );
4638 assert_eq!(
4639 RoundFunc.invoke(&[SqliteValue::Float(-big)]).unwrap(),
4640 SqliteValue::Float(-big)
4641 );
4642 }
4643
4644 #[test]
4647 fn test_sign_positive() {
4648 assert_eq!(
4649 invoke1(&SignFunc, SqliteValue::Integer(42)).unwrap(),
4650 SqliteValue::Integer(1)
4651 );
4652 }
4653
4654 #[test]
4655 fn test_sign_negative() {
4656 assert_eq!(
4657 invoke1(&SignFunc, SqliteValue::Integer(-42)).unwrap(),
4658 SqliteValue::Integer(-1)
4659 );
4660 }
4661
4662 #[test]
4663 fn test_sign_zero() {
4664 assert_eq!(
4665 invoke1(&SignFunc, SqliteValue::Integer(0)).unwrap(),
4666 SqliteValue::Integer(0)
4667 );
4668 }
4669
4670 #[test]
4671 fn test_sign_null() {
4672 assert_eq!(
4673 invoke1(&SignFunc, SqliteValue::Null).unwrap(),
4674 SqliteValue::Null
4675 );
4676 }
4677
4678 #[test]
4679 fn test_sign_non_numeric() {
4680 assert_eq!(
4682 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string("abc"))).unwrap(),
4683 SqliteValue::Null
4684 );
4685 }
4686
4687 #[test]
4688 fn test_sign_whitespace_padded_text() {
4689 assert_eq!(
4692 invoke1(
4693 &SignFunc,
4694 SqliteValue::Text(SmallText::from_string(" 5 "))
4695 )
4696 .unwrap(),
4697 SqliteValue::Integer(1)
4698 );
4699 assert_eq!(
4700 invoke1(
4701 &SignFunc,
4702 SqliteValue::Text(SmallText::from_string(" -3.14 "))
4703 )
4704 .unwrap(),
4705 SqliteValue::Integer(-1)
4706 );
4707 }
4708
4709 #[test]
4710 fn test_sign_unicode_space_and_blob_return_null() {
4711 assert_eq!(
4712 invoke1(
4713 &SignFunc,
4714 SqliteValue::Text(SmallText::from_string("\u{00a0}123"))
4715 )
4716 .unwrap(),
4717 SqliteValue::Null
4718 );
4719 assert_eq!(
4720 invoke1(&SignFunc, SqliteValue::Blob(Arc::from(b"123".as_slice()))).unwrap(),
4721 SqliteValue::Null
4722 );
4723 }
4724
4725 #[test]
4726 fn test_sign_nan_inf_text_returns_null() {
4727 for s in &[
4730 "NaN",
4731 "nan",
4732 "inf",
4733 "-inf",
4734 "Infinity",
4735 "-Infinity",
4736 "INF",
4737 "+nan",
4738 "+inf",
4739 ] {
4740 assert_eq!(
4741 invoke1(&SignFunc, SqliteValue::Text(SmallText::from_string(*s))).unwrap(),
4742 SqliteValue::Null,
4743 "sign('{s}') should be NULL"
4744 );
4745 }
4746 }
4747
4748 #[test]
4749 fn test_sign_numeric_overflow_to_infinity() {
4750 assert_eq!(
4753 invoke1(
4754 &SignFunc,
4755 SqliteValue::Text(SmallText::from_string("1e999"))
4756 )
4757 .unwrap(),
4758 SqliteValue::Integer(1)
4759 );
4760 assert_eq!(
4761 invoke1(
4762 &SignFunc,
4763 SqliteValue::Text(SmallText::from_string("-1e999"))
4764 )
4765 .unwrap(),
4766 SqliteValue::Integer(-1)
4767 );
4768 assert_eq!(
4770 invoke1(
4771 &SignFunc,
4772 SqliteValue::Text(SmallText::from_string("1e-999"))
4773 )
4774 .unwrap(),
4775 SqliteValue::Integer(0)
4776 );
4777 }
4778
4779 #[test]
4780 fn test_sign_float_nan_returns_null() {
4781 assert_eq!(
4783 invoke1(&SignFunc, SqliteValue::Float(f64::NAN)).unwrap(),
4784 SqliteValue::Null
4785 );
4786 }
4787
4788 #[test]
4791 fn test_scalar_max_null() {
4792 let f = ScalarMaxFunc;
4793 let result = f
4794 .invoke(&[
4795 SqliteValue::Integer(1),
4796 SqliteValue::Null,
4797 SqliteValue::Integer(3),
4798 ])
4799 .unwrap();
4800 assert_eq!(result, SqliteValue::Null);
4801 }
4802
4803 #[test]
4804 fn test_scalar_max_values() {
4805 let f = ScalarMaxFunc;
4806 let result = f
4807 .invoke(&[
4808 SqliteValue::Integer(3),
4809 SqliteValue::Integer(1),
4810 SqliteValue::Integer(2),
4811 ])
4812 .unwrap();
4813 assert_eq!(result, SqliteValue::Integer(3));
4814 }
4815
4816 #[test]
4817 fn test_scalar_min_null() {
4818 let f = ScalarMinFunc;
4819 let result = f
4820 .invoke(&[
4821 SqliteValue::Integer(1),
4822 SqliteValue::Null,
4823 SqliteValue::Integer(3),
4824 ])
4825 .unwrap();
4826 assert_eq!(result, SqliteValue::Null);
4827 }
4828
4829 #[test]
4830 fn test_scalar_min_selects_later_equal_value_while_max_keeps_first() {
4831 let min = ScalarMinFunc;
4832 let max = ScalarMaxFunc;
4833 let numeric = [SqliteValue::Integer(1), SqliteValue::Float(1.0)];
4834 assert!(matches!(
4835 min.invoke(&numeric).unwrap(),
4836 SqliteValue::Float(value) if value == 1.0
4837 ));
4838 assert_eq!(max.invoke(&numeric).unwrap(), SqliteValue::Integer(1));
4839
4840 let text = [
4841 SqliteValue::Text(SmallText::new("a")),
4842 SqliteValue::Text(SmallText::new("A")),
4843 ];
4844 let nocase = crate::collation::NoCaseCollation;
4845 assert_eq!(
4846 min.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4847 SqliteValue::Text(SmallText::new("A"))
4848 );
4849 assert_eq!(
4850 max.invoke_with_collation(&text, Some(&nocase)).unwrap(),
4851 SqliteValue::Text(SmallText::new("a"))
4852 );
4853 }
4854
4855 #[test]
4858 fn test_quote_text() {
4859 assert_eq!(
4860 invoke1(
4861 &QuoteFunc,
4862 SqliteValue::Text(SmallText::from_string("it's"))
4863 )
4864 .unwrap(),
4865 SqliteValue::Text(SmallText::from_string("'it''s'"))
4866 );
4867 }
4868
4869 #[test]
4870 fn test_quote_null() {
4871 assert_eq!(
4872 invoke1(&QuoteFunc, SqliteValue::Null).unwrap(),
4873 SqliteValue::Text(SmallText::from_string("NULL"))
4874 );
4875 }
4876
4877 #[test]
4878 fn test_quote_blob() {
4879 assert_eq!(
4880 invoke1(&QuoteFunc, SqliteValue::Blob(Arc::from([0xAB].as_slice()))).unwrap(),
4881 SqliteValue::Text(SmallText::from_string("X'AB'"))
4882 );
4883 }
4884
4885 #[test]
4886 fn test_quote_text_truncates_at_first_nul() {
4887 assert_eq!(
4888 invoke1(
4889 &QuoteFunc,
4890 SqliteValue::Text(SmallText::from_string("A\0B"))
4891 )
4892 .unwrap(),
4893 SqliteValue::Text(SmallText::from_string("'A'"))
4894 );
4895 }
4896
4897 #[test]
4898 fn test_unistr_quote_plain_text_matches_quote() {
4899 assert_eq!(
4900 invoke1(
4901 &UnistrQuoteFunc,
4902 SqliteValue::Text(SmallText::from_string("it's"))
4903 )
4904 .unwrap(),
4905 SqliteValue::Text(SmallText::from_string("'it''s'"))
4906 );
4907 }
4908
4909 #[test]
4910 fn test_unistr_quote_escapes_control_chars_and_backslashes() {
4911 assert_eq!(
4912 invoke1(
4913 &UnistrQuoteFunc,
4914 SqliteValue::Text(SmallText::from_string("a\nb\\c\x01d"))
4915 )
4916 .unwrap(),
4917 SqliteValue::Text(SmallText::from_string("unistr('a\\u000ab\\\\c\\u0001d')"))
4918 );
4919 }
4920
4921 #[test]
4922 fn test_unistr_quote_truncates_at_first_nul_before_wrapping() {
4923 assert_eq!(
4924 invoke1(
4925 &UnistrQuoteFunc,
4926 SqliteValue::Text(SmallText::from_string("A\0\nB"))
4927 )
4928 .unwrap(),
4929 SqliteValue::Text(SmallText::from_string("'A'"))
4930 );
4931 }
4932
4933 #[test]
4934 fn test_unistr_decodes_backslash_and_unicode_escapes() {
4935 assert_eq!(
4936 invoke1(
4937 &UnistrFunc,
4938 SqliteValue::Text(SmallText::from_string(
4939 "a\\\\b\\u0020\\U0001f600\\0041\\+000042"
4940 ))
4941 )
4942 .unwrap(),
4943 SqliteValue::Text(SmallText::from_string("a\\b \u{1f600}AB"))
4944 );
4945 }
4946
4947 #[test]
4948 fn test_unistr_invalid_escape_returns_error() {
4949 for input in [
4950 "\\u12xz",
4951 "\\12xz",
4952 "\\+00xz",
4953 "\\",
4954 "\\x",
4955 "\\U00110000",
4956 "\\D800",
4957 ] {
4958 let err = invoke1(
4959 &UnistrFunc,
4960 SqliteValue::Text(SmallText::from_string(input)),
4961 )
4962 .unwrap_err();
4963 assert_eq!(err.to_string(), INVALID_UNISTR_ESCAPE);
4964 }
4965 }
4966
4967 #[test]
4968 #[ignore = "perf-only benchmark"]
4969 fn perf_unistr_text_args() {
4970 use std::hint::black_box;
4971 use std::time::Instant;
4972
4973 const INVOCATIONS: usize = 500_000;
4974 const REPEATS: usize = 7;
4975
4976 let f = UnistrFunc;
4977 let plain_args = [SqliteValue::Text(SmallText::from_string(
4978 "plain unicode payload",
4979 ))];
4980 let escaped_args = [SqliteValue::Text(SmallText::from_string(
4981 "a\\\\b\\u0020\\u0048\\u0069\\U0001f600",
4982 ))];
4983
4984 let mut plain_best_ns = u128::MAX;
4985 let mut escaped_best_ns = u128::MAX;
4986 let mut checksum = 0usize;
4987 for _ in 0..REPEATS {
4988 let started = Instant::now();
4989 for _ in 0..INVOCATIONS {
4990 let result = black_box(
4991 f.invoke(black_box(plain_args.as_slice()))
4992 .expect("unistr plain benchmark invocation must succeed"),
4993 );
4994 if let SqliteValue::Text(text) = result {
4995 checksum = checksum.wrapping_add(text.len());
4996 }
4997 }
4998 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
4999
5000 let started = Instant::now();
5001 for _ in 0..INVOCATIONS {
5002 let result = black_box(
5003 f.invoke(black_box(escaped_args.as_slice()))
5004 .expect("unistr escaped benchmark invocation must succeed"),
5005 );
5006 if let SqliteValue::Text(text) = result {
5007 checksum = checksum.wrapping_add(text.len());
5008 }
5009 }
5010 escaped_best_ns = escaped_best_ns.min(started.elapsed().as_nanos());
5011 }
5012
5013 println!(
5014 "unistr_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} escaped_best_ns={escaped_best_ns} checksum={checksum}"
5015 );
5016 }
5017
5018 #[test]
5021 fn test_random_range() {
5022 let f = RandomFunc;
5023 let result = f.invoke(&[]).unwrap();
5024 assert!(matches!(result, SqliteValue::Integer(_)));
5025 }
5026
5027 #[test]
5030 fn test_randomblob_length() {
5031 let result = invoke1(&RandomblobFunc, SqliteValue::Integer(16)).unwrap();
5032 match result {
5033 SqliteValue::Blob(b) => assert_eq!(b.len(), 16),
5034 other => unreachable!("expected blob, got {other:?}"),
5035 }
5036 }
5037
5038 #[test]
5039 fn test_randomblob_null_zero_and_negative_lengths_are_one_byte() {
5040 for arg in [
5041 SqliteValue::Null,
5042 SqliteValue::Integer(0),
5043 SqliteValue::Integer(-5),
5044 ] {
5045 let result = invoke1(&RandomblobFunc, arg).unwrap();
5046 match result {
5047 SqliteValue::Blob(b) => assert_eq!(b.len(), 1),
5048 other => unreachable!("expected one-byte blob, got {other:?}"),
5049 }
5050 }
5051 }
5052
5053 #[test]
5056 fn test_zeroblob_length() {
5057 let result = invoke1(&ZeroblobFunc, SqliteValue::Integer(100)).unwrap();
5058 match result {
5059 SqliteValue::Blob(b) => {
5060 assert_eq!(b.len(), 100);
5061 assert!(b.iter().all(|&x| x == 0));
5062 }
5063 other => unreachable!("expected blob, got {other:?}"),
5064 }
5065 }
5066
5067 #[test]
5070 fn test_unhex_valid() {
5071 let result = invoke1(
5072 &UnhexFunc,
5073 SqliteValue::Text(SmallText::from_string("48656C6C6F")),
5074 )
5075 .unwrap();
5076 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hello".as_slice())));
5077 }
5078
5079 #[test]
5080 fn test_unhex_invalid() {
5081 let result = invoke1(
5082 &UnhexFunc,
5083 SqliteValue::Text(SmallText::from_string("ZZZZ")),
5084 )
5085 .unwrap();
5086 assert_eq!(result, SqliteValue::Null);
5087 }
5088
5089 #[test]
5090 fn test_unhex_ignore_chars() {
5091 let f = UnhexFunc;
5092 let result = f
5093 .invoke(&[
5094 SqliteValue::Text(SmallText::from_string("48-65-6C")),
5095 SqliteValue::Text(SmallText::from_string("-")),
5096 ])
5097 .unwrap();
5098 assert_eq!(result, SqliteValue::Blob(Arc::from(b"Hel".as_slice())));
5099 }
5100
5101 #[test]
5102 fn test_unhex_ignore_chars_only_between_byte_pairs() {
5103 let f = UnhexFunc;
5104 let result = f
5105 .invoke(&[
5106 SqliteValue::Text(SmallText::from_string("AB CD")),
5107 SqliteValue::Text(SmallText::from_string(" ")),
5108 ])
5109 .unwrap();
5110 assert_eq!(result, SqliteValue::Blob(Arc::from([0xAB, 0xCD])));
5111
5112 let result = f
5113 .invoke(&[
5114 SqliteValue::Text(SmallText::from_string("A BCD")),
5115 SqliteValue::Text(SmallText::from_string(" ")),
5116 ])
5117 .unwrap();
5118 assert_eq!(result, SqliteValue::Null);
5119 }
5120
5121 #[test]
5122 fn test_unhex_null_ignore_argument_returns_null() {
5123 let f = UnhexFunc;
5124 let result = f
5125 .invoke(&[
5126 SqliteValue::Text(SmallText::from_string("41")),
5127 SqliteValue::Null,
5128 ])
5129 .unwrap();
5130 assert_eq!(result, SqliteValue::Null);
5131 }
5132
5133 #[test]
5134 fn test_unhex_hex_digits_in_ignore_argument_do_not_ignore_digits() {
5135 let f = UnhexFunc;
5136 let result = f
5137 .invoke(&[
5138 SqliteValue::Text(SmallText::from_string("41")),
5139 SqliteValue::Text(SmallText::from_string("4")),
5140 ])
5141 .unwrap();
5142 assert_eq!(result, SqliteValue::Blob(Arc::from(b"A".as_slice())));
5143 }
5144
5145 #[test]
5146 #[ignore = "perf-only benchmark"]
5147 fn perf_unhex_text_args() {
5148 use std::hint::black_box;
5149 use std::time::Instant;
5150
5151 const INVOCATIONS: usize = 300_000;
5152 const REPEATS: usize = 7;
5153
5154 let f = UnhexFunc;
5155 let plain_args = [SqliteValue::Text(SmallText::from_string(
5156 "48656C6C6F776F726C64",
5157 ))];
5158 let ignore_args = [
5159 SqliteValue::Text(SmallText::from_string("48-65-6C-6C-6F")),
5160 SqliteValue::Text(SmallText::from_string("-")),
5161 ];
5162 let mut plain_best_ns = u128::MAX;
5163 let mut ignore_best_ns = u128::MAX;
5164 let mut checksum = 0usize;
5165
5166 for _ in 0..REPEATS {
5167 let started = Instant::now();
5168 for _ in 0..INVOCATIONS {
5169 let result = black_box(
5170 f.invoke(black_box(plain_args.as_slice()))
5171 .expect("unhex benchmark invocation must succeed"),
5172 );
5173 if let SqliteValue::Blob(blob) = result {
5174 checksum = checksum.wrapping_add(blob.len());
5175 }
5176 }
5177 plain_best_ns = plain_best_ns.min(started.elapsed().as_nanos());
5178
5179 let started = Instant::now();
5180 for _ in 0..INVOCATIONS {
5181 let result = black_box(
5182 f.invoke(black_box(ignore_args.as_slice()))
5183 .expect("unhex ignore benchmark invocation must succeed"),
5184 );
5185 if let SqliteValue::Blob(blob) = result {
5186 checksum = checksum.wrapping_add(blob.len());
5187 }
5188 }
5189 ignore_best_ns = ignore_best_ns.min(started.elapsed().as_nanos());
5190 }
5191
5192 println!(
5193 "unhex_text_args invocations={INVOCATIONS} repeats={REPEATS} plain_best_ns={plain_best_ns} ignore_best_ns={ignore_best_ns} checksum={checksum}"
5194 );
5195 }
5196
5197 #[test]
5200 fn test_unicode_first_char() {
5201 assert_eq!(
5202 invoke1(&UnicodeFunc, SqliteValue::Text(SmallText::from_string("A"))).unwrap(),
5203 SqliteValue::Integer(65)
5204 );
5205 }
5206
5207 #[test]
5208 fn test_unicode_text_stops_at_nul() {
5209 assert_eq!(
5210 invoke1(
5211 &UnicodeFunc,
5212 SqliteValue::Text(SmallText::from_string("\0A"))
5213 )
5214 .unwrap(),
5215 SqliteValue::Null
5216 );
5217 assert_eq!(
5218 invoke1(
5219 &UnicodeFunc,
5220 SqliteValue::Text(SmallText::from_string("A\0"))
5221 )
5222 .unwrap(),
5223 SqliteValue::Integer(65)
5224 );
5225 }
5226
5227 #[test]
5228 fn test_unicode_blob_uses_sqlite_utf8_reader() {
5229 let cases: &[(&[u8], SqliteValue)] = &[
5230 (&[0x00, 0x41], SqliteValue::Null),
5231 (&[0x80], SqliteValue::Integer(128)),
5232 (&[0xC2, 0x80], SqliteValue::Integer(128)),
5233 (&[0xC2, 0x80, 0x80], SqliteValue::Integer(8192)),
5234 (&[0xED, 0xA0, 0x80], SqliteValue::Integer(65_533)),
5235 (&[0xF4, 0x90, 0x80, 0x80], SqliteValue::Integer(1_114_112)),
5236 ];
5237
5238 for (bytes, expected) in cases {
5239 assert_eq!(
5240 invoke1(&UnicodeFunc, SqliteValue::Blob(Arc::from(*bytes))).unwrap(),
5241 expected.clone()
5242 );
5243 }
5244 }
5245
5246 #[test]
5247 #[ignore = "perf-only benchmark"]
5248 fn perf_unicode_text_arg() {
5249 use std::hint::black_box;
5250 use std::time::Instant;
5251
5252 const INVOCATIONS: usize = 1_000_000;
5253 const REPEATS: usize = 7;
5254
5255 let f = UnicodeFunc;
5256 let args = [SqliteValue::Text(SmallText::from_string("Alphabet soup"))];
5257 let mut text_best_ns = u128::MAX;
5258 let mut checksum = 0i64;
5259
5260 for _ in 0..REPEATS {
5261 let started = Instant::now();
5262 for _ in 0..INVOCATIONS {
5263 let result = black_box(
5264 f.invoke(black_box(args.as_slice()))
5265 .expect("unicode benchmark invocation must succeed"),
5266 );
5267 if let SqliteValue::Integer(codepoint) = result {
5268 checksum = checksum.wrapping_add(codepoint);
5269 }
5270 }
5271 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
5272 }
5273
5274 println!(
5275 "unicode_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
5276 );
5277 }
5278
5279 #[test]
5282 fn test_soundex_basic() {
5283 assert_eq!(
5284 invoke1(
5285 &SoundexFunc,
5286 SqliteValue::Text(SmallText::from_string("Robert"))
5287 )
5288 .unwrap(),
5289 SqliteValue::Text(SmallText::from_string("R163"))
5290 );
5291 }
5292
5293 #[test]
5294 #[ignore = "perf-only benchmark"]
5295 fn perf_soundex_text_arg() {
5296 use std::hint::black_box;
5297 use std::time::Instant;
5298
5299 const INVOCATIONS: usize = 1_000_000;
5300 const REPEATS: usize = 7;
5301
5302 let f = SoundexFunc;
5303 let args = [SqliteValue::Text(SmallText::from_string("Robert"))];
5304 let mut text_best_ns = u128::MAX;
5305 let mut checksum = 0usize;
5306
5307 for _ in 0..REPEATS {
5308 let started = Instant::now();
5309 for _ in 0..INVOCATIONS {
5310 let result = black_box(
5311 f.invoke(black_box(args.as_slice()))
5312 .expect("soundex benchmark invocation must succeed"),
5313 );
5314 if let SqliteValue::Text(text) = result {
5315 checksum = checksum.wrapping_add(text.len());
5316 }
5317 }
5318 text_best_ns = text_best_ns.min(started.elapsed().as_nanos());
5319 }
5320
5321 println!(
5322 "soundex_text_arg invocations={INVOCATIONS} repeats={REPEATS} text_best_ns={text_best_ns} checksum={checksum}"
5323 );
5324 }
5325
5326 #[test]
5329 fn test_substr_basic() {
5330 let f = SubstrFunc;
5331 assert_eq!(
5332 f.invoke(&[
5333 SqliteValue::Text(SmallText::from_string("hello")),
5334 SqliteValue::Integer(2),
5335 SqliteValue::Integer(3),
5336 ])
5337 .unwrap(),
5338 SqliteValue::Text(SmallText::from_string("ell"))
5339 );
5340 }
5341
5342 #[test]
5343 fn test_substr_truncates_at_embedded_nul() {
5344 let f = SubstrFunc;
5348 let s = SqliteValue::Text(SmallText::from_string("a\u{0}bc"));
5349 assert_eq!(
5350 f.invoke(&[s.clone(), SqliteValue::Integer(1), SqliteValue::Integer(4)])
5351 .unwrap(),
5352 SqliteValue::Text(SmallText::from_string("a"))
5353 );
5354 assert_eq!(
5355 f.invoke(&[s, SqliteValue::Integer(3)]).unwrap(),
5356 SqliteValue::Text(SmallText::from_string(""))
5357 );
5358 }
5359
5360 #[test]
5361 fn test_substr_start_zero_quirk() {
5362 let f = SubstrFunc;
5364 let result = f
5365 .invoke(&[
5366 SqliteValue::Text(SmallText::from_string("hello")),
5367 SqliteValue::Integer(0),
5368 SqliteValue::Integer(3),
5369 ])
5370 .unwrap();
5371 assert_eq!(result, SqliteValue::Text(SmallText::from_string("he")));
5372 }
5373
5374 #[test]
5375 fn test_substr_negative_start() {
5376 let f = SubstrFunc;
5378 let result = f
5379 .invoke(&[
5380 SqliteValue::Text(SmallText::from_string("hello")),
5381 SqliteValue::Integer(-2),
5382 ])
5383 .unwrap();
5384 assert_eq!(result, SqliteValue::Text(SmallText::from_string("lo")));
5385 }
5386
5387 #[test]
5388 fn test_substr_negative_length() {
5389 let f = SubstrFunc;
5390 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5391 let i = SqliteValue::Integer;
5392 assert_eq!(f.invoke(&[t("hello"), i(3), i(-2)]).unwrap(), t("he"));
5394 assert_eq!(f.invoke(&[t("hello"), i(3), i(-5)]).unwrap(), t("he"));
5396 assert_eq!(f.invoke(&[t("hello"), i(1), i(-1)]).unwrap(), t(""));
5398 }
5399
5400 #[test]
5401 fn test_substr_negative_start_negative_length() {
5402 let f = SubstrFunc;
5403 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5404 let i = SqliteValue::Integer;
5405 assert_eq!(f.invoke(&[t("hello"), i(-2), i(-2)]).unwrap(), t("el"));
5407 }
5408
5409 #[test]
5410 fn test_substr_edge_cases() {
5411 let f = SubstrFunc;
5412 let t = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5413 let i = SqliteValue::Integer;
5414 assert_eq!(f.invoke(&[t("hello"), i(6), i(2)]).unwrap(), t(""));
5416 assert_eq!(f.invoke(&[t("hello"), i(-10), i(3)]).unwrap(), t(""));
5418 assert_eq!(f.invoke(&[t("hello"), i(-5), i(6)]).unwrap(), t("hello"));
5420 assert_eq!(f.invoke(&[t("hello"), i(0), i(1)]).unwrap(), t(""));
5422 assert_eq!(f.invoke(&[t("hello"), i(0), i(-1)]).unwrap(), t(""));
5424 assert_eq!(f.invoke(&[t(""), i(1), i(1)]).unwrap(), t(""));
5426 }
5427
5428 #[test]
5429 fn test_substr_blob_negative_length() {
5430 let f = SubstrFunc;
5431 let i = SqliteValue::Integer;
5432 let blob = SqliteValue::Blob(Arc::from([1, 2, 3, 4, 5].as_slice()));
5433 assert_eq!(
5435 f.invoke(&[blob, i(-2), i(-2)]).unwrap(),
5436 SqliteValue::Blob(Arc::from([2, 3].as_slice()))
5437 );
5438 }
5439
5440 #[test]
5443 fn test_like_case_insensitive() {
5444 assert_eq!(
5445 invoke2(
5446 &LikeFunc,
5447 SqliteValue::Text(SmallText::from_string("ABC")),
5448 SqliteValue::Text(SmallText::from_string("abc"))
5449 )
5450 .unwrap(),
5451 SqliteValue::Integer(1)
5452 );
5453 }
5454
5455 #[test]
5456 fn test_like_escape() {
5457 let f = LikeFunc;
5458 let result = f
5459 .invoke(&[
5460 SqliteValue::Text(SmallText::from_string("10\\%")),
5461 SqliteValue::Text(SmallText::from_string("10%")),
5462 SqliteValue::Text(SmallText::from_string("\\")),
5463 ])
5464 .unwrap();
5465 assert_eq!(result, SqliteValue::Integer(1));
5466 }
5467
5468 #[test]
5469 fn test_like_escape_rejects_empty_string() {
5470 let err = LikeFunc
5471 .invoke(&[
5472 SqliteValue::Text(SmallText::from_string("a")),
5473 SqliteValue::Text(SmallText::from_string("a")),
5474 SqliteValue::Text(SmallText::new("")),
5475 ])
5476 .unwrap_err();
5477 assert!(
5478 err.to_string()
5479 .contains("ESCAPE expression must be a single character")
5480 );
5481 }
5482
5483 #[test]
5484 fn test_like_escape_rejects_multi_character_string() {
5485 let err = LikeFunc
5486 .invoke(&[
5487 SqliteValue::Text(SmallText::from_string("a")),
5488 SqliteValue::Text(SmallText::from_string("a")),
5489 SqliteValue::Text(SmallText::from_string("xx")),
5490 ])
5491 .unwrap_err();
5492 assert!(
5493 err.to_string()
5494 .contains("ESCAPE expression must be a single character")
5495 );
5496 }
5497
5498 #[test]
5499 fn test_like_percent() {
5500 assert_eq!(
5501 invoke2(
5502 &LikeFunc,
5503 SqliteValue::Text(SmallText::from_string("%ell%")),
5504 SqliteValue::Text(SmallText::from_string("Hello"))
5505 )
5506 .unwrap(),
5507 SqliteValue::Integer(1)
5508 );
5509 }
5510
5511 #[test]
5514 fn test_glob_star() {
5515 assert_eq!(
5516 invoke2(
5517 &GlobFunc,
5518 SqliteValue::Text(SmallText::from_string("*.txt")),
5519 SqliteValue::Text(SmallText::from_string("file.txt"))
5520 )
5521 .unwrap(),
5522 SqliteValue::Integer(1)
5523 );
5524 }
5525
5526 #[test]
5527 fn test_glob_case_sensitive() {
5528 assert_eq!(
5529 invoke2(
5530 &GlobFunc,
5531 SqliteValue::Text(SmallText::from_string("ABC")),
5532 SqliteValue::Text(SmallText::from_string("abc"))
5533 )
5534 .unwrap(),
5535 SqliteValue::Integer(0)
5536 );
5537 }
5538
5539 #[test]
5540 fn test_glob_unterminated_character_class_does_not_match() {
5541 assert_eq!(
5544 invoke2(
5545 &GlobFunc,
5546 SqliteValue::Text(SmallText::from_string("[a")),
5547 SqliteValue::Text(SmallText::from_string("a"))
5548 )
5549 .unwrap(),
5550 SqliteValue::Integer(0)
5551 );
5552 assert_eq!(
5554 invoke2(
5555 &GlobFunc,
5556 SqliteValue::Text(SmallText::from_string("[a]")),
5557 SqliteValue::Text(SmallText::from_string("a"))
5558 )
5559 .unwrap(),
5560 SqliteValue::Integer(1)
5561 );
5562 }
5563
5564 #[test]
5565 fn test_glob_trailing_dash_in_character_class_is_literal() {
5566 let glob = |pattern: &str, text: &str| {
5574 invoke2(
5575 &GlobFunc,
5576 SqliteValue::Text(SmallText::from_string(pattern)),
5577 SqliteValue::Text(SmallText::from_string(text)),
5578 )
5579 .unwrap()
5580 };
5581 assert_eq!(
5583 glob("*[^A-Za-z0-9._:-]*", "peer_abc/123"),
5584 SqliteValue::Integer(1)
5585 );
5586 assert_eq!(
5588 glob("*[^A-Za-z0-9._:-]*", "peer_a.b:c-"),
5589 SqliteValue::Integer(0)
5590 );
5591 assert_eq!(glob("[a-c-]", "-"), SqliteValue::Integer(1));
5593 assert_eq!(glob("[a-c-]", "b"), SqliteValue::Integer(1));
5594 assert_eq!(glob("[a-c-]", "d"), SqliteValue::Integer(0));
5595 assert_eq!(glob("[-a]", "-"), SqliteValue::Integer(1));
5597 assert_eq!(glob("[-a]", "b"), SqliteValue::Integer(0));
5598 }
5599
5600 #[test]
5601 fn test_iif_two_argument_form() {
5602 let f = IifFunc;
5604 assert_eq!(
5605 f.invoke(&[
5606 SqliteValue::Integer(1),
5607 SqliteValue::Text(SmallText::from_string("y")),
5608 ])
5609 .unwrap(),
5610 SqliteValue::Text(SmallText::from_string("y"))
5611 );
5612 assert_eq!(
5613 f.invoke(&[
5614 SqliteValue::Integer(0),
5615 SqliteValue::Text(SmallText::from_string("y")),
5616 ])
5617 .unwrap(),
5618 SqliteValue::Null
5619 );
5620 }
5621
5622 #[test]
5623 fn test_format_g_negative_zero() {
5624 let f = FormatFunc;
5626 assert_eq!(
5627 f.invoke(&[
5628 SqliteValue::Text(SmallText::from_string("%g")),
5629 SqliteValue::Float(-0.0),
5630 ])
5631 .unwrap(),
5632 SqliteValue::Text(SmallText::from_string("0"))
5633 );
5634 }
5635
5636 #[test]
5637 fn test_format_signed_zero_all_specs() {
5638 let f = FormatFunc;
5642 let fmt = |spec: &str, v: f64| -> String {
5643 match f
5644 .invoke(&[
5645 SqliteValue::Text(SmallText::from_string(spec)),
5646 SqliteValue::Float(v),
5647 ])
5648 .unwrap()
5649 {
5650 SqliteValue::Text(s) => s.as_str().to_owned(),
5651 other => panic!("expected text, got {other:?}"),
5652 }
5653 };
5654 assert_eq!(fmt("%f", -0.0), "0.000000");
5656 assert_eq!(fmt("%e", -0.0), "0.000000e+00");
5657 assert_eq!(fmt("%E", -0.0), "0.000000E+00");
5658 assert_eq!(fmt("%G", -0.0), "0");
5659 assert_eq!(fmt("%+g", -0.0), "+0");
5661 assert_eq!(fmt("% g", -0.0), " 0");
5662 assert_eq!(fmt("%+f", -0.0), "+0.000000");
5663 assert_eq!(fmt("%8.2f", -0.0), " 0.00");
5665 assert_eq!(fmt("%!g", -0.0), "0.0");
5667 assert_eq!(fmt("%g", -1e-320 * 1e-10), "0");
5669 assert_eq!(fmt("%g", -1.5), "-1.5");
5671 assert_eq!(fmt("%f", -2.25), "-2.250000");
5672 assert_eq!(fmt("%+g", -1.5), "-1.5");
5673 }
5674
5675 #[test]
5676 fn test_format_g_integer_trailing_zeros() {
5677 let f = FormatFunc;
5682 let fmt = |spec: &str, v: f64| -> String {
5683 match f
5684 .invoke(&[
5685 SqliteValue::Text(SmallText::from_string(spec)),
5686 SqliteValue::Float(v),
5687 ])
5688 .unwrap()
5689 {
5690 SqliteValue::Text(s) => s.as_str().to_owned(),
5691 other => panic!("expected text, got {other:?}"),
5692 }
5693 };
5694 assert_eq!(fmt("%g", 100000.0), "100000");
5696 assert_eq!(fmt("%g", 120000.0), "120000");
5697 assert_eq!(fmt("%g", 250000.0), "250000");
5698 assert_eq!(fmt("%g", 100.0), "100");
5699 assert_eq!(fmt("%g", 999999.0), "999999");
5700 assert_eq!(fmt("%G", 100000.0), "100000");
5701 assert_eq!(fmt("%g", 0.5), "0.5");
5703 assert_eq!(fmt("%g", 1.5), "1.5");
5704 assert_eq!(fmt("%g", 1000000.0), "1e+06");
5706 assert_eq!(fmt("%g", 1234560.0), "1.23456e+06");
5707 assert_eq!(fmt("%G", 1000000.0), "1E+06");
5708 }
5709
5710 #[test]
5711 fn test_format_c_field_width_bd_ul4c0() {
5712 let f = FormatFunc;
5718 let fmt = |spec: &str, v: SqliteValue| -> String {
5719 match f
5720 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
5721 .unwrap()
5722 {
5723 SqliteValue::Text(s) => s.as_str().to_owned(),
5724 other => panic!("expected text, got {other:?}"),
5725 }
5726 };
5727 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5728 assert_eq!(fmt(">%3c<", SqliteValue::Integer(65)), "> 6<");
5730 assert_eq!(fmt(">%-3c<", SqliteValue::Integer(65)), ">6 <");
5731 assert_eq!(fmt(">%5c<", txt("abc")), "> a<");
5733 assert_eq!(fmt(">%-5c<", txt("abc")), ">a <");
5734 assert_eq!(fmt(">%03c<", SqliteValue::Integer(65)), "> 6<");
5736 assert_eq!(fmt(">%3c<", txt("é")), "> é<");
5738 assert_eq!(fmt(">%c<", SqliteValue::Integer(65)), ">6<");
5740 assert_eq!(fmt(">%c<", txt("abc")), ">a<");
5741 }
5742
5743 #[test]
5744 fn test_format_quote_specifiers_field_width_bd_8959m() {
5745 let f = FormatFunc;
5750 let fmt = |spec: &str, v: SqliteValue| -> String {
5751 match f
5752 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
5753 .unwrap()
5754 {
5755 SqliteValue::Text(s) => s.as_str().to_owned(),
5756 other => panic!("expected text, got {other:?}"),
5757 }
5758 };
5759 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
5760 assert_eq!(fmt(">%6q<", txt("ab")), "> ab<");
5762 assert_eq!(fmt(">%-6q<", txt("ab")), ">ab <");
5763 assert_eq!(fmt(">%8q<", SqliteValue::Null), "> (NULL)<");
5764 assert_eq!(fmt(">%8q<", txt("a'b")), "> a''b<");
5765 assert_eq!(fmt(">%3q<", txt("abcde")), ">abcde<"); assert_eq!(fmt(">%6Q<", txt("ab")), "> 'ab'<");
5768 assert_eq!(fmt(">%-6Q<", txt("ab")), ">'ab' <");
5769 assert_eq!(fmt(">%6Q<", SqliteValue::Null), "> NULL<");
5770 assert_eq!(fmt(">%6w<", txt("ab")), "> ab<");
5772 assert_eq!(fmt(">%-6w<", txt("ab")), ">ab <");
5773 assert_eq!(fmt(">%4q<", txt("é")), "> é<");
5775 assert_eq!(fmt(">%q<", txt("ab")), ">ab<");
5777 assert_eq!(fmt(">%Q<", txt("ab")), ">'ab'<");
5778 }
5779
5780 #[test]
5781 fn test_format_round_half_away_from_zero_bd_o1tu1() {
5782 let f = FormatFunc;
5790 let fmt = |spec: &str, v: f64| -> String {
5791 match f
5792 .invoke(&[
5793 SqliteValue::Text(SmallText::from_string(spec)),
5794 SqliteValue::Float(v),
5795 ])
5796 .unwrap()
5797 {
5798 SqliteValue::Text(s) => s.as_str().to_owned(),
5799 other => panic!("expected text, got {other:?}"),
5800 }
5801 };
5802 let cases: &[(&str, f64, &str)] = &[
5803 ("%.0f", 2.5, "3"),
5805 ("%.0f", 0.5, "1"),
5806 ("%.0f", -2.5, "-3"),
5807 ("%.0f", 3.5, "4"),
5808 ("%.0f", -0.5, "-1"),
5809 ("%.0f", -3.5, "-4"),
5810 ("%.0f", 1.5, "2"),
5811 ("%.2f", 0.125, "0.13"),
5812 ("%.2f", 0.375, "0.38"),
5813 ("%.2f", 0.625, "0.63"),
5814 ("%.2f", 2.125, "2.13"),
5815 ("%.2f", -0.125, "-0.13"),
5816 ("%.1f", 0.25, "0.3"),
5817 ("%.1f", 0.75, "0.8"),
5818 ("%.1f", 2.25, "2.3"),
5819 ("%.1f", -0.25, "-0.3"),
5820 ("%.1f", 0.05, "0.1"),
5821 ("%.0f", 12.5, "13"),
5822 ("%.2f", 12.5, "12.50"),
5823 ("%.2f", 0.135, "0.14"),
5825 ("%.2f", 0.35, "0.35"),
5826 ("%.2f", 0.15, "0.15"),
5827 ("%.2f", 0.85, "0.85"),
5828 ("%.2f", 0.95, "0.95"),
5829 ("%.2f", 1.005, "1.00"),
5830 ("%.2f", 2.675, "2.67"),
5831 ("%.2f", 0.005, "0.01"),
5832 ("%.2f", 0.015, "0.01"),
5833 ("%.2f", 0.025, "0.03"),
5834 ("%.1f", 0.35, "0.3"),
5835 ("%.1f", 0.15, "0.1"),
5836 ("%.1f", 0.135, "0.1"),
5837 ("%.0f", 2.675, "3"),
5838 ("%.0f", 0.49999, "0"),
5839 ("%+.0f", 2.5, "+3"),
5841 ("%8.0f", 2.5, " 3"),
5842 ("%.0e", 2.5, "3e+00"),
5844 ("%.0e", 9.5, "1e+01"),
5845 ("%.0e", 1.5, "2e+00"),
5846 ("%.0e", 250.0, "3e+02"),
5847 ("%.0e", 0.25, "3e-01"),
5848 ("%.1e", 1.25, "1.3e+00"),
5849 ("%.1e", 12.5, "1.3e+01"),
5850 ("%.0E", 2.5, "3E+00"),
5851 ("%.1e", 0.5, "5.0e-01"),
5853 ("%.1e", 9.95, "9.9e+00"),
5854 ("%.1e", 1.005, "1.0e+00"),
5855 ("%.1e", 2.675, "2.7e+00"),
5856 ("%.1e", 1.35, "1.4e+00"),
5857 ("%.0e", 0.5, "5e-01"),
5858 ("%.0e", 9.95, "1e+01"),
5859 ("%.0e", 1.005, "1e+00"),
5860 ("%.1g", 0.25, "0.3"),
5862 ("%.1g", 2.5, "3"),
5863 ("%.1g", 25.0, "3e+01"),
5864 ("%.2g", 0.125, "0.13"),
5865 ("%.2g", 1.25, "1.3"),
5866 ("%.2g", 12.5, "13"),
5867 ("%.1g", 0.35, "0.3"),
5869 ("%.1g", 0.15, "0.1"),
5870 ("%.1g", 0.45, "0.5"),
5871 ("%.1g", 0.125, "0.1"),
5872 ("%.2g", 0.135, "0.14"),
5873 ("%.2g", 1.005, "1"),
5874 ("%.2g", 2.675, "2.7"),
5875 ];
5876 for (spec, v, want) in cases {
5877 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
5878 }
5879 }
5880
5881 #[test]
5882 fn test_round_half_away_near_ties_match_oracle_bd_o1tu1() {
5883 #[allow(clippy::float_cmp)]
5889 fn round1(v: f64) -> f64 {
5890 match RoundFunc
5891 .invoke(&[SqliteValue::Float(v), SqliteValue::Integer(1)])
5892 .unwrap()
5893 {
5894 SqliteValue::Float(x) => x,
5895 other => panic!("expected float, got {other:?}"),
5896 }
5897 }
5898 let cases: &[(f64, f64)] = &[
5899 (0.15, 0.1),
5900 (0.35, 0.3),
5901 (0.85, 0.8),
5902 (0.95, 0.9),
5903 (0.135, 0.1),
5904 (1.005, 1.0),
5905 (2.675, 2.7),
5906 (0.25, 0.3),
5910 (0.45, 0.5),
5911 (2.5, 2.5),
5912 ];
5913 for (v, want) in cases {
5914 #[allow(clippy::float_cmp)]
5915 let got = round1(*v);
5916 assert_eq!(got, *want, "round({v}, 1)");
5917 }
5918 }
5919
5920 #[test]
5921 fn test_format_altform2_flag() {
5922 let f = FormatFunc;
5926 assert_eq!(
5927 f.invoke(&[
5928 SqliteValue::Text(SmallText::from_string("%!5s")),
5929 SqliteValue::Text(SmallText::from_string("ab")),
5930 ])
5931 .unwrap(),
5932 SqliteValue::Text(SmallText::from_string(" ab"))
5933 );
5934 assert_eq!(
5935 f.invoke(&[
5936 SqliteValue::Text(SmallText::from_string("%!d")),
5937 SqliteValue::Integer(3),
5938 ])
5939 .unwrap(),
5940 SqliteValue::Text(SmallText::from_string("3"))
5941 );
5942 assert_eq!(
5943 f.invoke(&[
5944 SqliteValue::Text(SmallText::from_string("%!f")),
5945 SqliteValue::Float(0.1),
5946 ])
5947 .unwrap(),
5948 SqliteValue::Text(SmallText::from_string("0.1"))
5949 );
5950 }
5951
5952 #[test]
5953 fn test_format_altform2_precision_and_width() {
5954 let f = FormatFunc;
5961 let fmt = |spec: &str, v: f64| -> String {
5962 match f
5963 .invoke(&[
5964 SqliteValue::Text(SmallText::from_string(spec)),
5965 SqliteValue::Float(v),
5966 ])
5967 .unwrap()
5968 {
5969 SqliteValue::Text(s) => s.as_str().to_owned(),
5970 other => panic!("expected text, got {other:?}"),
5971 }
5972 };
5973 let cases: &[(&str, f64, &str)] = &[
5974 ("%!f", 0.1, "0.1"),
5975 ("%!5.2f", 3.14159, " 3.14"),
5976 ("%!.3f", 1.5, "1.5"),
5977 ("%!f", 3.14159, "3.14159"),
5978 ("%!f", 5.0, "5.0"),
5979 ("%!f", 5.5, "5.5"),
5980 ("%!.0f", 5.5, "6.0"),
5981 ("%!f", -0.5, "-0.5"),
5982 ("%+!f", 0.5, "+0.5"),
5983 ("%!f", 100.0, "100.0"),
5984 ("%!8.2f", 3.14159, " 3.14"),
5985 ("%!08.3f", 1.5, "000001.5"),
5986 ("%!10.2f", 3.14159, " 3.14"),
5987 ];
5988 for (spec, v, want) in cases {
5989 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
5990 }
5991 }
5992
5993 #[test]
5994 fn test_format_comma_grouping_flag() {
5995 let f = FormatFunc;
6003 let fmt = |spec: &str, v: SqliteValue| -> String {
6004 match f
6005 .invoke(&[SqliteValue::Text(SmallText::from_string(spec)), v])
6006 .unwrap()
6007 {
6008 SqliteValue::Text(s) => s.as_str().to_owned(),
6009 other => panic!("expected text, got {other:?}"),
6010 }
6011 };
6012 let int_cases: &[(&str, i64, &str)] = &[
6013 ("%,d", 1234567, "1,234,567"),
6014 ("%,d", -1234567, "-1,234,567"),
6015 ("%,d", 123, "123"),
6016 ("%,d", 1000, "1,000"),
6017 ("%,d", 0, "0"),
6018 ("%,d", -100, "-100"),
6019 ("%,d", 1000000, "1,000,000"),
6020 ("%,10d", 1234567, " 1,234,567"),
6021 ("%,08d", 1234, "00,001,234"),
6022 ("%+,d", 1234567, "+1,234,567"),
6023 ("%, d", 1234567, " 1,234,567"),
6024 ("%-,12d", 1234567, "1,234,567 "),
6025 ("%,i", 1234567, "1,234,567"),
6026 ("%,u", 1234567, "1,234,567"),
6027 ("%,x", 1234567, "12d687"),
6028 ];
6029 for (spec, v, want) in int_cases {
6030 assert_eq!(
6031 fmt(spec, SqliteValue::Integer(*v)),
6032 *want,
6033 "spec={spec} v={v}"
6034 );
6035 }
6036 let float_cases: &[(&str, f64, &str)] = &[
6037 ("%,f", 1234567.5, "1,234,567.500000"),
6038 ("%,.2f", 1234567.891, "1,234,567.89"),
6039 ("%,f", -1234.5, "-1,234.500000"),
6040 ("%,e", 1234.5, "1.234500e+03"),
6041 ("%,g", 1234.5, "1,234.5"),
6043 ("%,g", 12.0, "12"),
6044 ("%,g", 1234567.0, "1.23457e+06"),
6045 ("%,g", 1000000.0, "1e+06"),
6046 ("%,.2g", 1234.5, "1.2e+03"),
6047 ];
6048 for (spec, v, want) in float_cases {
6049 assert_eq!(
6050 fmt(spec, SqliteValue::Float(*v)),
6051 *want,
6052 "spec={spec} v={v}"
6053 );
6054 }
6055 }
6056
6057 #[test]
6058 fn test_format_integer_precision() {
6059 let f = FormatFunc;
6064 let fmt = |spec: &str, v: i64| -> String {
6065 match f
6066 .invoke(&[
6067 SqliteValue::Text(SmallText::from_string(spec)),
6068 SqliteValue::Integer(v),
6069 ])
6070 .unwrap()
6071 {
6072 SqliteValue::Text(s) => s.as_str().to_owned(),
6073 other => panic!("expected text, got {other:?}"),
6074 }
6075 };
6076 let cases: &[(&str, i64, &str)] = &[
6077 ("%.3d", 5, "005"),
6078 ("%.3d", -5, "-005"),
6079 ("%.0d", 0, "0"),
6080 ("%.0d", 5, "5"),
6081 ("%5.3d", 42, " 042"),
6082 ("%-5.3d", 42, "042 "),
6083 ("%.3d", 12345, "12345"),
6084 ("%+.3d", 5, "+005"),
6085 ("% .3d", 5, " 005"),
6086 ("%08.3d", 42, "00000042"),
6087 ("%.3i", 9, "009"),
6088 ("%.3u", 7, "007"),
6089 ("%.3x", 10, "00a"),
6090 ("%.3o", 8, "010"),
6091 ];
6092 for (spec, v, want) in cases {
6093 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6094 }
6095 }
6096
6097 #[test]
6098 fn test_format_altform2_exponential() {
6099 let f = FormatFunc;
6103 let fmt = |spec: &str, v: f64| -> String {
6104 match f
6105 .invoke(&[
6106 SqliteValue::Text(SmallText::from_string(spec)),
6107 SqliteValue::Float(v),
6108 ])
6109 .unwrap()
6110 {
6111 SqliteValue::Text(s) => s.as_str().to_owned(),
6112 other => panic!("expected text, got {other:?}"),
6113 }
6114 };
6115 let cases: &[(&str, f64, &str)] = &[
6116 ("%!e", 3.14159, "3.14159e+00"),
6117 ("%!E", 3.14159, "3.14159E+00"),
6118 ("%!e", 5.0, "5.0e+00"),
6119 ("%!.2e", 3.14159, "3.14e+00"),
6120 ("%!.0e", 3.0, "3.0e+00"),
6121 ];
6122 for (spec, v, want) in cases {
6123 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6124 }
6125 }
6126
6127 #[test]
6128 fn test_format_altform2_g_honors_precision() {
6129 let f = FormatFunc;
6135 let fmt = |spec: &str, v: f64| -> String {
6136 match f
6137 .invoke(&[
6138 SqliteValue::Text(SmallText::from_string(spec)),
6139 SqliteValue::Float(v),
6140 ])
6141 .unwrap()
6142 {
6143 SqliteValue::Text(s) => s.as_str().to_owned(),
6144 other => panic!("expected text, got {other:?}"),
6145 }
6146 };
6147 let cases: &[(&str, f64, &str)] = &[
6148 ("%!g", 12345.0, "12345.0"),
6149 ("%!.0g", 12345.0, "1.0e+04"),
6150 ("%!.1g", 12345.0, "1.0e+04"),
6151 ("%!.3g", 12345.0, "1.23e+04"),
6152 ("%!.2g", 0.000123, "0.00012"),
6153 ("%!g", 100.0, "100.0"),
6154 ("%!.0g", 5.0, "5.0"),
6155 ("%!g", 0.1, "0.1"),
6156 ("%!G", 12345.0, "12345.0"),
6157 ("%!.0G", 12345.0, "1.0E+04"),
6158 ];
6159 for (spec, v, want) in cases {
6160 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6161 }
6162 }
6163
6164 #[test]
6165 #[allow(clippy::excessive_precision, clippy::unreadable_literal)]
6166 fn test_format_altform2_high_precision_bd_ixizz() {
6167 let f = FormatFunc;
6174 let fmt = |spec: &str, v: f64| -> String {
6175 match f
6176 .invoke(&[
6177 SqliteValue::Text(SmallText::from_string(spec)),
6178 SqliteValue::Float(v),
6179 ])
6180 .unwrap()
6181 {
6182 SqliteValue::Text(s) => s.as_str().to_owned(),
6183 other => panic!("expected text, got {other:?}"),
6184 }
6185 };
6186 let third = 1.0 / 3.0;
6187 let two_thirds = 2.0 / 3.0;
6188 let seventh = 1.0 / 7.0;
6189 let cases: &[(&str, f64, &str)] = &[
6190 ("%!.40e", two_thirds, "6.66666666666666629e-01"),
6192 ("%!.40e", third, "3.33333333333333314e-01"),
6193 ("%!.40e", 0.1, "1.00000000000000005e-01"),
6194 ("%!.40e", seventh, "1.42857142857142849e-01"),
6195 ("%!.40e", 1e300, "1.000000000000000052e+300"),
6196 ("%!.40E", two_thirds, "6.66666666666666629E-01"),
6197 ("%!.40e", -two_thirds, "-6.66666666666666629e-01"),
6198 ("%!.16e", two_thirds, "6.6666666666666663e-01"),
6200 ("%!.17e", two_thirds, "6.66666666666666629e-01"),
6201 ("%!.40f", third, "0.333333333333333314"),
6203 ("%!.40f", 0.1, "0.100000000000000005"),
6204 ("%!.18f", third, "0.333333333333333314"),
6205 ("%!.40f", -two_thirds, "-0.666666666666666629"),
6206 ("%!.40f", 12345678901234567890.0, "12345678901234567160.0"),
6208 ("%!.17g", two_thirds, "0.66666666666666663"),
6210 ("%!.18g", two_thirds, "0.666666666666666629"),
6211 ("%!.40g", two_thirds, "0.666666666666666629"),
6212 ("%!.40g", -two_thirds, "-0.666666666666666629"),
6213 ];
6214 for (spec, v, want) in cases {
6215 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6216 }
6217 }
6218
6219 #[test]
6220 fn test_format_alt_form_hash_floats() {
6221 let f = FormatFunc;
6225 let fmt = |spec: &str, v: f64| -> String {
6226 match f
6227 .invoke(&[
6228 SqliteValue::Text(SmallText::from_string(spec)),
6229 SqliteValue::Float(v),
6230 ])
6231 .unwrap()
6232 {
6233 SqliteValue::Text(s) => s.as_str().to_owned(),
6234 other => panic!("expected text, got {other:?}"),
6235 }
6236 };
6237 let cases: &[(&str, f64, &str)] = &[
6238 ("%#.0f", 3.0, "3."),
6239 ("%#.2f", 3.5, "3.50"),
6240 ("%#.0f", -3.0, "-3."),
6241 ("%#5.0f", 3.0, " 3."),
6242 ("%#.0f", 0.0, "0."),
6243 ("%#.0e", 3.0, "3.e+00"),
6244 ("%#e", 3.0, "3.000000e+00"),
6245 ("%#.0g", 3.0, "3."),
6246 ("%#g", 3.0, "3.00000"),
6247 ("%#.3g", 3.0, "3.00"),
6248 ("%#g", 100000.0, "100000."),
6249 ("%#g", 0.0001, "0.000100000"),
6250 ("%#.1g", 9.9, "1.e+01"),
6251 ("%#g", 1234567.0, "1.23457e+06"),
6252 ];
6253 for (spec, v, want) in cases {
6254 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6255 }
6256 }
6257
6258 #[test]
6259 fn test_printf_bd_9zzr0_review_fixes() {
6260 let f = FormatFunc;
6262 let run = |args: &[SqliteValue]| -> String {
6263 match f.invoke(args).unwrap() {
6264 SqliteValue::Text(s) => s.as_str().to_owned(),
6265 other => panic!("expected text, got {other:?}"),
6266 }
6267 };
6268 let txt = |s: &str| SqliteValue::Text(SmallText::from_string(s));
6269 let int = SqliteValue::Integer;
6270
6271 assert_eq!(run(&[txt("[%.5c]"), txt("A")]), "[AAAAA]");
6273 assert_eq!(run(&[txt("[%c]"), txt("A")]), "[A]");
6274 assert_eq!(run(&[txt("[%3c]"), txt("B")]), "[ B]");
6275 assert_eq!(run(&[txt("[%-05d]"), int(42)]), "[00042]");
6277 assert_eq!(run(&[txt("[%05d]"), int(42)]), "[00042]");
6278 assert_eq!(run(&[txt("[%-5d]"), int(42)]), "[42 ]");
6279 assert_eq!(run(&[txt("[%.*d]"), int(-3), int(42)]), "[042]");
6281 assert_eq!(run(&[txt("[%.*d]"), int(3), int(42)]), "[042]");
6282 assert_eq!(run(&[txt("[%w]"), SqliteValue::Null]), "[(NULL)]");
6284 assert_eq!(run(&[txt("[%10w]"), SqliteValue::Null]), "[ (NULL)]");
6285 assert_eq!(run(&[txt("[%.3q]"), txt("ab'cdef")]), "[ab'']");
6287 assert_eq!(run(&[txt("[%.3Q]"), txt("ab'cdef")]), "['ab''']");
6288 assert_eq!(run(&[txt("[%.3w]"), txt("a\"bcdef")]), "[a\"\"b]");
6289 assert_eq!(run(&[txt("[%.0c]"), txt("A")]), "[A]");
6292 assert_eq!(run(&[txt("[%.1c]"), txt("A")]), "[A]");
6293 assert_eq!(run(&[txt("[%.*d]"), int(-4_294_967_293), int(42)]), "[042]");
6297 assert_eq!(run(&[txt("[%.*d]"), int(-2_147_483_648), int(42)]), "[42]");
6298 assert_eq!(run(&[txt("[%.*d]"), int(-1), int(42)]), "[42]");
6299 }
6300
6301 #[test]
6302 #[allow(clippy::excessive_precision)] fn test_format_high_precision_shortest_round_trip() {
6304 let f = FormatFunc;
6309 let fmt = |spec: &str, v: f64| -> String {
6310 match f
6311 .invoke(&[
6312 SqliteValue::Text(SmallText::from_string(spec)),
6313 SqliteValue::Float(v),
6314 ])
6315 .unwrap()
6316 {
6317 SqliteValue::Text(s) => s.as_str().to_owned(),
6318 other => panic!("expected text, got {other:?}"),
6319 }
6320 };
6321 let third = 1.0 / 3.0;
6322 let pi = std::f64::consts::PI;
6323 let cases: &[(&str, f64, &str)] = &[
6324 ("%.20f", 0.1, "0.10000000000000000000"),
6326 ("%.18f", 0.1, "0.100000000000000000"),
6327 ("%.30f", 1.5, "1.500000000000000000000000000000"),
6328 ("%.25f", 1.5, "1.5000000000000000000000000"),
6329 ("%.17f", third, "0.33333333333333330"),
6330 ("%.18f", third, "0.333333333333333300"),
6331 ("%.17g", 0.1, "0.1"),
6332 ("%.25g", 0.1, "0.1"),
6333 ("%.17g", third, "0.3333333333333333"),
6334 ("%.18g", third, "0.3333333333333333"),
6335 ("%.17g", pi, "3.141592653589793"),
6336 ("%.17e", 0.1, "1.00000000000000000e-01"),
6337 ("%.16e", 0.1, "1.0000000000000000e-01"),
6338 ("%.19e", third, "3.3333333333333330000e-01"),
6339 ("%.17e", 2.675, "2.67500000000000000e+00"),
6340 ("%.17g", 1e-20, "9.999999999999999e-21"),
6343 ("%.20g", 1e-20, "9.999999999999999e-21"),
6344 ("%.17e", 1e-20, "9.99999999999999900e-21"),
6345 ("%.30g", 1.0 / 7.0, "0.1428571428571428"),
6346 ("%.2f", 123_456_789_012_345.678, "123456789012345.70"),
6348 ("%.6f", 123_456_789_012_345.678, "123456789012345.700000"),
6349 ("%.17g", 123_456_789_012_345.678, "123456789012345.7"),
6350 ("%f", 6.022e23, "602200000000000000000000.000000"),
6351 ("%.17f", 2.675, "2.67500000000000000"),
6353 ("%.20f", -0.1, "-0.10000000000000000000"),
6354 ("%.17f", -1.0 / 3.0, "-0.33333333333333330"),
6355 ("%!f", 0.1, "0.1"),
6357 ("%!f", 1.5, "1.5"),
6358 ("%.2f", 0.1, "0.10"),
6360 ("%.6f", 0.1, "0.100000"),
6361 ("%.15f", 0.1, "0.100000000000000"),
6362 ("%.1f", 0.15, "0.1"),
6363 ("%.2f", 2.675, "2.67"),
6364 ("%.0f", 2.5, "3"),
6365 ("%g", third, "0.333333"),
6366 ("%.6e", 0.1, "1.000000e-01"),
6367 ("%f", 1.5, "1.500000"),
6368 ];
6369 for (spec, v, want) in cases {
6370 assert_eq!(fmt(spec, *v), *want, "spec={spec} v={v}");
6371 }
6372 }
6373
6374 #[test]
6377 fn test_format_specifiers() {
6378 let f = FormatFunc;
6379 let result = f
6380 .invoke(&[
6381 SqliteValue::Text(SmallText::from_string("%d %s")),
6382 SqliteValue::Integer(42),
6383 SqliteValue::Text(SmallText::from_string("hello")),
6384 ])
6385 .unwrap();
6386 assert_eq!(
6387 result,
6388 SqliteValue::Text(SmallText::from_string("42 hello"))
6389 );
6390 }
6391
6392 #[test]
6393 fn test_format_n_noop() {
6394 let f = FormatFunc;
6395 let result = f
6397 .invoke(&[SqliteValue::Text(SmallText::from_string("before%nafter"))])
6398 .unwrap();
6399 assert_eq!(
6400 result,
6401 SqliteValue::Text(SmallText::from_string("beforeafter"))
6402 );
6403 }
6404
6405 #[test]
6406 fn test_format_literal_percent_honors_width() {
6407 let f = FormatFunc;
6411 let fmt = |spec: &str| -> String {
6412 match f
6413 .invoke(&[SqliteValue::Text(SmallText::from_string(spec))])
6414 .unwrap()
6415 {
6416 SqliteValue::Text(s) => s.as_str().to_owned(),
6417 other => panic!("expected text, got {other:?}"),
6418 }
6419 };
6420 assert_eq!(fmt("%%"), "%");
6421 assert_eq!(fmt("%5%"), " %");
6422 assert_eq!(fmt("%-5%"), "% ");
6423 assert_eq!(fmt("%05%"), " %");
6424 assert_eq!(fmt("[%3%]"), "[ %]");
6425 }
6426
6427 #[test]
6428 fn test_format_alternate_form_hex_octal() {
6429 let cases: &[(&str, i64, &str)] = &[
6431 ("%#x", 255, "0xff"),
6432 ("%#X", 255, "0XFF"),
6433 ("%#o", 64, "0100"),
6434 ("%#x", 0, "0"), ("%#o", 0, "0"), ("%#5x", 255, " 0xff"), ("%#8x", 255, " 0xff"),
6438 ("%#08x", 255, "0x000000ff"), ("%-#8x", 255, "0xff "), ("%-08x", 255, "000000ff"), ("%#08o", 64, "000000100"),
6442 ("%#x", -1, "0xffffffffffffffff"),
6443 ];
6444 for (fmt, arg, want) in cases {
6445 let f = FormatFunc;
6446 let result = f
6447 .invoke(&[
6448 SqliteValue::Text(SmallText::from_string(*fmt)),
6449 SqliteValue::Integer(*arg),
6450 ])
6451 .unwrap();
6452 assert_eq!(
6453 result,
6454 SqliteValue::Text(SmallText::from_string((*want).to_owned())),
6455 "format({fmt:?}, {arg})"
6456 );
6457 }
6458 }
6459
6460 #[test]
6461 fn test_format_empty_string_is_null() {
6462 let f = FormatFunc;
6466 assert_eq!(
6467 f.invoke(&[SqliteValue::Text(SmallText::from_string(""))])
6468 .unwrap(),
6469 SqliteValue::Null
6470 );
6471 assert_eq!(
6473 f.invoke(&[
6474 SqliteValue::Text(SmallText::from_string("%s")),
6475 SqliteValue::Null,
6476 ])
6477 .unwrap(),
6478 SqliteValue::Text(SmallText::from_string(String::new()))
6479 );
6480 }
6481
6482 #[test]
6485 fn test_sqlite_version_format() {
6486 let result = SqliteVersionFunc.invoke(&[]).unwrap();
6487 match result {
6488 SqliteValue::Text(v) => {
6489 assert_eq!(v.split('.').count(), 3, "version must be N.N.N format");
6490 }
6491 other => unreachable!("expected text, got {other:?}"),
6492 }
6493 }
6494
6495 #[test]
6496 fn test_sqlite_compileoption_used_matches_sqlite_prefix_and_value_options() {
6497 let func = SqliteCompileoptionUsedFunc;
6498 assert_eq!(
6499 invoke1(
6500 &func,
6501 SqliteValue::Text(SmallText::from_string("THREADSAFE"))
6502 )
6503 .unwrap(),
6504 SqliteValue::Integer(1)
6505 );
6506 let expected_icu_enabled = i64::from(cfg!(feature = "ext-icu"));
6507 assert_eq!(
6508 invoke1(
6509 &func,
6510 SqliteValue::Text(SmallText::from_string("SQLITE_ENABLE_ICU"))
6511 )
6512 .unwrap(),
6513 SqliteValue::Integer(expected_icu_enabled)
6514 );
6515 assert_eq!(
6516 invoke1(
6517 &func,
6518 SqliteValue::Text(SmallText::from_string("sqlite_enable_icu"))
6519 )
6520 .unwrap(),
6521 SqliteValue::Integer(expected_icu_enabled)
6522 );
6523 assert_eq!(
6524 invoke1(
6525 &func,
6526 SqliteValue::Text(SmallText::from_string("OMIT_LOAD_EXTENSION"))
6527 )
6528 .unwrap(),
6529 SqliteValue::Integer(1)
6530 );
6531 assert_eq!(
6532 invoke1(
6533 &func,
6534 SqliteValue::Text(SmallText::from_string("ENABLE_FTS3"))
6535 )
6536 .unwrap(),
6537 SqliteValue::Integer(0)
6538 );
6539 assert_eq!(
6540 invoke1(&func, SqliteValue::Null).unwrap(),
6541 SqliteValue::Null
6542 );
6543 }
6544
6545 #[test]
6546 #[ignore = "perf-only benchmark"]
6547 fn perf_compileoption_used_text_args() {
6548 use std::hint::black_box;
6549 use std::time::Instant;
6550
6551 const INVOCATIONS: usize = 1_000_000;
6552 const REPEATS: usize = 7;
6553
6554 let f = SqliteCompileoptionUsedFunc;
6555 let present_args = [SqliteValue::Text(SmallText::from_string(
6556 "SQLITE_ENABLE_ICU",
6557 ))];
6558 let absent_args = [SqliteValue::Text(SmallText::from_string(
6559 "ENABLE_NOT_PRESENT",
6560 ))];
6561
6562 let mut present_best_ns = u128::MAX;
6563 let mut absent_best_ns = u128::MAX;
6564 let mut checksum = 0i64;
6565 for _ in 0..REPEATS {
6566 let started = Instant::now();
6567 for _ in 0..INVOCATIONS {
6568 let result = black_box(
6569 f.invoke(black_box(present_args.as_slice()))
6570 .expect("compileoption present benchmark invocation must succeed"),
6571 );
6572 if let SqliteValue::Integer(value) = result {
6573 checksum = checksum.wrapping_add(value);
6574 }
6575 }
6576 present_best_ns = present_best_ns.min(started.elapsed().as_nanos());
6577
6578 let started = Instant::now();
6579 for _ in 0..INVOCATIONS {
6580 let result = black_box(
6581 f.invoke(black_box(absent_args.as_slice()))
6582 .expect("compileoption absent benchmark invocation must succeed"),
6583 );
6584 if let SqliteValue::Integer(value) = result {
6585 checksum = checksum.wrapping_add(value);
6586 }
6587 }
6588 absent_best_ns = absent_best_ns.min(started.elapsed().as_nanos());
6589 }
6590
6591 println!(
6592 "compileoption_used_text_args invocations={INVOCATIONS} repeats={REPEATS} present_best_ns={present_best_ns} absent_best_ns={absent_best_ns} checksum={checksum}"
6593 );
6594 }
6595
6596 #[test]
6597 fn test_sqlite_compileoption_get_enumerates_canonical_option_list() {
6598 let func = SqliteCompileoptionGetFunc;
6599 for (index, option) in sqlite_compile_options().iter().enumerate() {
6600 assert_eq!(
6601 invoke1(&func, SqliteValue::Integer(index as i64)).unwrap(),
6602 SqliteValue::Text(SmallText::new(option))
6603 );
6604 }
6605 assert_eq!(
6606 invoke1(&func, SqliteValue::Integer(-1)).unwrap(),
6607 SqliteValue::Null
6608 );
6609 assert_eq!(
6610 invoke1(
6611 &func,
6612 SqliteValue::Integer(sqlite_compile_options().len() as i64)
6613 )
6614 .unwrap(),
6615 SqliteValue::Null
6616 );
6617 }
6618
6619 #[test]
6622 fn test_register_builtins_all_present() {
6623 let mut registry = FunctionRegistry::new();
6624 register_builtins(&mut registry);
6625
6626 assert!(registry.find_scalar("abs", 1).is_some());
6628 assert!(registry.find_scalar("typeof", 1).is_some());
6629 assert!(registry.find_scalar("length", 1).is_some());
6630 assert!(registry.find_scalar("lower", 1).is_some());
6631 assert!(registry.find_scalar("upper", 1).is_some());
6632 assert!(registry.find_scalar("hex", 1).is_some());
6633 assert!(registry.find_scalar("coalesce", 3).is_some());
6634 assert!(registry.find_scalar("concat", 2).is_some());
6635 assert!(registry.find_scalar("like", 2).is_some());
6636 assert!(registry.find_scalar("glob", 2).is_some());
6637 assert!(registry.find_scalar("round", 1).is_some());
6638 assert!(registry.find_scalar("substr", 2).is_some());
6639 assert!(registry.find_scalar("substring", 3).is_some());
6640 assert!(registry.find_scalar("sqlite_version", 0).is_some());
6641 assert!(registry.find_scalar("iif", 3).is_some());
6642 assert!(registry.find_scalar("if", 3).is_some());
6643 assert!(registry.find_scalar("format", 1).is_some());
6644 assert!(registry.find_scalar("printf", 1).is_some());
6645 assert!(registry.find_scalar("max", 2).is_some());
6646 assert!(registry.find_scalar("min", 2).is_some());
6647 assert!(registry.find_scalar("sign", 1).is_some());
6648 assert!(registry.find_scalar("random", 0).is_some());
6649
6650 assert!(registry.find_scalar("concat_ws", 3).is_some());
6652 assert!(registry.find_scalar("octet_length", 1).is_some());
6653 assert!(registry.find_scalar("unhex", 1).is_some());
6654 assert!(registry.find_scalar("timediff", 2).is_some());
6655 assert!(registry.find_scalar("unistr", 1).is_some());
6656 assert!(registry.find_scalar("unistr_quote", 1).is_some());
6657
6658 assert!(registry.find_aggregate("median", 1).is_some());
6660 assert!(registry.find_aggregate("percentile", 2).is_some());
6661 assert!(registry.find_aggregate("percentile_cont", 2).is_some());
6662 assert!(registry.find_aggregate("percentile_disc", 2).is_some());
6663
6664 assert!(registry.find_scalar("load_extension", 1).is_none());
6666 assert!(registry.find_scalar("load_extension", 2).is_none());
6667 }
6668
6669 #[test]
6670 fn test_register_builtins_rejects_invalid_variadic_arities() {
6671 let mut registry = FunctionRegistry::new();
6672 register_builtins(&mut registry);
6673
6674 for (name, too_few, valid, too_many) in [
6675 ("coalesce", 1, 2, None),
6676 ("concat", 0, 1, None),
6677 ("concat_ws", 1, 2, None),
6678 ("trim", 0, 1, Some(3)),
6679 ("ltrim", 0, 1, Some(3)),
6680 ("rtrim", 0, 1, Some(3)),
6681 ("round", 0, 1, Some(3)),
6682 ("unhex", 0, 1, Some(3)),
6683 ("substr", 1, 2, Some(4)),
6684 ("substring", 1, 2, Some(4)),
6685 ("max", 0, 1, None),
6686 ("min", 0, 1, None),
6687 ] {
6688 assert_wrong_arg_count(®istry, name, too_few);
6689 assert!(
6690 registry.find_scalar(name, valid).is_some(),
6691 "{name}/{valid} should resolve"
6692 );
6693 if let Some(arity) = too_many {
6694 assert_wrong_arg_count(®istry, name, arity);
6695 }
6696 }
6697
6698 assert!(registry.find_scalar("char", 0).is_some());
6699 assert!(registry.find_scalar("format", 0).is_some());
6700 assert!(registry.find_scalar("printf", 0).is_some());
6701 }
6702
6703 #[test]
6704 fn test_e2e_registry_invoke_through_lookup() {
6705 let mut registry = FunctionRegistry::new();
6706 register_builtins(&mut registry);
6707
6708 let abs = registry.find_scalar("ABS", 1).unwrap();
6710 assert_eq!(
6711 abs.invoke(&[SqliteValue::Integer(-42)]).unwrap(),
6712 SqliteValue::Integer(42)
6713 );
6714
6715 let typeof_fn = registry.find_scalar("typeof", 1).unwrap();
6717 assert_eq!(
6718 typeof_fn
6719 .invoke(&[SqliteValue::Text(SmallText::from_string("hello"))])
6720 .unwrap(),
6721 SqliteValue::Text(SmallText::from_string("text"))
6722 );
6723
6724 let coalesce = registry.find_scalar("COALESCE", 4).unwrap();
6726 assert_eq!(
6727 coalesce
6728 .invoke(&[
6729 SqliteValue::Null,
6730 SqliteValue::Null,
6731 SqliteValue::Integer(42),
6732 SqliteValue::Integer(99),
6733 ])
6734 .unwrap(),
6735 SqliteValue::Integer(42)
6736 );
6737 }
6738
6739 #[test]
6742 fn test_nondeterministic_functions_flagged() {
6743 assert!(!RandomFunc.is_deterministic());
6746 assert!(!RandomblobFunc.is_deterministic());
6747 assert!(!ChangesFunc.is_deterministic());
6748 assert!(!TotalChangesFunc.is_deterministic());
6749 assert!(!LastInsertRowidFunc.is_deterministic());
6750 assert!(!SqliteVersionFunc.is_deterministic());
6751 assert!(!SqliteSourceIdFunc.is_deterministic());
6752 assert!(!SqliteCompileoptionUsedFunc.is_deterministic());
6753 assert!(!SqliteCompileoptionGetFunc.is_deterministic());
6754 }
6755
6756 #[test]
6757 fn test_deterministic_functions_flagged() {
6758 assert!(AbsFunc.is_deterministic());
6760 assert!(LengthFunc.is_deterministic());
6761 assert!(TypeofFunc.is_deterministic());
6762 assert!(UpperFunc.is_deterministic());
6763 assert!(LowerFunc.is_deterministic());
6764 assert!(HexFunc.is_deterministic());
6765 assert!(CoalesceFunc.is_deterministic());
6766 assert!(IifFunc.is_deterministic());
6767 }
6768
6769 #[test]
6770 fn test_random_produces_different_values() {
6771 let a = RandomFunc.invoke(&[]).unwrap();
6774 let b = RandomFunc.invoke(&[]).unwrap();
6775 assert_ne!(a.as_integer(), b.as_integer());
6778 }
6779
6780 #[test]
6781 fn test_registry_nondeterministic_lookup() {
6782 let mut registry = FunctionRegistry::default();
6783 register_builtins(&mut registry);
6784
6785 let random = registry.find_scalar("random", 0).unwrap();
6787 assert!(!random.is_deterministic());
6788
6789 let changes = registry.find_scalar("changes", 0).unwrap();
6790 assert!(!changes.is_deterministic());
6791
6792 let lir = registry.find_scalar("last_insert_rowid", 0).unwrap();
6793 assert!(!lir.is_deterministic());
6794
6795 for (name, num_args) in [
6796 ("sqlite_version", 0),
6797 ("sqlite_source_id", 0),
6798 ("sqlite_compileoption_used", 1),
6799 ("sqlite_compileoption_get", 1),
6800 ] {
6801 assert_eq!(
6802 registry.scalar_is_deterministic(name, num_args),
6803 Some(false),
6804 "{name} must publish non-deterministic registry metadata"
6805 );
6806 }
6807
6808 let abs = registry.find_scalar("abs", 1).unwrap();
6810 assert!(abs.is_deterministic());
6811 }
6812
6813 #[test]
6814 fn test_registry_builtin_query_constancy_metadata() {
6815 use crate::{ScalarQueryConstancy, ScalarSchemaSafety};
6816
6817 let mut registry = FunctionRegistry::default();
6818 register_builtins(&mut registry);
6819
6820 for (name, num_args) in [
6821 ("sqlite_version", 0),
6822 ("sqlite_source_id", 0),
6823 ("sqlite_compileoption_used", 1),
6824 ("sqlite_compileoption_get", 1),
6825 ] {
6826 let resolved = registry.resolve_scalar(name, num_args).unwrap();
6827 assert_eq!(resolved.schema_safety(), ScalarSchemaSafety::Never);
6828 assert_eq!(
6829 resolved.query_constancy(),
6830 ScalarQueryConstancy::SlowChanging,
6831 "{name}/{num_args} must match SQLite's slow-changing metadata"
6832 );
6833 }
6834
6835 for (name, num_args) in [
6836 ("date", 0),
6837 ("time", 0),
6838 ("datetime", 0),
6839 ("julianday", 0),
6840 ("unixepoch", 0),
6841 ("strftime", 1),
6842 ("timediff", 2),
6843 ] {
6844 let resolved = registry.resolve_scalar(name, num_args).unwrap();
6845 assert_eq!(
6846 resolved.schema_safety(),
6847 ScalarSchemaSafety::DateTimeConditional
6848 );
6849 assert_eq!(
6850 resolved.query_constancy(),
6851 ScalarQueryConstancy::SlowChanging,
6852 "{name}/{num_args} must be query-constant despite conditional schema safety"
6853 );
6854 }
6855
6856 for (name, num_args) in [
6857 ("random", 0),
6858 ("randomblob", 1),
6859 ("changes", 0),
6860 ("total_changes", 0),
6861 ("last_insert_rowid", 0),
6862 ] {
6863 assert_eq!(
6864 registry
6865 .resolve_scalar(name, num_args)
6866 .unwrap()
6867 .query_constancy(),
6868 ScalarQueryConstancy::Volatile,
6869 "{name}/{num_args} must remain volatile"
6870 );
6871 }
6872
6873 for (name, num_args) in [("abs", 1), ("like", 2), ("like", 3), ("glob", 2)] {
6874 assert_eq!(
6875 registry
6876 .resolve_scalar(name, num_args)
6877 .unwrap()
6878 .query_constancy(),
6879 ScalarQueryConstancy::Constant,
6880 "{name}/{num_args} must remain constant"
6881 );
6882 }
6883
6884 for (name, num_args) in [
6885 ("sqlite_version", 1),
6886 ("sqlite_compileoption_used", 0),
6887 ("like", 1),
6888 ("like", 4),
6889 ("glob", 1),
6890 ] {
6891 assert_eq!(
6892 registry
6893 .resolve_scalar(name, num_args)
6894 .unwrap()
6895 .query_constancy(),
6896 ScalarQueryConstancy::Volatile,
6897 "{name}/{num_args} wrong-arity sentinel must fail closed"
6898 );
6899 }
6900 }
6901}