Skip to main content

sqlmodel_core/
value.rs

1//! Dynamic SQL values.
2
3use serde::{Deserialize, Serialize};
4
5/// A dynamically-typed SQL value.
6///
7/// This enum represents all possible SQL values and is used
8/// for parameter binding and result fetching.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum Value {
11    /// NULL value
12    Null,
13
14    /// Boolean value
15    Bool(bool),
16
17    /// 8-bit signed integer
18    TinyInt(i8),
19
20    /// 16-bit signed integer
21    SmallInt(i16),
22
23    /// 32-bit signed integer
24    Int(i32),
25
26    /// 64-bit signed integer
27    BigInt(i64),
28
29    /// 32-bit floating point
30    Float(f32),
31
32    /// 64-bit floating point
33    Double(f64),
34
35    /// Arbitrary precision decimal (stored as string)
36    Decimal(String),
37
38    /// Text string
39    Text(String),
40
41    /// Binary data
42    Bytes(Vec<u8>),
43
44    /// Date (days since epoch)
45    Date(i32),
46
47    /// Time (microseconds since midnight)
48    Time(i64),
49
50    /// Timestamp (microseconds since epoch)
51    Timestamp(i64),
52
53    /// Timestamp with timezone (microseconds since epoch, UTC)
54    TimestampTz(i64),
55
56    /// UUID (as 16 bytes)
57    Uuid([u8; 16]),
58
59    /// JSON value
60    Json(serde_json::Value),
61
62    /// Array of values
63    Array(Vec<Value>),
64
65    /// SQL DEFAULT keyword
66    Default,
67}
68
69impl Value {
70    /// Check if this value is NULL.
71    pub const fn is_null(&self) -> bool {
72        matches!(self, Value::Null)
73    }
74
75    /// Get the type name of this value.
76    pub const fn type_name(&self) -> &'static str {
77        match self {
78            Value::Null => "NULL",
79            Value::Bool(_) => "BOOLEAN",
80            Value::TinyInt(_) => "TINYINT",
81            Value::SmallInt(_) => "SMALLINT",
82            Value::Int(_) => "INTEGER",
83            Value::BigInt(_) => "BIGINT",
84            Value::Float(_) => "REAL",
85            Value::Double(_) => "DOUBLE",
86            Value::Decimal(_) => "DECIMAL",
87            Value::Text(_) => "TEXT",
88            Value::Bytes(_) => "BLOB",
89            Value::Date(_) => "DATE",
90            Value::Time(_) => "TIME",
91            Value::Timestamp(_) => "TIMESTAMP",
92            Value::TimestampTz(_) => "TIMESTAMPTZ",
93            Value::Uuid(_) => "UUID",
94            Value::Json(_) => "JSON",
95            Value::Array(_) => "ARRAY",
96            Value::Default => "DEFAULT",
97        }
98    }
99
100    /// Try to convert this value to a bool.
101    pub fn as_bool(&self) -> Option<bool> {
102        match self {
103            Value::Bool(v) => Some(*v),
104            Value::TinyInt(v) => Some(*v != 0),
105            Value::SmallInt(v) => Some(*v != 0),
106            Value::Int(v) => Some(*v != 0),
107            Value::BigInt(v) => Some(*v != 0),
108            _ => None,
109        }
110    }
111
112    /// Try to convert this value to an i64.
113    pub fn as_i64(&self) -> Option<i64> {
114        match self {
115            Value::TinyInt(v) => Some(i64::from(*v)),
116            Value::SmallInt(v) => Some(i64::from(*v)),
117            Value::Int(v) => Some(i64::from(*v)),
118            Value::BigInt(v) => Some(*v),
119            Value::Bool(v) => Some(if *v { 1 } else { 0 }),
120            _ => None,
121        }
122    }
123
124    /// Try to convert this value to an f64.
125    pub fn as_f64(&self) -> Option<f64> {
126        match self {
127            Value::Float(v) => Some(f64::from(*v)),
128            Value::Double(v) => Some(*v),
129            Value::TinyInt(v) => Some(f64::from(*v)),
130            Value::SmallInt(v) => Some(f64::from(*v)),
131            Value::Int(v) => Some(f64::from(*v)),
132            Value::BigInt(v) => Some(*v as f64),
133            Value::Decimal(s) => s.parse().ok(),
134            _ => None,
135        }
136    }
137
138    /// Try to get this value as a string reference.
139    pub fn as_str(&self) -> Option<&str> {
140        match self {
141            Value::Text(s) => Some(s),
142            Value::Decimal(s) => Some(s),
143            _ => None,
144        }
145    }
146
147    /// Try to get this value as a byte slice.
148    pub fn as_bytes(&self) -> Option<&[u8]> {
149        match self {
150            Value::Bytes(b) => Some(b),
151            Value::Text(s) => Some(s.as_bytes()),
152            _ => None,
153        }
154    }
155
156    /// Convert a `u64` to `Value`, clamping to `i64::MAX` if it overflows.
157    ///
158    /// This is a convenience method for cases where you want to store large `u64`
159    /// values as the largest representable signed integer rather than erroring.
160    /// A warning is logged when clamping occurs.
161    ///
162    /// For strict conversion that errors on overflow, use `Value::try_from(u64)`.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use sqlmodel_core::Value;
168    ///
169    /// // Small values convert normally
170    /// assert_eq!(Value::from_u64_clamped(42), Value::BigInt(42));
171    ///
172    /// // Large values are clamped to i64::MAX
173    /// assert_eq!(Value::from_u64_clamped(u64::MAX), Value::BigInt(i64::MAX));
174    /// ```
175    #[must_use]
176    pub fn from_u64_clamped(v: u64) -> Self {
177        if let Ok(signed) = i64::try_from(v) {
178            Value::BigInt(signed)
179        } else {
180            tracing::warn!(
181                value = v,
182                clamped_to = i64::MAX,
183                "u64 value exceeds i64::MAX; clamping to i64::MAX"
184            );
185            Value::BigInt(i64::MAX)
186        }
187    }
188
189    /// Convert to f32, allowing precision loss for large values.
190    ///
191    /// This is more lenient than `TryFrom<Value> for f32`, which errors on precision loss.
192    /// Only returns an error for values that cannot be represented at all (infinity, NaN).
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// use sqlmodel_core::Value;
198    ///
199    /// // Normal values work
200    /// assert!(Value::Double(1.5).to_f32_lossy().is_ok());
201    ///
202    /// // Large integers are converted with precision loss (no error)
203    /// assert!(Value::BigInt(i64::MAX).to_f32_lossy().is_ok());
204    /// ```
205    #[allow(clippy::cast_possible_truncation, clippy::result_large_err)]
206    pub fn to_f32_lossy(&self) -> crate::Result<f32> {
207        match self {
208            Value::Float(v) => Ok(*v),
209            Value::Double(v) => {
210                let converted = *v as f32;
211                if converted.is_infinite() && !v.is_infinite() {
212                    return Err(Error::Type(TypeError {
213                        expected: "f32-representable value",
214                        actual: format!("f64 value {} overflows f32", v),
215                        column: None,
216                        rust_type: Some("f32"),
217                    }));
218                }
219                Ok(converted)
220            }
221            Value::TinyInt(v) => Ok(f32::from(*v)),
222            Value::SmallInt(v) => Ok(f32::from(*v)),
223            Value::Int(v) => Ok(*v as f32),
224            Value::BigInt(v) => Ok(*v as f32),
225            Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
226            other => Err(Error::Type(TypeError {
227                expected: "numeric value",
228                actual: other.type_name().to_string(),
229                column: None,
230                rust_type: Some("f32"),
231            })),
232        }
233    }
234
235    /// Convert to f64, allowing precision loss for very large integers.
236    ///
237    /// This is more lenient than `TryFrom<Value> for f64`, which errors on precision loss.
238    /// Only returns an error for non-numeric values.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use sqlmodel_core::Value;
244    ///
245    /// // Normal values work
246    /// assert!(Value::BigInt(42).to_f64_lossy().is_ok());
247    ///
248    /// // Large integers are converted with precision loss (no error)
249    /// assert!(Value::BigInt(i64::MAX).to_f64_lossy().is_ok());
250    /// ```
251    #[allow(clippy::cast_precision_loss, clippy::result_large_err)]
252    pub fn to_f64_lossy(&self) -> crate::Result<f64> {
253        match self {
254            Value::Float(v) => Ok(f64::from(*v)),
255            Value::Double(v) => Ok(*v),
256            Value::TinyInt(v) => Ok(f64::from(*v)),
257            Value::SmallInt(v) => Ok(f64::from(*v)),
258            Value::Int(v) => Ok(f64::from(*v)),
259            Value::BigInt(v) => Ok(*v as f64), // May lose precision for |v| > 2^53
260            Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
261            other => Err(Error::Type(TypeError {
262                expected: "numeric value",
263                actual: other.type_name().to_string(),
264                column: None,
265                rust_type: Some("f64"),
266            })),
267        }
268    }
269}
270
271// Conversion implementations
272impl From<bool> for Value {
273    fn from(v: bool) -> Self {
274        Value::Bool(v)
275    }
276}
277
278impl From<i8> for Value {
279    fn from(v: i8) -> Self {
280        Value::TinyInt(v)
281    }
282}
283
284impl From<i16> for Value {
285    fn from(v: i16) -> Self {
286        Value::SmallInt(v)
287    }
288}
289
290impl From<i32> for Value {
291    fn from(v: i32) -> Self {
292        Value::Int(v)
293    }
294}
295
296impl From<i64> for Value {
297    fn from(v: i64) -> Self {
298        Value::BigInt(v)
299    }
300}
301
302impl From<f32> for Value {
303    fn from(v: f32) -> Self {
304        Value::Float(v)
305    }
306}
307
308impl From<f64> for Value {
309    fn from(v: f64) -> Self {
310        Value::Double(v)
311    }
312}
313
314impl From<String> for Value {
315    fn from(v: String) -> Self {
316        Value::Text(v)
317    }
318}
319
320impl From<&str> for Value {
321    fn from(v: &str) -> Self {
322        Value::Text(v.to_string())
323    }
324}
325
326impl From<Vec<u8>> for Value {
327    fn from(v: Vec<u8>) -> Self {
328        Value::Bytes(v)
329    }
330}
331
332impl<T: Into<Value>> From<Option<T>> for Value {
333    fn from(v: Option<T>) -> Self {
334        match v {
335            Some(v) => v.into(),
336            None => Value::Null,
337        }
338    }
339}
340
341impl From<&[u8]> for Value {
342    fn from(v: &[u8]) -> Self {
343        Value::Bytes(v.to_vec())
344    }
345}
346
347impl From<u8> for Value {
348    fn from(v: u8) -> Self {
349        Value::SmallInt(i16::from(v))
350    }
351}
352
353impl From<u16> for Value {
354    fn from(v: u16) -> Self {
355        Value::Int(i32::from(v))
356    }
357}
358
359impl From<u32> for Value {
360    fn from(v: u32) -> Self {
361        Value::BigInt(i64::from(v))
362    }
363}
364
365/// Convert a `u64` to `Value`, returning an error if the value exceeds `i64::MAX`.
366///
367/// SQL BIGINT is signed, so values larger than `i64::MAX` cannot be stored directly.
368/// Use `Value::from_u64_clamped()` if you want silent clamping instead of an error.
369impl TryFrom<u64> for Value {
370    type Error = Error;
371
372    fn try_from(v: u64) -> Result<Self, Self::Error> {
373        i64::try_from(v).map(Value::BigInt).map_err(|_| {
374            Error::Type(TypeError {
375                expected: "u64 <= i64::MAX",
376                actual: format!("u64 value {} exceeds i64::MAX ({})", v, i64::MAX),
377                column: None,
378                rust_type: Some("u64"),
379            })
380        })
381    }
382}
383
384impl From<serde_json::Value> for Value {
385    fn from(v: serde_json::Value) -> Self {
386        Value::Json(v)
387    }
388}
389
390impl From<[u8; 16]> for Value {
391    fn from(v: [u8; 16]) -> Self {
392        Value::Uuid(v)
393    }
394}
395
396/// Convert a `Vec<String>` into a `Value::Array`.
397impl From<Vec<String>> for Value {
398    fn from(v: Vec<String>) -> Self {
399        Value::Array(v.into_iter().map(Value::Text).collect())
400    }
401}
402
403/// Convert a `Vec<i32>` into a `Value::Array`.
404impl From<Vec<i32>> for Value {
405    fn from(v: Vec<i32>) -> Self {
406        Value::Array(v.into_iter().map(Value::Int).collect())
407    }
408}
409
410/// Convert a `Vec<i64>` into a `Value::Array`.
411impl From<Vec<i64>> for Value {
412    fn from(v: Vec<i64>) -> Self {
413        Value::Array(v.into_iter().map(Value::BigInt).collect())
414    }
415}
416
417/// Convert a `Vec<f64>` into a `Value::Array`.
418impl From<Vec<f64>> for Value {
419    fn from(v: Vec<f64>) -> Self {
420        Value::Array(v.into_iter().map(Value::Double).collect())
421    }
422}
423
424/// Convert a `Vec<bool>` into a `Value::Array`.
425impl From<Vec<bool>> for Value {
426    fn from(v: Vec<bool>) -> Self {
427        Value::Array(v.into_iter().map(Value::Bool).collect())
428    }
429}
430
431// TryFrom implementations for extracting values
432
433use crate::error::{Error, TypeError};
434
435impl TryFrom<Value> for bool {
436    type Error = Error;
437
438    fn try_from(value: Value) -> Result<Self, Self::Error> {
439        match value {
440            Value::Bool(v) => Ok(v),
441            Value::TinyInt(v) => Ok(v != 0),
442            Value::SmallInt(v) => Ok(v != 0),
443            Value::Int(v) => Ok(v != 0),
444            Value::BigInt(v) => Ok(v != 0),
445            other => Err(Error::Type(TypeError {
446                expected: "bool",
447                actual: other.type_name().to_string(),
448                column: None,
449                rust_type: None,
450            })),
451        }
452    }
453}
454
455impl TryFrom<Value> for i8 {
456    type Error = Error;
457
458    fn try_from(value: Value) -> Result<Self, Self::Error> {
459        match value {
460            Value::TinyInt(v) => Ok(v),
461            Value::Bool(v) => Ok(if v { 1 } else { 0 }),
462            other => Err(Error::Type(TypeError {
463                expected: "i8",
464                actual: other.type_name().to_string(),
465                column: None,
466                rust_type: None,
467            })),
468        }
469    }
470}
471
472impl TryFrom<Value> for i16 {
473    type Error = Error;
474
475    fn try_from(value: Value) -> Result<Self, Self::Error> {
476        match value {
477            Value::TinyInt(v) => Ok(i16::from(v)),
478            Value::SmallInt(v) => Ok(v),
479            Value::Bool(v) => Ok(if v { 1 } else { 0 }),
480            other => Err(Error::Type(TypeError {
481                expected: "i16",
482                actual: other.type_name().to_string(),
483                column: None,
484                rust_type: None,
485            })),
486        }
487    }
488}
489
490impl TryFrom<Value> for i32 {
491    type Error = Error;
492
493    fn try_from(value: Value) -> Result<Self, Self::Error> {
494        match value {
495            Value::TinyInt(v) => Ok(i32::from(v)),
496            Value::SmallInt(v) => Ok(i32::from(v)),
497            Value::Int(v) => Ok(v),
498            Value::Bool(v) => Ok(if v { 1 } else { 0 }),
499            other => Err(Error::Type(TypeError {
500                expected: "i32",
501                actual: other.type_name().to_string(),
502                column: None,
503                rust_type: None,
504            })),
505        }
506    }
507}
508
509impl TryFrom<Value> for i64 {
510    type Error = Error;
511
512    fn try_from(value: Value) -> Result<Self, Self::Error> {
513        match value {
514            Value::TinyInt(v) => Ok(i64::from(v)),
515            Value::SmallInt(v) => Ok(i64::from(v)),
516            Value::Int(v) => Ok(i64::from(v)),
517            Value::BigInt(v) => Ok(v),
518            Value::Bool(v) => Ok(if v { 1 } else { 0 }),
519            other => Err(Error::Type(TypeError {
520                expected: "i64",
521                actual: other.type_name().to_string(),
522                column: None,
523                rust_type: None,
524            })),
525        }
526    }
527}
528
529/// Maximum integer value exactly representable in f32: 2^24 = 16,777,216
530const F32_MAX_EXACT_INT: i64 = 1 << 24;
531
532impl TryFrom<Value> for f32 {
533    type Error = Error;
534
535    /// Convert a Value to f32, returning an error if precision would be lost.
536    ///
537    /// For lossy conversion (accepting precision loss), use `Value::to_f32_lossy()`.
538    fn try_from(value: Value) -> Result<Self, Self::Error> {
539        match value {
540            Value::Float(v) => Ok(v),
541            #[allow(clippy::cast_possible_truncation)]
542            Value::Double(v) => {
543                let converted = v as f32;
544                // Check round-trip: if converting back doesn't match, we lost precision
545                if (f64::from(converted) - v).abs() > f64::EPSILON * v.abs().max(1.0) {
546                    return Err(Error::Type(TypeError {
547                        expected: "f32-representable f64",
548                        actual: format!("f64 value {} loses precision as f32", v),
549                        column: None,
550                        rust_type: Some("f32"),
551                    }));
552                }
553                Ok(converted)
554            }
555            Value::TinyInt(v) => Ok(f32::from(v)),
556            Value::SmallInt(v) => Ok(f32::from(v)),
557            #[allow(clippy::cast_possible_truncation)]
558            Value::Int(v) => {
559                if i64::from(v).abs() > F32_MAX_EXACT_INT {
560                    return Err(Error::Type(TypeError {
561                        expected: "f32-representable i32",
562                        actual: format!(
563                            "i32 value {} exceeds f32 exact integer range (±{})",
564                            v, F32_MAX_EXACT_INT
565                        ),
566                        column: None,
567                        rust_type: Some("f32"),
568                    }));
569                }
570                Ok(v as f32)
571            }
572            #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
573            Value::BigInt(v) => {
574                // Use unsigned_abs to avoid overflow on i64::MIN
575                if v.unsigned_abs() > F32_MAX_EXACT_INT as u64 {
576                    return Err(Error::Type(TypeError {
577                        expected: "f32-representable i64",
578                        actual: format!(
579                            "i64 value {} exceeds f32 exact integer range (±{})",
580                            v, F32_MAX_EXACT_INT
581                        ),
582                        column: None,
583                        rust_type: Some("f32"),
584                    }));
585                }
586                Ok(v as f32)
587            }
588            // Bool to f32 is lossless (0.0 or 1.0)
589            Value::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
590            other => Err(Error::Type(TypeError {
591                expected: "f32",
592                actual: other.type_name().to_string(),
593                column: None,
594                rust_type: None,
595            })),
596        }
597    }
598}
599
600/// Maximum integer value exactly representable in f64: 2^53 = 9,007,199,254,740,992
601const F64_MAX_EXACT_INT: i64 = 1 << 53;
602
603impl TryFrom<Value> for f64 {
604    type Error = Error;
605
606    /// Convert a Value to f64, returning an error if precision would be lost.
607    ///
608    /// For lossy conversion (accepting precision loss), use `Value::to_f64_lossy()`.
609    fn try_from(value: Value) -> Result<Self, Self::Error> {
610        match value {
611            Value::Float(v) => Ok(f64::from(v)),
612            Value::Double(v) => Ok(v),
613            Value::TinyInt(v) => Ok(f64::from(v)),
614            Value::SmallInt(v) => Ok(f64::from(v)),
615            Value::Int(v) => Ok(f64::from(v)),
616            #[allow(clippy::cast_precision_loss)]
617            #[allow(clippy::cast_sign_loss)]
618            Value::BigInt(v) => {
619                // Use unsigned_abs to avoid overflow on i64::MIN
620                if v.unsigned_abs() > F64_MAX_EXACT_INT as u64 {
621                    return Err(Error::Type(TypeError {
622                        expected: "f64-representable i64",
623                        actual: format!(
624                            "i64 value {} exceeds f64 exact integer range (±{})",
625                            v, F64_MAX_EXACT_INT
626                        ),
627                        column: None,
628                        rust_type: Some("f64"),
629                    }));
630                }
631                Ok(v as f64)
632            }
633            // Bool to f64 is lossless (0.0 or 1.0)
634            Value::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
635            other => Err(Error::Type(TypeError {
636                expected: "f64",
637                actual: other.type_name().to_string(),
638                column: None,
639                rust_type: None,
640            })),
641        }
642    }
643}
644
645impl TryFrom<Value> for String {
646    type Error = Error;
647
648    fn try_from(value: Value) -> Result<Self, Self::Error> {
649        match value {
650            Value::Text(v) => Ok(v),
651            Value::Decimal(v) => Ok(v),
652            other => Err(Error::Type(TypeError {
653                expected: "String",
654                actual: other.type_name().to_string(),
655                column: None,
656                rust_type: None,
657            })),
658        }
659    }
660}
661
662impl TryFrom<Value> for Vec<u8> {
663    type Error = Error;
664
665    fn try_from(value: Value) -> Result<Self, Self::Error> {
666        match value {
667            Value::Bytes(v) => Ok(v),
668            Value::Text(v) => Ok(v.into_bytes()),
669            other => Err(Error::Type(TypeError {
670                expected: "Vec<u8>",
671                actual: other.type_name().to_string(),
672                column: None,
673                rust_type: None,
674            })),
675        }
676    }
677}
678
679impl TryFrom<Value> for serde_json::Value {
680    type Error = Error;
681
682    fn try_from(value: Value) -> Result<Self, Self::Error> {
683        match value {
684            Value::Json(v) => Ok(v),
685            Value::Text(s) => serde_json::from_str(&s).map_err(|e| {
686                Error::Type(TypeError {
687                    expected: "valid JSON",
688                    actual: format!("invalid JSON: {}", e),
689                    column: None,
690                    rust_type: None,
691                })
692            }),
693            other => Err(Error::Type(TypeError {
694                expected: "JSON",
695                actual: other.type_name().to_string(),
696                column: None,
697                rust_type: None,
698            })),
699        }
700    }
701}
702
703impl TryFrom<Value> for [u8; 16] {
704    type Error = Error;
705
706    fn try_from(value: Value) -> Result<Self, Self::Error> {
707        match value {
708            Value::Uuid(v) => Ok(v),
709            Value::Bytes(v) if v.len() == 16 => {
710                let mut arr = [0u8; 16];
711                arr.copy_from_slice(&v);
712                Ok(arr)
713            }
714            other => Err(Error::Type(TypeError {
715                expected: "UUID",
716                actual: other.type_name().to_string(),
717                column: None,
718                rust_type: None,
719            })),
720        }
721    }
722}
723
724/// TryFrom for `Option<T>` - returns None for Null, tries to convert otherwise
725impl<T> TryFrom<Value> for Option<T>
726where
727    T: TryFrom<Value, Error = Error>,
728{
729    type Error = Error;
730
731    fn try_from(value: Value) -> Result<Self, Self::Error> {
732        match value {
733            Value::Null => Ok(None),
734            v => T::try_from(v).map(Some),
735        }
736    }
737}
738
739/// TryFrom for `Vec<String>` - extracts text array.
740impl TryFrom<Value> for Vec<String> {
741    type Error = Error;
742
743    fn try_from(value: Value) -> Result<Self, Self::Error> {
744        match value {
745            Value::Array(arr) => arr.into_iter().map(String::try_from).collect(),
746            other => Err(Error::Type(TypeError {
747                expected: "ARRAY",
748                actual: other.type_name().to_string(),
749                column: None,
750                rust_type: None,
751            })),
752        }
753    }
754}
755
756/// TryFrom for `Vec<i32>` - extracts integer array.
757impl TryFrom<Value> for Vec<i32> {
758    type Error = Error;
759
760    fn try_from(value: Value) -> Result<Self, Self::Error> {
761        match value {
762            Value::Array(arr) => arr.into_iter().map(i32::try_from).collect(),
763            other => Err(Error::Type(TypeError {
764                expected: "ARRAY",
765                actual: other.type_name().to_string(),
766                column: None,
767                rust_type: None,
768            })),
769        }
770    }
771}
772
773/// TryFrom for `Vec<i64>` - extracts bigint array.
774impl TryFrom<Value> for Vec<i64> {
775    type Error = Error;
776
777    fn try_from(value: Value) -> Result<Self, Self::Error> {
778        match value {
779            Value::Array(arr) => arr.into_iter().map(i64::try_from).collect(),
780            other => Err(Error::Type(TypeError {
781                expected: "ARRAY",
782                actual: other.type_name().to_string(),
783                column: None,
784                rust_type: None,
785            })),
786        }
787    }
788}
789
790/// TryFrom for `Vec<bool>` - extracts boolean array.
791impl TryFrom<Value> for Vec<bool> {
792    type Error = Error;
793
794    fn try_from(value: Value) -> Result<Self, Self::Error> {
795        match value {
796            Value::Array(arr) => arr.into_iter().map(bool::try_from).collect(),
797            other => Err(Error::Type(TypeError {
798                expected: "ARRAY",
799                actual: other.type_name().to_string(),
800                column: None,
801                rust_type: None,
802            })),
803        }
804    }
805}
806
807impl TryFrom<Value> for Vec<f64> {
808    type Error = Error;
809
810    fn try_from(value: Value) -> Result<Self, Self::Error> {
811        match value {
812            Value::Array(arr) => arr.into_iter().map(f64::try_from).collect(),
813            other => Err(Error::Type(TypeError {
814                expected: "ARRAY",
815                actual: other.type_name().to_string(),
816                column: None,
817                rust_type: None,
818            })),
819        }
820    }
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826
827    #[test]
828    fn test_from_bool() {
829        let v: Value = true.into();
830        assert_eq!(v, Value::Bool(true));
831    }
832
833    #[test]
834    fn test_from_integers() {
835        assert_eq!(Value::from(42i8), Value::TinyInt(42));
836        assert_eq!(Value::from(42i16), Value::SmallInt(42));
837        assert_eq!(Value::from(42i32), Value::Int(42));
838        assert_eq!(Value::from(42i64), Value::BigInt(42));
839    }
840
841    #[test]
842    fn test_from_unsigned_integers() {
843        assert_eq!(Value::from(42u8), Value::SmallInt(42));
844        assert_eq!(Value::from(42u16), Value::Int(42));
845        assert_eq!(Value::from(42u32), Value::BigInt(42));
846        // u64 uses TryFrom, not From (see test_try_from_u64 and test_from_u64_clamped)
847    }
848
849    #[test]
850    fn test_from_floats() {
851        let pi_f32 = std::f32::consts::PI;
852        let pi_f64 = std::f64::consts::PI;
853        assert_eq!(Value::from(pi_f32), Value::Float(pi_f32));
854        assert_eq!(Value::from(pi_f64), Value::Double(pi_f64));
855    }
856
857    #[test]
858    fn test_from_strings() {
859        assert_eq!(Value::from("hello"), Value::Text("hello".to_string()));
860        assert_eq!(
861            Value::from("hello".to_string()),
862            Value::Text("hello".to_string())
863        );
864    }
865
866    #[test]
867    fn test_from_bytes() {
868        let bytes = vec![1u8, 2, 3];
869        assert_eq!(Value::from(bytes.clone()), Value::Bytes(bytes.clone()));
870        assert_eq!(Value::from(bytes.as_slice()), Value::Bytes(bytes));
871    }
872
873    #[test]
874    fn test_from_option() {
875        let some: Value = Some(42i32).into();
876        assert_eq!(some, Value::Int(42));
877
878        let none: Value = Option::<i32>::None.into();
879        assert_eq!(none, Value::Null);
880    }
881
882    #[test]
883    fn test_try_from_bool() {
884        assert!(bool::try_from(Value::Bool(true)).unwrap());
885        assert!(bool::try_from(Value::Int(1)).unwrap());
886        assert!(!bool::try_from(Value::Int(0)).unwrap());
887        assert!(bool::try_from(Value::Text("true".to_string())).is_err());
888    }
889
890    #[test]
891    fn test_try_from_i64() {
892        assert_eq!(i64::try_from(Value::BigInt(42)).unwrap(), 42);
893        assert_eq!(i64::try_from(Value::Int(42)).unwrap(), 42);
894        assert_eq!(i64::try_from(Value::SmallInt(42)).unwrap(), 42);
895        assert_eq!(i64::try_from(Value::TinyInt(42)).unwrap(), 42);
896        assert!(i64::try_from(Value::Text("42".to_string())).is_err());
897    }
898
899    #[test]
900    fn test_try_from_f64() {
901        let pi = std::f64::consts::PI;
902        let pi_f32 = std::f32::consts::PI;
903        let double = f64::try_from(Value::Double(pi)).unwrap();
904        assert!((double - pi).abs() < 1e-12);
905
906        let from_float = f64::try_from(Value::Float(pi_f32)).unwrap();
907        assert!((from_float - f64::from(pi_f32)).abs() < 1e-6);
908
909        let from_int = f64::try_from(Value::Int(42)).unwrap();
910        assert!((from_int - 42.0).abs() < 1e-12);
911        assert!(f64::try_from(Value::Text("3.14".to_string())).is_err());
912    }
913
914    #[test]
915    fn test_try_from_string() {
916        assert_eq!(
917            String::try_from(Value::Text("hello".to_string())).unwrap(),
918            "hello"
919        );
920        assert!(String::try_from(Value::Int(42)).is_err());
921    }
922
923    #[test]
924    fn test_try_from_bytes() {
925        let bytes = vec![1u8, 2, 3];
926        assert_eq!(
927            Vec::<u8>::try_from(Value::Bytes(bytes.clone())).unwrap(),
928            bytes
929        );
930        assert_eq!(
931            Vec::<u8>::try_from(Value::Text("abc".to_string())).unwrap(),
932            b"abc".to_vec()
933        );
934    }
935
936    #[test]
937    fn test_try_from_option() {
938        let result: Option<i32> = Option::try_from(Value::Int(42)).unwrap();
939        assert_eq!(result, Some(42));
940
941        let result: Option<i32> = Option::try_from(Value::Null).unwrap();
942        assert_eq!(result, None);
943    }
944
945    #[test]
946    fn test_round_trip_bool() {
947        let original = true;
948        let value: Value = original.into();
949        let recovered: bool = value.try_into().unwrap();
950        assert_eq!(original, recovered);
951    }
952
953    #[test]
954    fn test_round_trip_i64() {
955        let original: i64 = i64::MAX;
956        let value: Value = original.into();
957        let recovered: i64 = value.try_into().unwrap();
958        assert_eq!(original, recovered);
959    }
960
961    #[test]
962    fn test_round_trip_f64() {
963        let original: f64 = std::f64::consts::PI;
964        let value: Value = original.into();
965        let recovered: f64 = value.try_into().unwrap();
966        assert!((original - recovered).abs() < f64::EPSILON);
967    }
968
969    #[test]
970    fn test_round_trip_string() {
971        let original = "hello world".to_string();
972        let value: Value = original.clone().into();
973        let recovered: String = value.try_into().unwrap();
974        assert_eq!(original, recovered);
975    }
976
977    #[test]
978    fn test_round_trip_bytes() {
979        let original = vec![0u8, 127, 255];
980        let value: Value = original.clone().into();
981        let recovered: Vec<u8> = value.try_into().unwrap();
982        assert_eq!(original, recovered);
983    }
984
985    #[test]
986    fn test_is_null() {
987        assert!(Value::Null.is_null());
988        assert!(!Value::Int(0).is_null());
989        assert!(!Value::Bool(false).is_null());
990    }
991
992    #[test]
993    fn test_as_i64() {
994        assert_eq!(Value::BigInt(42).as_i64(), Some(42));
995        assert_eq!(Value::Int(42).as_i64(), Some(42));
996        assert_eq!(Value::Null.as_i64(), None);
997        assert_eq!(Value::Text("42".to_string()).as_i64(), None);
998    }
999
1000    #[test]
1001    fn test_as_str() {
1002        assert_eq!(Value::Text("hello".to_string()).as_str(), Some("hello"));
1003        assert_eq!(
1004            Value::Decimal("123.45".to_string()).as_str(),
1005            Some("123.45")
1006        );
1007        assert_eq!(Value::Int(42).as_str(), None);
1008    }
1009
1010    #[test]
1011    fn test_type_name() {
1012        assert_eq!(Value::Null.type_name(), "NULL");
1013        assert_eq!(Value::Bool(true).type_name(), "BOOLEAN");
1014        assert_eq!(Value::Int(42).type_name(), "INTEGER");
1015        assert_eq!(Value::Text(String::new()).type_name(), "TEXT");
1016    }
1017
1018    #[test]
1019    fn test_edge_cases() {
1020        // Empty string
1021        let value: Value = "".into();
1022        let recovered: String = value.try_into().unwrap();
1023        assert_eq!(recovered, "");
1024
1025        // Empty bytes
1026        let value: Value = Vec::<u8>::new().into();
1027        let recovered: Vec<u8> = value.try_into().unwrap();
1028        assert!(recovered.is_empty());
1029
1030        // Max values
1031        let value: Value = i64::MAX.into();
1032        let recovered: i64 = value.try_into().unwrap();
1033        assert_eq!(recovered, i64::MAX);
1034
1035        let value: Value = i64::MIN.into();
1036        let recovered: i64 = value.try_into().unwrap();
1037        assert_eq!(recovered, i64::MIN);
1038    }
1039
1040    #[test]
1041    fn test_array_string_roundtrip() {
1042        let v: Value = vec!["a".to_string(), "b".to_string()].into();
1043        assert_eq!(
1044            v,
1045            Value::Array(vec![
1046                Value::Text("a".to_string()),
1047                Value::Text("b".to_string())
1048            ])
1049        );
1050        let recovered: Vec<String> = v.try_into().unwrap();
1051        assert_eq!(recovered, vec!["a", "b"]);
1052    }
1053
1054    #[test]
1055    fn test_array_i32_roundtrip() {
1056        let v: Value = vec![1i32, 2, 3].into();
1057        assert_eq!(
1058            v,
1059            Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
1060        );
1061        let recovered: Vec<i32> = v.try_into().unwrap();
1062        assert_eq!(recovered, vec![1, 2, 3]);
1063    }
1064
1065    #[test]
1066    fn test_array_empty() {
1067        let v: Value = Vec::<String>::new().into();
1068        assert_eq!(v, Value::Array(vec![]));
1069        let recovered: Vec<String> = v.try_into().unwrap();
1070        assert!(recovered.is_empty());
1071    }
1072
1073    #[test]
1074    fn test_array_type_error() {
1075        let v = Value::Text("not an array".to_string());
1076        let result: Result<Vec<String>, _> = v.try_into();
1077        assert!(result.is_err());
1078    }
1079
1080    #[test]
1081    fn test_try_from_u64_success() {
1082        // Values within i64 range should succeed
1083        let v: Value = Value::try_from(42u64).unwrap();
1084        assert_eq!(v, Value::BigInt(42));
1085
1086        // Maximum valid value: i64::MAX
1087        let v: Value = Value::try_from(i64::MAX as u64).unwrap();
1088        assert_eq!(v, Value::BigInt(i64::MAX));
1089
1090        // Zero should work
1091        let v: Value = Value::try_from(0u64).unwrap();
1092        assert_eq!(v, Value::BigInt(0));
1093    }
1094
1095    #[test]
1096    fn test_try_from_u64_overflow_error() {
1097        // Values exceeding i64::MAX should error
1098        let result = Value::try_from(u64::MAX);
1099        assert!(result.is_err());
1100        let err = result.unwrap_err();
1101        assert!(matches!(err, Error::Type(_)));
1102
1103        // One more than i64::MAX should also error
1104        let result = Value::try_from((i64::MAX as u64) + 1);
1105        assert!(result.is_err());
1106    }
1107
1108    #[test]
1109    fn test_from_u64_clamped_normal() {
1110        // Normal values convert without clamping
1111        assert_eq!(Value::from_u64_clamped(0), Value::BigInt(0));
1112        assert_eq!(Value::from_u64_clamped(42), Value::BigInt(42));
1113        assert_eq!(
1114            Value::from_u64_clamped(i64::MAX as u64),
1115            Value::BigInt(i64::MAX)
1116        );
1117    }
1118
1119    #[test]
1120    fn test_from_u64_clamped_overflow() {
1121        // Values exceeding i64::MAX are clamped
1122        assert_eq!(Value::from_u64_clamped(u64::MAX), Value::BigInt(i64::MAX));
1123        assert_eq!(
1124            Value::from_u64_clamped((i64::MAX as u64) + 1),
1125            Value::BigInt(i64::MAX)
1126        );
1127    }
1128
1129    // ==================== Precision Loss Detection Tests ====================
1130
1131    const F32_MAX_EXACT: i64 = 1 << 24; // 16,777,216
1132    const F64_MAX_EXACT: i64 = 1 << 53; // 9,007,199,254,740,992
1133
1134    #[test]
1135    fn test_f32_from_double_precision_ok() {
1136        // Values exactly representable in f32
1137        let v: f32 = Value::Double(1.5).try_into().unwrap();
1138        assert!((v - 1.5).abs() < f32::EPSILON);
1139
1140        let v: f32 = Value::Double(0.0).try_into().unwrap();
1141        assert!((v - 0.0).abs() < f32::EPSILON);
1142    }
1143
1144    #[test]
1145    fn test_f32_from_double_precision_loss() {
1146        // f64 value that cannot be exactly represented in f32
1147        // 1e20 as f64 cannot round-trip through f32 exactly
1148        let high_precision = 1e20_f64;
1149        let result = f32::try_from(Value::Double(high_precision));
1150        assert!(result.is_err());
1151
1152        // A more subtle case: numbers with more precision than f32 mantissa can hold
1153        // f64 has 52 mantissa bits, f32 has 23, so values with >23 bits of precision lose info
1154        let precise_value = 16_777_217.0_f64; // 2^24 + 1, needs 25 bits, loses precision as f32
1155        let result2 = f32::try_from(Value::Double(precise_value));
1156        assert!(result2.is_err());
1157    }
1158
1159    #[test]
1160    #[allow(clippy::cast_possible_truncation)]
1161    fn test_f32_from_int_boundary() {
1162        // At boundary: exactly representable (F32_MAX_EXACT = 2^24 = 16,777,216 fits in i32)
1163        let boundary = F32_MAX_EXACT as i32;
1164        let v: f32 = Value::Int(boundary).try_into().unwrap();
1165        assert!((v - F32_MAX_EXACT as f32).abs() < 1.0);
1166
1167        // Just over boundary: error
1168        let over_boundary = (F32_MAX_EXACT + 1) as i32;
1169        let result = f32::try_from(Value::Int(over_boundary));
1170        assert!(result.is_err());
1171
1172        // Negative boundary
1173        let v: f32 = Value::Int(-boundary).try_into().unwrap();
1174        assert!((v - -(F32_MAX_EXACT as f32)).abs() < 1.0);
1175    }
1176
1177    #[test]
1178    fn test_f32_from_bigint_boundary() {
1179        // At boundary: exactly representable
1180        let v: f32 = Value::BigInt(F32_MAX_EXACT).try_into().unwrap();
1181        assert!((v - F32_MAX_EXACT as f32).abs() < 1.0);
1182
1183        // Just over boundary: error
1184        let result = f32::try_from(Value::BigInt(F32_MAX_EXACT + 1));
1185        assert!(result.is_err());
1186    }
1187
1188    #[test]
1189    fn test_f64_from_bigint_boundary() {
1190        // At boundary: exactly representable
1191        let v: f64 = Value::BigInt(F64_MAX_EXACT).try_into().unwrap();
1192        assert!((v - F64_MAX_EXACT as f64).abs() < 1.0);
1193
1194        // Just over boundary: error
1195        let result = f64::try_from(Value::BigInt(F64_MAX_EXACT + 1));
1196        assert!(result.is_err());
1197    }
1198
1199    #[test]
1200    fn test_f32_lossy_accepts_large_values() {
1201        // Lossy conversion accepts values that strict conversion rejects
1202        assert!(Value::BigInt(i64::MAX).to_f32_lossy().is_ok());
1203        assert!(
1204            Value::Double(1.000_000_119_209_289_6)
1205                .to_f32_lossy()
1206                .is_ok()
1207        );
1208        assert!(Value::Int(i32::MAX).to_f32_lossy().is_ok());
1209    }
1210
1211    #[test]
1212    fn test_f32_lossy_rejects_overflow() {
1213        // But rejects truly unrepresentable values (overflow to infinity)
1214        let result = Value::Double(f64::MAX).to_f32_lossy();
1215        assert!(result.is_err());
1216    }
1217
1218    #[test]
1219    fn test_f64_lossy_accepts_large_integers() {
1220        // Lossy conversion accepts values that strict conversion rejects
1221        assert!(Value::BigInt(i64::MAX).to_f64_lossy().is_ok());
1222        assert!(Value::BigInt(i64::MIN).to_f64_lossy().is_ok());
1223    }
1224
1225    #[test]
1226    fn test_f64_lossy_rejects_non_numeric() {
1227        let result = Value::Text("not a number".to_string()).to_f64_lossy();
1228        assert!(result.is_err());
1229    }
1230
1231    // ==================== Error Message Quality Tests ====================
1232
1233    #[test]
1234    fn test_u64_error_message_includes_value() {
1235        let big_val = u64::MAX;
1236        let result = Value::try_from(big_val);
1237        assert!(result.is_err());
1238        let err = result.unwrap_err();
1239        let msg = err.to_string();
1240        // Error should include the actual value for debugging
1241        assert!(
1242            msg.contains("18446744073709551615") || msg.contains(&big_val.to_string()),
1243            "Error should include the u64 value, got: {}",
1244            msg
1245        );
1246    }
1247
1248    #[test]
1249    fn test_f32_precision_error_is_descriptive() {
1250        let result = f32::try_from(Value::BigInt(i64::MAX));
1251        assert!(result.is_err());
1252        let err = result.unwrap_err();
1253        let msg = err.to_string();
1254        // Error should explain the precision issue
1255        assert!(
1256            msg.contains("f32") && (msg.contains("exact") || msg.contains("precision")),
1257            "Error should describe f32 precision issue, got: {}",
1258            msg
1259        );
1260    }
1261
1262    #[test]
1263    fn test_f64_precision_error_is_descriptive() {
1264        let result = f64::try_from(Value::BigInt(i64::MAX));
1265        assert!(result.is_err());
1266        let err = result.unwrap_err();
1267        let msg = err.to_string();
1268        assert!(
1269            msg.contains("f64") && (msg.contains("exact") || msg.contains("precision")),
1270            "Error should describe f64 precision issue, got: {}",
1271            msg
1272        );
1273    }
1274
1275    // ==================== Negative Boundary Tests ====================
1276
1277    #[test]
1278    fn test_f64_negative_boundary() {
1279        // Exactly -2^53 is representable
1280        let neg_boundary = -(1i64 << 53);
1281        let v: f64 = Value::BigInt(neg_boundary).try_into().unwrap();
1282        assert!((v - neg_boundary as f64).abs() < 1.0);
1283
1284        // -2^53 - 1 should error
1285        let result = f64::try_from(Value::BigInt(neg_boundary - 1));
1286        assert!(result.is_err());
1287    }
1288
1289    #[test]
1290    fn test_f32_negative_boundary() {
1291        let neg_boundary = -(F32_MAX_EXACT);
1292        let v: f32 = Value::BigInt(neg_boundary).try_into().unwrap();
1293        assert!((v - neg_boundary as f32).abs() < 1.0);
1294
1295        // Just past negative boundary should error
1296        let result = f32::try_from(Value::BigInt(neg_boundary - 1));
1297        assert!(result.is_err());
1298    }
1299
1300    // ==================== Error Type Verification Tests ====================
1301
1302    #[test]
1303    fn test_conversion_errors_are_type_errors() {
1304        use crate::Error;
1305
1306        // u64 overflow
1307        let err = Value::try_from(u64::MAX).unwrap_err();
1308        assert!(
1309            matches!(err, Error::Type(_)),
1310            "u64 overflow should be TypeError"
1311        );
1312
1313        // f32 precision loss
1314        let err = f32::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1315        assert!(
1316            matches!(err, Error::Type(_)),
1317            "f32 precision loss should be TypeError"
1318        );
1319
1320        // f64 precision loss
1321        let err = f64::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1322        assert!(
1323            matches!(err, Error::Type(_)),
1324            "f64 precision loss should be TypeError"
1325        );
1326    }
1327
1328    #[test]
1329    fn test_conversion_errors_include_expected_type() {
1330        // f32 conversion error should mention f32
1331        let err = f32::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1332        assert!(
1333            err.to_string().to_lowercase().contains("f32"),
1334            "Error should mention f32, got: {}",
1335            err
1336        );
1337
1338        // f64 conversion error should mention f64
1339        let err = f64::try_from(Value::BigInt(i64::MAX)).unwrap_err();
1340        assert!(
1341            err.to_string().to_lowercase().contains("f64"),
1342            "Error should mention f64, got: {}",
1343            err
1344        );
1345    }
1346
1347    // ==================== Roundtrip Tests ====================
1348
1349    #[test]
1350    fn test_u64_roundtrip_within_range() {
1351        // Values within i64 range should roundtrip through Value
1352        let original = 42u64;
1353        let value: Value = original.try_into().unwrap();
1354        let recovered: i64 = value.try_into().unwrap();
1355        assert_eq!(recovered, original as i64);
1356
1357        // Max representable value
1358        let original = i64::MAX as u64;
1359        let value: Value = original.try_into().unwrap();
1360        let recovered: i64 = value.try_into().unwrap();
1361        assert_eq!(recovered, i64::MAX);
1362    }
1363
1364    #[test]
1365    fn test_f32_roundtrip_preserves_value() {
1366        let original = std::f32::consts::PI;
1367        let value: Value = original.into();
1368        let recovered: f32 = value.try_into().unwrap();
1369        assert!((original - recovered).abs() < f32::EPSILON);
1370    }
1371
1372    #[test]
1373    fn test_f64_i64_roundtrip() {
1374        // Small i64 values should roundtrip through f64 exactly
1375        let original = 12345i64;
1376        let value = Value::BigInt(original);
1377        let as_f64: f64 = value.try_into().unwrap();
1378        assert!((as_f64 - original as f64).abs() < 1e-10);
1379    }
1380
1381    // ==================== Additional Boundary Tests ====================
1382
1383    #[test]
1384    fn test_u64_boundary_edge_cases() {
1385        // Zero is always valid
1386        let v: Value = 0u64.try_into().unwrap();
1387        assert_eq!(v, Value::BigInt(0));
1388
1389        // i64::MAX - 1 is valid
1390        let v: Value = ((i64::MAX - 1) as u64).try_into().unwrap();
1391        assert_eq!(v, Value::BigInt(i64::MAX - 1));
1392
1393        // i64::MAX is valid
1394        let v: Value = (i64::MAX as u64).try_into().unwrap();
1395        assert_eq!(v, Value::BigInt(i64::MAX));
1396    }
1397
1398    #[test]
1399    fn test_i64_min_max_to_f64() {
1400        // i64::MAX exceeds f64 exact range (2^63-1 > 2^53)
1401        let result = f64::try_from(Value::BigInt(i64::MAX));
1402        assert!(result.is_err());
1403
1404        // i64::MIN also exceeds f64 exact range (-2^63 < -2^53)
1405        let result = f64::try_from(Value::BigInt(i64::MIN));
1406        assert!(result.is_err());
1407    }
1408
1409    #[test]
1410    fn test_f32_from_float_is_lossless() {
1411        // Converting from Float variant should always succeed
1412        let original = std::f32::consts::E;
1413        let v: f32 = Value::Float(original).try_into().unwrap();
1414        assert!((v - original).abs() < f32::EPSILON);
1415    }
1416}