Skip to main content

toasty_core/stmt/
value.rs

1use super::{Entry, EntryPath, Type, TypeUnion, ValueRecord, sparse_record::SparseRecord};
2use std::cmp::Ordering;
3
4/// A dynamically typed value used throughout Toasty's query engine.
5///
6/// `Value` represents any concrete data value that flows through the query
7/// pipeline: field values read from or written to the database, literal
8/// constants in expressions, and intermediate results during query evaluation.
9///
10/// Each variant wraps a Rust type that corresponds to a [`Type`] variant.
11/// Use [`Value::infer_ty`] to obtain the matching type, and [`Value::is_a`]
12/// to check compatibility.
13///
14/// # Construction
15///
16/// Values are typically created via `From` conversions from Rust primitives:
17///
18/// ```
19/// use toasty_core::stmt::Value;
20///
21/// let v = Value::from(42_i64);
22/// assert_eq!(v, 42_i64);
23///
24/// let v = Value::from("hello");
25/// assert_eq!(v, "hello");
26///
27/// let v = Value::null();
28/// assert!(v.is_null());
29///
30/// let v = Value::from(true);
31/// assert_eq!(v, true);
32/// ```
33#[derive(Debug, Default, Clone, PartialEq)]
34pub enum Value {
35    /// Boolean value
36    Bool(bool),
37
38    /// Signed 8-bit integer
39    I8(i8),
40
41    /// Signed 16-bit integer
42    I16(i16),
43
44    /// Signed 32-bit integer
45    I32(i32),
46
47    /// Signed 64-bit integer
48    I64(i64),
49
50    /// Unsigned 8-bit integer
51    U8(u8),
52
53    /// Unsigned 16-bit integer
54    U16(u16),
55
56    /// Unsigned 32-bit integer
57    U32(u32),
58
59    /// Unsigned 64-bit integer
60    U64(u64),
61
62    /// 32-bit floating point number
63    F32(f32),
64
65    /// 64-bit floating point number
66    F64(f64),
67
68    /// A typed record
69    SparseRecord(SparseRecord),
70
71    /// Null value
72    #[default]
73    Null,
74
75    /// Record value, either borrowed or owned
76    Record(ValueRecord),
77
78    /// A list of values of the same type
79    List(Vec<Value>),
80
81    /// String value, either borrowed or owned
82    String(String),
83
84    /// An array of bytes that is more efficient than List(u8)
85    Bytes(Vec<u8>),
86
87    /// 128-bit universally unique identifier (UUID)
88    Uuid(uuid::Uuid),
89
90    /// A fixed-precision decimal number.
91    /// See [`rust_decimal::Decimal`].
92    #[cfg(feature = "rust_decimal")]
93    Decimal(rust_decimal::Decimal),
94
95    /// An arbitrary-precision decimal number.
96    /// See [`bigdecimal::BigDecimal`].
97    #[cfg(feature = "bigdecimal")]
98    BigDecimal(bigdecimal::BigDecimal),
99
100    /// An instant in time represented as the number of nanoseconds since the Unix epoch.
101    /// See [`jiff::Timestamp`].
102    #[cfg(feature = "jiff")]
103    Timestamp(jiff::Timestamp),
104
105    /// A time zone aware instant in time.
106    /// See [`jiff::Zoned`]
107    #[cfg(feature = "jiff")]
108    Zoned(jiff::Zoned),
109
110    /// A representation of a civil date in the Gregorian calendar.
111    /// See [`jiff::civil::Date`].
112    #[cfg(feature = "jiff")]
113    Date(jiff::civil::Date),
114
115    /// A representation of civil “wall clock” time.
116    /// See [`jiff::civil::Time`].
117    #[cfg(feature = "jiff")]
118    Time(jiff::civil::Time),
119
120    /// A representation of a civil datetime in the Gregorian calendar.
121    /// See [`jiff::civil::DateTime`].
122    #[cfg(feature = "jiff")]
123    DateTime(jiff::civil::DateTime),
124}
125
126impl Value {
127    /// Returns a null value.
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// # use toasty_core::stmt::Value;
133    /// let v = Value::null();
134    /// assert!(v.is_null());
135    /// ```
136    pub const fn null() -> Self {
137        Self::Null
138    }
139
140    /// Returns `true` if this value is [`Value::Null`].
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// # use toasty_core::stmt::Value;
146    /// assert!(Value::Null.is_null());
147    /// assert!(!Value::from(1_i64).is_null());
148    /// ```
149    pub const fn is_null(&self) -> bool {
150        matches!(self, Self::Null)
151    }
152
153    /// Returns `true` if this value is a [`Value::Record`].
154    pub const fn is_record(&self) -> bool {
155        matches!(self, Self::Record(_))
156    }
157
158    /// Creates a [`Value::Record`] from a vector of field values.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// # use toasty_core::stmt::Value;
164    /// let record = Value::record_from_vec(vec![Value::from(1_i64), Value::from("name")]);
165    /// assert!(record.is_record());
166    /// ```
167    pub fn record_from_vec(fields: Vec<Self>) -> Self {
168        ValueRecord::from_vec(fields).into()
169    }
170
171    /// Creates a boolean value.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// # use toasty_core::stmt::Value;
177    /// let v = Value::from_bool(true);
178    /// assert_eq!(v, true);
179    /// ```
180    pub const fn from_bool(src: bool) -> Self {
181        Self::Bool(src)
182    }
183
184    /// Returns the contained string slice if this is a [`Value::String`],
185    /// or `None` otherwise.
186    pub fn as_str(&self) -> Option<&str> {
187        match self {
188            Self::String(v) => Some(&**v),
189            _ => None,
190        }
191    }
192
193    /// Returns the contained string slice, panicking if this is not a
194    /// [`Value::String`].
195    ///
196    /// # Panics
197    ///
198    /// Panics if the value is not a `String` variant.
199    pub fn as_string_unwrap(&self) -> &str {
200        match self {
201            Self::String(v) => v,
202            _ => todo!(),
203        }
204    }
205
206    /// Returns a reference to the contained [`ValueRecord`] if this is a
207    /// [`Value::Record`], or `None` otherwise.
208    pub fn as_record(&self) -> Option<&ValueRecord> {
209        match self {
210            Self::Record(record) => Some(record),
211            _ => None,
212        }
213    }
214
215    /// Returns a reference to the contained [`ValueRecord`], panicking if
216    /// this is not a [`Value::Record`].
217    ///
218    /// # Panics
219    ///
220    /// Panics if the value is not a `Record` variant.
221    pub fn as_record_unwrap(&self) -> &ValueRecord {
222        match self {
223            Self::Record(record) => record,
224            _ => panic!("{self:#?}"),
225        }
226    }
227
228    /// Returns a mutable reference to the contained [`ValueRecord`],
229    /// panicking if this is not a [`Value::Record`].
230    ///
231    /// # Panics
232    ///
233    /// Panics if the value is not a `Record` variant.
234    pub fn as_record_mut_unwrap(&mut self) -> &mut ValueRecord {
235        match self {
236            Self::Record(record) => record,
237            _ => panic!(),
238        }
239    }
240
241    /// Consumes this value and returns the contained [`ValueRecord`],
242    /// panicking if this is not a [`Value::Record`].
243    ///
244    /// # Panics
245    ///
246    /// Panics if the value is not a `Record` variant.
247    pub fn into_record(self) -> ValueRecord {
248        match self {
249            Self::Record(record) => record,
250            _ => panic!(),
251        }
252    }
253
254    /// Returns `true` if this value is compatible with the given [`Type`].
255    ///
256    /// Null values are compatible with any type. For union types, the value
257    /// must be compatible with at least one member type.
258    pub fn is_a(&self, ty: &Type) -> bool {
259        if let Type::Union(types) = ty {
260            return types.iter().any(|t| self.is_a(t));
261        }
262        match self {
263            Self::Null => true,
264            Self::Bool(_) => ty.is_bool(),
265            Self::I8(_) => ty.is_i8(),
266            Self::I16(_) => ty.is_i16(),
267            Self::I32(_) => ty.is_i32(),
268            Self::I64(_) => ty.is_i64(),
269            Self::U8(_) => ty.is_u8(),
270            Self::U16(_) => ty.is_u16(),
271            Self::U32(_) => ty.is_u32(),
272            Self::U64(_) => ty.is_u64(),
273            Self::F32(_) => ty.is_f32(),
274            Self::F64(_) => ty.is_f64(),
275            Self::List(value) => match ty {
276                Type::List(ty) => {
277                    if value.is_empty() {
278                        true
279                    } else {
280                        value[0].is_a(ty)
281                    }
282                }
283                _ => false,
284            },
285            Self::Record(value) => match ty {
286                Type::Record(fields) if value.len() == fields.len() => value
287                    .fields
288                    .iter()
289                    .zip(fields.iter())
290                    .all(|(value, ty)| value.is_a(ty)),
291                _ => false,
292            },
293            Self::SparseRecord(value) => match ty {
294                Type::SparseRecord(fields) => value.fields == *fields,
295                _ => false,
296            },
297            Self::String(_) => ty.is_string(),
298            Self::Bytes(_) => ty.is_bytes(),
299            Self::Uuid(_) => ty.is_uuid(),
300            #[cfg(feature = "rust_decimal")]
301            Value::Decimal(_) => *ty == Type::Decimal,
302            #[cfg(feature = "bigdecimal")]
303            Value::BigDecimal(_) => *ty == Type::BigDecimal,
304            #[cfg(feature = "jiff")]
305            Value::Timestamp(_) => *ty == Type::Timestamp,
306            #[cfg(feature = "jiff")]
307            Value::Zoned(_) => *ty == Type::Zoned,
308            #[cfg(feature = "jiff")]
309            Value::Date(_) => *ty == Type::Date,
310            #[cfg(feature = "jiff")]
311            Value::Time(_) => *ty == Type::Time,
312            #[cfg(feature = "jiff")]
313            Value::DateTime(_) => *ty == Type::DateTime,
314        }
315    }
316
317    /// Infers and returns the [`Type`] of this value.
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// # use toasty_core::stmt::{Value, Type};
323    /// assert_eq!(Value::from(42_i64).infer_ty(), Type::I64);
324    /// assert_eq!(Value::from("hello").infer_ty(), Type::String);
325    /// assert_eq!(Value::Null.infer_ty(), Type::Null);
326    /// ```
327    pub fn infer_ty(&self) -> Type {
328        match self {
329            Value::Bool(_) => Type::Bool,
330            Value::I8(_) => Type::I8,
331            Value::I16(_) => Type::I16,
332            Value::I32(_) => Type::I32,
333            Value::I64(_) => Type::I64,
334            Value::SparseRecord(v) => Type::SparseRecord(v.fields.clone()),
335            Value::Null => Type::Null,
336            Value::Record(v) => Type::Record(v.fields.iter().map(Self::infer_ty).collect()),
337            Value::String(_) => Type::String,
338            Value::List(items) if items.is_empty() => Type::list(Type::Null),
339            Value::List(items) => {
340                let mut union = TypeUnion::new();
341                for item in items {
342                    union.insert(item.infer_ty());
343                }
344                Type::list(union.simplify())
345            }
346            Value::U8(_) => Type::U8,
347            Value::U16(_) => Type::U16,
348            Value::U32(_) => Type::U32,
349            Value::U64(_) => Type::U64,
350            Value::F32(_) => Type::F32,
351            Value::F64(_) => Type::F64,
352            Value::Bytes(_) => Type::Bytes,
353            Value::Uuid(_) => Type::Uuid,
354            #[cfg(feature = "rust_decimal")]
355            Value::Decimal(_) => Type::Decimal,
356            #[cfg(feature = "bigdecimal")]
357            Value::BigDecimal(_) => Type::BigDecimal,
358            #[cfg(feature = "jiff")]
359            Value::Timestamp(_) => Type::Timestamp,
360            #[cfg(feature = "jiff")]
361            Value::Zoned(_) => Type::Zoned,
362            #[cfg(feature = "jiff")]
363            Value::Date(_) => Type::Date,
364            #[cfg(feature = "jiff")]
365            Value::Time(_) => Type::Time,
366            #[cfg(feature = "jiff")]
367            Value::DateTime(_) => Type::DateTime,
368        }
369    }
370
371    /// Navigates into this value using the given path and returns an [`Entry`]
372    /// reference to the nested value.
373    ///
374    /// For records, each step indexes into the record's fields. For lists,
375    /// each step indexes into the list's elements.
376    ///
377    /// # Panics
378    ///
379    /// Panics if the path is invalid for the value's structure.
380    #[track_caller]
381    pub fn entry(&self, path: impl EntryPath) -> Entry<'_> {
382        let mut ret = Entry::Value(self);
383
384        for step in path.step_iter() {
385            ret = match ret {
386                Entry::Value(Self::Record(record)) => Entry::Value(&record[step]),
387                Entry::Value(Self::List(items)) => Entry::Value(&items[step]),
388                _ => todo!("ret={ret:#?}; base={self:#?}; step={step:#?}"),
389            }
390        }
391
392        ret
393    }
394
395    /// Takes the value out, replacing it with [`Value::Null`].
396    ///
397    /// # Examples
398    ///
399    /// ```
400    /// # use toasty_core::stmt::Value;
401    /// let mut v = Value::from(42_i64);
402    /// let taken = v.take();
403    /// assert_eq!(taken, 42_i64);
404    /// assert!(v.is_null());
405    /// ```
406    pub fn take(&mut self) -> Self {
407        std::mem::take(self)
408    }
409}
410
411impl AsRef<Self> for Value {
412    fn as_ref(&self) -> &Self {
413        self
414    }
415}
416
417impl PartialOrd for Value {
418    /// Compares two values if they are of the same type.
419    ///
420    /// Returns `None` for:
421    ///
422    /// - `null` values (SQL semantics, e.g., `null` comparisons are undefined)
423    /// - Comparisons across different types
424    /// - Types without natural ordering (records, lists, etc.)
425    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
426        match (self, other) {
427            // `null` comparisons are undefined.
428            (Value::Null, _) | (_, Value::Null) => None,
429
430            // Booleans.
431            (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
432
433            // Signed integers.
434            (Value::I8(a), Value::I8(b)) => a.partial_cmp(b),
435            (Value::I16(a), Value::I16(b)) => a.partial_cmp(b),
436            (Value::I32(a), Value::I32(b)) => a.partial_cmp(b),
437            (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
438
439            // Unsigned integers.
440            (Value::U8(a), Value::U8(b)) => a.partial_cmp(b),
441            (Value::U16(a), Value::U16(b)) => a.partial_cmp(b),
442            (Value::U32(a), Value::U32(b)) => a.partial_cmp(b),
443            (Value::U64(a), Value::U64(b)) => a.partial_cmp(b),
444
445            // Floating point.
446            (Value::F32(a), Value::F32(b)) => a.partial_cmp(b),
447            (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
448
449            // Strings: lexicographic ordering.
450            (Value::String(a), Value::String(b)) => a.partial_cmp(b),
451
452            // Bytes: lexicographic ordering.
453            (Value::Bytes(a), Value::Bytes(b)) => a.partial_cmp(b),
454
455            // UUIDs.
456            (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
457
458            // Decimal: fixed-precision decimal numbers.
459            #[cfg(feature = "rust_decimal")]
460            (Value::Decimal(a), Value::Decimal(b)) => a.partial_cmp(b),
461
462            // BigDecimal: arbitrary-precision decimal numbers.
463            #[cfg(feature = "bigdecimal")]
464            (Value::BigDecimal(a), Value::BigDecimal(b)) => a.partial_cmp(b),
465
466            // Date/time types.
467            #[cfg(feature = "jiff")]
468            (Value::Timestamp(a), Value::Timestamp(b)) => a.partial_cmp(b),
469            #[cfg(feature = "jiff")]
470            (Value::Zoned(a), Value::Zoned(b)) => a.partial_cmp(b),
471            #[cfg(feature = "jiff")]
472            (Value::Date(a), Value::Date(b)) => a.partial_cmp(b),
473            #[cfg(feature = "jiff")]
474            (Value::Time(a), Value::Time(b)) => a.partial_cmp(b),
475            #[cfg(feature = "jiff")]
476            (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b),
477
478            // Types without natural ordering or different types.
479            _ => None,
480        }
481    }
482}
483
484impl From<bool> for Value {
485    fn from(src: bool) -> Self {
486        Self::Bool(src)
487    }
488}
489
490impl TryFrom<Value> for bool {
491    type Error = crate::Error;
492
493    fn try_from(value: Value) -> Result<Self, Self::Error> {
494        match value {
495            Value::Bool(v) => Ok(v),
496            _ => Err(crate::Error::type_conversion(value, "bool")),
497        }
498    }
499}
500
501impl From<String> for Value {
502    fn from(src: String) -> Self {
503        Self::String(src)
504    }
505}
506
507impl From<&String> for Value {
508    fn from(src: &String) -> Self {
509        Self::String(src.clone())
510    }
511}
512
513impl From<&str> for Value {
514    fn from(src: &str) -> Self {
515        Self::String(src.to_string())
516    }
517}
518
519impl From<ValueRecord> for Value {
520    fn from(value: ValueRecord) -> Self {
521        Self::Record(value)
522    }
523}
524
525impl<T> From<Option<T>> for Value
526where
527    Self: From<T>,
528{
529    fn from(value: Option<T>) -> Self {
530        match value {
531            Some(value) => Self::from(value),
532            None => Self::Null,
533        }
534    }
535}
536
537impl TryFrom<Value> for String {
538    type Error = crate::Error;
539
540    fn try_from(value: Value) -> Result<Self, Self::Error> {
541        match value {
542            Value::String(v) => Ok(v),
543            _ => Err(crate::Error::type_conversion(value, "String")),
544        }
545    }
546}
547
548impl From<Vec<u8>> for Value {
549    fn from(value: Vec<u8>) -> Self {
550        Self::Bytes(value)
551    }
552}
553
554impl TryFrom<Value> for Vec<u8> {
555    type Error = crate::Error;
556
557    fn try_from(value: Value) -> Result<Self, Self::Error> {
558        match value {
559            Value::Bytes(v) => Ok(v),
560            _ => Err(crate::Error::type_conversion(value, "Bytes")),
561        }
562    }
563}
564
565impl From<uuid::Uuid> for Value {
566    fn from(value: uuid::Uuid) -> Self {
567        Self::Uuid(value)
568    }
569}
570
571impl TryFrom<Value> for uuid::Uuid {
572    type Error = crate::Error;
573
574    fn try_from(value: Value) -> Result<Self, Self::Error> {
575        match value {
576            Value::Uuid(v) => Ok(v),
577            _ => Err(crate::Error::type_conversion(value, "uuid::Uuid")),
578        }
579    }
580}
581
582#[cfg(feature = "rust_decimal")]
583impl From<rust_decimal::Decimal> for Value {
584    fn from(value: rust_decimal::Decimal) -> Self {
585        Self::Decimal(value)
586    }
587}
588
589#[cfg(feature = "rust_decimal")]
590impl TryFrom<Value> for rust_decimal::Decimal {
591    type Error = crate::Error;
592
593    fn try_from(value: Value) -> Result<Self, Self::Error> {
594        match value {
595            Value::Decimal(v) => Ok(v),
596            _ => Err(crate::Error::type_conversion(
597                value,
598                "rust_decimal::Decimal",
599            )),
600        }
601    }
602}
603
604#[cfg(feature = "bigdecimal")]
605impl From<bigdecimal::BigDecimal> for Value {
606    fn from(value: bigdecimal::BigDecimal) -> Self {
607        Self::BigDecimal(value)
608    }
609}
610
611#[cfg(feature = "bigdecimal")]
612impl TryFrom<Value> for bigdecimal::BigDecimal {
613    type Error = crate::Error;
614
615    fn try_from(value: Value) -> Result<Self, Self::Error> {
616        match value {
617            Value::BigDecimal(v) => Ok(v),
618            _ => Err(crate::Error::type_conversion(
619                value,
620                "bigdecimal::BigDecimal",
621            )),
622        }
623    }
624}