1use std::cmp::Ordering;
2use std::fmt;
3
4use crate::{StorageClass, StrictColumnType, StrictTypeError, TypeAffinity};
5
6#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
11pub enum SqliteValue {
12 Null,
14 Integer(i64),
16 Float(f64),
18 Text(String),
20 Blob(Vec<u8>),
22}
23
24impl SqliteValue {
25 pub const fn affinity(&self) -> TypeAffinity {
27 match self {
28 Self::Null | Self::Blob(_) => TypeAffinity::Blob,
29 Self::Integer(_) => TypeAffinity::Integer,
30 Self::Float(_) => TypeAffinity::Real,
31 Self::Text(_) => TypeAffinity::Text,
32 }
33 }
34
35 pub const fn storage_class(&self) -> StorageClass {
37 match self {
38 Self::Null => StorageClass::Null,
39 Self::Integer(_) => StorageClass::Integer,
40 Self::Float(_) => StorageClass::Real,
41 Self::Text(_) => StorageClass::Text,
42 Self::Blob(_) => StorageClass::Blob,
43 }
44 }
45
46 #[must_use]
58 #[allow(
59 clippy::cast_possible_truncation,
60 clippy::cast_precision_loss,
61 clippy::float_cmp
62 )]
63 pub fn apply_affinity(self, affinity: TypeAffinity) -> Self {
64 match affinity {
65 TypeAffinity::Blob => self,
66 TypeAffinity::Text => match self {
67 Self::Null | Self::Text(_) | Self::Blob(_) => self,
68 Self::Integer(_) | Self::Float(_) => {
69 let t = self.to_text();
70 Self::Text(t)
71 }
72 },
73 TypeAffinity::Numeric => match &self {
74 Self::Text(s) => try_coerce_text_to_numeric(s).unwrap_or(self),
75 _ => self,
76 },
77 TypeAffinity::Integer => match &self {
78 Self::Text(s) => try_coerce_text_to_numeric(s).unwrap_or(self),
79 Self::Float(f) => {
80 if *f >= -9_223_372_036_854_775_808.0 && *f < 9_223_372_036_854_775_808.0 {
81 let i = *f as i64;
82 if (i as f64) == *f {
83 return Self::Integer(i);
84 }
85 }
86 self
87 }
88 _ => self,
89 },
90 TypeAffinity::Real => match &self {
91 Self::Text(s) => try_coerce_text_to_numeric(s)
92 .map(|v| match v {
93 Self::Integer(i) => Self::Float(i as f64),
94 other => other,
95 })
96 .unwrap_or(self),
97 Self::Integer(i) => Self::Float(*i as f64),
98 _ => self,
99 },
100 }
101 }
102
103 #[allow(clippy::cast_precision_loss)]
110 pub fn validate_strict(self, col_type: StrictColumnType) -> Result<Self, StrictTypeError> {
111 if matches!(self, Self::Null) {
112 return Ok(self);
113 }
114 match col_type {
115 StrictColumnType::Any => Ok(self),
116 StrictColumnType::Integer => match self {
117 Self::Integer(_) => Ok(self),
118 other => Err(StrictTypeError {
119 expected: col_type,
120 actual: other.storage_class(),
121 }),
122 },
123 StrictColumnType::Real => match self {
124 Self::Float(_) => Ok(self),
125 Self::Integer(i) => Ok(Self::Float(i as f64)),
126 other => Err(StrictTypeError {
127 expected: col_type,
128 actual: other.storage_class(),
129 }),
130 },
131 StrictColumnType::Text => match self {
132 Self::Text(_) => Ok(self),
133 other => Err(StrictTypeError {
134 expected: col_type,
135 actual: other.storage_class(),
136 }),
137 },
138 StrictColumnType::Blob => match self {
139 Self::Blob(_) => Ok(self),
140 other => Err(StrictTypeError {
141 expected: col_type,
142 actual: other.storage_class(),
143 }),
144 },
145 }
146 }
147
148 pub const fn is_null(&self) -> bool {
150 matches!(self, Self::Null)
151 }
152
153 pub const fn as_integer(&self) -> Option<i64> {
155 match self {
156 Self::Integer(i) => Some(*i),
157 _ => None,
158 }
159 }
160
161 pub fn as_float(&self) -> Option<f64> {
163 match self {
164 Self::Float(f) => Some(*f),
165 _ => None,
166 }
167 }
168
169 pub fn as_text(&self) -> Option<&str> {
171 match self {
172 Self::Text(s) => Some(s),
173 _ => None,
174 }
175 }
176
177 pub fn as_blob(&self) -> Option<&[u8]> {
179 match self {
180 Self::Blob(b) => Some(b),
181 _ => None,
182 }
183 }
184
185 #[allow(clippy::cast_possible_truncation)]
193 pub fn to_integer(&self) -> i64 {
194 match self {
195 Self::Null | Self::Blob(_) => 0,
196 Self::Integer(i) => *i,
197 Self::Float(f) => *f as i64,
198 Self::Text(s) => s.trim().parse::<i64>().unwrap_or_else(|_| {
199 s.trim().parse::<f64>().map_or(0, |f| f as i64)
201 }),
202 }
203 }
204
205 #[allow(clippy::cast_precision_loss)]
213 pub fn to_float(&self) -> f64 {
214 match self {
215 Self::Null | Self::Blob(_) => 0.0,
216 Self::Integer(i) => *i as f64,
217 Self::Float(f) => *f,
218 Self::Text(s) => s.trim().parse::<f64>().unwrap_or(0.0),
219 }
220 }
221
222 pub fn to_text(&self) -> String {
224 match self {
225 Self::Null => String::new(),
226 Self::Integer(i) => i.to_string(),
227 Self::Float(f) => format!("{f}"),
228 Self::Text(s) => s.clone(),
229 Self::Blob(b) => {
230 use std::fmt::Write;
231 let mut hex = String::with_capacity(2 + b.len() * 2);
232 hex.push_str("X'");
233 for byte in b {
234 let _ = write!(hex, "{byte:02X}");
235 }
236 hex.push('\'');
237 hex
238 }
239 }
240 }
241
242 pub const fn typeof_str(&self) -> &'static str {
246 match self {
247 Self::Null => "null",
248 Self::Integer(_) => "integer",
249 Self::Float(_) => "real",
250 Self::Text(_) => "text",
251 Self::Blob(_) => "blob",
252 }
253 }
254
255 pub fn sql_length(&self) -> Option<i64> {
262 match self {
263 Self::Null => None,
264 Self::Text(s) => Some(i64::try_from(s.chars().count()).unwrap_or(i64::MAX)),
265 Self::Blob(b) => Some(i64::try_from(b.len()).unwrap_or(i64::MAX)),
266 Self::Integer(_) | Self::Float(_) => {
267 let t = self.to_text();
268 Some(i64::try_from(t.chars().count()).unwrap_or(i64::MAX))
269 }
270 }
271 }
272
273 pub fn unique_eq(&self, other: &Self) -> bool {
279 if self.is_null() || other.is_null() {
280 return false;
281 }
282 matches!(self.partial_cmp(other), Some(Ordering::Equal))
283 }
284
285 fn float_result_or_null(result: f64) -> Self {
289 if result.is_nan() {
290 Self::Null
291 } else {
292 Self::Float(result)
293 }
294 }
295
296 #[must_use]
303 #[allow(clippy::cast_precision_loss)]
304 pub fn sql_add(&self, other: &Self) -> Self {
305 match (self, other) {
306 (Self::Null, _) | (_, Self::Null) => Self::Null,
307 (Self::Integer(a), Self::Integer(b)) => match a.checked_add(*b) {
308 Some(result) => Self::Integer(result),
309 None => Self::float_result_or_null(*a as f64 + *b as f64),
310 },
311 _ => Self::float_result_or_null(self.to_float() + other.to_float()),
312 }
313 }
314
315 #[must_use]
319 #[allow(clippy::cast_precision_loss)]
320 pub fn sql_sub(&self, other: &Self) -> Self {
321 match (self, other) {
322 (Self::Null, _) | (_, Self::Null) => Self::Null,
323 (Self::Integer(a), Self::Integer(b)) => match a.checked_sub(*b) {
324 Some(result) => Self::Integer(result),
325 None => Self::float_result_or_null(*a as f64 - *b as f64),
326 },
327 _ => Self::float_result_or_null(self.to_float() - other.to_float()),
328 }
329 }
330
331 #[must_use]
335 #[allow(clippy::cast_precision_loss)]
336 pub fn sql_mul(&self, other: &Self) -> Self {
337 match (self, other) {
338 (Self::Null, _) | (_, Self::Null) => Self::Null,
339 (Self::Integer(a), Self::Integer(b)) => match a.checked_mul(*b) {
340 Some(result) => Self::Integer(result),
341 None => Self::float_result_or_null(*a as f64 * *b as f64),
342 },
343 _ => Self::float_result_or_null(self.to_float() * other.to_float()),
344 }
345 }
346
347 const fn sort_class(&self) -> u8 {
349 match self {
350 Self::Null => 0,
351 Self::Integer(_) | Self::Float(_) => 1,
352 Self::Text(_) => 2,
353 Self::Blob(_) => 3,
354 }
355 }
356}
357
358pub fn unique_key_duplicates(a: &[SqliteValue], b: &[SqliteValue]) -> bool {
366 assert_eq!(a.len(), b.len(), "UNIQUE key columns must match");
367 a.iter().zip(b.iter()).all(|(va, vb)| va.unique_eq(vb))
368}
369
370pub fn sql_like(pattern: &str, text: &str, escape: Option<char>) -> bool {
377 sql_like_inner(
378 &pattern.chars().collect::<Vec<_>>(),
379 &text.chars().collect::<Vec<_>>(),
380 escape,
381 0,
382 0,
383 )
384}
385
386fn sql_like_inner(
387 pattern: &[char],
388 text: &[char],
389 escape: Option<char>,
390 pi: usize,
391 ti: usize,
392) -> bool {
393 let mut pi = pi;
394 let mut ti = ti;
395
396 while pi < pattern.len() {
397 let pc = pattern[pi];
398
399 if Some(pc) == escape {
401 pi += 1;
402 if pi >= pattern.len() {
403 return false; }
405 if ti >= text.len() || !ascii_ci_eq(pattern[pi], text[ti]) {
407 return false;
408 }
409 pi += 1;
410 ti += 1;
411 continue;
412 }
413
414 match pc {
415 '%' => {
416 while pi < pattern.len() && pattern[pi] == '%' {
418 pi += 1;
419 }
420 if pi >= pattern.len() {
422 return true;
423 }
424 for start in ti..=text.len() {
426 if sql_like_inner(pattern, text, escape, pi, start) {
427 return true;
428 }
429 }
430 return false;
431 }
432 '_' => {
433 if ti >= text.len() {
434 return false;
435 }
436 pi += 1;
437 ti += 1;
438 }
439 _ => {
440 if ti >= text.len() || !ascii_ci_eq(pc, text[ti]) {
441 return false;
442 }
443 pi += 1;
444 ti += 1;
445 }
446 }
447 }
448 ti >= text.len()
449}
450
451fn ascii_ci_eq(a: char, b: char) -> bool {
453 if a == b {
454 return true;
455 }
456 a.is_ascii() && b.is_ascii() && a.eq_ignore_ascii_case(&b)
458}
459
460#[derive(Debug, Clone)]
465pub struct SumAccumulator {
466 int_sum: i64,
468 float_sum: f64,
470 has_value: bool,
472 is_float: bool,
474 overflow: bool,
476}
477
478impl Default for SumAccumulator {
479 fn default() -> Self {
480 Self::new()
481 }
482}
483
484impl SumAccumulator {
485 pub const fn new() -> Self {
487 Self {
488 int_sum: 0,
489 float_sum: 0.0,
490 has_value: false,
491 is_float: false,
492 overflow: false,
493 }
494 }
495
496 #[allow(clippy::cast_precision_loss)]
498 pub fn accumulate(&mut self, val: &SqliteValue) {
499 match val {
500 SqliteValue::Null => {}
501 SqliteValue::Integer(i) => {
502 self.has_value = true;
503 if self.is_float {
504 self.float_sum += *i as f64;
505 } else {
506 match self.int_sum.checked_add(*i) {
507 Some(result) => self.int_sum = result,
508 None => self.overflow = true,
509 }
510 }
511 }
512 SqliteValue::Float(f) => {
513 self.has_value = true;
514 if !self.is_float {
515 self.float_sum = self.int_sum as f64;
516 self.is_float = true;
517 }
518 self.float_sum += f;
519 }
520 other => {
521 self.has_value = true;
523 let n = other.to_float();
524 if !self.is_float {
525 self.float_sum = self.int_sum as f64;
526 self.is_float = true;
527 }
528 self.float_sum += n;
529 }
530 }
531 }
532
533 pub fn finish(&self) -> Result<SqliteValue, SumOverflowError> {
536 if self.overflow {
537 return Err(SumOverflowError);
538 }
539 if !self.has_value {
540 return Ok(SqliteValue::Null);
541 }
542 if self.is_float {
543 Ok(SqliteValue::Float(self.float_sum))
544 } else {
545 Ok(SqliteValue::Integer(self.int_sum))
546 }
547 }
548}
549
550#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct SumOverflowError;
553
554impl fmt::Display for SumOverflowError {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 f.write_str("integer overflow in sum()")
557 }
558}
559
560impl fmt::Display for SqliteValue {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 match self {
563 Self::Null => f.write_str("NULL"),
564 Self::Integer(i) => write!(f, "{i}"),
565 Self::Float(v) => write!(f, "{v}"),
566 Self::Text(s) => write!(f, "'{s}'"),
567 Self::Blob(b) => {
568 f.write_str("X'")?;
569 for byte in b {
570 write!(f, "{byte:02X}")?;
571 }
572 f.write_str("'")
573 }
574 }
575 }
576}
577
578impl PartialEq for SqliteValue {
579 fn eq(&self, other: &Self) -> bool {
580 matches!(self.partial_cmp(other), Some(Ordering::Equal))
581 }
582}
583
584impl PartialOrd for SqliteValue {
585 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
586 let class_a = self.sort_class();
588 let class_b = other.sort_class();
589
590 if class_a != class_b {
591 return Some(class_a.cmp(&class_b));
592 }
593
594 match (self, other) {
595 (Self::Null, Self::Null) => Some(Ordering::Equal),
596 (Self::Integer(a), Self::Integer(b)) => Some(a.cmp(b)),
597 (Self::Float(a), Self::Float(b)) => a.partial_cmp(b),
598 (Self::Integer(a), Self::Float(b)) => Some(int_float_cmp(*a, *b)),
599 (Self::Float(a), Self::Integer(b)) => Some(int_float_cmp(*b, *a).reverse()),
600 (Self::Text(a), Self::Text(b)) => Some(a.cmp(b)),
601 (Self::Blob(a), Self::Blob(b)) => Some(a.cmp(b)),
602 _ => None,
603 }
604 }
605}
606
607impl From<i64> for SqliteValue {
608 fn from(i: i64) -> Self {
609 Self::Integer(i)
610 }
611}
612
613impl From<i32> for SqliteValue {
614 fn from(i: i32) -> Self {
615 Self::Integer(i64::from(i))
616 }
617}
618
619impl From<f64> for SqliteValue {
620 fn from(f: f64) -> Self {
621 Self::float_result_or_null(f)
622 }
623}
624
625impl From<String> for SqliteValue {
626 fn from(s: String) -> Self {
627 Self::Text(s)
628 }
629}
630
631impl From<&str> for SqliteValue {
632 fn from(s: &str) -> Self {
633 Self::Text(s.to_owned())
634 }
635}
636
637impl From<Vec<u8>> for SqliteValue {
638 fn from(b: Vec<u8>) -> Self {
639 Self::Blob(b)
640 }
641}
642
643impl From<&[u8]> for SqliteValue {
644 fn from(b: &[u8]) -> Self {
645 Self::Blob(b.to_vec())
646 }
647}
648
649impl<T: Into<Self>> From<Option<T>> for SqliteValue {
650 fn from(opt: Option<T>) -> Self {
651 match opt {
652 Some(v) => v.into(),
653 None => Self::Null,
654 }
655 }
656}
657
658#[allow(
662 clippy::cast_possible_truncation,
663 clippy::cast_precision_loss,
664 clippy::float_cmp
665)]
666fn try_coerce_text_to_numeric(s: &str) -> Option<SqliteValue> {
667 let trimmed = s.trim();
668 if trimmed.is_empty() {
669 return None;
670 }
671 if let Ok(i) = trimmed.parse::<i64>() {
673 return Some(SqliteValue::Integer(i));
674 }
675 if let Ok(f) = trimmed.parse::<f64>() {
678 if !f.is_finite() {
679 return None;
680 }
681 if (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&f) {
684 #[allow(clippy::cast_possible_truncation)]
685 let i = f as i64;
686 #[allow(clippy::cast_precision_loss)]
687 if (i as f64) == f {
688 return Some(SqliteValue::Integer(i));
689 }
690 }
691 return Some(SqliteValue::Float(f));
692 }
693 None
694}
695
696#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
701fn int_float_cmp(i: i64, r: f64) -> Ordering {
702 if r.is_nan() {
703 return Ordering::Greater;
705 }
706 if r < -9_223_372_036_854_775_808.0 {
708 return Ordering::Greater;
709 }
710 if r >= 9_223_372_036_854_775_808.0 {
711 return Ordering::Less;
712 }
713 let y = r as i64;
715 match i.cmp(&y) {
716 Ordering::Less => Ordering::Less,
717 Ordering::Greater => Ordering::Greater,
718 Ordering::Equal => {
720 let s = i as f64;
721 s.partial_cmp(&r).unwrap_or(Ordering::Equal)
722 }
723 }
724}
725
726#[cfg(test)]
727#[allow(clippy::float_cmp, clippy::approx_constant)]
728mod tests {
729 use super::*;
730
731 #[test]
732 fn null_properties() {
733 let v = SqliteValue::Null;
734 assert!(v.is_null());
735 assert_eq!(v.to_integer(), 0);
736 assert_eq!(v.to_float(), 0.0);
737 assert_eq!(v.to_text(), "");
738 assert_eq!(v.to_string(), "NULL");
739 }
740
741 #[test]
742 fn integer_properties() {
743 let v = SqliteValue::Integer(42);
744 assert!(!v.is_null());
745 assert_eq!(v.as_integer(), Some(42));
746 assert_eq!(v.to_integer(), 42);
747 assert_eq!(v.to_float(), 42.0);
748 assert_eq!(v.to_text(), "42");
749 }
750
751 #[test]
752 fn float_properties() {
753 let v = SqliteValue::Float(3.14);
754 assert_eq!(v.as_float(), Some(3.14));
755 assert_eq!(v.to_integer(), 3);
756 assert_eq!(v.to_text(), "3.14");
757 }
758
759 #[test]
760 fn text_properties() {
761 let v = SqliteValue::Text("hello".to_owned());
762 assert_eq!(v.as_text(), Some("hello"));
763 assert_eq!(v.to_integer(), 0);
764 assert_eq!(v.to_float(), 0.0);
765 }
766
767 #[test]
768 fn text_numeric_coercion() {
769 let v = SqliteValue::Text("123".to_owned());
770 assert_eq!(v.to_integer(), 123);
771 assert_eq!(v.to_float(), 123.0);
772
773 let v = SqliteValue::Text("3.14".to_owned());
774 assert_eq!(v.to_integer(), 3);
775 assert_eq!(v.to_float(), 3.14);
776 }
777
778 #[test]
779 fn test_sqlite_value_integer_real_comparison_equal() {
780 let int_value = SqliteValue::Integer(3);
781 let real_value = SqliteValue::Float(3.0);
782 assert_eq!(int_value.partial_cmp(&real_value), Some(Ordering::Equal));
783 assert_eq!(real_value.partial_cmp(&int_value), Some(Ordering::Equal));
784 }
785
786 #[test]
787 fn test_sqlite_value_text_to_integer_coercion() {
788 let text_value = SqliteValue::Text("123".to_owned());
789 let coerced = text_value.apply_affinity(TypeAffinity::Integer);
790 assert_eq!(coerced, SqliteValue::Integer(123));
791 }
792
793 #[test]
794 fn blob_properties() {
795 let v = SqliteValue::Blob(vec![0xDE, 0xAD]);
796 assert_eq!(v.as_blob(), Some(&[0xDE, 0xAD][..]));
797 assert_eq!(v.to_integer(), 0);
798 assert_eq!(v.to_float(), 0.0);
799 assert_eq!(v.to_text(), "X'DEAD'");
800 }
801
802 #[test]
803 fn display_formatting() {
804 assert_eq!(SqliteValue::Null.to_string(), "NULL");
805 assert_eq!(SqliteValue::Integer(42).to_string(), "42");
806 assert_eq!(SqliteValue::Integer(-1).to_string(), "-1");
807 assert_eq!(SqliteValue::Float(1.5).to_string(), "1.5");
808 assert_eq!(SqliteValue::Text("hi".to_owned()).to_string(), "'hi'");
809 assert_eq!(SqliteValue::Blob(vec![0xCA, 0xFE]).to_string(), "X'CAFE'");
810 }
811
812 #[test]
813 fn sort_order_null_first() {
814 let null = SqliteValue::Null;
815 let int = SqliteValue::Integer(0);
816 let text = SqliteValue::Text(String::new());
817 let blob = SqliteValue::Blob(vec![]);
818
819 assert!(null < int);
820 assert!(int < text);
821 assert!(text < blob);
822 }
823
824 #[test]
825 fn sort_order_integers() {
826 let a = SqliteValue::Integer(1);
827 let b = SqliteValue::Integer(2);
828 assert!(a < b);
829 assert_eq!(a.partial_cmp(&a), Some(Ordering::Equal));
830 }
831
832 #[test]
833 fn sort_order_mixed_numeric() {
834 let int = SqliteValue::Integer(1);
835 let float = SqliteValue::Float(1.5);
836 assert!(int < float);
837
838 let int = SqliteValue::Integer(2);
839 assert!(int > float);
840 }
841
842 #[test]
843 fn test_int_float_precision_at_i64_boundary() {
844 let imax = SqliteValue::Integer(i64::MAX);
848 let fmax = SqliteValue::Float(9_223_372_036_854_775_808.0);
849 assert_eq!(
850 imax.partial_cmp(&fmax),
851 Some(Ordering::Less),
852 "i64::MAX must be Less than 9223372036854775808.0"
853 );
854
855 let a = SqliteValue::Integer(i64::MAX);
857 let b = SqliteValue::Integer(i64::MAX - 1);
858 let f = SqliteValue::Float(i64::MAX as f64);
859 assert_eq!(a.partial_cmp(&b), Some(Ordering::Greater));
861 assert_eq!(a.partial_cmp(&f), Some(Ordering::Less));
863 assert_eq!(b.partial_cmp(&f), Some(Ordering::Less));
864 }
865
866 #[test]
867 fn test_int_float_precision_symmetric() {
868 let i = SqliteValue::Integer(i64::MAX);
870 let f = SqliteValue::Float(9_223_372_036_854_775_808.0);
871 assert_eq!(f.partial_cmp(&i), Some(Ordering::Greater));
872 }
873
874 #[test]
875 fn test_int_float_exact_representation() {
876 let i = SqliteValue::Integer(42);
878 let f = SqliteValue::Float(42.0);
879 assert_eq!(i.partial_cmp(&f), Some(Ordering::Equal));
880 assert_eq!(f.partial_cmp(&i), Some(Ordering::Equal));
881
882 let i = SqliteValue::Integer(3);
884 let f = SqliteValue::Float(3.5);
885 assert_eq!(i.partial_cmp(&f), Some(Ordering::Less));
886 assert_eq!(f.partial_cmp(&i), Some(Ordering::Greater));
887 }
888
889 #[test]
890 fn from_conversions() {
891 assert_eq!(SqliteValue::from(42i64).as_integer(), Some(42));
892 assert_eq!(SqliteValue::from(42i32).as_integer(), Some(42));
893 assert_eq!(SqliteValue::from(1.5f64).as_float(), Some(1.5));
894 assert_eq!(SqliteValue::from("hello").as_text(), Some("hello"));
895 assert_eq!(
896 SqliteValue::from(String::from("world")).as_text(),
897 Some("world")
898 );
899 assert_eq!(SqliteValue::from(vec![1u8, 2]).as_blob(), Some(&[1, 2][..]));
900 assert!(SqliteValue::from(None::<i64>).is_null());
901 assert_eq!(SqliteValue::from(Some(42i64)).as_integer(), Some(42));
902 }
903
904 #[test]
905 fn affinity() {
906 assert_eq!(SqliteValue::Null.affinity(), TypeAffinity::Blob);
907 assert_eq!(SqliteValue::Integer(0).affinity(), TypeAffinity::Integer);
908 assert_eq!(SqliteValue::Float(0.0).affinity(), TypeAffinity::Real);
909 assert_eq!(
910 SqliteValue::Text(String::new()).affinity(),
911 TypeAffinity::Text
912 );
913 assert_eq!(SqliteValue::Blob(vec![]).affinity(), TypeAffinity::Blob);
914 }
915
916 #[test]
917 fn null_equality() {
918 let a = SqliteValue::Null;
920 let b = SqliteValue::Null;
921 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
922 }
923
924 #[test]
927 fn test_storage_class_variants() {
928 assert_eq!(SqliteValue::Null.storage_class(), StorageClass::Null);
929 assert_eq!(
930 SqliteValue::Integer(42).storage_class(),
931 StorageClass::Integer
932 );
933 assert_eq!(SqliteValue::Float(3.14).storage_class(), StorageClass::Real);
934 assert_eq!(
935 SqliteValue::Text("hi".into()).storage_class(),
936 StorageClass::Text
937 );
938 assert_eq!(
939 SqliteValue::Blob(vec![1]).storage_class(),
940 StorageClass::Blob
941 );
942 }
943
944 #[test]
945 fn test_type_affinity_advisory_text_into_integer_ok() {
946 let val = SqliteValue::Text("hello".into());
949 let coerced = val.apply_affinity(TypeAffinity::Integer);
950 assert!(coerced.as_text().is_some());
951 assert_eq!(coerced.as_text().unwrap(), "hello");
952
953 let val = SqliteValue::Text("42".into());
955 let coerced = val.apply_affinity(TypeAffinity::Integer);
956 assert_eq!(coerced.as_integer(), Some(42));
957 }
958
959 #[test]
960 fn test_type_affinity_advisory_integer_into_text_ok() {
961 let val = SqliteValue::Integer(42);
963 let coerced = val.apply_affinity(TypeAffinity::Text);
964 assert_eq!(coerced.as_text(), Some("42"));
965 }
966
967 #[test]
968 fn test_type_affinity_comparison_coercion_matches_oracle() {
969 let val = SqliteValue::Text("123".into());
971 let coerced = val.apply_affinity(TypeAffinity::Numeric);
972 assert_eq!(coerced.as_integer(), Some(123));
973
974 let val = SqliteValue::Text("3.14".into());
976 let coerced = val.apply_affinity(TypeAffinity::Numeric);
977 assert_eq!(coerced.as_float(), Some(3.14));
978
979 let val = SqliteValue::Text("hello".into());
981 let coerced = val.apply_affinity(TypeAffinity::Numeric);
982 assert!(coerced.as_text().is_some());
983
984 let val = SqliteValue::Integer(42);
986 let coerced = val.apply_affinity(TypeAffinity::Blob);
987 assert_eq!(coerced.as_integer(), Some(42));
988
989 let val = SqliteValue::Float(5.0);
991 let coerced = val.apply_affinity(TypeAffinity::Integer);
992 assert_eq!(coerced.as_integer(), Some(5));
993
994 let val = SqliteValue::Float(5.5);
996 let coerced = val.apply_affinity(TypeAffinity::Integer);
997 assert_eq!(coerced.as_float(), Some(5.5));
998
999 let val = SqliteValue::Integer(7);
1001 let coerced = val.apply_affinity(TypeAffinity::Real);
1002 assert_eq!(coerced.as_float(), Some(7.0));
1003
1004 let val = SqliteValue::Text("9".into());
1006 let coerced = val.apply_affinity(TypeAffinity::Real);
1007 assert_eq!(coerced.as_float(), Some(9.0));
1008 }
1009
1010 #[test]
1011 fn test_strict_table_rejects_text_into_integer() {
1012 let val = SqliteValue::Text("hello".into());
1013 let result = val.validate_strict(StrictColumnType::Integer);
1014 assert!(result.is_err());
1015 let err = result.unwrap_err();
1016 assert_eq!(err.expected, StrictColumnType::Integer);
1017 assert_eq!(err.actual, StorageClass::Text);
1018 }
1019
1020 #[test]
1021 fn test_strict_table_allows_exact_type() {
1022 let val = SqliteValue::Integer(42);
1024 assert!(val.validate_strict(StrictColumnType::Integer).is_ok());
1025
1026 let val = SqliteValue::Float(3.14);
1028 assert!(val.validate_strict(StrictColumnType::Real).is_ok());
1029
1030 let val = SqliteValue::Text("hello".into());
1032 assert!(val.validate_strict(StrictColumnType::Text).is_ok());
1033
1034 let val = SqliteValue::Blob(vec![1, 2, 3]);
1036 assert!(val.validate_strict(StrictColumnType::Blob).is_ok());
1037
1038 assert!(
1040 SqliteValue::Null
1041 .validate_strict(StrictColumnType::Integer)
1042 .is_ok()
1043 );
1044 assert!(
1045 SqliteValue::Null
1046 .validate_strict(StrictColumnType::Text)
1047 .is_ok()
1048 );
1049
1050 let val = SqliteValue::Integer(42);
1052 assert!(val.validate_strict(StrictColumnType::Any).is_ok());
1053 let val = SqliteValue::Text("hi".into());
1054 assert!(val.validate_strict(StrictColumnType::Any).is_ok());
1055 }
1056
1057 #[test]
1058 fn test_strict_real_accepts_integer_with_coercion() {
1059 let val = SqliteValue::Integer(42);
1061 let result = val.validate_strict(StrictColumnType::Real).unwrap();
1062 assert_eq!(result.as_float(), Some(42.0));
1063 }
1064
1065 #[test]
1066 fn test_strict_rejects_wrong_storage_classes() {
1067 assert!(
1069 SqliteValue::Float(3.14)
1070 .validate_strict(StrictColumnType::Integer)
1071 .is_err()
1072 );
1073
1074 assert!(
1076 SqliteValue::Blob(vec![1])
1077 .validate_strict(StrictColumnType::Text)
1078 .is_err()
1079 );
1080
1081 assert!(
1083 SqliteValue::Integer(1)
1084 .validate_strict(StrictColumnType::Text)
1085 .is_err()
1086 );
1087
1088 assert!(
1090 SqliteValue::Text("x".into())
1091 .validate_strict(StrictColumnType::Blob)
1092 .is_err()
1093 );
1094 }
1095
1096 #[test]
1097 fn test_strict_column_type_parsing() {
1098 assert_eq!(
1099 StrictColumnType::from_type_name("INT"),
1100 Some(StrictColumnType::Integer)
1101 );
1102 assert_eq!(
1103 StrictColumnType::from_type_name("INTEGER"),
1104 Some(StrictColumnType::Integer)
1105 );
1106 assert_eq!(
1107 StrictColumnType::from_type_name("REAL"),
1108 Some(StrictColumnType::Real)
1109 );
1110 assert_eq!(
1111 StrictColumnType::from_type_name("TEXT"),
1112 Some(StrictColumnType::Text)
1113 );
1114 assert_eq!(
1115 StrictColumnType::from_type_name("BLOB"),
1116 Some(StrictColumnType::Blob)
1117 );
1118 assert_eq!(
1119 StrictColumnType::from_type_name("ANY"),
1120 Some(StrictColumnType::Any)
1121 );
1122 assert_eq!(StrictColumnType::from_type_name("VARCHAR(255)"), None);
1124 assert_eq!(StrictColumnType::from_type_name("NUMERIC"), None);
1125 }
1126
1127 #[test]
1128 fn test_affinity_advisory_never_rejects() {
1129 let values = vec![
1131 SqliteValue::Null,
1132 SqliteValue::Integer(42),
1133 SqliteValue::Float(3.14),
1134 SqliteValue::Text("hello".into()),
1135 SqliteValue::Blob(vec![0xDE, 0xAD]),
1136 ];
1137 let affinities = [
1138 TypeAffinity::Integer,
1139 TypeAffinity::Text,
1140 TypeAffinity::Blob,
1141 TypeAffinity::Real,
1142 TypeAffinity::Numeric,
1143 ];
1144 for val in &values {
1145 for aff in &affinities {
1146 let _ = val.clone().apply_affinity(*aff);
1148 }
1149 }
1150 }
1151
1152 #[test]
1155 fn test_unique_allows_multiple_nulls_single_column() {
1156 let a = SqliteValue::Null;
1158 let b = SqliteValue::Null;
1159 assert!(!a.unique_eq(&b));
1160 }
1161
1162 #[test]
1163 fn test_unique_allows_multiple_nulls_multi_column_partial_null() {
1164 let row_a = [SqliteValue::Null, SqliteValue::Integer(1)];
1167 let row_b = [SqliteValue::Null, SqliteValue::Integer(1)];
1168 assert!(!unique_key_duplicates(&row_a, &row_b));
1169
1170 let row_a = [SqliteValue::Integer(1), SqliteValue::Null];
1172 let row_b = [SqliteValue::Integer(1), SqliteValue::Null];
1173 assert!(!unique_key_duplicates(&row_a, &row_b));
1174
1175 let row_a = [SqliteValue::Null, SqliteValue::Null];
1177 let row_b = [SqliteValue::Null, SqliteValue::Null];
1178 assert!(!unique_key_duplicates(&row_a, &row_b));
1179 }
1180
1181 #[test]
1182 fn test_unique_rejects_duplicate_non_null() {
1183 let a = SqliteValue::Integer(42);
1185 let b = SqliteValue::Integer(42);
1186 assert!(a.unique_eq(&b));
1187
1188 let row_a = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1190 let row_b = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1191 assert!(unique_key_duplicates(&row_a, &row_b));
1192
1193 let row_a = [SqliteValue::Integer(1), SqliteValue::Text("hello".into())];
1195 let row_b = [SqliteValue::Integer(1), SqliteValue::Text("world".into())];
1196 assert!(!unique_key_duplicates(&row_a, &row_b));
1197 }
1198
1199 #[test]
1200 fn test_unique_null_vs_non_null_distinct() {
1201 let a = SqliteValue::Null;
1203 let b = SqliteValue::Integer(1);
1204 assert!(!a.unique_eq(&b));
1205 assert!(!b.unique_eq(&a));
1206
1207 let row_a = [SqliteValue::Null, SqliteValue::Integer(1)];
1209 let row_b = [SqliteValue::Integer(2), SqliteValue::Integer(1)];
1210 assert!(!unique_key_duplicates(&row_a, &row_b));
1211 }
1212
1213 #[test]
1216 #[allow(clippy::cast_precision_loss)]
1217 fn test_integer_overflow_promotes_real_expr_add() {
1218 let max = SqliteValue::Integer(i64::MAX);
1219 let one = SqliteValue::Integer(1);
1220 let result = max.sql_add(&one);
1221 assert!(result.as_integer().is_none());
1223 assert!(result.as_float().is_some());
1224 assert!(result.as_float().unwrap() >= i64::MAX as f64);
1226 }
1227
1228 #[test]
1229 fn test_integer_overflow_promotes_real_expr_mul() {
1230 let max = SqliteValue::Integer(i64::MAX);
1231 let two = SqliteValue::Integer(2);
1232 let result = max.sql_mul(&two);
1233 assert!(result.as_float().is_some());
1235 }
1236
1237 #[test]
1238 fn test_integer_overflow_promotes_real_expr_sub() {
1239 let min = SqliteValue::Integer(i64::MIN);
1240 let one = SqliteValue::Integer(1);
1241 let result = min.sql_sub(&one);
1242 assert!(result.as_float().is_some());
1244 }
1245
1246 #[test]
1247 fn test_sum_overflow_errors() {
1248 let mut acc = SumAccumulator::new();
1249 acc.accumulate(&SqliteValue::Integer(i64::MAX));
1250 acc.accumulate(&SqliteValue::Integer(1));
1251 let result = acc.finish();
1252 assert!(result.is_err());
1253 }
1254
1255 #[test]
1256 fn test_no_overflow_stays_integer() {
1257 let a = SqliteValue::Integer(100);
1259 let b = SqliteValue::Integer(200);
1260 let result = a.sql_add(&b);
1261 assert_eq!(result.as_integer(), Some(300));
1262
1263 let result = SqliteValue::Integer(7).sql_mul(&SqliteValue::Integer(6));
1265 assert_eq!(result.as_integer(), Some(42));
1266
1267 let result = SqliteValue::Integer(50).sql_sub(&SqliteValue::Integer(8));
1269 assert_eq!(result.as_integer(), Some(42));
1270 }
1271
1272 #[test]
1273 fn test_sum_null_only_returns_null() {
1274 let mut acc = SumAccumulator::new();
1275 acc.accumulate(&SqliteValue::Null);
1276 acc.accumulate(&SqliteValue::Null);
1277 let result = acc.finish().unwrap();
1278 assert!(result.is_null());
1279 }
1280
1281 #[test]
1282 fn test_sum_mixed_int_float() {
1283 let mut acc = SumAccumulator::new();
1284 acc.accumulate(&SqliteValue::Integer(10));
1285 acc.accumulate(&SqliteValue::Float(2.5));
1286 acc.accumulate(&SqliteValue::Integer(3));
1287 let result = acc.finish().unwrap();
1288 assert_eq!(result.as_float(), Some(15.5));
1290 }
1291
1292 #[test]
1293 fn test_sum_integer_only() {
1294 let mut acc = SumAccumulator::new();
1295 acc.accumulate(&SqliteValue::Integer(10));
1296 acc.accumulate(&SqliteValue::Integer(20));
1297 acc.accumulate(&SqliteValue::Integer(30));
1298 let result = acc.finish().unwrap();
1299 assert_eq!(result.as_integer(), Some(60));
1300 }
1301
1302 #[test]
1303 fn test_sql_arithmetic_null_propagation() {
1304 let n = SqliteValue::Null;
1305 let i = SqliteValue::Integer(42);
1306 assert!(n.sql_add(&i).is_null());
1307 assert!(i.sql_add(&n).is_null());
1308 assert!(n.sql_sub(&i).is_null());
1309 assert!(n.sql_mul(&i).is_null());
1310 }
1311
1312 #[test]
1313 fn test_sql_inf_arithmetic_nan_normalized_to_null() {
1314 let pos_inf = SqliteValue::Float(f64::INFINITY);
1316 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1317 assert!(pos_inf.sql_add(&neg_inf).is_null());
1318
1319 assert!(pos_inf.sql_sub(&pos_inf).is_null());
1321 }
1322
1323 #[test]
1324 fn test_sql_mul_zero_times_inf_normalized_to_null() {
1325 let zero = SqliteValue::Float(0.0);
1327 let pos_inf = SqliteValue::Float(f64::INFINITY);
1328 assert!(zero.sql_mul(&pos_inf).is_null());
1329 }
1330
1331 #[test]
1332 fn test_sql_inf_propagates_when_not_nan() {
1333 let pos_inf = SqliteValue::Float(f64::INFINITY);
1334 let one = SqliteValue::Integer(1);
1335 let add_result = pos_inf.sql_add(&one);
1336 assert!(
1337 matches!(add_result, SqliteValue::Float(v) if v.is_infinite() && v.is_sign_positive()),
1338 "expected +Inf propagation, got {add_result:?}"
1339 );
1340
1341 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1342 let sub_result = neg_inf.sql_sub(&one);
1343 assert!(
1344 matches!(sub_result, SqliteValue::Float(v) if v.is_infinite() && v.is_sign_negative()),
1345 "expected -Inf propagation, got {sub_result:?}"
1346 );
1347 }
1348
1349 #[test]
1350 fn test_from_f64_nan_normalizes_to_null() {
1351 let value = SqliteValue::from(f64::NAN);
1352 assert!(value.is_null());
1353 }
1354
1355 #[test]
1356 fn test_inf_comparisons_against_finite_values() {
1357 let pos_inf = SqliteValue::Float(f64::INFINITY);
1358 let neg_inf = SqliteValue::Float(f64::NEG_INFINITY);
1359 let finite_hi = SqliteValue::Float(1.0e308);
1360 let finite_lo = SqliteValue::Float(-1.0e308);
1361
1362 assert_eq!(pos_inf.partial_cmp(&finite_hi), Some(Ordering::Greater));
1363 assert_eq!(neg_inf.partial_cmp(&finite_lo), Some(Ordering::Less));
1364 }
1365
1366 #[test]
1369 fn test_empty_string_is_not_null() {
1370 let empty = SqliteValue::Text(String::new());
1371 assert!(!empty.is_null());
1373 assert!(!empty.is_null());
1375 assert!(SqliteValue::Null.is_null());
1377 }
1378
1379 #[test]
1380 fn test_length_empty_string_zero() {
1381 let empty = SqliteValue::Text(String::new());
1382 assert_eq!(empty.sql_length(), Some(0));
1383 }
1384
1385 #[test]
1386 fn test_typeof_empty_string_text() {
1387 let empty = SqliteValue::Text(String::new());
1388 assert_eq!(empty.typeof_str(), "text");
1389 assert_eq!(SqliteValue::Null.typeof_str(), "null");
1391 }
1392
1393 #[test]
1394 fn test_empty_string_comparisons() {
1395 let empty1 = SqliteValue::Text(String::new());
1396 let empty2 = SqliteValue::Text(String::new());
1397 assert_eq!(empty1.partial_cmp(&empty2), Some(std::cmp::Ordering::Equal));
1399
1400 let null = SqliteValue::Null;
1404 assert_ne!(empty1.partial_cmp(&null), Some(std::cmp::Ordering::Equal));
1405 }
1406
1407 #[test]
1408 fn test_typeof_all_variants() {
1409 assert_eq!(SqliteValue::Null.typeof_str(), "null");
1410 assert_eq!(SqliteValue::Integer(0).typeof_str(), "integer");
1411 assert_eq!(SqliteValue::Float(0.0).typeof_str(), "real");
1412 assert_eq!(SqliteValue::Text("x".into()).typeof_str(), "text");
1413 assert_eq!(SqliteValue::Blob(vec![]).typeof_str(), "blob");
1414 }
1415
1416 #[test]
1417 fn test_sql_length_all_types() {
1418 assert_eq!(SqliteValue::Null.sql_length(), None);
1420 assert_eq!(SqliteValue::Text("hello".into()).sql_length(), Some(5));
1422 assert_eq!(SqliteValue::Text(String::new()).sql_length(), Some(0));
1423 assert_eq!(SqliteValue::Blob(vec![1, 2, 3]).sql_length(), Some(3));
1425 assert_eq!(SqliteValue::Integer(42).sql_length(), Some(2));
1427 assert_eq!(SqliteValue::Float(3.14).sql_length(), Some(4)); }
1430
1431 #[test]
1434 fn test_like_ascii_case_insensitive() {
1435 assert!(sql_like("A", "a", None));
1436 assert!(sql_like("a", "A", None));
1437 assert!(sql_like("hello", "HELLO", None));
1438 assert!(sql_like("HELLO", "hello", None));
1439 assert!(sql_like("HeLLo", "hEllO", None));
1440 }
1441
1442 #[test]
1443 fn test_like_unicode_case_sensitive_without_icu() {
1444 assert!(!sql_like("ä", "Ä", None));
1446 assert!(!sql_like("Ä", "ä", None));
1447 assert!(sql_like("ä", "ä", None));
1449 }
1450
1451 #[test]
1452 fn test_like_escape_handling() {
1453 assert!(sql_like("100\\%", "100%", Some('\\')));
1455 assert!(!sql_like("100\\%", "100x", Some('\\')));
1456
1457 assert!(sql_like("a\\_b", "a_b", Some('\\')));
1459 assert!(!sql_like("a\\_b", "axb", Some('\\')));
1460 }
1461
1462 #[test]
1463 fn test_like_wildcards_basic() {
1464 assert!(sql_like("%", "", None));
1466 assert!(sql_like("%", "anything", None));
1467 assert!(sql_like("a%", "abc", None));
1468 assert!(sql_like("%c", "abc", None));
1469 assert!(sql_like("a%c", "abc", None));
1470 assert!(sql_like("a%c", "aXYZc", None));
1471 assert!(!sql_like("a%c", "abd", None));
1472
1473 assert!(sql_like("_", "x", None));
1475 assert!(!sql_like("_", "", None));
1476 assert!(!sql_like("_", "xy", None));
1477 assert!(sql_like("a_c", "abc", None));
1478 assert!(!sql_like("a_c", "abbc", None));
1479 }
1480
1481 #[test]
1482 fn test_like_combined_wildcards() {
1483 assert!(sql_like("%_", "a", None));
1484 assert!(!sql_like("%_", "", None));
1485 assert!(sql_like("_%_", "ab", None));
1486 assert!(!sql_like("_%_", "a", None));
1487 assert!(sql_like("%a%b%", "xaybz", None));
1488 assert!(!sql_like("%a%b%", "xyz", None));
1489 }
1490
1491 #[test]
1492 fn test_like_exact_match() {
1493 assert!(sql_like("hello", "hello", None));
1494 assert!(!sql_like("hello", "world", None));
1495 assert!(sql_like("", "", None));
1496 assert!(!sql_like("a", "", None));
1497 assert!(!sql_like("", "a", None));
1498 }
1499}