1use std::cmp::Ordering;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::{StorageClass, StrictColumnType, StrictTypeError, TypeAffinity};
6
7fn scan_numeric_prefix(bytes: &[u8]) -> usize {
13 if bytes.is_empty() {
14 return 0;
15 }
16
17 let mut i = 0usize;
18 if bytes[i] == b'+' || bytes[i] == b'-' {
19 i += 1;
20 }
21
22 let mut has_digit = false;
23 while i < bytes.len() && bytes[i].is_ascii_digit() {
24 has_digit = true;
25 i += 1;
26 }
27
28 if i < bytes.len() && bytes[i] == b'.' {
29 i += 1;
30 while i < bytes.len() && bytes[i].is_ascii_digit() {
31 has_digit = true;
32 i += 1;
33 }
34 }
35
36 if !has_digit {
37 return 0;
38 }
39
40 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
41 let exp_start = i;
42 i += 1;
43 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
44 i += 1;
45 }
46 if i < bytes.len() && bytes[i].is_ascii_digit() {
47 while i < bytes.len() && bytes[i].is_ascii_digit() {
48 i += 1;
49 }
50 } else {
51 i = exp_start;
52 }
53 }
54
55 i
56}
57
58#[allow(clippy::cast_possible_truncation)]
60fn parse_integer_prefix_bytes(b: &[u8]) -> i64 {
61 let mut start = 0;
62 while start < b.len() && b[start].is_ascii_whitespace() {
63 start += 1;
64 }
65 let trimmed = &b[start..];
66 let end = scan_numeric_prefix(trimmed);
67 if end == 0 {
68 return 0;
69 }
70 let s = std::str::from_utf8(&trimmed[..end]).unwrap_or("");
73 let f = s.parse::<f64>().unwrap_or(0.0);
74 #[allow(clippy::manual_clamp)]
75 if f >= i64::MAX as f64 {
76 i64::MAX
77 } else if f <= i64::MIN as f64 {
78 i64::MIN
79 } else {
80 f as i64
81 }
82}
83
84#[allow(clippy::cast_possible_truncation)]
86fn parse_integer_prefix(s: &str) -> i64 {
87 parse_integer_prefix_bytes(s.as_bytes())
88}
89
90fn parse_float_prefix_bytes(b: &[u8]) -> f64 {
92 let mut start = 0;
93 while start < b.len() && b[start].is_ascii_whitespace() {
94 start += 1;
95 }
96 let trimmed = &b[start..];
97 let end = scan_numeric_prefix(trimmed);
98 if end == 0 {
99 return 0.0;
100 }
101 let s = std::str::from_utf8(&trimmed[..end]).unwrap_or("");
104 s.parse::<f64>().unwrap_or(0.0)
105}
106
107fn parse_float_prefix(s: &str) -> f64 {
109 parse_float_prefix_bytes(s.as_bytes())
110}
111
112fn cast_text_prefix_to_numeric(s: &str) -> SqliteValue {
113 let trimmed = s.trim();
114 let end = scan_numeric_prefix(trimmed.as_bytes());
115 if end == 0 {
116 return SqliteValue::Integer(0);
117 }
118
119 let prefix = &trimmed[..end];
120 let is_integer_syntax = !prefix
121 .as_bytes()
122 .iter()
123 .any(|byte| matches!(*byte, b'.' | b'e' | b'E'));
124
125 if is_integer_syntax && let Ok(value) = prefix.parse::<i64>() {
126 return SqliteValue::Integer(value);
127 }
128
129 if let Ok(value) = prefix.parse::<f64>() {
130 if value.is_finite()
131 && (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&value)
132 {
133 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
134 let truncated = value as i64;
135 #[allow(clippy::float_cmp, clippy::cast_precision_loss)]
136 if truncated as f64 == value {
137 return SqliteValue::Integer(truncated);
138 }
139 }
140 return SqliteValue::Float(value);
141 }
142
143 SqliteValue::Integer(0)
144}
145
146#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
151pub enum SqliteValue {
152 Null,
154 Integer(i64),
156 Float(f64),
158 Text(Arc<str>),
163 Blob(Arc<[u8]>),
167}
168
169impl SqliteValue {
170 pub const fn affinity(&self) -> TypeAffinity {
172 match self {
173 Self::Null | Self::Blob(_) => TypeAffinity::Blob,
174 Self::Integer(_) => TypeAffinity::Integer,
175 Self::Float(_) => TypeAffinity::Real,
176 Self::Text(_) => TypeAffinity::Text,
177 }
178 }
179
180 pub const fn storage_class(&self) -> StorageClass {
182 match self {
183 Self::Null => StorageClass::Null,
184 Self::Integer(_) => StorageClass::Integer,
185 Self::Float(_) => StorageClass::Real,
186 Self::Text(_) => StorageClass::Text,
187 Self::Blob(_) => StorageClass::Blob,
188 }
189 }
190
191 #[must_use]
203 #[allow(
204 clippy::cast_possible_truncation,
205 clippy::cast_precision_loss,
206 clippy::float_cmp
207 )]
208 pub fn apply_affinity(self, affinity: TypeAffinity) -> Self {
209 match affinity {
210 TypeAffinity::Blob => self,
211 TypeAffinity::Text => match self {
212 Self::Null | Self::Text(_) | Self::Blob(_) => self,
213 Self::Integer(_) | Self::Float(_) => {
214 let t = self.to_text();
215 Self::Text(Arc::from(t))
216 }
217 },
218 TypeAffinity::Numeric => match &self {
219 Self::Text(s) => try_coerce_text_to_numeric(s).unwrap_or(self),
220 _ => self,
221 },
222 TypeAffinity::Integer => match &self {
223 Self::Text(s) => try_coerce_text_to_numeric(s).unwrap_or(self),
224 Self::Float(f) => {
225 if *f >= -9_223_372_036_854_775_808.0 && *f < 9_223_372_036_854_775_808.0 {
226 let i = *f as i64;
227 if (i as f64) == *f {
228 return Self::Integer(i);
229 }
230 }
231 self
232 }
233 _ => self,
234 },
235 TypeAffinity::Real => match &self {
236 Self::Text(s) => try_coerce_text_to_numeric(s)
237 .map(|v| match v {
238 Self::Integer(i) => Self::Float(i as f64),
239 other => other,
240 })
241 .unwrap_or(self),
242 Self::Integer(i) => Self::Float(*i as f64),
243 _ => self,
244 },
245 }
246 }
247
248 #[allow(clippy::cast_precision_loss)]
255 pub fn validate_strict(self, col_type: StrictColumnType) -> Result<Self, StrictTypeError> {
256 if matches!(self, Self::Null) {
257 return Ok(self);
258 }
259 match col_type {
260 StrictColumnType::Any => Ok(self),
261 StrictColumnType::Integer => match self {
262 Self::Integer(_) => Ok(self),
263 other => Err(StrictTypeError {
264 expected: col_type,
265 actual: other.storage_class(),
266 }),
267 },
268 StrictColumnType::Real => match self {
269 Self::Float(_) => Ok(self),
270 Self::Integer(i) => Ok(Self::Float(i as f64)),
271 other => Err(StrictTypeError {
272 expected: col_type,
273 actual: other.storage_class(),
274 }),
275 },
276 StrictColumnType::Text => match self {
277 Self::Text(_) => Ok(self),
278 other => Err(StrictTypeError {
279 expected: col_type,
280 actual: other.storage_class(),
281 }),
282 },
283 StrictColumnType::Blob => match self {
284 Self::Blob(_) => Ok(self),
285 other => Err(StrictTypeError {
286 expected: col_type,
287 actual: other.storage_class(),
288 }),
289 },
290 }
291 }
292
293 #[inline(always)]
295 #[allow(clippy::inline_always)]
296 pub const fn is_null(&self) -> bool {
297 matches!(self, Self::Null)
298 }
299
300 #[inline]
302 pub const fn as_integer(&self) -> Option<i64> {
303 match self {
304 Self::Integer(i) => Some(*i),
305 _ => None,
306 }
307 }
308
309 #[inline]
311 pub fn as_float(&self) -> Option<f64> {
312 match self {
313 Self::Float(f) => Some(*f),
314 _ => None,
315 }
316 }
317
318 #[inline]
320 pub fn as_text(&self) -> Option<&str> {
321 match self {
322 Self::Text(s) => Some(s),
323 _ => None,
324 }
325 }
326
327 #[inline]
329 pub fn as_blob(&self) -> Option<&[u8]> {
330 match self {
331 Self::Blob(b) => Some(b),
332 _ => None,
333 }
334 }
335
336 #[inline(always)]
344 #[allow(clippy::inline_always)]
345 #[allow(clippy::cast_possible_truncation)]
346 pub fn to_integer(&self) -> i64 {
347 match self {
348 Self::Null => 0,
349 Self::Integer(i) => *i,
350 Self::Float(f) => *f as i64,
351 Self::Text(s) => parse_integer_prefix(s),
352 Self::Blob(b) => parse_integer_prefix_bytes(b),
353 }
354 }
355
356 #[inline(always)]
364 #[allow(clippy::inline_always)]
365 #[allow(clippy::cast_precision_loss)]
366 pub fn to_float(&self) -> f64 {
367 match self {
368 Self::Null => 0.0,
369 Self::Integer(i) => *i as f64,
370 Self::Float(f) => *f,
371 Self::Text(s) => parse_float_prefix(s),
372 Self::Blob(b) => parse_float_prefix_bytes(b),
373 }
374 }
375
376 #[inline]
382 #[must_use]
383 pub fn as_text_str(&self) -> Option<&str> {
384 match self {
385 Self::Text(s) => Some(s),
386 _ => None,
387 }
388 }
389
390 #[inline]
392 #[must_use]
393 pub fn as_blob_bytes(&self) -> Option<&[u8]> {
394 match self {
395 Self::Blob(b) => Some(b),
396 _ => None,
397 }
398 }
399
400 pub fn to_text(&self) -> String {
406 match self {
407 Self::Null => String::new(),
408 Self::Integer(i) => i.to_string(),
409 Self::Float(f) => format_sqlite_float(*f),
410 Self::Text(s) => s.to_string(),
411 Self::Blob(b) => String::from_utf8_lossy(b).into_owned(),
412 }
413 }
414
415 #[must_use]
421 pub fn cast_to_numeric(&self) -> Self {
422 match self {
423 Self::Null => Self::Null,
424 Self::Integer(i) => Self::Integer(*i),
425 Self::Float(f) => Self::Float(*f),
426 Self::Text(s) => cast_text_prefix_to_numeric(s),
427 Self::Blob(b) => cast_text_prefix_to_numeric(&String::from_utf8_lossy(b)),
428 }
429 }
430
431 pub const fn typeof_str(&self) -> &'static str {
435 match self {
436 Self::Null => "null",
437 Self::Integer(_) => "integer",
438 Self::Float(_) => "real",
439 Self::Text(_) => "text",
440 Self::Blob(_) => "blob",
441 }
442 }
443
444 pub fn sql_length(&self) -> Option<i64> {
451 match self {
452 Self::Null => None,
453 Self::Text(s) => Some(i64::try_from(s.chars().count()).unwrap_or(i64::MAX)),
454 Self::Blob(b) => Some(i64::try_from(b.len()).unwrap_or(i64::MAX)),
455 Self::Integer(_) | Self::Float(_) => {
456 let t = self.to_text();
457 Some(i64::try_from(t.chars().count()).unwrap_or(i64::MAX))
458 }
459 }
460 }
461
462 pub fn unique_eq(&self, other: &Self) -> bool {
468 if self.is_null() || other.is_null() {
469 return false;
470 }
471 matches!(self.partial_cmp(other), Some(Ordering::Equal))
472 }
473
474 fn float_result_or_null(result: f64) -> Self {
478 if result.is_nan() {
479 Self::Null
480 } else {
481 Self::Float(result)
482 }
483 }
484
485 #[inline]
491 pub fn is_integer_numeric_type(&self) -> bool {
492 fn text_is_integer_numeric_type(s: &str) -> bool {
493 let trimmed = s.trim_start();
494 let end = scan_numeric_prefix(trimmed.as_bytes());
495 end > 0
496 && !trimmed.as_bytes()[..end]
497 .iter()
498 .any(|byte| matches!(*byte, b'.' | b'e' | b'E'))
499 }
500
501 match self {
502 Self::Integer(_) => true,
503 Self::Float(_) | Self::Null => false,
504 Self::Text(s) => text_is_integer_numeric_type(s),
505 Self::Blob(b) => text_is_integer_numeric_type(&String::from_utf8_lossy(b)),
506 }
507 }
508
509 #[inline]
514 fn is_float_numeric_type(&self) -> bool {
515 fn text_is_float(s: &str) -> bool {
516 let trimmed = s.trim_start();
517 let end = scan_numeric_prefix(trimmed.as_bytes());
518 end > 0
519 && trimmed.as_bytes()[..end]
520 .iter()
521 .any(|byte| matches!(*byte, b'.' | b'e' | b'E'))
522 }
523 match self {
524 Self::Float(_) => true,
525 Self::Integer(_) | Self::Null => false,
526 Self::Text(s) => text_is_float(s),
527 Self::Blob(b) => text_is_float(&String::from_utf8_lossy(b)),
528 }
529 }
530
531 #[inline(always)]
539 #[allow(clippy::inline_always)]
540 #[must_use]
541 #[allow(clippy::cast_precision_loss)]
542 pub fn sql_add(&self, other: &Self) -> Self {
543 match (self, other) {
544 (Self::Null, _) | (_, Self::Null) => Self::Null,
545 (Self::Integer(a), Self::Integer(b)) => match a.checked_add(*b) {
546 Some(result) => Self::Integer(result),
547 None => Self::float_result_or_null(*a as f64 + *b as f64),
548 },
549 _ if !self.is_float_numeric_type() && !other.is_float_numeric_type() => {
553 let a = self.to_integer();
554 let b = other.to_integer();
555 match a.checked_add(b) {
556 Some(result) => Self::Integer(result),
557 None => Self::float_result_or_null(a as f64 + b as f64),
558 }
559 }
560 _ => Self::float_result_or_null(self.to_float() + other.to_float()),
561 }
562 }
563
564 #[inline(always)]
568 #[allow(clippy::inline_always)]
569 #[must_use]
570 #[allow(clippy::cast_precision_loss)]
571 pub fn sql_sub(&self, other: &Self) -> Self {
572 match (self, other) {
573 (Self::Null, _) | (_, Self::Null) => Self::Null,
574 (Self::Integer(a), Self::Integer(b)) => match a.checked_sub(*b) {
575 Some(result) => Self::Integer(result),
576 None => Self::float_result_or_null(*a as f64 - *b as f64),
577 },
578 _ if !self.is_float_numeric_type() && !other.is_float_numeric_type() => {
579 let a = self.to_integer();
580 let b = other.to_integer();
581 match a.checked_sub(b) {
582 Some(result) => Self::Integer(result),
583 None => Self::float_result_or_null(a as f64 - b as f64),
584 }
585 }
586 _ => Self::float_result_or_null(self.to_float() - other.to_float()),
587 }
588 }
589
590 #[inline(always)]
594 #[allow(clippy::inline_always)]
595 #[must_use]
596 #[allow(clippy::cast_precision_loss)]
597 pub fn sql_mul(&self, other: &Self) -> Self {
598 match (self, other) {
599 (Self::Null, _) | (_, Self::Null) => Self::Null,
600 (Self::Integer(a), Self::Integer(b)) => match a.checked_mul(*b) {
601 Some(result) => Self::Integer(result),
602 None => Self::float_result_or_null(*a as f64 * *b as f64),
603 },
604 _ if !self.is_float_numeric_type() && !other.is_float_numeric_type() => {
605 let a = self.to_integer();
606 let b = other.to_integer();
607 match a.checked_mul(b) {
608 Some(result) => Self::Integer(result),
609 None => Self::float_result_or_null(a as f64 * b as f64),
610 }
611 }
612 _ => Self::float_result_or_null(self.to_float() * other.to_float()),
613 }
614 }
615
616 const fn sort_class(&self) -> u8 {
618 match self {
619 Self::Null => 0,
620 Self::Integer(_) | Self::Float(_) => 1,
621 Self::Text(_) => 2,
622 Self::Blob(_) => 3,
623 }
624 }
625}
626
627pub fn unique_key_duplicates(a: &[SqliteValue], b: &[SqliteValue]) -> bool {
635 assert_eq!(a.len(), b.len(), "UNIQUE key columns must match");
636 a.iter().zip(b.iter()).all(|(va, vb)| va.unique_eq(vb))
637}
638
639pub fn sql_like(pattern: &str, text: &str, escape: Option<char>) -> bool {
646 sql_like_inner(
647 &pattern.chars().collect::<Vec<_>>(),
648 &text.chars().collect::<Vec<_>>(),
649 escape,
650 0,
651 0,
652 )
653}
654
655fn sql_like_inner(
656 pattern: &[char],
657 text: &[char],
658 escape: Option<char>,
659 pi: usize,
660 ti: usize,
661) -> bool {
662 let mut pi = pi;
663 let mut ti = ti;
664
665 while pi < pattern.len() {
666 let pc = pattern[pi];
667
668 if Some(pc) == escape {
670 pi += 1;
671 if pi >= pattern.len() {
672 return false; }
674 if ti >= text.len() || !ascii_ci_eq(pattern[pi], text[ti]) {
676 return false;
677 }
678 pi += 1;
679 ti += 1;
680 continue;
681 }
682
683 match pc {
684 '%' => {
685 while pi < pattern.len() && pattern[pi] == '%' {
687 pi += 1;
688 }
689 if pi >= pattern.len() {
691 return true;
692 }
693 for start in ti..=text.len() {
695 if sql_like_inner(pattern, text, escape, pi, start) {
696 return true;
697 }
698 }
699 return false;
700 }
701 '_' => {
702 if ti >= text.len() {
703 return false;
704 }
705 pi += 1;
706 ti += 1;
707 }
708 _ => {
709 if ti >= text.len() || !ascii_ci_eq(pc, text[ti]) {
710 return false;
711 }
712 pi += 1;
713 ti += 1;
714 }
715 }
716 }
717 ti >= text.len()
718}
719
720fn ascii_ci_eq(a: char, b: char) -> bool {
722 if a == b {
723 return true;
724 }
725 a.is_ascii() && b.is_ascii() && a.eq_ignore_ascii_case(&b)
727}
728
729#[derive(Debug, Clone)]
734pub struct SumAccumulator {
735 int_sum: i64,
737 float_sum: f64,
739 float_err: f64,
741 has_value: bool,
743 is_float: bool,
745 overflow: bool,
747}
748
749impl Default for SumAccumulator {
750 fn default() -> Self {
751 Self::new()
752 }
753}
754
755#[inline]
757fn kbn_step(sum: &mut f64, err: &mut f64, value: f64) {
758 let s = *sum;
759 let t = s + value;
760 if s.abs() > value.abs() {
761 *err += (s - t) + value;
762 } else {
763 *err += (value - t) + s;
764 }
765 *sum = t;
766}
767
768impl SumAccumulator {
769 pub const fn new() -> Self {
771 Self {
772 int_sum: 0,
773 float_sum: 0.0,
774 float_err: 0.0,
775 has_value: false,
776 is_float: false,
777 overflow: false,
778 }
779 }
780
781 #[allow(clippy::cast_precision_loss)]
783 pub fn accumulate(&mut self, val: &SqliteValue) {
784 match val {
785 SqliteValue::Null => {}
786 SqliteValue::Integer(i) => {
787 self.has_value = true;
788 if self.is_float {
789 kbn_step(&mut self.float_sum, &mut self.float_err, *i as f64);
790 } else {
791 match self.int_sum.checked_add(*i) {
792 Some(result) => self.int_sum = result,
793 None => self.overflow = true,
794 }
795 }
796 }
797 SqliteValue::Float(f) => {
798 self.has_value = true;
799 if !self.is_float {
800 self.float_sum = self.int_sum as f64;
801 self.float_err = 0.0;
802 self.is_float = true;
803 }
804 kbn_step(&mut self.float_sum, &mut self.float_err, *f);
805 }
806 other => {
807 self.has_value = true;
809 let n = other.to_float();
810 if !self.is_float {
811 self.float_sum = self.int_sum as f64;
812 self.float_err = 0.0;
813 self.is_float = true;
814 }
815 kbn_step(&mut self.float_sum, &mut self.float_err, n);
816 }
817 }
818 }
819
820 pub fn finish(&self) -> Result<SqliteValue, SumOverflowError> {
823 if self.overflow {
824 return Err(SumOverflowError);
825 }
826 if !self.has_value {
827 return Ok(SqliteValue::Null);
828 }
829 if self.is_float {
830 Ok(SqliteValue::Float(self.float_sum + self.float_err))
831 } else {
832 Ok(SqliteValue::Integer(self.int_sum))
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Eq)]
839pub struct SumOverflowError;
840
841impl fmt::Display for SumOverflowError {
842 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
843 f.write_str("integer overflow in sum()")
844 }
845}
846
847impl fmt::Display for SqliteValue {
848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
849 match self {
850 Self::Null => f.write_str("NULL"),
851 Self::Integer(i) => write!(f, "{i}"),
852 Self::Float(v) => f.write_str(&format_sqlite_float(*v)),
853 Self::Text(s) => write!(f, "'{s}'"),
854 Self::Blob(b) => {
855 f.write_str("X'")?;
856 for byte in b.iter() {
857 write!(f, "{byte:02X}")?;
858 }
859 f.write_str("'")
860 }
861 }
862 }
863}
864
865impl PartialEq for SqliteValue {
866 fn eq(&self, other: &Self) -> bool {
867 matches!(self.partial_cmp(other), Some(Ordering::Equal))
868 }
869}
870
871impl Eq for SqliteValue {}
872
873impl PartialOrd for SqliteValue {
874 #[inline]
875 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
876 Some(self.cmp(other))
877 }
878}
879
880impl Ord for SqliteValue {
881 #[inline]
882 fn cmp(&self, other: &Self) -> Ordering {
883 let class_a = self.sort_class();
885 let class_b = other.sort_class();
886
887 if class_a != class_b {
888 return class_a.cmp(&class_b);
889 }
890
891 match (self, other) {
892 (Self::Null, Self::Null) => Ordering::Equal,
893 (Self::Integer(a), Self::Integer(b)) => a.cmp(b),
894 (Self::Float(a), Self::Float(b)) => a.partial_cmp(b).unwrap_or_else(|| a.total_cmp(b)),
895 (Self::Integer(a), Self::Float(b)) => int_float_cmp(*a, *b),
896 (Self::Float(a), Self::Integer(b)) => int_float_cmp(*b, *a).reverse(),
897 (Self::Text(a), Self::Text(b)) => a.cmp(b),
898 (Self::Blob(a), Self::Blob(b)) => a.cmp(b),
899 _ => unreachable!(),
900 }
901 }
902}
903
904impl From<i64> for SqliteValue {
905 fn from(i: i64) -> Self {
906 Self::Integer(i)
907 }
908}
909
910impl From<i32> for SqliteValue {
911 fn from(i: i32) -> Self {
912 Self::Integer(i64::from(i))
913 }
914}
915
916impl From<f64> for SqliteValue {
917 fn from(f: f64) -> Self {
918 Self::float_result_or_null(f)
919 }
920}
921
922impl From<String> for SqliteValue {
923 fn from(s: String) -> Self {
924 Self::Text(Arc::from(s))
927 }
928}
929
930impl From<&str> for SqliteValue {
931 fn from(s: &str) -> Self {
932 Self::Text(Arc::from(s))
933 }
934}
935
936impl From<Arc<str>> for SqliteValue {
937 fn from(s: Arc<str>) -> Self {
938 Self::Text(s)
939 }
940}
941
942impl From<Vec<u8>> for SqliteValue {
943 fn from(b: Vec<u8>) -> Self {
944 Self::Blob(Arc::from(b))
947 }
948}
949
950impl From<&[u8]> for SqliteValue {
951 fn from(b: &[u8]) -> Self {
952 Self::Blob(Arc::from(b))
953 }
954}
955
956impl From<Arc<[u8]>> for SqliteValue {
957 fn from(b: Arc<[u8]>) -> Self {
958 Self::Blob(b)
959 }
960}
961
962impl<T: Into<Self>> From<Option<T>> for SqliteValue {
963 fn from(opt: Option<T>) -> Self {
964 match opt {
965 Some(v) => v.into(),
966 None => Self::Null,
967 }
968 }
969}
970
971#[allow(
975 clippy::cast_possible_truncation,
976 clippy::cast_precision_loss,
977 clippy::float_cmp
978)]
979fn try_coerce_text_to_numeric(s: &str) -> Option<SqliteValue> {
980 let trimmed = s.trim();
981 if trimmed.is_empty() {
982 return None;
983 }
984 if let Ok(i) = trimmed.parse::<i64>() {
986 return Some(SqliteValue::Integer(i));
987 }
988 if let Ok(f) = trimmed.parse::<f64>() {
992 if !f.is_finite() {
993 let lower = trimmed.to_ascii_lowercase();
994 if lower.contains("inf") || lower.contains("nan") {
995 return None;
996 }
997 }
998 if (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&f) {
1001 #[allow(clippy::cast_possible_truncation)]
1002 let i = f as i64;
1003 #[allow(clippy::cast_precision_loss)]
1004 if (i as f64) == f {
1005 return Some(SqliteValue::Integer(i));
1006 }
1007 }
1008 return Some(SqliteValue::Float(f));
1009 }
1010 None
1011}
1012
1013#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
1018pub fn int_float_cmp(i: i64, r: f64) -> Ordering {
1019 if r.is_nan() {
1020 return Ordering::Greater;
1022 }
1023 if r < -9_223_372_036_854_775_808.0 {
1025 return Ordering::Greater;
1026 }
1027 if r >= 9_223_372_036_854_775_808.0 {
1028 return Ordering::Less;
1029 }
1030 let y = r as i64;
1032 match i.cmp(&y) {
1033 Ordering::Less => Ordering::Less,
1034 Ordering::Greater => Ordering::Greater,
1035 Ordering::Equal => {
1037 let s = i as f64;
1038 s.partial_cmp(&r).unwrap_or(Ordering::Equal)
1039 }
1040 }
1041}
1042
1043#[must_use]
1049pub fn format_sqlite_float(f: f64) -> String {
1050 if f.is_nan() {
1051 return "NaN".to_owned();
1052 }
1053 if f.is_infinite() {
1054 return if f.is_sign_positive() {
1055 "Inf".to_owned()
1056 } else {
1057 "-Inf".to_owned()
1058 };
1059 }
1060 let abs = f.abs();
1065 let s = if abs == 0.0 {
1066 if f.is_sign_negative() {
1068 "-0.0".to_owned()
1069 } else {
1070 "0.0".to_owned()
1071 }
1072 } else {
1073 let exp = abs.log10().floor() as i32;
1076 if exp >= 15 || exp < -4 {
1077 let mut s = format!("{f:.14e}");
1079 if let Some(e_pos) = s.find('e') {
1081 let mantissa = &s[..e_pos];
1082 let exp_str = &s[e_pos + 1..]; let trimmed = mantissa.trim_end_matches('0');
1084 let trimmed = if trimmed.ends_with('.') {
1086 format!("{trimmed}0")
1087 } else {
1088 trimmed.to_owned()
1089 };
1090 let (exp_sign, exp_digits) = if let Some(rest) = exp_str.strip_prefix('-') {
1093 ("-", rest)
1094 } else if let Some(rest) = exp_str.strip_prefix('+') {
1095 ("+", rest)
1096 } else {
1097 ("+", exp_str)
1098 };
1099 let exp_num: u32 = exp_digits.parse().unwrap_or(0);
1100 s = format!("{trimmed}e{exp_sign}{exp_num:02}");
1101 }
1102 s
1103 } else {
1104 #[allow(clippy::cast_sign_loss)]
1106 let decimal_places = (14 - exp).max(0) as usize;
1107 let mut s = format!("{f:.decimal_places$}");
1108 if s.contains('.') {
1110 let trimmed = s.trim_end_matches('0');
1111 s = if trimmed.ends_with('.') {
1112 format!("{trimmed}0")
1113 } else {
1114 trimmed.to_owned()
1115 };
1116 } else {
1117 s.push_str(".0");
1118 }
1119 s
1120 }
1121 };
1122 s
1123}
1124
1125#[cfg(test)]
1126#[allow(clippy::float_cmp, clippy::approx_constant)]
1127mod tests {
1128 use super::*;
1129
1130 #[test]
1131 fn null_properties() {
1132 let v = SqliteValue::Null;
1133 assert!(v.is_null());
1134 assert_eq!(v.to_integer(), 0);
1135 assert_eq!(v.to_float(), 0.0);
1136 assert_eq!(v.to_text(), "");
1137 assert_eq!(v.to_string(), "NULL");
1138 }
1139
1140 #[test]
1141 fn integer_properties() {
1142 let v = SqliteValue::Integer(42);
1143 assert!(!v.is_null());
1144 assert_eq!(v.as_integer(), Some(42));
1145 assert_eq!(v.to_integer(), 42);
1146 assert_eq!(v.to_float(), 42.0);
1147 assert_eq!(v.to_text(), "42");
1148 }
1149
1150 #[test]
1151 fn float_properties() {
1152 let v = SqliteValue::Float(3.14);
1153 assert_eq!(v.as_float(), Some(3.14));
1154 assert_eq!(v.to_integer(), 3);
1155 assert_eq!(v.to_text(), "3.14");
1156 }
1157
1158 #[test]
1159 fn text_properties() {
1160 let v = SqliteValue::Text(Arc::from("hello"));
1161 assert_eq!(v.as_text(), Some("hello"));
1162 assert_eq!(v.to_integer(), 0);
1163 assert_eq!(v.to_float(), 0.0);
1164 }
1165
1166 #[test]
1167 fn text_numeric_coercion() {
1168 let v = SqliteValue::Text(Arc::from("123"));
1169 assert_eq!(v.to_integer(), 123);
1170 assert_eq!(v.to_float(), 123.0);
1171
1172 let v = SqliteValue::Text(Arc::from("3.14"));
1173 assert_eq!(v.to_integer(), 3);
1174 assert_eq!(v.to_float(), 3.14);
1175 }
1176
1177 #[test]
1178 fn text_numeric_coercion_ignores_hex_text_prefixes() {
1179 let v = SqliteValue::Text(Arc::from("0x10"));
1180 assert_eq!(v.to_integer(), 0);
1181 assert_eq!(v.to_float(), 0.0);
1182
1183 let v = SqliteValue::Blob(Arc::from(b"0x10".as_slice()));
1184 assert_eq!(v.to_integer(), 0);
1185 assert_eq!(v.to_float(), 0.0);
1186 }
1187
1188 #[test]
1189 fn test_integer_numeric_type_uses_sqlite_prefix_rules() {
1190 assert!(SqliteValue::Text(Arc::from("123abc")).is_integer_numeric_type());
1191 assert!(SqliteValue::Blob(Arc::from(b"123a".as_slice())).is_integer_numeric_type());
1192 assert!(!SqliteValue::Text(Arc::from("1.5e2abc")).is_integer_numeric_type());
1193 assert!(!SqliteValue::Text(Arc::from("abc")).is_integer_numeric_type());
1194 }
1195
1196 #[test]
1197 fn test_sqlite_value_integer_real_comparison_equal() {
1198 let int_value = SqliteValue::Integer(3);
1199 let real_value = SqliteValue::Float(3.0);
1200 assert_eq!(int_value.partial_cmp(&real_value), Some(Ordering::Equal));
1201 assert_eq!(real_value.partial_cmp(&int_value), Some(Ordering::Equal));
1202 }
1203
1204 #[test]
1205 fn test_sqlite_value_text_to_integer_coercion() {
1206 let text_value = SqliteValue::Text(Arc::from("123"));
1207 let coerced = text_value.apply_affinity(TypeAffinity::Integer);
1208 assert_eq!(coerced, SqliteValue::Integer(123));
1209 }
1210
1211 #[test]
1212 fn blob_properties() {
1213 let v = SqliteValue::Blob(Arc::from([0xDE, 0xAD].as_slice()));
1214 assert_eq!(v.as_blob(), Some(&[0xDE, 0xAD][..]));
1215 assert_eq!(v.to_integer(), 0);
1216 assert_eq!(v.to_float(), 0.0);
1217 assert_eq!(v.to_text(), "\u{07AD}");
1220 }
1221
1222 #[test]
1223 fn display_formatting() {
1224 assert_eq!(SqliteValue::Null.to_string(), "NULL");
1225 assert_eq!(SqliteValue::Integer(42).to_string(), "42");
1226 assert_eq!(SqliteValue::Integer(-1).to_string(), "-1");
1227 assert_eq!(SqliteValue::Float(1.5).to_string(), "1.5");
1228 assert_eq!(SqliteValue::Text(Arc::from("hi")).to_string(), "'hi'");
1229 assert_eq!(
1230 SqliteValue::Blob(Arc::from([0xCA, 0xFE].as_slice())).to_string(),
1231 "X'CAFE'"
1232 );
1233 }
1234
1235 #[test]
1236 fn sort_order_null_first() {
1237 let null = SqliteValue::Null;
1238 let int = SqliteValue::Integer(0);
1239 let text = SqliteValue::Text(Arc::from(""));
1240 let blob = SqliteValue::Blob(Arc::from(&[] as &[u8]));
1241
1242 assert!(null < int);
1243 assert!(int < text);
1244 assert!(text < blob);
1245 }
1246
1247 #[test]
1248 fn sort_order_integers() {
1249 let a = SqliteValue::Integer(1);
1250 let b = SqliteValue::Integer(2);
1251 assert!(a < b);
1252 assert_eq!(a.partial_cmp(&a), Some(Ordering::Equal));
1253 }
1254
1255 #[test]
1256 fn sort_order_mixed_numeric() {
1257 let int = SqliteValue::Integer(1);
1258 let float = SqliteValue::Float(1.5);
1259 assert!(int < float);
1260
1261 let int = SqliteValue::Integer(2);
1262 assert!(int > float);
1263 }
1264
1265 #[test]
1266 fn test_int_float_precision_at_i64_boundary() {
1267 let imax = SqliteValue::Integer(i64::MAX);
1271 let fmax = SqliteValue::Float(9_223_372_036_854_775_808.0);
1272 assert_eq!(
1273 imax.partial_cmp(&fmax),
1274 Some(Ordering::Less),
1275 "i64::MAX must be Less than 9223372036854775808.0"
1276 );
1277
1278 let a = SqliteValue::Integer(i64::MAX);
1280 let b = SqliteValue::Integer(i64::MAX - 1);
1281 let f = SqliteValue::Float(i64::MAX as f64);
1282 assert_eq!(a.partial_cmp(&b), Some(Ordering::Greater));
1284 assert_eq!(a.partial_cmp(&f), Some(Ordering::Less));
1286 assert_eq!(b.partial_cmp(&f), Some(Ordering::Less));
1287 }
1288
1289 #[test]
1290 fn test_int_float_precision_symmetric() {
1291 let i = SqliteValue::Integer(i64::MAX);
1293 let f = SqliteValue::Float(9_223_372_036_854_775_808.0);
1294 assert_eq!(f.partial_cmp(&i), Some(Ordering::Greater));
1295 }
1296
1297 #[test]
1298 fn test_int_float_exact_representation() {
1299 let i = SqliteValue::Integer(42);
1301 let f = SqliteValue::Float(42.0);
1302 assert_eq!(i.partial_cmp(&f), Some(Ordering::Equal));
1303 assert_eq!(f.partial_cmp(&i), Some(Ordering::Equal));
1304
1305 let i = SqliteValue::Integer(3);
1307 let f = SqliteValue::Float(3.5);
1308 assert_eq!(i.partial_cmp(&f), Some(Ordering::Less));
1309 assert_eq!(f.partial_cmp(&i), Some(Ordering::Greater));
1310 }
1311
1312 #[test]
1313 fn from_conversions() {
1314 assert_eq!(SqliteValue::from(42i64).as_integer(), Some(42));
1315 assert_eq!(SqliteValue::from(42i32).as_integer(), Some(42));
1316 assert_eq!(SqliteValue::from(1.5f64).as_float(), Some(1.5));
1317 assert_eq!(SqliteValue::from("hello").as_text(), Some("hello"));
1318 assert_eq!(
1319 SqliteValue::from(String::from("world")).as_text(),
1320 Some("world")
1321 );
1322 assert_eq!(SqliteValue::from(vec![1u8, 2]).as_blob(), Some(&[1, 2][..]));
1323 assert!(SqliteValue::from(None::<i64>).is_null());
1324 assert_eq!(SqliteValue::from(Some(42i64)).as_integer(), Some(42));
1325 }
1326
1327 #[test]
1328 fn affinity() {
1329 assert_eq!(SqliteValue::Null.affinity(), TypeAffinity::Blob);
1330 assert_eq!(SqliteValue::Integer(0).affinity(), TypeAffinity::Integer);
1331 assert_eq!(SqliteValue::Float(0.0).affinity(), TypeAffinity::Real);
1332 assert_eq!(
1333 SqliteValue::Text(Arc::from("")).affinity(),
1334 TypeAffinity::Text
1335 );
1336 assert_eq!(
1337 SqliteValue::Blob(Arc::from(&[] as &[u8])).affinity(),
1338 TypeAffinity::Blob
1339 );
1340 }
1341
1342 #[test]
1343 fn null_equality() {
1344 let a = SqliteValue::Null;
1346 let b = SqliteValue::Null;
1347 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
1348 }
1349
1350 #[test]
1353 fn test_storage_class_variants() {
1354 assert_eq!(SqliteValue::Null.storage_class(), StorageClass::Null);
1355 assert_eq!(
1356 SqliteValue::Integer(42).storage_class(),
1357 StorageClass::Integer
1358 );
1359 assert_eq!(SqliteValue::Float(3.14).storage_class(), StorageClass::Real);
1360 assert_eq!(
1361 SqliteValue::Text("hi".into()).storage_class(),
1362 StorageClass::Text
1363 );
1364 assert_eq!(
1365 SqliteValue::Blob(Arc::from([1u8].as_slice())).storage_class(),
1366 StorageClass::Blob
1367 );
1368 }
1369
1370 #[test]
1371 fn test_type_affinity_advisory_text_into_integer_ok() {
1372 let val = SqliteValue::Text("hello".into());
1375 let coerced = val.apply_affinity(TypeAffinity::Integer);
1376 assert!(coerced.as_text().is_some());
1377 assert_eq!(coerced.as_text().unwrap(), "hello");
1378
1379 let val = SqliteValue::Text("42".into());
1381 let coerced = val.apply_affinity(TypeAffinity::Integer);
1382 assert_eq!(coerced.as_integer(), Some(42));
1383 }
1384
1385 #[test]
1386 fn test_type_affinity_advisory_integer_into_text_ok() {
1387 let val = SqliteValue::Integer(42);
1389 let coerced = val.apply_affinity(TypeAffinity::Text);
1390 assert_eq!(coerced.as_text(), Some("42"));
1391 }
1392
1393 #[test]
1394 fn test_type_affinity_comparison_coercion_matches_oracle() {
1395 let val = SqliteValue::Text("123".into());
1397 let coerced = val.apply_affinity(TypeAffinity::Numeric);
1398 assert_eq!(coerced.as_integer(), Some(123));
1399
1400 let val = SqliteValue::Text("3.14".into());
1402 let coerced = val.apply_affinity(TypeAffinity::Numeric);
1403 assert_eq!(coerced.as_float(), Some(3.14));
1404
1405 let val = SqliteValue::Text("hello".into());
1407 let coerced = val.apply_affinity(TypeAffinity::Numeric);
1408 assert!(coerced.as_text().is_some());
1409
1410 let val = SqliteValue::Integer(42);
1412 let coerced = val.apply_affinity(TypeAffinity::Blob);
1413 assert_eq!(coerced.as_integer(), Some(42));
1414
1415 let val = SqliteValue::Float(5.0);
1417 let coerced = val.apply_affinity(TypeAffinity::Integer);
1418 assert_eq!(coerced.as_integer(), Some(5));
1419
1420 let val = SqliteValue::Float(5.5);
1422 let coerced = val.apply_affinity(TypeAffinity::Integer);
1423 assert_eq!(coerced.as_float(), Some(5.5));
1424
1425 let val = SqliteValue::Integer(7);
1427 let coerced = val.apply_affinity(TypeAffinity::Real);
1428 assert_eq!(coerced.as_float(), Some(7.0));
1429
1430 let val = SqliteValue::Text("9".into());
1432 let coerced = val.apply_affinity(TypeAffinity::Real);
1433 assert_eq!(coerced.as_float(), Some(9.0));
1434 }
1435
1436 #[test]
1437 fn test_cast_to_numeric_uses_sqlite_cast_rules() {
1438 assert_eq!(
1439 SqliteValue::Text(Arc::from("123abc")).cast_to_numeric(),
1440 SqliteValue::Integer(123)
1441 );
1442 assert_eq!(
1443 SqliteValue::Text(Arc::from("1.5e2abc")).cast_to_numeric(),
1444 SqliteValue::Integer(150)
1445 );
1446 assert_eq!(
1447 SqliteValue::Text(Arc::from("abc")).cast_to_numeric(),
1448 SqliteValue::Integer(0)
1449 );
1450 assert_eq!(
1451 SqliteValue::Blob(Arc::from(b"123a".as_slice())).cast_to_numeric(),
1452 SqliteValue::Integer(123)
1453 );
1454
1455 match SqliteValue::Text(Arc::from("1e999")).cast_to_numeric() {
1456 SqliteValue::Float(value) => assert!(value.is_infinite() && value.is_sign_positive()),
1457 other => panic!("expected +inf REAL from NUMERIC cast, got {other:?}"),
1458 }
1459 }
1460
1461 #[test]
1462 fn test_strict_table_rejects_text_into_integer() {
1463 let val = SqliteValue::Text("hello".into());
1464 let result = val.validate_strict(StrictColumnType::Integer);
1465 assert!(result.is_err());
1466 let err = result.unwrap_err();
1467 assert_eq!(err.expected, StrictColumnType::Integer);
1468 assert_eq!(err.actual, StorageClass::Text);
1469 }
1470
1471 #[test]
1472 fn test_strict_table_allows_exact_type() {
1473 let val = SqliteValue::Integer(42);
1475 assert!(val.validate_strict(StrictColumnType::Integer).is_ok());
1476
1477 let val = SqliteValue::Float(3.14);
1479 assert!(val.validate_strict(StrictColumnType::Real).is_ok());
1480
1481 let val = SqliteValue::Text("hello".into());
1483 assert!(val.validate_strict(StrictColumnType::Text).is_ok());
1484
1485 let val = SqliteValue::Blob(Arc::from([1u8, 2, 3].as_slice()));
1487 assert!(val.validate_strict(StrictColumnType::Blob).is_ok());
1488
1489 assert!(
1491 SqliteValue::Null
1492 .validate_strict(StrictColumnType::Integer)
1493 .is_ok()
1494 );
1495 assert!(
1496 SqliteValue::Null
1497 .validate_strict(StrictColumnType::Text)
1498 .is_ok()
1499 );
1500
1501 let val = SqliteValue::Integer(42);
1503 assert!(val.validate_strict(StrictColumnType::Any).is_ok());
1504 let val = SqliteValue::Text("hi".into());
1505 assert!(val.validate_strict(StrictColumnType::Any).is_ok());
1506 }
1507
1508 #[test]
1509 fn test_strict_real_accepts_integer_with_coercion() {
1510 let val = SqliteValue::Integer(42);
1512 let result = val.validate_strict(StrictColumnType::Real).unwrap();
1513 assert_eq!(result.as_float(), Some(42.0));
1514 }
1515
1516 #[test]
1517 fn test_strict_rejects_wrong_storage_classes() {
1518 assert!(
1520 SqliteValue::Float(3.14)
1521 .validate_strict(StrictColumnType::Integer)
1522 .is_err()
1523 );
1524
1525 assert!(
1527 SqliteValue::Blob(Arc::from([1u8].as_slice()))
1528 .validate_strict(StrictColumnType::Text)
1529 .is_err()
1530 );
1531
1532 assert!(
1534 SqliteValue::Integer(1)
1535 .validate_strict(StrictColumnType::Text)
1536 .is_err()
1537 );
1538
1539 assert!(
1541 SqliteValue::Text("x".into())
1542 .validate_strict(StrictColumnType::Blob)
1543 .is_err()
1544 );
1545 }
1546
1547 #[test]
1548 fn test_strict_column_type_parsing() {
1549 assert_eq!(
1550 StrictColumnType::from_type_name("INT"),
1551 Some(StrictColumnType::Integer)
1552 );
1553 assert_eq!(
1554 StrictColumnType::from_type_name("INTEGER"),
1555 Some(StrictColumnType::Integer)
1556 );
1557 assert_eq!(
1558 StrictColumnType::from_type_name("REAL"),
1559 Some(StrictColumnType::Real)
1560 );
1561 assert_eq!(
1562 StrictColumnType::from_type_name("TEXT"),
1563 Some(StrictColumnType::Text)
1564 );
1565 assert_eq!(
1566 StrictColumnType::from_type_name("BLOB"),
1567 Some(StrictColumnType::Blob)
1568 );
1569 assert_eq!(
1570 StrictColumnType::from_type_name("ANY"),
1571 Some(StrictColumnType::Any)
1572 );
1573 assert_eq!(StrictColumnType::from_type_name("VARCHAR(255)"), None);
1575 assert_eq!(StrictColumnType::from_type_name("NUMERIC"), None);
1576 }
1577
1578 #[test]
1579 fn test_affinity_advisory_never_rejects() {
1580 let values = vec![
1582 SqliteValue::Null,
1583 SqliteValue::Integer(42),
1584 SqliteValue::Float(3.14),
1585 SqliteValue::Text("hello".into()),
1586 SqliteValue::Blob(Arc::from([0xDE, 0xAD].as_slice())),
1587 ];
1588 let affinities = [
1589 TypeAffinity::Integer,
1590 TypeAffinity::Text,
1591 TypeAffinity::Blob,
1592 TypeAffinity::Real,
1593 TypeAffinity::Numeric,
1594 ];
1595 for val in &values {
1596 for aff in &affinities {
1597 let _ = val.clone().apply_affinity(*aff);
1599 }
1600 }
1601 }
1602
1603 #[test]
1606 fn test_unique_allows_multiple_nulls_single_column() {
1607 let a = SqliteValue::Null;
1609 let b = SqliteValue::Null;
1610 assert!(!a.unique_eq(&b));
1611 }
1612
1613 #[test]
1614 fn test_unique_allows_multiple_nulls_multi_column_partial_null() {
1615 let row_a = [SqliteValue::Null, SqliteValue::Integer(1)];
1618 let row_b = [SqliteValue::Null, SqliteValue::Integer(1)];
1619 assert!(!unique_key_duplicates(&row_a, &row_b));
1620
1621 let row_a = [SqliteValue::Integer(1), SqliteValue::Null];
1623 let row_b = [SqliteValue::Integer(1), SqliteValue::Null];
1624 assert!(!unique_key_duplicates(&row_a, &row_b));
1625
1626 let row_a = [SqliteValue::Null, SqliteValue::Null];
1628 let row_b = [SqliteValue::Null, SqliteValue::Null];
1629 assert!(!unique_key_duplicates(&row_a, &row_b));
1630 }
1631
1632 #[test]
1633 fn test_unique_rejects_duplicate_non_null() {
1634 let a = SqliteValue::Integer(42);
1636 let b = SqliteValue::Integer(42);
1637 assert!(a.unique_eq(&b));
1638
1639 let row_a = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1641 let row_b = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1642 assert!(unique_key_duplicates(&row_a, &row_b));
1643
1644 let row_a = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1646 let row_b = [SqliteValue::Integer(1), SqliteValue::Text("world".into())];
1647 assert!(!unique_key_duplicates(&row_a, &row_b));
1648 }
1649
1650 #[test]
1651 fn test_unique_null_vs_non_null_distinct() {
1652 let a = SqliteValue::Null;
1654 let b = SqliteValue::Integer(1);
1655 assert!(!a.unique_eq(&b));
1656 assert!(!b.unique_eq(&a));
1657
1658 let row_a = [SqliteValue::Null, SqliteValue::Integer(1)];
1660 let row_b = [SqliteValue::Integer(2), SqliteValue::Integer(1)];
1661 assert!(!unique_key_duplicates(&row_a, &row_b));
1662 }
1663
1664 #[test]
1667 #[allow(clippy::cast_precision_loss)]
1668 fn test_integer_overflow_promotes_real_expr_add() {
1669 let max = SqliteValue::Integer(i64::MAX);
1670 let one = SqliteValue::Integer(1);
1671 let result = max.sql_add(&one);
1672 assert!(result.as_integer().is_none());
1674 assert!(result.as_float().is_some());
1675 assert!(result.as_float().unwrap() >= i64::MAX as f64);
1677 }
1678
1679 #[test]
1680 fn test_integer_overflow_promotes_real_expr_mul() {
1681 let max = SqliteValue::Integer(i64::MAX);
1682 let two = SqliteValue::Integer(2);
1683 let result = max.sql_mul(&two);
1684 assert!(result.as_float().is_some());
1686 }
1687
1688 #[test]
1689 fn test_integer_overflow_promotes_real_expr_sub() {
1690 let min = SqliteValue::Integer(i64::MIN);
1691 let one = SqliteValue::Integer(1);
1692 let result = min.sql_sub(&one);
1693 assert!(result.as_float().is_some());
1695 }
1696
1697 #[test]
1698 fn test_sum_overflow_errors() {
1699 let mut acc = SumAccumulator::new();
1700 acc.accumulate(&SqliteValue::Integer(i64::MAX));
1701 acc.accumulate(&SqliteValue::Integer(1));
1702 let result = acc.finish();
1703 assert!(result.is_err());
1704 }
1705
1706 #[test]
1707 fn test_no_overflow_stays_integer() {
1708 let a = SqliteValue::Integer(100);
1710 let b = SqliteValue::Integer(200);
1711 let result = a.sql_add(&b);
1712 assert_eq!(result.as_integer(), Some(300));
1713
1714 let result = SqliteValue::Integer(7).sql_mul(&SqliteValue::Integer(6));
1716 assert_eq!(result.as_integer(), Some(42));
1717
1718 let result = SqliteValue::Integer(50).sql_sub(&SqliteValue::Integer(8));
1720 assert_eq!(result.as_integer(), Some(42));
1721 }
1722
1723 #[test]
1724 fn test_sum_null_only_returns_null() {
1725 let mut acc = SumAccumulator::new();
1726 acc.accumulate(&SqliteValue::Null);
1727 acc.accumulate(&SqliteValue::Null);
1728 let result = acc.finish().unwrap();
1729 assert!(result.is_null());
1730 }
1731
1732 #[test]
1733 fn test_sum_mixed_int_float() {
1734 let mut acc = SumAccumulator::new();
1735 acc.accumulate(&SqliteValue::Integer(10));
1736 acc.accumulate(&SqliteValue::Float(2.5));
1737 acc.accumulate(&SqliteValue::Integer(3));
1738 let result = acc.finish().unwrap();
1739 assert_eq!(result.as_float(), Some(15.5));
1741 }
1742
1743 #[test]
1744 fn test_sum_integer_only() {
1745 let mut acc = SumAccumulator::new();
1746 acc.accumulate(&SqliteValue::Integer(10));
1747 acc.accumulate(&SqliteValue::Integer(20));
1748 acc.accumulate(&SqliteValue::Integer(30));
1749 let result = acc.finish().unwrap();
1750 assert_eq!(result.as_integer(), Some(60));
1751 }
1752
1753 #[test]
1754 fn test_sql_arithmetic_null_propagation() {
1755 let n = SqliteValue::Null;
1756 let i = SqliteValue::Integer(42);
1757 assert!(n.sql_add(&i).is_null());
1758 assert!(i.sql_add(&n).is_null());
1759 assert!(n.sql_sub(&i).is_null());
1760 assert!(n.sql_mul(&i).is_null());
1761 }
1762
1763 #[test]
1764 fn test_sql_inf_arithmetic_nan_normalized_to_null() {
1765 let pos_inf = SqliteValue::Float(f64::INFINITY);
1767 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1768 assert!(pos_inf.sql_add(&neg_inf).is_null());
1769
1770 assert!(pos_inf.sql_sub(&pos_inf).is_null());
1772 }
1773
1774 #[test]
1775 fn test_sql_mul_zero_times_inf_normalized_to_null() {
1776 let zero = SqliteValue::Float(0.0);
1778 let pos_inf = SqliteValue::Float(f64::INFINITY);
1779 assert!(zero.sql_mul(&pos_inf).is_null());
1780 }
1781
1782 #[test]
1783 fn test_sql_inf_propagates_when_not_nan() {
1784 let pos_inf = SqliteValue::Float(f64::INFINITY);
1785 let one = SqliteValue::Integer(1);
1786 let add_result = pos_inf.sql_add(&one);
1787 assert!(
1788 matches!(add_result, SqliteValue::Float(v) if v.is_infinite() && v.is_sign_positive()),
1789 "expected +Inf propagation, got {add_result:?}"
1790 );
1791
1792 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1793 let sub_result = neg_inf.sql_sub(&one);
1794 assert!(
1795 matches!(sub_result, SqliteValue::Float(v) if v.is_infinite() && v.is_sign_negative()),
1796 "expected -Inf propagation, got {sub_result:?}"
1797 );
1798 }
1799
1800 #[test]
1801 fn test_from_f64_nan_normalizes_to_null() {
1802 let value = SqliteValue::from(f64::NAN);
1803 assert!(value.is_null());
1804 }
1805
1806 #[test]
1807 fn test_inf_comparisons_against_finite_values() {
1808 let pos_inf = SqliteValue::Float(f64::INFINITY);
1809 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1810 let finite_hi = SqliteValue::Float(1.0e308);
1811 let finite_lo = SqliteValue::Float(-1.0e308);
1812
1813 assert_eq!(pos_inf.partial_cmp(&finite_hi), Some(Ordering::Greater));
1814 assert_eq!(neg_inf.partial_cmp(&finite_lo), Some(Ordering::Less));
1815 }
1816
1817 #[test]
1820 fn test_empty_string_is_not_null() {
1821 let empty = SqliteValue::Text(Arc::from(""));
1822 assert!(!empty.is_null());
1824 assert!(!empty.is_null());
1826 assert!(SqliteValue::Null.is_null());
1828 }
1829
1830 #[test]
1831 fn test_length_empty_string_zero() {
1832 let empty = SqliteValue::Text(Arc::from(""));
1833 assert_eq!(empty.sql_length(), Some(0));
1834 }
1835
1836 #[test]
1837 fn test_typeof_empty_string_text() {
1838 let empty = SqliteValue::Text(Arc::from(""));
1839 assert_eq!(empty.typeof_str(), "text");
1840 assert_eq!(SqliteValue::Null.typeof_str(), "null");
1842 }
1843
1844 #[test]
1845 fn test_empty_string_comparisons() {
1846 let empty1 = SqliteValue::Text(Arc::from(""));
1847 let empty2 = SqliteValue::Text(Arc::from(""));
1848 assert_eq!(empty1.partial_cmp(&empty2), Some(std::cmp::Ordering::Equal));
1850
1851 let null = SqliteValue::Null;
1855 assert_ne!(empty1.partial_cmp(&null), Some(std::cmp::Ordering::Equal));
1856 }
1857
1858 #[test]
1859 fn test_typeof_all_variants() {
1860 assert_eq!(SqliteValue::Null.typeof_str(), "null");
1861 assert_eq!(SqliteValue::Integer(0).typeof_str(), "integer");
1862 assert_eq!(SqliteValue::Float(0.0).typeof_str(), "real");
1863 assert_eq!(SqliteValue::Text("x".into()).typeof_str(), "text");
1864 assert_eq!(
1865 SqliteValue::Blob(Arc::from(&[] as &[u8])).typeof_str(),
1866 "blob"
1867 );
1868 }
1869
1870 #[test]
1871 fn test_sql_length_all_types() {
1872 assert_eq!(SqliteValue::Null.sql_length(), None);
1874 assert_eq!(SqliteValue::Text("hello".into()).sql_length(), Some(5));
1876 assert_eq!(SqliteValue::Text(Arc::from("")).sql_length(), Some(0));
1877 assert_eq!(
1879 SqliteValue::Blob(Arc::from([1u8, 2, 3].as_slice())).sql_length(),
1880 Some(3)
1881 );
1882 assert_eq!(SqliteValue::Integer(42).sql_length(), Some(2));
1884 assert_eq!(SqliteValue::Float(3.14).sql_length(), Some(4)); }
1887
1888 #[test]
1891 fn test_like_ascii_case_insensitive() {
1892 assert!(sql_like("A", "a", None));
1893 assert!(sql_like("a", "A", None));
1894 assert!(sql_like("hello", "HELLO", None));
1895 assert!(sql_like("HELLO", "hello", None));
1896 assert!(sql_like("HeLLo", "hEllO", None));
1897 }
1898
1899 #[test]
1900 fn test_like_unicode_case_sensitive_without_icu() {
1901 assert!(!sql_like("ä", "Ä", None));
1903 assert!(!sql_like("Ä", "ä", None));
1904 assert!(sql_like("ä", "ä", None));
1906 }
1907
1908 #[test]
1909 fn test_like_escape_handling() {
1910 assert!(sql_like("100\\%", "100%", Some('\\')));
1912 assert!(!sql_like("100\\%", "100x", Some('\\')));
1913
1914 assert!(sql_like("a\\_b", "a_b", Some('\\')));
1916 assert!(!sql_like("a\\_b", "axb", Some('\\')));
1917 }
1918
1919 #[test]
1920 fn test_like_wildcards_basic() {
1921 assert!(sql_like("%", "", None));
1923 assert!(sql_like("%", "anything", None));
1924 assert!(sql_like("a%", "abc", None));
1925 assert!(sql_like("%c", "abc", None));
1926 assert!(sql_like("a%c", "abc", None));
1927 assert!(sql_like("a%c", "aXYZc", None));
1928 assert!(!sql_like("a%c", "abd", None));
1929
1930 assert!(sql_like("_", "x", None));
1932 assert!(!sql_like("_", "", None));
1933 assert!(!sql_like("_", "xy", None));
1934 assert!(sql_like("a_c", "abc", None));
1935 assert!(!sql_like("a_c", "abbc", None));
1936 }
1937
1938 #[test]
1939 fn test_like_combined_wildcards() {
1940 assert!(sql_like("%_", "a", None));
1941 assert!(!sql_like("%_", "", None));
1942 assert!(sql_like("_%_", "ab", None));
1943 assert!(!sql_like("_%_", "a", None));
1944 assert!(sql_like("%a%b%", "xaybz", None));
1945 assert!(!sql_like("%a%b%", "xyz", None));
1946 }
1947
1948 #[test]
1949 fn test_like_exact_match() {
1950 assert!(sql_like("hello", "hello", None));
1951 assert!(!sql_like("hello", "world", None));
1952 assert!(sql_like("", "", None));
1953 assert!(!sql_like("a", "", None));
1954 assert!(!sql_like("", "a", None));
1955 }
1956
1957 #[test]
1960 fn test_format_sqlite_float_whole_number() {
1961 assert_eq!(format_sqlite_float(120.0), "120.0");
1962 assert_eq!(format_sqlite_float(0.0), "0.0");
1963 assert_eq!(format_sqlite_float(-42.0), "-42.0");
1964 assert_eq!(format_sqlite_float(1.0), "1.0");
1965 }
1966
1967 #[test]
1968 fn test_format_sqlite_float_fractional() {
1969 assert_eq!(format_sqlite_float(3.14), "3.14");
1970 assert_eq!(format_sqlite_float(0.5), "0.5");
1971 assert_eq!(format_sqlite_float(-0.001), "-0.001");
1972 }
1973
1974 #[test]
1975 fn test_format_sqlite_float_special() {
1976 assert_eq!(format_sqlite_float(f64::NAN), "NaN");
1977 assert_eq!(format_sqlite_float(f64::INFINITY), "Inf");
1978 assert_eq!(format_sqlite_float(f64::NEG_INFINITY), "-Inf");
1979 }
1980
1981 #[test]
1982 fn test_format_sqlite_float_negative_zero() {
1983 assert_eq!(format_sqlite_float(-0.0), "-0.0");
1985 assert_eq!(format_sqlite_float(0.0), "0.0");
1986 }
1987
1988 #[test]
1989 fn test_float_to_text_includes_decimal_point() {
1990 let v = SqliteValue::Float(100.0);
1991 assert_eq!(v.to_text(), "100.0");
1992 let v = SqliteValue::Float(3.14);
1993 assert_eq!(v.to_text(), "3.14");
1994 }
1995
1996 #[test]
1999 fn test_scan_numeric_prefix_bare_dot() {
2000 assert_eq!(scan_numeric_prefix(b"."), 0);
2002 assert_eq!(scan_numeric_prefix(b"-."), 0);
2003 assert_eq!(scan_numeric_prefix(b"+."), 0);
2004 assert_eq!(scan_numeric_prefix(b"..1"), 0);
2005 }
2006
2007 #[test]
2008 fn test_scan_numeric_prefix_valid() {
2009 assert_eq!(scan_numeric_prefix(b"123"), 3);
2010 assert_eq!(scan_numeric_prefix(b"3.14"), 4);
2011 assert_eq!(scan_numeric_prefix(b".5"), 2);
2012 assert_eq!(scan_numeric_prefix(b"1e10"), 4);
2013 assert_eq!(scan_numeric_prefix(b"-42abc"), 3);
2014 assert_eq!(scan_numeric_prefix(b"+.5x"), 3);
2015 assert_eq!(scan_numeric_prefix(b"0.0"), 3);
2016 }
2017
2018 #[test]
2019 fn test_scan_numeric_prefix_empty_and_non_numeric() {
2020 assert_eq!(scan_numeric_prefix(b""), 0);
2021 assert_eq!(scan_numeric_prefix(b"abc"), 0);
2022 assert_eq!(scan_numeric_prefix(b"+"), 0);
2023 assert_eq!(scan_numeric_prefix(b"-"), 0);
2024 }
2025}