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    /// An IPv4 or IPv6 network prefix.
133    #[cfg(feature = "net")]
134    Cidr(cidr::IpCidr),
135
136    /// An IPv4 or IPv6 host address with a network prefix.
137    #[cfg(feature = "net")]
138    Inet(cidr::IpInet),
139
140    /// A six-byte IEEE EUI-48 address.
141    #[cfg(feature = "net")]
142    MacAddr(macaddr::MacAddr6),
143
144    /// An eight-byte IEEE EUI-64 address.
145    #[cfg(feature = "net")]
146    MacAddr8(macaddr::MacAddr8),
147}
148
149impl Value {
150    /// Returns a null value.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// # use toasty_core::stmt::Value;
156    /// let v = Value::null();
157    /// assert!(v.is_null());
158    /// ```
159    pub const fn null() -> Self {
160        Self::Null
161    }
162
163    /// Adds two numeric values of the same type, returning `None` on overflow
164    /// or for non-numeric / mismatched-type combinations. Integer arithmetic
165    /// uses checked semantics; floating-point arithmetic uses IEEE-754.
166    pub fn checked_add(&self, other: &Self) -> Option<Self> {
167        match (self, other) {
168            (Self::I8(a), Self::I8(b)) => a.checked_add(*b).map(Self::I8),
169            (Self::I16(a), Self::I16(b)) => a.checked_add(*b).map(Self::I16),
170            (Self::I32(a), Self::I32(b)) => a.checked_add(*b).map(Self::I32),
171            (Self::I64(a), Self::I64(b)) => a.checked_add(*b).map(Self::I64),
172            (Self::U8(a), Self::U8(b)) => a.checked_add(*b).map(Self::U8),
173            (Self::U16(a), Self::U16(b)) => a.checked_add(*b).map(Self::U16),
174            (Self::U32(a), Self::U32(b)) => a.checked_add(*b).map(Self::U32),
175            (Self::U64(a), Self::U64(b)) => a.checked_add(*b).map(Self::U64),
176            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a + b)),
177            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a + b)),
178            _ => None,
179        }
180    }
181
182    /// Subtracts two numeric values of the same type, returning `None` on
183    /// overflow or for non-numeric / mismatched-type combinations. Integer
184    /// arithmetic uses checked semantics; floating-point arithmetic uses
185    /// IEEE-754.
186    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
187        match (self, other) {
188            (Self::I8(a), Self::I8(b)) => a.checked_sub(*b).map(Self::I8),
189            (Self::I16(a), Self::I16(b)) => a.checked_sub(*b).map(Self::I16),
190            (Self::I32(a), Self::I32(b)) => a.checked_sub(*b).map(Self::I32),
191            (Self::I64(a), Self::I64(b)) => a.checked_sub(*b).map(Self::I64),
192            (Self::U8(a), Self::U8(b)) => a.checked_sub(*b).map(Self::U8),
193            (Self::U16(a), Self::U16(b)) => a.checked_sub(*b).map(Self::U16),
194            (Self::U32(a), Self::U32(b)) => a.checked_sub(*b).map(Self::U32),
195            (Self::U64(a), Self::U64(b)) => a.checked_sub(*b).map(Self::U64),
196            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a - b)),
197            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a - b)),
198            _ => None,
199        }
200    }
201
202    /// Returns `true` if this value is [`Value::Null`].
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// # use toasty_core::stmt::Value;
208    /// assert!(Value::Null.is_null());
209    /// assert!(!Value::from(1_i64).is_null());
210    /// ```
211    pub const fn is_null(&self) -> bool {
212        matches!(self, Self::Null)
213    }
214
215    /// Returns `true` if this value is a [`Value::Record`].
216    pub const fn is_record(&self) -> bool {
217        matches!(self, Self::Record(_))
218    }
219
220    /// Creates a [`Value::Record`] from a vector of field values.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # use toasty_core::stmt::Value;
226    /// let record = Value::record_from_vec(vec![Value::from(1_i64), Value::from("name")]);
227    /// assert!(record.is_record());
228    /// ```
229    pub fn record_from_vec(fields: Vec<Self>) -> Self {
230        ValueRecord::from_vec(fields).into()
231    }
232
233    /// Creates a boolean value.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// # use toasty_core::stmt::Value;
239    /// let v = Value::from_bool(true);
240    /// assert_eq!(v, true);
241    /// ```
242    pub const fn from_bool(src: bool) -> Self {
243        Self::Bool(src)
244    }
245
246    /// Returns the contained string slice if this is a [`Value::String`],
247    /// or `None` otherwise.
248    pub fn as_str(&self) -> Option<&str> {
249        match self {
250            Self::String(v) => Some(&**v),
251            _ => None,
252        }
253    }
254
255    /// Returns the contained string slice, panicking if this is not a
256    /// [`Value::String`].
257    ///
258    /// # Panics
259    ///
260    /// Panics if the value is not a `String` variant.
261    pub fn as_string_unwrap(&self) -> &str {
262        match self {
263            Self::String(v) => v,
264            _ => todo!(),
265        }
266    }
267
268    /// Returns a reference to the contained [`ValueRecord`] if this is a
269    /// [`Value::Record`], or `None` otherwise.
270    pub fn as_record(&self) -> Option<&ValueRecord> {
271        match self {
272            Self::Record(record) => Some(record),
273            _ => None,
274        }
275    }
276
277    /// Returns a reference to the contained [`ValueRecord`], panicking if
278    /// this is not a [`Value::Record`].
279    ///
280    /// # Panics
281    ///
282    /// Panics if the value is not a `Record` variant.
283    pub fn as_record_unwrap(&self) -> &ValueRecord {
284        match self {
285            Self::Record(record) => record,
286            _ => panic!("{self:#?}"),
287        }
288    }
289
290    /// Returns a mutable reference to the contained [`ValueRecord`],
291    /// panicking if this is not a [`Value::Record`].
292    ///
293    /// # Panics
294    ///
295    /// Panics if the value is not a `Record` variant.
296    pub fn as_record_mut_unwrap(&mut self) -> &mut ValueRecord {
297        match self {
298            Self::Record(record) => record,
299            _ => panic!(),
300        }
301    }
302
303    /// Consumes this value and returns the contained [`ValueRecord`],
304    /// panicking if this is not a [`Value::Record`].
305    ///
306    /// # Panics
307    ///
308    /// Panics if the value is not a `Record` variant.
309    pub fn into_record(self) -> ValueRecord {
310        match self {
311            Self::Record(record) => record,
312            _ => panic!(),
313        }
314    }
315
316    /// Returns `true` if this value is compatible with the given [`Type`].
317    ///
318    /// Null values are compatible with any type. For union types, the value
319    /// must be compatible with at least one member type. A `Type::Model`
320    /// (a `#[document]` embed) is checked field-by-field against the embedded
321    /// model's layout, resolved via `resolve`. When `resolve` cannot resolve
322    /// the model (a schema-free context such as `()`), there is no layout to
323    /// check against and the document pairing is accepted without inspection.
324    pub fn is_a(&self, resolve: &impl super::Resolve, ty: &Type) -> bool {
325        if let Type::Union(types) = ty {
326            return types.iter().any(|t| self.is_a(resolve, t));
327        }
328        match self {
329            Self::Null => true,
330            Self::Bool(_) => ty.is_bool(),
331            Self::I8(_) => ty.is_i8(),
332            Self::I16(_) => ty.is_i16(),
333            Self::I32(_) => ty.is_i32(),
334            Self::I64(_) => ty.is_i64(),
335            Self::U8(_) => ty.is_u8(),
336            Self::U16(_) => ty.is_u16(),
337            Self::U32(_) => ty.is_u32(),
338            Self::U64(_) => ty.is_u64(),
339            Self::F32(_) => ty.is_f32(),
340            Self::F64(_) => ty.is_f64(),
341            Self::List(value) => match ty {
342                Type::List(ty) => {
343                    if value.is_empty() {
344                        true
345                    } else {
346                        value[0].is_a(resolve, ty)
347                    }
348                }
349                _ => false,
350            },
351            Self::Record(value) => match ty {
352                Type::Record(field_tys) if value.len() == field_tys.len() => {
353                    Self::fields_match(resolve, &value.fields, field_tys.iter())
354                }
355                // A positional `Value::Record` is the engine's load form for a
356                // document value (an embedded model, field names dropped).
357                // Resolve the embed's field types from the schema and check each
358                // positionally.
359                Type::Model(id) => match resolve.model(*id) {
360                    Some(model) => {
361                        let fields = model.fields();
362                        value.len() == fields.len()
363                            && Self::fields_match(
364                                resolve,
365                                &value.fields,
366                                fields.iter().map(|field| field.expr_ty()),
367                            )
368                    }
369                    None => true,
370                },
371                _ => false,
372            },
373            // A named `Value::Object` is the driver-boundary form of a
374            // document value. Against the structural `Type::Object` (how the
375            // database schema types a `#[document]` column) any object is
376            // compatible — the type carries no field layout. Against
377            // `Type::Model` (the engine's view) check each embed field against
378            // the entry of the same name (an absent key is `None`, compatible
379            // with any field type).
380            Self::Object(object) => match ty {
381                Type::Object => true,
382                Type::Model(id) => match resolve.model(*id) {
383                    Some(model) => model.fields().iter().all(|field| {
384                        let name = field.name().app_unwrap();
385                        object
386                            .iter()
387                            .find(|(key, _)| key == name)
388                            .is_none_or(|(_, v)| v.is_a(resolve, field.expr_ty()))
389                    }),
390                    None => true,
391                },
392                _ => false,
393            },
394            Self::SparseRecord(value) => match ty {
395                Type::SparseRecord(fields) => value.fields == *fields,
396                _ => false,
397            },
398            Self::String(_) => ty.is_string(),
399            Self::Bytes(_) => ty.is_bytes(),
400            Self::Uuid(_) => ty.is_uuid(),
401            #[cfg(feature = "rust_decimal")]
402            Value::Decimal(_) => *ty == Type::Decimal,
403            #[cfg(feature = "bigdecimal")]
404            Value::BigDecimal(_) => *ty == Type::BigDecimal,
405            #[cfg(feature = "jiff")]
406            Value::Timestamp(_) => *ty == Type::Timestamp,
407            #[cfg(feature = "jiff")]
408            Value::Zoned(_) => *ty == Type::Zoned,
409            #[cfg(feature = "jiff")]
410            Value::Date(_) => *ty == Type::Date,
411            #[cfg(feature = "jiff")]
412            Value::Time(_) => *ty == Type::Time,
413            #[cfg(feature = "jiff")]
414            Value::DateTime(_) => *ty == Type::DateTime,
415            #[cfg(feature = "net")]
416            Value::Cidr(_) => *ty == Type::Cidr,
417            #[cfg(feature = "net")]
418            Value::Inet(_) => *ty == Type::Inet,
419            #[cfg(feature = "net")]
420            Value::MacAddr(_) => *ty == Type::MacAddr,
421            #[cfg(feature = "net")]
422            Value::MacAddr8(_) => *ty == Type::MacAddr8,
423        }
424    }
425
426    /// Whether each value `is_a` the type at the same position. Callers guard
427    /// the lengths first — `zip` would otherwise accept a short prefix.
428    fn fields_match<'a>(
429        resolve: &impl super::Resolve,
430        values: &[Value],
431        tys: impl Iterator<Item = &'a Type>,
432    ) -> bool {
433        values
434            .iter()
435            .zip(tys)
436            .all(|(value, ty)| value.is_a(resolve, ty))
437    }
438
439    /// Infers and returns the [`Type`] of this value.
440    ///
441    /// # Examples
442    ///
443    /// ```
444    /// # use toasty_core::stmt::{Value, Type};
445    /// assert_eq!(Value::from(42_i64).infer_ty(), Type::I64);
446    /// assert_eq!(Value::from("hello").infer_ty(), Type::String);
447    /// assert_eq!(Value::Null.infer_ty(), Type::Null);
448    /// ```
449    pub fn infer_ty(&self) -> Type {
450        match self {
451            Value::Bool(_) => Type::Bool,
452            Value::I8(_) => Type::I8,
453            Value::I16(_) => Type::I16,
454            Value::I32(_) => Type::I32,
455            Value::I64(_) => Type::I64,
456            Value::SparseRecord(v) => Type::SparseRecord(v.fields.clone()),
457            Value::Null => Type::Null,
458            Value::Record(v) => Type::Record(v.fields.iter().map(Self::infer_ty).collect()),
459            // An object's inferred type, names dropped, is a positional record;
460            // the named document type is only known from the schema.
461            Value::Object(v) => Type::Record(
462                v.entries
463                    .iter()
464                    .map(|(_, value)| value.infer_ty())
465                    .collect(),
466            ),
467            Value::String(_) => Type::String,
468            Value::List(items) if items.is_empty() => Type::list(Type::Null),
469            Value::List(items) => {
470                let mut union = TypeUnion::new();
471                for item in items {
472                    union.insert(item.infer_ty());
473                }
474                Type::list(union.simplify())
475            }
476            Value::U8(_) => Type::U8,
477            Value::U16(_) => Type::U16,
478            Value::U32(_) => Type::U32,
479            Value::U64(_) => Type::U64,
480            Value::F32(_) => Type::F32,
481            Value::F64(_) => Type::F64,
482            Value::Bytes(_) => Type::Bytes,
483            Value::Uuid(_) => Type::Uuid,
484            #[cfg(feature = "rust_decimal")]
485            Value::Decimal(_) => Type::Decimal,
486            #[cfg(feature = "bigdecimal")]
487            Value::BigDecimal(_) => Type::BigDecimal,
488            #[cfg(feature = "jiff")]
489            Value::Timestamp(_) => Type::Timestamp,
490            #[cfg(feature = "jiff")]
491            Value::Zoned(_) => Type::Zoned,
492            #[cfg(feature = "jiff")]
493            Value::Date(_) => Type::Date,
494            #[cfg(feature = "jiff")]
495            Value::Time(_) => Type::Time,
496            #[cfg(feature = "jiff")]
497            Value::DateTime(_) => Type::DateTime,
498            #[cfg(feature = "net")]
499            Value::Cidr(_) => Type::Cidr,
500            #[cfg(feature = "net")]
501            Value::Inet(_) => Type::Inet,
502            #[cfg(feature = "net")]
503            Value::MacAddr(_) => Type::MacAddr,
504            #[cfg(feature = "net")]
505            Value::MacAddr8(_) => Type::MacAddr8,
506        }
507    }
508
509    /// Infers the database storage type ([`db::Type`]) for this value.
510    ///
511    /// This maps each value variant straight to its storage type. It is a
512    /// lighter-weight alternative to going through [`infer_ty`] and
513    /// [`db::Type::from_app`], which first builds the richer [`Type`] only for
514    /// the database layer to immediately collapse it again. String, UUID,
515    /// bytes, decimal, date/time, and network-address variants resolve through
516    /// the driver's [`StorageTypes`] defaults; a list maps to the storage type
517    /// of its uniform element type.
518    ///
519    /// Returns an error for values whose storage type cannot be determined from
520    /// the value alone — `NULL`, records, and empty, all-`NULL`, or mixed-type
521    /// lists. Those binds need an explicit type.
522    ///
523    /// [`db::Type`]: crate::schema::db::Type
524    /// [`db::Type::from_app`]: crate::schema::db::Type::from_app
525    /// [`StorageTypes`]: crate::driver::StorageTypes
526    /// [`infer_ty`]: Self::infer_ty
527    ///
528    /// # Examples
529    ///
530    /// ```
531    /// # use toasty_core::stmt::Value;
532    /// # use toasty_core::driver::StorageTypes;
533    /// # use toasty_core::schema::db;
534    /// let storage = &StorageTypes::SQLITE;
535    /// assert_eq!(Value::from(42_i64).infer_db_ty(storage).unwrap(), db::Type::Integer(8));
536    /// assert_eq!(Value::from("hi").infer_db_ty(storage).unwrap(), db::Type::Text);
537    /// assert!(Value::Null.infer_db_ty(storage).is_err());
538    ///
539    /// // A uniform list infers its element's array type; a mixed-type list does not.
540    /// assert_eq!(
541    ///     Value::List(vec![Value::I64(1), Value::I64(2)]).infer_db_ty(storage).unwrap(),
542    ///     db::Type::List(Box::new(db::Type::Integer(8))),
543    /// );
544    /// assert!(Value::List(vec![Value::I64(1), Value::Bool(true)]).infer_db_ty(storage).is_err());
545    /// ```
546    pub fn infer_db_ty(
547        &self,
548        storage: &crate::driver::StorageTypes,
549    ) -> crate::Result<crate::schema::db::Type> {
550        use crate::schema::db::Type as DbType;
551
552        let cannot_infer = || {
553            crate::Error::unsupported_feature(format!(
554                "cannot infer a database storage type for {:?}",
555                self.infer_ty()
556            ))
557        };
558
559        Ok(match self {
560            Value::Bool(_) => DbType::Boolean,
561            Value::I8(_) => DbType::Integer(1),
562            Value::I16(_) => DbType::Integer(2),
563            Value::I32(_) => DbType::Integer(4),
564            Value::I64(_) => DbType::Integer(8),
565            Value::U8(_) => DbType::UnsignedInteger(1),
566            Value::U16(_) => DbType::UnsignedInteger(2),
567            Value::U32(_) => DbType::UnsignedInteger(4),
568            Value::U64(_) => DbType::UnsignedInteger(8),
569            Value::F32(_) => DbType::Float(4),
570            Value::F64(_) => DbType::Float(8),
571            Value::String(_) => storage.default_string_type.clone(),
572            Value::Uuid(_) => storage.default_uuid_type.clone(),
573            Value::Bytes(_) => storage.default_bytes_type.clone(),
574            #[cfg(feature = "rust_decimal")]
575            Value::Decimal(_) => storage.default_decimal_type.clone(),
576            #[cfg(feature = "bigdecimal")]
577            Value::BigDecimal(_) => storage.default_bigdecimal_type.clone(),
578            #[cfg(feature = "jiff")]
579            Value::Timestamp(_) => storage.default_timestamp_type.clone(),
580            #[cfg(feature = "jiff")]
581            Value::Zoned(_) => storage.default_zoned_type.clone(),
582            #[cfg(feature = "jiff")]
583            Value::Date(_) => storage.default_date_type.clone(),
584            #[cfg(feature = "jiff")]
585            Value::Time(_) => storage.default_time_type.clone(),
586            #[cfg(feature = "jiff")]
587            Value::DateTime(_) => storage.default_datetime_type.clone(),
588            #[cfg(feature = "net")]
589            Value::Cidr(_) => storage.default_cidr_type.clone(),
590            #[cfg(feature = "net")]
591            Value::Inet(_) => storage.default_inet_type.clone(),
592            #[cfg(feature = "net")]
593            Value::MacAddr(_) => storage.default_macaddr_type.clone(),
594            #[cfg(feature = "net")]
595            Value::MacAddr8(_) => storage.default_macaddr8_type.clone(),
596            // A list stores as the array type of its element, but only when the
597            // elements are uniform at the *app-type* level. Reuse the full
598            // inference path to enforce that: comparing storage types alone is
599            // too permissive, because distinct value types can collapse to one
600            // backend type (e.g. `String` and `Date` both map to TEXT on
601            // SQLite) and a heterogeneous list would slip through. Inferring
602            // through the `TypeUnion` also rejects empty and all-`NULL` lists,
603            // which have no element type.
604            Value::List(_) => DbType::from_app(&self.infer_ty(), None, storage)
605                .map_err(|err| err.context(cannot_infer()))?,
606            Value::Null | Value::Record(_) | Value::Object(_) | Value::SparseRecord(_) => {
607                return Err(cannot_infer());
608            }
609        })
610    }
611
612    /// Navigates into this value using the given path and returns an [`Entry`]
613    /// reference to the nested value.
614    ///
615    /// For records, each step indexes into the record's fields. For lists,
616    /// each step indexes into the list's elements.
617    ///
618    /// # Panics
619    ///
620    /// Panics if the path is invalid for the value's structure.
621    #[track_caller]
622    pub fn entry(&self, path: impl EntryPath) -> Entry<'_> {
623        let mut value = self;
624
625        for step in path.step_iter() {
626            value = match value {
627                Self::Record(record) => &record[step],
628                Self::List(items) => &items[step],
629                // Projecting a field out of a `NULL` composite is `NULL` (e.g.
630                // an `Option<Embed>` whose value is `None`), and stays `NULL`
631                // for the rest of the path — return it directly.
632                Self::Null => return Entry::Value(value),
633                _ => todo!("base={self:#?}; step={step:#?}"),
634            };
635        }
636
637        Entry::Value(value)
638    }
639
640    /// Takes the value out, replacing it with [`Value::Null`].
641    ///
642    /// # Examples
643    ///
644    /// ```
645    /// # use toasty_core::stmt::Value;
646    /// let mut v = Value::from(42_i64);
647    /// let taken = v.take();
648    /// assert_eq!(taken, 42_i64);
649    /// assert!(v.is_null());
650    /// ```
651    pub fn take(&mut self) -> Self {
652        std::mem::take(self)
653    }
654}
655
656impl AsRef<Self> for Value {
657    fn as_ref(&self) -> &Self {
658        self
659    }
660}
661
662impl PartialOrd for Value {
663    /// Compares two values if they are of the same type.
664    ///
665    /// Returns `None` for:
666    ///
667    /// - `null` values (SQL semantics, e.g., `null` comparisons are undefined)
668    /// - Comparisons across different types
669    /// - Types without natural ordering (records, lists, etc.)
670    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
671        match (self, other) {
672            // `null` comparisons are undefined.
673            (Value::Null, _) | (_, Value::Null) => None,
674
675            // Booleans.
676            (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
677
678            // Signed integers.
679            (Value::I8(a), Value::I8(b)) => a.partial_cmp(b),
680            (Value::I16(a), Value::I16(b)) => a.partial_cmp(b),
681            (Value::I32(a), Value::I32(b)) => a.partial_cmp(b),
682            (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
683
684            // Unsigned integers.
685            (Value::U8(a), Value::U8(b)) => a.partial_cmp(b),
686            (Value::U16(a), Value::U16(b)) => a.partial_cmp(b),
687            (Value::U32(a), Value::U32(b)) => a.partial_cmp(b),
688            (Value::U64(a), Value::U64(b)) => a.partial_cmp(b),
689
690            // Floating point.
691            (Value::F32(a), Value::F32(b)) => a.partial_cmp(b),
692            (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
693
694            // Strings: lexicographic ordering.
695            (Value::String(a), Value::String(b)) => a.partial_cmp(b),
696
697            // Bytes: lexicographic ordering.
698            (Value::Bytes(a), Value::Bytes(b)) => a.partial_cmp(b),
699
700            // UUIDs.
701            (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
702
703            // Decimal: fixed-precision decimal numbers.
704            #[cfg(feature = "rust_decimal")]
705            (Value::Decimal(a), Value::Decimal(b)) => a.partial_cmp(b),
706
707            // BigDecimal: arbitrary-precision decimal numbers.
708            #[cfg(feature = "bigdecimal")]
709            (Value::BigDecimal(a), Value::BigDecimal(b)) => a.partial_cmp(b),
710
711            // Date/time types.
712            #[cfg(feature = "jiff")]
713            (Value::Timestamp(a), Value::Timestamp(b)) => a.partial_cmp(b),
714            #[cfg(feature = "jiff")]
715            (Value::Zoned(a), Value::Zoned(b)) => a.partial_cmp(b),
716            #[cfg(feature = "jiff")]
717            (Value::Date(a), Value::Date(b)) => a.partial_cmp(b),
718            #[cfg(feature = "jiff")]
719            (Value::Time(a), Value::Time(b)) => a.partial_cmp(b),
720            #[cfg(feature = "jiff")]
721            (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b),
722
723            // Network address types.
724            #[cfg(feature = "net")]
725            (Value::Cidr(a), Value::Cidr(b)) => a.partial_cmp(b),
726            #[cfg(feature = "net")]
727            (Value::Inet(a), Value::Inet(b)) => a.partial_cmp(b),
728            #[cfg(feature = "net")]
729            (Value::MacAddr(a), Value::MacAddr(b)) => a.partial_cmp(b),
730            #[cfg(feature = "net")]
731            (Value::MacAddr8(a), Value::MacAddr8(b)) => a.partial_cmp(b),
732
733            // Types without natural ordering or different types.
734            _ => None,
735        }
736    }
737}
738
739impl From<bool> for Value {
740    fn from(src: bool) -> Self {
741        Self::Bool(src)
742    }
743}
744
745impl TryFrom<Value> for bool {
746    type Error = crate::Error;
747
748    fn try_from(value: Value) -> Result<Self, Self::Error> {
749        match value {
750            Value::Bool(v) => Ok(v),
751            _ => Err(crate::Error::type_conversion(value, "bool")),
752        }
753    }
754}
755
756impl From<String> for Value {
757    fn from(src: String) -> Self {
758        Self::String(src)
759    }
760}
761
762impl From<&String> for Value {
763    fn from(src: &String) -> Self {
764        Self::String(src.clone())
765    }
766}
767
768impl From<&str> for Value {
769    fn from(src: &str) -> Self {
770        Self::String(src.to_string())
771    }
772}
773
774impl From<ValueRecord> for Value {
775    fn from(value: ValueRecord) -> Self {
776        Self::Record(value)
777    }
778}
779
780impl<T> From<Option<T>> for Value
781where
782    Self: From<T>,
783{
784    fn from(value: Option<T>) -> Self {
785        match value {
786            Some(value) => Self::from(value),
787            None => Self::Null,
788        }
789    }
790}
791
792impl TryFrom<Value> for String {
793    type Error = crate::Error;
794
795    fn try_from(value: Value) -> Result<Self, Self::Error> {
796        match value {
797            Value::String(v) => Ok(v),
798            _ => Err(crate::Error::type_conversion(value, "String")),
799        }
800    }
801}
802
803impl From<Vec<u8>> for Value {
804    fn from(value: Vec<u8>) -> Self {
805        Self::Bytes(value)
806    }
807}
808
809impl TryFrom<Value> for Vec<u8> {
810    type Error = crate::Error;
811
812    fn try_from(value: Value) -> Result<Self, Self::Error> {
813        match value {
814            Value::Bytes(v) => Ok(v),
815            _ => Err(crate::Error::type_conversion(value, "Bytes")),
816        }
817    }
818}
819
820impl From<uuid::Uuid> for Value {
821    fn from(value: uuid::Uuid) -> Self {
822        Self::Uuid(value)
823    }
824}
825
826impl TryFrom<Value> for uuid::Uuid {
827    type Error = crate::Error;
828
829    fn try_from(value: Value) -> Result<Self, Self::Error> {
830        match value {
831            Value::Uuid(v) => Ok(v),
832            _ => Err(crate::Error::type_conversion(value, "uuid::Uuid")),
833        }
834    }
835}
836
837#[cfg(feature = "rust_decimal")]
838impl From<rust_decimal::Decimal> for Value {
839    fn from(value: rust_decimal::Decimal) -> Self {
840        Self::Decimal(value)
841    }
842}
843
844#[cfg(feature = "rust_decimal")]
845impl TryFrom<Value> for rust_decimal::Decimal {
846    type Error = crate::Error;
847
848    fn try_from(value: Value) -> Result<Self, Self::Error> {
849        match value {
850            Value::Decimal(v) => Ok(v),
851            _ => Err(crate::Error::type_conversion(
852                value,
853                "rust_decimal::Decimal",
854            )),
855        }
856    }
857}
858
859#[cfg(feature = "bigdecimal")]
860impl From<bigdecimal::BigDecimal> for Value {
861    fn from(value: bigdecimal::BigDecimal) -> Self {
862        Self::BigDecimal(value)
863    }
864}
865
866#[cfg(feature = "bigdecimal")]
867impl TryFrom<Value> for bigdecimal::BigDecimal {
868    type Error = crate::Error;
869
870    fn try_from(value: Value) -> Result<Self, Self::Error> {
871        match value {
872            Value::BigDecimal(v) => Ok(v),
873            _ => Err(crate::Error::type_conversion(
874                value,
875                "bigdecimal::BigDecimal",
876            )),
877        }
878    }
879}