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    /// Adds two numeric values of the same type, returning `None` on overflow
141    /// or for non-numeric / mismatched-type combinations. Integer arithmetic
142    /// uses checked semantics; floating-point arithmetic uses IEEE-754.
143    pub fn checked_add(&self, other: &Self) -> Option<Self> {
144        match (self, other) {
145            (Self::I8(a), Self::I8(b)) => a.checked_add(*b).map(Self::I8),
146            (Self::I16(a), Self::I16(b)) => a.checked_add(*b).map(Self::I16),
147            (Self::I32(a), Self::I32(b)) => a.checked_add(*b).map(Self::I32),
148            (Self::I64(a), Self::I64(b)) => a.checked_add(*b).map(Self::I64),
149            (Self::U8(a), Self::U8(b)) => a.checked_add(*b).map(Self::U8),
150            (Self::U16(a), Self::U16(b)) => a.checked_add(*b).map(Self::U16),
151            (Self::U32(a), Self::U32(b)) => a.checked_add(*b).map(Self::U32),
152            (Self::U64(a), Self::U64(b)) => a.checked_add(*b).map(Self::U64),
153            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a + b)),
154            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a + b)),
155            _ => None,
156        }
157    }
158
159    /// Subtracts two numeric values of the same type, returning `None` on
160    /// overflow or for non-numeric / mismatched-type combinations. Integer
161    /// arithmetic uses checked semantics; floating-point arithmetic uses
162    /// IEEE-754.
163    pub fn checked_sub(&self, other: &Self) -> Option<Self> {
164        match (self, other) {
165            (Self::I8(a), Self::I8(b)) => a.checked_sub(*b).map(Self::I8),
166            (Self::I16(a), Self::I16(b)) => a.checked_sub(*b).map(Self::I16),
167            (Self::I32(a), Self::I32(b)) => a.checked_sub(*b).map(Self::I32),
168            (Self::I64(a), Self::I64(b)) => a.checked_sub(*b).map(Self::I64),
169            (Self::U8(a), Self::U8(b)) => a.checked_sub(*b).map(Self::U8),
170            (Self::U16(a), Self::U16(b)) => a.checked_sub(*b).map(Self::U16),
171            (Self::U32(a), Self::U32(b)) => a.checked_sub(*b).map(Self::U32),
172            (Self::U64(a), Self::U64(b)) => a.checked_sub(*b).map(Self::U64),
173            (Self::F32(a), Self::F32(b)) => Some(Self::F32(a - b)),
174            (Self::F64(a), Self::F64(b)) => Some(Self::F64(a - b)),
175            _ => None,
176        }
177    }
178
179    /// Returns `true` if this value is [`Value::Null`].
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// # use toasty_core::stmt::Value;
185    /// assert!(Value::Null.is_null());
186    /// assert!(!Value::from(1_i64).is_null());
187    /// ```
188    pub const fn is_null(&self) -> bool {
189        matches!(self, Self::Null)
190    }
191
192    /// Returns `true` if this value is a [`Value::Record`].
193    pub const fn is_record(&self) -> bool {
194        matches!(self, Self::Record(_))
195    }
196
197    /// Creates a [`Value::Record`] from a vector of field values.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// # use toasty_core::stmt::Value;
203    /// let record = Value::record_from_vec(vec![Value::from(1_i64), Value::from("name")]);
204    /// assert!(record.is_record());
205    /// ```
206    pub fn record_from_vec(fields: Vec<Self>) -> Self {
207        ValueRecord::from_vec(fields).into()
208    }
209
210    /// Creates a boolean value.
211    ///
212    /// # Examples
213    ///
214    /// ```
215    /// # use toasty_core::stmt::Value;
216    /// let v = Value::from_bool(true);
217    /// assert_eq!(v, true);
218    /// ```
219    pub const fn from_bool(src: bool) -> Self {
220        Self::Bool(src)
221    }
222
223    /// Returns the contained string slice if this is a [`Value::String`],
224    /// or `None` otherwise.
225    pub fn as_str(&self) -> Option<&str> {
226        match self {
227            Self::String(v) => Some(&**v),
228            _ => None,
229        }
230    }
231
232    /// Returns the contained string slice, panicking if this is not a
233    /// [`Value::String`].
234    ///
235    /// # Panics
236    ///
237    /// Panics if the value is not a `String` variant.
238    pub fn as_string_unwrap(&self) -> &str {
239        match self {
240            Self::String(v) => v,
241            _ => todo!(),
242        }
243    }
244
245    /// Returns a reference to the contained [`ValueRecord`] if this is a
246    /// [`Value::Record`], or `None` otherwise.
247    pub fn as_record(&self) -> Option<&ValueRecord> {
248        match self {
249            Self::Record(record) => Some(record),
250            _ => None,
251        }
252    }
253
254    /// Returns a reference to the contained [`ValueRecord`], panicking if
255    /// this is not a [`Value::Record`].
256    ///
257    /// # Panics
258    ///
259    /// Panics if the value is not a `Record` variant.
260    pub fn as_record_unwrap(&self) -> &ValueRecord {
261        match self {
262            Self::Record(record) => record,
263            _ => panic!("{self:#?}"),
264        }
265    }
266
267    /// Returns a mutable reference to the contained [`ValueRecord`],
268    /// panicking if this is not a [`Value::Record`].
269    ///
270    /// # Panics
271    ///
272    /// Panics if the value is not a `Record` variant.
273    pub fn as_record_mut_unwrap(&mut self) -> &mut ValueRecord {
274        match self {
275            Self::Record(record) => record,
276            _ => panic!(),
277        }
278    }
279
280    /// Consumes this value and returns the contained [`ValueRecord`],
281    /// panicking if this is not a [`Value::Record`].
282    ///
283    /// # Panics
284    ///
285    /// Panics if the value is not a `Record` variant.
286    pub fn into_record(self) -> ValueRecord {
287        match self {
288            Self::Record(record) => record,
289            _ => panic!(),
290        }
291    }
292
293    /// Returns `true` if this value is compatible with the given [`Type`].
294    ///
295    /// Null values are compatible with any type. For union types, the value
296    /// must be compatible with at least one member type.
297    pub fn is_a(&self, ty: &Type) -> bool {
298        if let Type::Union(types) = ty {
299            return types.iter().any(|t| self.is_a(t));
300        }
301        match self {
302            Self::Null => true,
303            Self::Bool(_) => ty.is_bool(),
304            Self::I8(_) => ty.is_i8(),
305            Self::I16(_) => ty.is_i16(),
306            Self::I32(_) => ty.is_i32(),
307            Self::I64(_) => ty.is_i64(),
308            Self::U8(_) => ty.is_u8(),
309            Self::U16(_) => ty.is_u16(),
310            Self::U32(_) => ty.is_u32(),
311            Self::U64(_) => ty.is_u64(),
312            Self::F32(_) => ty.is_f32(),
313            Self::F64(_) => ty.is_f64(),
314            Self::List(value) => match ty {
315                Type::List(ty) => {
316                    if value.is_empty() {
317                        true
318                    } else {
319                        value[0].is_a(ty)
320                    }
321                }
322                _ => false,
323            },
324            Self::Record(value) => match ty {
325                Type::Record(fields) if value.len() == fields.len() => value
326                    .fields
327                    .iter()
328                    .zip(fields.iter())
329                    .all(|(value, ty)| value.is_a(ty)),
330                _ => false,
331            },
332            Self::SparseRecord(value) => match ty {
333                Type::SparseRecord(fields) => value.fields == *fields,
334                _ => false,
335            },
336            Self::String(_) => ty.is_string(),
337            Self::Bytes(_) => ty.is_bytes(),
338            Self::Uuid(_) => ty.is_uuid(),
339            #[cfg(feature = "rust_decimal")]
340            Value::Decimal(_) => *ty == Type::Decimal,
341            #[cfg(feature = "bigdecimal")]
342            Value::BigDecimal(_) => *ty == Type::BigDecimal,
343            #[cfg(feature = "jiff")]
344            Value::Timestamp(_) => *ty == Type::Timestamp,
345            #[cfg(feature = "jiff")]
346            Value::Zoned(_) => *ty == Type::Zoned,
347            #[cfg(feature = "jiff")]
348            Value::Date(_) => *ty == Type::Date,
349            #[cfg(feature = "jiff")]
350            Value::Time(_) => *ty == Type::Time,
351            #[cfg(feature = "jiff")]
352            Value::DateTime(_) => *ty == Type::DateTime,
353        }
354    }
355
356    /// Infers and returns the [`Type`] of this value.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// # use toasty_core::stmt::{Value, Type};
362    /// assert_eq!(Value::from(42_i64).infer_ty(), Type::I64);
363    /// assert_eq!(Value::from("hello").infer_ty(), Type::String);
364    /// assert_eq!(Value::Null.infer_ty(), Type::Null);
365    /// ```
366    pub fn infer_ty(&self) -> Type {
367        match self {
368            Value::Bool(_) => Type::Bool,
369            Value::I8(_) => Type::I8,
370            Value::I16(_) => Type::I16,
371            Value::I32(_) => Type::I32,
372            Value::I64(_) => Type::I64,
373            Value::SparseRecord(v) => Type::SparseRecord(v.fields.clone()),
374            Value::Null => Type::Null,
375            Value::Record(v) => Type::Record(v.fields.iter().map(Self::infer_ty).collect()),
376            Value::String(_) => Type::String,
377            Value::List(items) if items.is_empty() => Type::list(Type::Null),
378            Value::List(items) => {
379                let mut union = TypeUnion::new();
380                for item in items {
381                    union.insert(item.infer_ty());
382                }
383                Type::list(union.simplify())
384            }
385            Value::U8(_) => Type::U8,
386            Value::U16(_) => Type::U16,
387            Value::U32(_) => Type::U32,
388            Value::U64(_) => Type::U64,
389            Value::F32(_) => Type::F32,
390            Value::F64(_) => Type::F64,
391            Value::Bytes(_) => Type::Bytes,
392            Value::Uuid(_) => Type::Uuid,
393            #[cfg(feature = "rust_decimal")]
394            Value::Decimal(_) => Type::Decimal,
395            #[cfg(feature = "bigdecimal")]
396            Value::BigDecimal(_) => Type::BigDecimal,
397            #[cfg(feature = "jiff")]
398            Value::Timestamp(_) => Type::Timestamp,
399            #[cfg(feature = "jiff")]
400            Value::Zoned(_) => Type::Zoned,
401            #[cfg(feature = "jiff")]
402            Value::Date(_) => Type::Date,
403            #[cfg(feature = "jiff")]
404            Value::Time(_) => Type::Time,
405            #[cfg(feature = "jiff")]
406            Value::DateTime(_) => Type::DateTime,
407        }
408    }
409
410    /// Navigates into this value using the given path and returns an [`Entry`]
411    /// reference to the nested value.
412    ///
413    /// For records, each step indexes into the record's fields. For lists,
414    /// each step indexes into the list's elements.
415    ///
416    /// # Panics
417    ///
418    /// Panics if the path is invalid for the value's structure.
419    #[track_caller]
420    pub fn entry(&self, path: impl EntryPath) -> Entry<'_> {
421        let mut value = self;
422
423        for step in path.step_iter() {
424            value = match value {
425                Self::Record(record) => &record[step],
426                Self::List(items) => &items[step],
427                // Projecting a field out of a `NULL` composite is `NULL` (e.g.
428                // an `Option<Embed>` whose value is `None`), and stays `NULL`
429                // for the rest of the path — return it directly.
430                Self::Null => return Entry::Value(value),
431                _ => todo!("base={self:#?}; step={step:#?}"),
432            };
433        }
434
435        Entry::Value(value)
436    }
437
438    /// Takes the value out, replacing it with [`Value::Null`].
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// # use toasty_core::stmt::Value;
444    /// let mut v = Value::from(42_i64);
445    /// let taken = v.take();
446    /// assert_eq!(taken, 42_i64);
447    /// assert!(v.is_null());
448    /// ```
449    pub fn take(&mut self) -> Self {
450        std::mem::take(self)
451    }
452}
453
454impl AsRef<Self> for Value {
455    fn as_ref(&self) -> &Self {
456        self
457    }
458}
459
460impl PartialOrd for Value {
461    /// Compares two values if they are of the same type.
462    ///
463    /// Returns `None` for:
464    ///
465    /// - `null` values (SQL semantics, e.g., `null` comparisons are undefined)
466    /// - Comparisons across different types
467    /// - Types without natural ordering (records, lists, etc.)
468    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
469        match (self, other) {
470            // `null` comparisons are undefined.
471            (Value::Null, _) | (_, Value::Null) => None,
472
473            // Booleans.
474            (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
475
476            // Signed integers.
477            (Value::I8(a), Value::I8(b)) => a.partial_cmp(b),
478            (Value::I16(a), Value::I16(b)) => a.partial_cmp(b),
479            (Value::I32(a), Value::I32(b)) => a.partial_cmp(b),
480            (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
481
482            // Unsigned integers.
483            (Value::U8(a), Value::U8(b)) => a.partial_cmp(b),
484            (Value::U16(a), Value::U16(b)) => a.partial_cmp(b),
485            (Value::U32(a), Value::U32(b)) => a.partial_cmp(b),
486            (Value::U64(a), Value::U64(b)) => a.partial_cmp(b),
487
488            // Floating point.
489            (Value::F32(a), Value::F32(b)) => a.partial_cmp(b),
490            (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
491
492            // Strings: lexicographic ordering.
493            (Value::String(a), Value::String(b)) => a.partial_cmp(b),
494
495            // Bytes: lexicographic ordering.
496            (Value::Bytes(a), Value::Bytes(b)) => a.partial_cmp(b),
497
498            // UUIDs.
499            (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
500
501            // Decimal: fixed-precision decimal numbers.
502            #[cfg(feature = "rust_decimal")]
503            (Value::Decimal(a), Value::Decimal(b)) => a.partial_cmp(b),
504
505            // BigDecimal: arbitrary-precision decimal numbers.
506            #[cfg(feature = "bigdecimal")]
507            (Value::BigDecimal(a), Value::BigDecimal(b)) => a.partial_cmp(b),
508
509            // Date/time types.
510            #[cfg(feature = "jiff")]
511            (Value::Timestamp(a), Value::Timestamp(b)) => a.partial_cmp(b),
512            #[cfg(feature = "jiff")]
513            (Value::Zoned(a), Value::Zoned(b)) => a.partial_cmp(b),
514            #[cfg(feature = "jiff")]
515            (Value::Date(a), Value::Date(b)) => a.partial_cmp(b),
516            #[cfg(feature = "jiff")]
517            (Value::Time(a), Value::Time(b)) => a.partial_cmp(b),
518            #[cfg(feature = "jiff")]
519            (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b),
520
521            // Types without natural ordering or different types.
522            _ => None,
523        }
524    }
525}
526
527impl From<bool> for Value {
528    fn from(src: bool) -> Self {
529        Self::Bool(src)
530    }
531}
532
533impl TryFrom<Value> for bool {
534    type Error = crate::Error;
535
536    fn try_from(value: Value) -> Result<Self, Self::Error> {
537        match value {
538            Value::Bool(v) => Ok(v),
539            _ => Err(crate::Error::type_conversion(value, "bool")),
540        }
541    }
542}
543
544impl From<String> for Value {
545    fn from(src: String) -> Self {
546        Self::String(src)
547    }
548}
549
550impl From<&String> for Value {
551    fn from(src: &String) -> Self {
552        Self::String(src.clone())
553    }
554}
555
556impl From<&str> for Value {
557    fn from(src: &str) -> Self {
558        Self::String(src.to_string())
559    }
560}
561
562impl From<ValueRecord> for Value {
563    fn from(value: ValueRecord) -> Self {
564        Self::Record(value)
565    }
566}
567
568impl<T> From<Option<T>> for Value
569where
570    Self: From<T>,
571{
572    fn from(value: Option<T>) -> Self {
573        match value {
574            Some(value) => Self::from(value),
575            None => Self::Null,
576        }
577    }
578}
579
580impl TryFrom<Value> for String {
581    type Error = crate::Error;
582
583    fn try_from(value: Value) -> Result<Self, Self::Error> {
584        match value {
585            Value::String(v) => Ok(v),
586            _ => Err(crate::Error::type_conversion(value, "String")),
587        }
588    }
589}
590
591impl From<Vec<u8>> for Value {
592    fn from(value: Vec<u8>) -> Self {
593        Self::Bytes(value)
594    }
595}
596
597impl TryFrom<Value> for Vec<u8> {
598    type Error = crate::Error;
599
600    fn try_from(value: Value) -> Result<Self, Self::Error> {
601        match value {
602            Value::Bytes(v) => Ok(v),
603            _ => Err(crate::Error::type_conversion(value, "Bytes")),
604        }
605    }
606}
607
608impl From<uuid::Uuid> for Value {
609    fn from(value: uuid::Uuid) -> Self {
610        Self::Uuid(value)
611    }
612}
613
614impl TryFrom<Value> for uuid::Uuid {
615    type Error = crate::Error;
616
617    fn try_from(value: Value) -> Result<Self, Self::Error> {
618        match value {
619            Value::Uuid(v) => Ok(v),
620            _ => Err(crate::Error::type_conversion(value, "uuid::Uuid")),
621        }
622    }
623}
624
625#[cfg(feature = "rust_decimal")]
626impl From<rust_decimal::Decimal> for Value {
627    fn from(value: rust_decimal::Decimal) -> Self {
628        Self::Decimal(value)
629    }
630}
631
632#[cfg(feature = "rust_decimal")]
633impl TryFrom<Value> for rust_decimal::Decimal {
634    type Error = crate::Error;
635
636    fn try_from(value: Value) -> Result<Self, Self::Error> {
637        match value {
638            Value::Decimal(v) => Ok(v),
639            _ => Err(crate::Error::type_conversion(
640                value,
641                "rust_decimal::Decimal",
642            )),
643        }
644    }
645}
646
647#[cfg(feature = "bigdecimal")]
648impl From<bigdecimal::BigDecimal> for Value {
649    fn from(value: bigdecimal::BigDecimal) -> Self {
650        Self::BigDecimal(value)
651    }
652}
653
654#[cfg(feature = "bigdecimal")]
655impl TryFrom<Value> for bigdecimal::BigDecimal {
656    type Error = crate::Error;
657
658    fn try_from(value: Value) -> Result<Self, Self::Error> {
659        match value {
660            Value::BigDecimal(v) => Ok(v),
661            _ => Err(crate::Error::type_conversion(
662                value,
663                "bigdecimal::BigDecimal",
664            )),
665        }
666    }
667}