toasty_core/stmt/
value.rs

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