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