Skip to main content

toasty_core/stmt/
value.rs

1use super::{
2    Entry, EntryPath, Type, TypeUnion, ValueObject, ValueRecord, sparse_record::SparseRecord,
3};
4use std::cmp::Ordering;
5
6/// A dynamically typed value used throughout Toasty's query engine.
7///
8/// `Value` represents any concrete data value that flows through the query
9/// pipeline: field values read from or written to the database, literal
10/// constants in expressions, and intermediate results during query evaluation.
11///
12/// Each variant wraps a Rust type that corresponds to a [`Type`] variant.
13/// Use [`Value::infer_ty`] to obtain the matching type, and [`Value::is_a`]
14/// to check compatibility.
15///
16/// # Construction
17///
18/// Values are typically created via `From` conversions from Rust primitives:
19///
20/// ```
21/// use toasty_core::stmt::Value;
22///
23/// let v = Value::from(42_i64);
24/// assert_eq!(v, 42_i64);
25///
26/// let v = Value::from("hello");
27/// assert_eq!(v, "hello");
28///
29/// let v = Value::null();
30/// assert!(v.is_null());
31///
32/// let v = Value::from(true);
33/// assert_eq!(v, true);
34/// ```
35#[derive(Debug, Default, Clone, PartialEq)]
36pub enum Value {
37    /// Boolean value
38    Bool(bool),
39
40    /// Signed 8-bit integer
41    I8(i8),
42
43    /// Signed 16-bit integer
44    I16(i16),
45
46    /// Signed 32-bit integer
47    I32(i32),
48
49    /// Signed 64-bit integer
50    I64(i64),
51
52    /// Unsigned 8-bit integer
53    U8(u8),
54
55    /// Unsigned 16-bit integer
56    U16(u16),
57
58    /// Unsigned 32-bit integer
59    U32(u32),
60
61    /// Unsigned 64-bit integer
62    U64(u64),
63
64    /// 32-bit floating point number
65    F32(f32),
66
67    /// 64-bit floating point number
68    F64(f64),
69
70    /// A typed record
71    SparseRecord(SparseRecord),
72
73    /// Null value
74    #[default]
75    Null,
76
77    /// Record value, either borrowed or owned
78    Record(ValueRecord),
79
80    /// A document value: a named, ordered set of fields. The named counterpart
81    /// to [`Value::Record`]. Produced by the engine at the driver boundary for
82    /// document-stored fields, and consumed structurally by drivers.
83    Object(ValueObject),
84
85    /// A list of values of the same type
86    List(Vec<Value>),
87
88    /// String value, either borrowed or owned
89    String(String),
90
91    /// An array of bytes that is more efficient than List(u8)
92    Bytes(Vec<u8>),
93
94    /// 128-bit universally unique identifier (UUID)
95    Uuid(uuid::Uuid),
96
97    /// A fixed-precision decimal number.
98    /// See [`rust_decimal::Decimal`].
99    #[cfg(feature = "rust_decimal")]
100    Decimal(rust_decimal::Decimal),
101
102    /// An arbitrary-precision decimal number.
103    /// See [`bigdecimal::BigDecimal`].
104    #[cfg(feature = "bigdecimal")]
105    BigDecimal(bigdecimal::BigDecimal),
106
107    /// An instant in time represented as the number of nanoseconds since the Unix epoch.
108    /// See [`jiff::Timestamp`].
109    #[cfg(feature = "jiff")]
110    Timestamp(jiff::Timestamp),
111
112    /// A time zone aware instant in time.
113    /// See [`jiff::Zoned`]
114    #[cfg(feature = "jiff")]
115    Zoned(jiff::Zoned),
116
117    /// A representation of a civil date in the Gregorian calendar.
118    /// See [`jiff::civil::Date`].
119    #[cfg(feature = "jiff")]
120    Date(jiff::civil::Date),
121
122    /// A representation of civil “wall clock” time.
123    /// See [`jiff::civil::Time`].
124    #[cfg(feature = "jiff")]
125    Time(jiff::civil::Time),
126
127    /// A representation of a civil datetime in the Gregorian calendar.
128    /// See [`jiff::civil::DateTime`].
129    #[cfg(feature = "jiff")]
130    DateTime(jiff::civil::DateTime),
131}
132
133impl Value {
134    /// Returns a null value.
135    ///
136    /// # Examples
137    ///
138    /// ```
139    /// # use toasty_core::stmt::Value;
140    /// let v = Value::null();
141    /// assert!(v.is_null());
142    /// ```
143    pub const fn null() -> Self {
144        Self::Null
145    }
146
147    /// Adds two numeric values of the same type, returning `None` on overflow
148    /// or for non-numeric / mismatched-type combinations. Integer arithmetic
149    /// uses checked semantics; floating-point arithmetic uses IEEE-754.
150    pub fn checked_add(&self, other: &Self) -> Option<Self> {
151        match (self, other) {
152            (Self::I8(a), Self::I8(b)) => a.checked_add(*b).map(Self::I8),
153            (Self::I16(a), Self::I16(b)) => a.checked_add(*b).map(Self::I16),
154            (Self::I32(a), Self::I32(b)) => a.checked_add(*b).map(Self::I32),
155            (Self::I64(a), Self::I64(b)) => a.checked_add(*b).map(Self::I64),
156            (Self::U8(a), Self::U8(b)) => a.checked_add(*b).map(Self::U8),
157            (Self::U16(a), Self::U16(b)) => a.checked_add(*b).map(Self::U16),
158            (Self::U32(a), Self::U32(b)) => a.checked_add(*b).map(Self::U32),
159            (Self::U64(a), Self::U64(b)) => a.checked_add(*b).map(Self::U64),
160            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a + b)),
161            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a + b)),
162            _ => None,
163        }
164    }
165
166    /// Subtracts two numeric values of the same type, returning `None` on
167    /// overflow or for non-numeric / mismatched-type combinations. Integer
168    /// arithmetic uses checked semantics; floating-point arithmetic uses
169    /// IEEE-754.
170    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
171        match (self, other) {
172            (Self::I8(a), Self::I8(b)) => a.checked_sub(*b).map(Self::I8),
173            (Self::I16(a), Self::I16(b)) => a.checked_sub(*b).map(Self::I16),
174            (Self::I32(a), Self::I32(b)) => a.checked_sub(*b).map(Self::I32),
175            (Self::I64(a), Self::I64(b)) => a.checked_sub(*b).map(Self::I64),
176            (Self::U8(a), Self::U8(b)) => a.checked_sub(*b).map(Self::U8),
177            (Self::U16(a), Self::U16(b)) => a.checked_sub(*b).map(Self::U16),
178            (Self::U32(a), Self::U32(b)) => a.checked_sub(*b).map(Self::U32),
179            (Self::U64(a), Self::U64(b)) => a.checked_sub(*b).map(Self::U64),
180            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a - b)),
181            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a - b)),
182            _ => None,
183        }
184    }
185
186    /// Returns `true` if this value is [`Value::Null`].
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// # use toasty_core::stmt::Value;
192    /// assert!(Value::Null.is_null());
193    /// assert!(!Value::from(1_i64).is_null());
194    /// ```
195    pub const fn is_null(&self) -> bool {
196        matches!(self, Self::Null)
197    }
198
199    /// Returns `true` if this value is a [`Value::Record`].
200    pub const fn is_record(&self) -> bool {
201        matches!(self, Self::Record(_))
202    }
203
204    /// Creates a [`Value::Record`] from a vector of field values.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// # use toasty_core::stmt::Value;
210    /// let record = Value::record_from_vec(vec![Value::from(1_i64), Value::from("name")]);
211    /// assert!(record.is_record());
212    /// ```
213    pub fn record_from_vec(fields: Vec<Self>) -> Self {
214        ValueRecord::from_vec(fields).into()
215    }
216
217    /// Creates a boolean value.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// # use toasty_core::stmt::Value;
223    /// let v = Value::from_bool(true);
224    /// assert_eq!(v, true);
225    /// ```
226    pub const fn from_bool(src: bool) -> Self {
227        Self::Bool(src)
228    }
229
230    /// Returns the contained string slice if this is a [`Value::String`],
231    /// or `None` otherwise.
232    pub fn as_str(&self) -> Option<&str> {
233        match self {
234            Self::String(v) => Some(&**v),
235            _ => None,
236        }
237    }
238
239    /// Returns the contained string slice, panicking if this is not a
240    /// [`Value::String`].
241    ///
242    /// # Panics
243    ///
244    /// Panics if the value is not a `String` variant.
245    pub fn as_string_unwrap(&self) -> &str {
246        match self {
247            Self::String(v) => v,
248            _ => todo!(),
249        }
250    }
251
252    /// Returns a reference to the contained [`ValueRecord`] if this is a
253    /// [`Value::Record`], or `None` otherwise.
254    pub fn as_record(&self) -> Option<&ValueRecord> {
255        match self {
256            Self::Record(record) => Some(record),
257            _ => None,
258        }
259    }
260
261    /// Returns a reference to the contained [`ValueRecord`], panicking if
262    /// this is not a [`Value::Record`].
263    ///
264    /// # Panics
265    ///
266    /// Panics if the value is not a `Record` variant.
267    pub fn as_record_unwrap(&self) -> &ValueRecord {
268        match self {
269            Self::Record(record) => record,
270            _ => panic!("{self:#?}"),
271        }
272    }
273
274    /// Returns a mutable reference to the contained [`ValueRecord`],
275    /// panicking if this is not a [`Value::Record`].
276    ///
277    /// # Panics
278    ///
279    /// Panics if the value is not a `Record` variant.
280    pub fn as_record_mut_unwrap(&mut self) -> &mut ValueRecord {
281        match self {
282            Self::Record(record) => record,
283            _ => panic!(),
284        }
285    }
286
287    /// Consumes this value and returns the contained [`ValueRecord`],
288    /// panicking if this is not a [`Value::Record`].
289    ///
290    /// # Panics
291    ///
292    /// Panics if the value is not a `Record` variant.
293    pub fn into_record(self) -> ValueRecord {
294        match self {
295            Self::Record(record) => record,
296            _ => panic!(),
297        }
298    }
299
300    /// Returns `true` if this value is compatible with the given [`Type`].
301    ///
302    /// Null values are compatible with any type. For union types, the value
303    /// must be compatible with at least one member type. A `Type::Model`
304    /// (a `#[document]` embed) is checked field-by-field against the embedded
305    /// model's layout, resolved via `resolve`. When `resolve` cannot resolve
306    /// the model (a schema-free context such as `()`), there is no layout to
307    /// check against and the document pairing is accepted without inspection.
308    pub fn is_a(&self, resolve: &impl super::Resolve, ty: &Type) -> bool {
309        if let Type::Union(types) = ty {
310            return types.iter().any(|t| self.is_a(resolve, t));
311        }
312        match self {
313            Self::Null => true,
314            Self::Bool(_) => ty.is_bool(),
315            Self::I8(_) => ty.is_i8(),
316            Self::I16(_) => ty.is_i16(),
317            Self::I32(_) => ty.is_i32(),
318            Self::I64(_) => ty.is_i64(),
319            Self::U8(_) => ty.is_u8(),
320            Self::U16(_) => ty.is_u16(),
321            Self::U32(_) => ty.is_u32(),
322            Self::U64(_) => ty.is_u64(),
323            Self::F32(_) => ty.is_f32(),
324            Self::F64(_) => ty.is_f64(),
325            Self::List(value) => match ty {
326                Type::List(ty) => {
327                    if value.is_empty() {
328                        true
329                    } else {
330                        value[0].is_a(resolve, ty)
331                    }
332                }
333                _ => false,
334            },
335            Self::Record(value) => match ty {
336                Type::Record(field_tys) if value.len() == field_tys.len() => {
337                    Self::fields_match(resolve, &value.fields, field_tys.iter())
338                }
339                // A positional `Value::Record` is the engine's load form for a
340                // document value (an embedded model, field names dropped).
341                // Resolve the embed's field types from the schema and check each
342                // positionally.
343                Type::Model(id) => match resolve.model(*id) {
344                    Some(model) => {
345                        let fields = model.fields();
346                        value.len() == fields.len()
347                            && Self::fields_match(
348                                resolve,
349                                &value.fields,
350                                fields.iter().map(|field| field.expr_ty()),
351                            )
352                    }
353                    None => true,
354                },
355                _ => false,
356            },
357            // A named `Value::Object` is the driver-boundary form of a
358            // document value. Against the structural `Type::Object` (how the
359            // database schema types a `#[document]` column) any object is
360            // compatible — the type carries no field layout. Against
361            // `Type::Model` (the engine's view) check each embed field against
362            // the entry of the same name (an absent key is `None`, compatible
363            // with any field type).
364            Self::Object(object) => match ty {
365                Type::Object => true,
366                Type::Model(id) => match resolve.model(*id) {
367                    Some(model) => model.fields().iter().all(|field| {
368                        let name = field.name().app_unwrap();
369                        object
370                            .iter()
371                            .find(|(key, _)| key == name)
372                            .is_none_or(|(_, v)| v.is_a(resolve, field.expr_ty()))
373                    }),
374                    None => true,
375                },
376                _ => false,
377            },
378            Self::SparseRecord(value) => match ty {
379                Type::SparseRecord(fields) => value.fields == *fields,
380                _ => false,
381            },
382            Self::String(_) => ty.is_string(),
383            Self::Bytes(_) => ty.is_bytes(),
384            Self::Uuid(_) => ty.is_uuid(),
385            #[cfg(feature = "rust_decimal")]
386            Value::Decimal(_) => *ty == Type::Decimal,
387            #[cfg(feature = "bigdecimal")]
388            Value::BigDecimal(_) => *ty == Type::BigDecimal,
389            #[cfg(feature = "jiff")]
390            Value::Timestamp(_) => *ty == Type::Timestamp,
391            #[cfg(feature = "jiff")]
392            Value::Zoned(_) => *ty == Type::Zoned,
393            #[cfg(feature = "jiff")]
394            Value::Date(_) => *ty == Type::Date,
395            #[cfg(feature = "jiff")]
396            Value::Time(_) => *ty == Type::Time,
397            #[cfg(feature = "jiff")]
398            Value::DateTime(_) => *ty == Type::DateTime,
399        }
400    }
401
402    /// Whether each value `is_a` the type at the same position. Callers guard
403    /// the lengths first — `zip` would otherwise accept a short prefix.
404    fn fields_match<'a>(
405        resolve: &impl super::Resolve,
406        values: &[Value],
407        tys: impl Iterator<Item = &'a Type>,
408    ) -> bool {
409        values
410            .iter()
411            .zip(tys)
412            .all(|(value, ty)| value.is_a(resolve, ty))
413    }
414
415    /// Infers and returns the [`Type`] of this value.
416    ///
417    /// # Examples
418    ///
419    /// ```
420    /// # use toasty_core::stmt::{Value, Type};
421    /// assert_eq!(Value::from(42_i64).infer_ty(), Type::I64);
422    /// assert_eq!(Value::from("hello").infer_ty(), Type::String);
423    /// assert_eq!(Value::Null.infer_ty(), Type::Null);
424    /// ```
425    pub fn infer_ty(&self) -> Type {
426        match self {
427            Value::Bool(_) => Type::Bool,
428            Value::I8(_) => Type::I8,
429            Value::I16(_) => Type::I16,
430            Value::I32(_) => Type::I32,
431            Value::I64(_) => Type::I64,
432            Value::SparseRecord(v) => Type::SparseRecord(v.fields.clone()),
433            Value::Null => Type::Null,
434            Value::Record(v) => Type::Record(v.fields.iter().map(Self::infer_ty).collect()),
435            // An object's inferred type, names dropped, is a positional record;
436            // the named document type is only known from the schema.
437            Value::Object(v) => Type::Record(
438                v.entries
439                    .iter()
440                    .map(|(_, value)| value.infer_ty())
441                    .collect(),
442            ),
443            Value::String(_) => Type::String,
444            Value::List(items) if items.is_empty() => Type::list(Type::Null),
445            Value::List(items) => {
446                let mut union = TypeUnion::new();
447                for item in items {
448                    union.insert(item.infer_ty());
449                }
450                Type::list(union.simplify())
451            }
452            Value::U8(_) => Type::U8,
453            Value::U16(_) => Type::U16,
454            Value::U32(_) => Type::U32,
455            Value::U64(_) => Type::U64,
456            Value::F32(_) => Type::F32,
457            Value::F64(_) => Type::F64,
458            Value::Bytes(_) => Type::Bytes,
459            Value::Uuid(_) => Type::Uuid,
460            #[cfg(feature = "rust_decimal")]
461            Value::Decimal(_) => Type::Decimal,
462            #[cfg(feature = "bigdecimal")]
463            Value::BigDecimal(_) => Type::BigDecimal,
464            #[cfg(feature = "jiff")]
465            Value::Timestamp(_) => Type::Timestamp,
466            #[cfg(feature = "jiff")]
467            Value::Zoned(_) => Type::Zoned,
468            #[cfg(feature = "jiff")]
469            Value::Date(_) => Type::Date,
470            #[cfg(feature = "jiff")]
471            Value::Time(_) => Type::Time,
472            #[cfg(feature = "jiff")]
473            Value::DateTime(_) => Type::DateTime,
474        }
475    }
476
477    /// Infers the database storage type ([`db::Type`]) for this value.
478    ///
479    /// This maps each value variant straight to its storage type. It is a
480    /// lighter-weight alternative to going through [`infer_ty`] and
481    /// [`db::Type::from_app`], which first builds the richer [`Type`] only for
482    /// the database layer to immediately collapse it again. String, UUID,
483    /// bytes, decimal, and date/time variants resolve through the driver's
484    /// [`StorageTypes`] defaults; a list maps to the storage type of its
485    /// uniform element type.
486    ///
487    /// Returns an error for values whose storage type cannot be determined from
488    /// the value alone — `NULL`, records, and empty, all-`NULL`, or mixed-type
489    /// lists. Those binds need an explicit type.
490    ///
491    /// [`db::Type`]: crate::schema::db::Type
492    /// [`db::Type::from_app`]: crate::schema::db::Type::from_app
493    /// [`StorageTypes`]: crate::driver::StorageTypes
494    /// [`infer_ty`]: Self::infer_ty
495    ///
496    /// # Examples
497    ///
498    /// ```
499    /// # use toasty_core::stmt::Value;
500    /// # use toasty_core::driver::StorageTypes;
501    /// # use toasty_core::schema::db;
502    /// let storage = &StorageTypes::SQLITE;
503    /// assert_eq!(Value::from(42_i64).infer_db_ty(storage).unwrap(), db::Type::Integer(8));
504    /// assert_eq!(Value::from("hi").infer_db_ty(storage).unwrap(), db::Type::Text);
505    /// assert!(Value::Null.infer_db_ty(storage).is_err());
506    ///
507    /// // A uniform list infers its element's array type; a mixed-type list does not.
508    /// assert_eq!(
509    ///     Value::List(vec![Value::I64(1), Value::I64(2)]).infer_db_ty(storage).unwrap(),
510    ///     db::Type::List(Box::new(db::Type::Integer(8))),
511    /// );
512    /// assert!(Value::List(vec![Value::I64(1), Value::Bool(true)]).infer_db_ty(storage).is_err());
513    /// ```
514    pub fn infer_db_ty(
515        &self,
516        storage: &crate::driver::StorageTypes,
517    ) -> crate::Result<crate::schema::db::Type> {
518        use crate::schema::db::Type as DbType;
519
520        let cannot_infer = || {
521            crate::Error::unsupported_feature(format!(
522                "cannot infer a database storage type for {:?}",
523                self.infer_ty()
524            ))
525        };
526
527        Ok(match self {
528            Value::Bool(_) => DbType::Boolean,
529            Value::I8(_) => DbType::Integer(1),
530            Value::I16(_) => DbType::Integer(2),
531            Value::I32(_) => DbType::Integer(4),
532            Value::I64(_) => DbType::Integer(8),
533            Value::U8(_) => DbType::UnsignedInteger(1),
534            Value::U16(_) => DbType::UnsignedInteger(2),
535            Value::U32(_) => DbType::UnsignedInteger(4),
536            Value::U64(_) => DbType::UnsignedInteger(8),
537            Value::F32(_) => DbType::Float(4),
538            Value::F64(_) => DbType::Float(8),
539            Value::String(_) => storage.default_string_type.clone(),
540            Value::Uuid(_) => storage.default_uuid_type.clone(),
541            Value::Bytes(_) => storage.default_bytes_type.clone(),
542            #[cfg(feature = "rust_decimal")]
543            Value::Decimal(_) => storage.default_decimal_type.clone(),
544            #[cfg(feature = "bigdecimal")]
545            Value::BigDecimal(_) => storage.default_bigdecimal_type.clone(),
546            #[cfg(feature = "jiff")]
547            Value::Timestamp(_) => storage.default_timestamp_type.clone(),
548            #[cfg(feature = "jiff")]
549            Value::Zoned(_) => storage.default_zoned_type.clone(),
550            #[cfg(feature = "jiff")]
551            Value::Date(_) => storage.default_date_type.clone(),
552            #[cfg(feature = "jiff")]
553            Value::Time(_) => storage.default_time_type.clone(),
554            #[cfg(feature = "jiff")]
555            Value::DateTime(_) => storage.default_datetime_type.clone(),
556            // A list stores as the array type of its element, but only when the
557            // elements are uniform at the *app-type* level. Reuse the full
558            // inference path to enforce that: comparing storage types alone is
559            // too permissive, because distinct value types can collapse to one
560            // backend type (e.g. `String` and `Date` both map to TEXT on
561            // SQLite) and a heterogeneous list would slip through. Inferring
562            // through the `TypeUnion` also rejects empty and all-`NULL` lists,
563            // which have no element type.
564            Value::List(_) => DbType::from_app(&self.infer_ty(), None, storage)
565                .map_err(|err| err.context(cannot_infer()))?,
566            Value::Null | Value::Record(_) | Value::Object(_) | Value::SparseRecord(_) => {
567                return Err(cannot_infer());
568            }
569        })
570    }
571
572    /// Navigates into this value using the given path and returns an [`Entry`]
573    /// reference to the nested value.
574    ///
575    /// For records, each step indexes into the record's fields. For lists,
576    /// each step indexes into the list's elements.
577    ///
578    /// # Panics
579    ///
580    /// Panics if the path is invalid for the value's structure.
581    #[track_caller]
582    pub fn entry(&self, path: impl EntryPath) -> Entry<'_> {
583        let mut value = self;
584
585        for step in path.step_iter() {
586            value = match value {
587                Self::Record(record) => &record[step],
588                Self::List(items) => &items[step],
589                // Projecting a field out of a `NULL` composite is `NULL` (e.g.
590                // an `Option<Embed>` whose value is `None`), and stays `NULL`
591                // for the rest of the path — return it directly.
592                Self::Null => return Entry::Value(value),
593                _ => todo!("base={self:#?}; step={step:#?}"),
594            };
595        }
596
597        Entry::Value(value)
598    }
599
600    /// Takes the value out, replacing it with [`Value::Null`].
601    ///
602    /// # Examples
603    ///
604    /// ```
605    /// # use toasty_core::stmt::Value;
606    /// let mut v = Value::from(42_i64);
607    /// let taken = v.take();
608    /// assert_eq!(taken, 42_i64);
609    /// assert!(v.is_null());
610    /// ```
611    pub fn take(&mut self) -> Self {
612        std::mem::take(self)
613    }
614}
615
616impl AsRef<Self> for Value {
617    fn as_ref(&self) -> &Self {
618        self
619    }
620}
621
622impl PartialOrd for Value {
623    /// Compares two values if they are of the same type.
624    ///
625    /// Returns `None` for:
626    ///
627    /// - `null` values (SQL semantics, e.g., `null` comparisons are undefined)
628    /// - Comparisons across different types
629    /// - Types without natural ordering (records, lists, etc.)
630    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
631        match (self, other) {
632            // `null` comparisons are undefined.
633            (Value::Null, _) | (_, Value::Null) => None,
634
635            // Booleans.
636            (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
637
638            // Signed integers.
639            (Value::I8(a), Value::I8(b)) => a.partial_cmp(b),
640            (Value::I16(a), Value::I16(b)) => a.partial_cmp(b),
641            (Value::I32(a), Value::I32(b)) => a.partial_cmp(b),
642            (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
643
644            // Unsigned integers.
645            (Value::U8(a), Value::U8(b)) => a.partial_cmp(b),
646            (Value::U16(a), Value::U16(b)) => a.partial_cmp(b),
647            (Value::U32(a), Value::U32(b)) => a.partial_cmp(b),
648            (Value::U64(a), Value::U64(b)) => a.partial_cmp(b),
649
650            // Floating point.
651            (Value::F32(a), Value::F32(b)) => a.partial_cmp(b),
652            (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
653
654            // Strings: lexicographic ordering.
655            (Value::String(a), Value::String(b)) => a.partial_cmp(b),
656
657            // Bytes: lexicographic ordering.
658            (Value::Bytes(a), Value::Bytes(b)) => a.partial_cmp(b),
659
660            // UUIDs.
661            (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
662
663            // Decimal: fixed-precision decimal numbers.
664            #[cfg(feature = "rust_decimal")]
665            (Value::Decimal(a), Value::Decimal(b)) => a.partial_cmp(b),
666
667            // BigDecimal: arbitrary-precision decimal numbers.
668            #[cfg(feature = "bigdecimal")]
669            (Value::BigDecimal(a), Value::BigDecimal(b)) => a.partial_cmp(b),
670
671            // Date/time types.
672            #[cfg(feature = "jiff")]
673            (Value::Timestamp(a), Value::Timestamp(b)) => a.partial_cmp(b),
674            #[cfg(feature = "jiff")]
675            (Value::Zoned(a), Value::Zoned(b)) => a.partial_cmp(b),
676            #[cfg(feature = "jiff")]
677            (Value::Date(a), Value::Date(b)) => a.partial_cmp(b),
678            #[cfg(feature = "jiff")]
679            (Value::Time(a), Value::Time(b)) => a.partial_cmp(b),
680            #[cfg(feature = "jiff")]
681            (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b),
682
683            // Types without natural ordering or different types.
684            _ => None,
685        }
686    }
687}
688
689impl From<bool> for Value {
690    fn from(src: bool) -> Self {
691        Self::Bool(src)
692    }
693}
694
695impl TryFrom<Value> for bool {
696    type Error = crate::Error;
697
698    fn try_from(value: Value) -> Result<Self, Self::Error> {
699        match value {
700            Value::Bool(v) => Ok(v),
701            _ => Err(crate::Error::type_conversion(value, "bool")),
702        }
703    }
704}
705
706impl From<String> for Value {
707    fn from(src: String) -> Self {
708        Self::String(src)
709    }
710}
711
712impl From<&String> for Value {
713    fn from(src: &String) -> Self {
714        Self::String(src.clone())
715    }
716}
717
718impl From<&str> for Value {
719    fn from(src: &str) -> Self {
720        Self::String(src.to_string())
721    }
722}
723
724impl From<ValueRecord> for Value {
725    fn from(value: ValueRecord) -> Self {
726        Self::Record(value)
727    }
728}
729
730impl<T> From<Option<T>> for Value
731where
732    Self: From<T>,
733{
734    fn from(value: Option<T>) -> Self {
735        match value {
736            Some(value) => Self::from(value),
737            None => Self::Null,
738        }
739    }
740}
741
742impl TryFrom<Value> for String {
743    type Error = crate::Error;
744
745    fn try_from(value: Value) -> Result<Self, Self::Error> {
746        match value {
747            Value::String(v) => Ok(v),
748            _ => Err(crate::Error::type_conversion(value, "String")),
749        }
750    }
751}
752
753impl From<Vec<u8>> for Value {
754    fn from(value: Vec<u8>) -> Self {
755        Self::Bytes(value)
756    }
757}
758
759impl TryFrom<Value> for Vec<u8> {
760    type Error = crate::Error;
761
762    fn try_from(value: Value) -> Result<Self, Self::Error> {
763        match value {
764            Value::Bytes(v) => Ok(v),
765            _ => Err(crate::Error::type_conversion(value, "Bytes")),
766        }
767    }
768}
769
770impl From<uuid::Uuid> for Value {
771    fn from(value: uuid::Uuid) -> Self {
772        Self::Uuid(value)
773    }
774}
775
776impl TryFrom<Value> for uuid::Uuid {
777    type Error = crate::Error;
778
779    fn try_from(value: Value) -> Result<Self, Self::Error> {
780        match value {
781            Value::Uuid(v) => Ok(v),
782            _ => Err(crate::Error::type_conversion(value, "uuid::Uuid")),
783        }
784    }
785}
786
787#[cfg(feature = "rust_decimal")]
788impl From<rust_decimal::Decimal> for Value {
789    fn from(value: rust_decimal::Decimal) -> Self {
790        Self::Decimal(value)
791    }
792}
793
794#[cfg(feature = "rust_decimal")]
795impl TryFrom<Value> for rust_decimal::Decimal {
796    type Error = crate::Error;
797
798    fn try_from(value: Value) -> Result<Self, Self::Error> {
799        match value {
800            Value::Decimal(v) => Ok(v),
801            _ => Err(crate::Error::type_conversion(
802                value,
803                "rust_decimal::Decimal",
804            )),
805        }
806    }
807}
808
809#[cfg(feature = "bigdecimal")]
810impl From<bigdecimal::BigDecimal> for Value {
811    fn from(value: bigdecimal::BigDecimal) -> Self {
812        Self::BigDecimal(value)
813    }
814}
815
816#[cfg(feature = "bigdecimal")]
817impl TryFrom<Value> for bigdecimal::BigDecimal {
818    type Error = crate::Error;
819
820    fn try_from(value: Value) -> Result<Self, Self::Error> {
821        match value {
822            Value::BigDecimal(v) => Ok(v),
823            _ => Err(crate::Error::type_conversion(
824                value,
825                "bigdecimal::BigDecimal",
826            )),
827        }
828    }
829}