Skip to main content

reifydb_value/value/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	cmp::Ordering,
6	fmt::{Display, Formatter},
7};
8
9use num_traits::ToPrimitive;
10use serde::{Deserialize, Serialize};
11pub mod as_string;
12pub mod blob;
13pub mod boolean;
14pub mod constraint;
15pub mod container;
16pub mod date;
17pub mod datetime;
18pub mod decimal;
19pub mod dictionary;
20pub mod diff_type;
21pub mod duration;
22pub mod frame;
23pub mod identity;
24pub mod int;
25pub mod into;
26pub mod is;
27pub mod iso;
28pub mod json;
29pub mod number;
30pub mod ordered_f32;
31pub mod ordered_f64;
32pub mod partition;
33pub mod percentile;
34pub mod row_number;
35pub mod sumtype;
36pub mod system_columns;
37pub mod temporal;
38pub mod time;
39pub mod to_value;
40pub mod try_from;
41pub mod uint;
42pub mod uuid;
43pub mod value_type;
44
45use std::{fmt, hash, mem};
46
47use blob::Blob;
48use date::Date;
49use datetime::DateTime;
50use decimal::Decimal;
51use dictionary::DictionaryEntryId;
52use duration::Duration;
53use identity::IdentityId;
54use int::Int;
55use ordered_f32::OrderedF32;
56use ordered_f64::OrderedF64;
57use time::Time;
58use uint::Uint;
59use uuid::{Uuid4, Uuid7};
60use value_type::ValueType;
61
62#[derive(Clone, Debug, Serialize, Deserialize)]
63pub enum Value {
64	None {
65		inner: ValueType,
66	},
67
68	Boolean(bool),
69
70	Float4(OrderedF32),
71
72	Float8(OrderedF64),
73
74	Int1(i8),
75
76	Int2(i16),
77
78	Int4(i32),
79
80	Int8(i64),
81
82	Int16(i128),
83
84	Utf8(String),
85
86	Uint1(u8),
87
88	Uint2(u16),
89
90	Uint4(u32),
91
92	Uint8(u64),
93
94	Uint16(u128),
95
96	Date(Date),
97
98	DateTime(DateTime),
99
100	Time(Time),
101
102	Duration(Duration),
103
104	IdentityId(IdentityId),
105
106	Uuid4(Uuid4),
107
108	Uuid7(Uuid7),
109
110	Blob(Blob),
111
112	Int(Int),
113
114	Uint(Uint),
115
116	Decimal(Decimal),
117
118	Any(Box<Value>),
119
120	DictionaryId(DictionaryEntryId),
121
122	Type(ValueType),
123
124	List(Vec<Value>),
125
126	Record(Vec<(String, Value)>),
127
128	Tuple(Vec<Value>),
129}
130
131impl Value {
132	pub fn none() -> Self {
133		Value::None {
134			inner: ValueType::Any,
135		}
136	}
137
138	pub fn none_of(ty: ValueType) -> Self {
139		Value::None {
140			inner: ty,
141		}
142	}
143
144	pub fn bool(v: impl Into<bool>) -> Self {
145		Value::Boolean(v.into())
146	}
147
148	pub fn float4(v: impl Into<f32>) -> Self {
149		OrderedF32::try_from(v.into()).map(Value::Float4).unwrap_or(Value::None {
150			inner: ValueType::Float4,
151		})
152	}
153
154	pub fn float8(v: impl Into<f64>) -> Self {
155		OrderedF64::try_from(v.into()).map(Value::Float8).unwrap_or(Value::None {
156			inner: ValueType::Float8,
157		})
158	}
159
160	pub fn int1(v: impl Into<i8>) -> Self {
161		Value::Int1(v.into())
162	}
163
164	pub fn int2(v: impl Into<i16>) -> Self {
165		Value::Int2(v.into())
166	}
167
168	pub fn int4(v: impl Into<i32>) -> Self {
169		Value::Int4(v.into())
170	}
171
172	pub fn int8(v: impl Into<i64>) -> Self {
173		Value::Int8(v.into())
174	}
175
176	pub fn int16(v: impl Into<i128>) -> Self {
177		Value::Int16(v.into())
178	}
179
180	pub fn utf8(v: impl Into<String>) -> Self {
181		Value::Utf8(v.into())
182	}
183
184	pub fn uint1(v: impl Into<u8>) -> Self {
185		Value::Uint1(v.into())
186	}
187
188	pub fn uint2(v: impl Into<u16>) -> Self {
189		Value::Uint2(v.into())
190	}
191
192	pub fn uint4(v: impl Into<u32>) -> Self {
193		Value::Uint4(v.into())
194	}
195
196	pub fn uint8(v: impl Into<u64>) -> Self {
197		Value::Uint8(v.into())
198	}
199
200	pub fn uint16(v: impl Into<u128>) -> Self {
201		Value::Uint16(v.into())
202	}
203
204	pub fn date(v: impl Into<Date>) -> Self {
205		Value::Date(v.into())
206	}
207
208	pub fn datetime(v: impl Into<DateTime>) -> Self {
209		Value::DateTime(v.into())
210	}
211
212	pub fn time(v: impl Into<Time>) -> Self {
213		Value::Time(v.into())
214	}
215
216	pub fn duration(v: impl Into<Duration>) -> Self {
217		Value::Duration(v.into())
218	}
219
220	pub fn duration_nanoseconds(nanoseconds: i64) -> Self {
221		Value::Duration(Duration::from_nanoseconds_const(nanoseconds))
222	}
223
224	pub fn duration_microseconds(microseconds: i64) -> Self {
225		Value::Duration(Duration::from_microseconds_const(microseconds))
226	}
227
228	pub fn duration_milliseconds(milliseconds: i64) -> Self {
229		Value::Duration(Duration::from_milliseconds_const(milliseconds))
230	}
231
232	pub fn duration_seconds(seconds: i64) -> Self {
233		Value::Duration(Duration::from_seconds_const(seconds))
234	}
235
236	pub fn duration_minutes(minutes: i64) -> Self {
237		Value::Duration(Duration::from_minutes_const(minutes))
238	}
239
240	pub fn duration_hours(hours: i64) -> Self {
241		Value::Duration(Duration::from_hours_const(hours))
242	}
243
244	pub fn identity_id(v: impl Into<IdentityId>) -> Self {
245		Value::IdentityId(v.into())
246	}
247
248	pub fn uuid4(v: impl Into<Uuid4>) -> Self {
249		Value::Uuid4(v.into())
250	}
251
252	pub fn uuid7(v: impl Into<Uuid7>) -> Self {
253		Value::Uuid7(v.into())
254	}
255
256	pub fn blob(v: impl Into<Blob>) -> Self {
257		Value::Blob(v.into())
258	}
259
260	pub fn any(v: impl Into<Value>) -> Self {
261		Value::Any(Box::new(v.into()))
262	}
263
264	pub fn list(items: Vec<Value>) -> Self {
265		Value::List(items)
266	}
267
268	pub fn record(fields: Vec<(String, Value)>) -> Self {
269		Value::Record(fields)
270	}
271
272	pub fn to_usize(&self) -> Option<usize> {
273		match self {
274			Value::Uint1(v) => Some(*v as usize),
275			Value::Uint2(v) => Some(*v as usize),
276			Value::Uint4(v) => Some(*v as usize),
277			Value::Uint8(v) => usize::try_from(*v).ok(),
278			Value::Uint16(v) => usize::try_from(*v).ok(),
279			Value::Int1(v) => usize::try_from(*v).ok(),
280			Value::Int2(v) => usize::try_from(*v).ok(),
281			Value::Int4(v) => usize::try_from(*v).ok(),
282			Value::Int8(v) => usize::try_from(*v).ok(),
283			Value::Int16(v) => usize::try_from(*v).ok(),
284			Value::Float4(v) => {
285				let f = v.value();
286				if f >= 0.0 {
287					Some(f as usize)
288				} else {
289					None
290				}
291			}
292			Value::Float8(v) => {
293				let f = v.value();
294				if f >= 0.0 {
295					Some(f as usize)
296				} else {
297					None
298				}
299			}
300			Value::Int(v) => v.0.to_u64().and_then(|n| usize::try_from(n).ok()),
301			Value::Uint(v) => v.0.to_u64().and_then(|n| usize::try_from(n).ok()),
302			Value::Decimal(v) => v.0.to_u64().and_then(|n| usize::try_from(n).ok()),
303			Value::Utf8(s) => {
304				let s = s.trim();
305				if let Ok(n) = s.parse::<u64>() {
306					usize::try_from(n).ok()
307				} else if let Ok(f) = s.parse::<f64>() {
308					if f >= 0.0 {
309						Some(f as usize)
310					} else {
311						None
312					}
313				} else {
314					None
315				}
316			}
317			_ => None,
318		}
319	}
320}
321
322impl PartialEq for Value {
323	fn eq(&self, other: &Self) -> bool {
324		match (self, other) {
325			(
326				Value::None {
327					inner: l,
328				},
329				Value::None {
330					inner: r,
331				},
332			) => l == r,
333			(Value::Boolean(l), Value::Boolean(r)) => l == r,
334			(Value::Float4(l), Value::Float4(r)) => l == r,
335			(Value::Float8(l), Value::Float8(r)) => l == r,
336			(Value::Int1(l), Value::Int1(r)) => l == r,
337			(Value::Int2(l), Value::Int2(r)) => l == r,
338			(Value::Int4(l), Value::Int4(r)) => l == r,
339			(Value::Int8(l), Value::Int8(r)) => l == r,
340			(Value::Int16(l), Value::Int16(r)) => l == r,
341			(Value::Utf8(l), Value::Utf8(r)) => l == r,
342			(Value::Uint1(l), Value::Uint1(r)) => l == r,
343			(Value::Uint2(l), Value::Uint2(r)) => l == r,
344			(Value::Uint4(l), Value::Uint4(r)) => l == r,
345			(Value::Uint8(l), Value::Uint8(r)) => l == r,
346			(Value::Uint16(l), Value::Uint16(r)) => l == r,
347			(Value::Date(l), Value::Date(r)) => l == r,
348			(Value::DateTime(l), Value::DateTime(r)) => l == r,
349			(Value::Time(l), Value::Time(r)) => l == r,
350			(Value::Duration(l), Value::Duration(r)) => l == r,
351			(Value::IdentityId(l), Value::IdentityId(r)) => l == r,
352			(Value::Uuid4(l), Value::Uuid4(r)) => l == r,
353			(Value::Uuid7(l), Value::Uuid7(r)) => l == r,
354			(Value::Blob(l), Value::Blob(r)) => l == r,
355			(Value::Int(l), Value::Int(r)) => l == r,
356			(Value::Uint(l), Value::Uint(r)) => l == r,
357			(Value::Decimal(l), Value::Decimal(r)) => l == r,
358			(Value::Any(l), Value::Any(r)) => l == r,
359			(Value::DictionaryId(l), Value::DictionaryId(r)) => l == r,
360			(Value::Type(l), Value::Type(r)) => l == r,
361			(Value::List(l), Value::List(r)) => l == r,
362			(Value::Record(l), Value::Record(r)) => l == r,
363			(Value::Tuple(l), Value::Tuple(r)) => l == r,
364			_ => false,
365		}
366	}
367}
368
369impl Eq for Value {}
370
371#[cfg(reifydb_assertions)]
372pub fn assert_equal_with_tolerance(left: &[Value], right: &[Value]) {
373	const REL_EPS: f64 = 1e-9;
374	const ABS_EPS: f64 = 1e-6;
375	fn close(x: f64, y: f64) -> bool {
376		if x.is_nan() && y.is_nan() {
377			return true;
378		}
379		(x - y).abs() <= ABS_EPS.max(REL_EPS * x.abs().max(y.abs()))
380	}
381	fn matches(a: &Value, b: &Value) -> bool {
382		match (a, b) {
383			(Value::Float8(x), Value::Float8(y)) => close(x.value(), y.value()),
384			(Value::Float4(x), Value::Float4(y)) => close(x.value() as f64, y.value() as f64),
385			_ => a == b,
386		}
387	}
388	assert_eq!(left.len(), right.len(), "value count diverges beyond tolerance: {} vs {}", left.len(), right.len());
389	for (i, (l, r)) in left.iter().zip(right).enumerate() {
390		assert!(matches(l, r), "value {i} diverges beyond tolerance: {l:?} vs {r:?}");
391	}
392}
393
394impl hash::Hash for Value {
395	fn hash<H: hash::Hasher>(&self, state: &mut H) {
396		mem::discriminant(self).hash(state);
397		match self {
398			Value::None {
399				..
400			} => {}
401			Value::Boolean(v) => v.hash(state),
402			Value::Float4(v) => v.hash(state),
403			Value::Float8(v) => v.hash(state),
404			Value::Int1(v) => v.hash(state),
405			Value::Int2(v) => v.hash(state),
406			Value::Int4(v) => v.hash(state),
407			Value::Int8(v) => v.hash(state),
408			Value::Int16(v) => v.hash(state),
409			Value::Utf8(v) => v.hash(state),
410			Value::Uint1(v) => v.hash(state),
411			Value::Uint2(v) => v.hash(state),
412			Value::Uint4(v) => v.hash(state),
413			Value::Uint8(v) => v.hash(state),
414			Value::Uint16(v) => v.hash(state),
415			Value::Date(v) => v.hash(state),
416			Value::DateTime(v) => v.hash(state),
417			Value::Time(v) => v.hash(state),
418			Value::Duration(v) => v.hash(state),
419			Value::IdentityId(v) => v.hash(state),
420			Value::Uuid4(v) => v.hash(state),
421			Value::Uuid7(v) => v.hash(state),
422			Value::Blob(v) => v.hash(state),
423			Value::Int(v) => v.hash(state),
424			Value::Uint(v) => v.hash(state),
425			Value::Decimal(v) => v.hash(state),
426			Value::Any(v) => v.hash(state),
427			Value::DictionaryId(v) => v.hash(state),
428			Value::Type(v) => v.hash(state),
429			Value::List(v) => v.hash(state),
430			Value::Record(fields) => {
431				for (k, v) in fields {
432					k.hash(state);
433					v.hash(state);
434				}
435			}
436			Value::Tuple(v) => v.hash(state),
437		}
438	}
439}
440
441impl PartialOrd for Value {
442	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
443		Some(self.cmp(other))
444	}
445}
446
447impl Ord for Value {
448	fn cmp(&self, other: &Self) -> Ordering {
449		match (self, other) {
450			(
451				Value::None {
452					..
453				},
454				Value::None {
455					..
456				},
457			) => Ordering::Equal,
458			(
459				Value::None {
460					..
461				},
462				_,
463			) => Ordering::Greater,
464			(
465				_,
466				Value::None {
467					..
468				},
469			) => Ordering::Less,
470			(Value::Boolean(l), Value::Boolean(r)) => l.cmp(r),
471			(Value::Float4(l), Value::Float4(r)) => l.cmp(r),
472			(Value::Float8(l), Value::Float8(r)) => l.cmp(r),
473			(Value::Int1(l), Value::Int1(r)) => l.cmp(r),
474			(Value::Int2(l), Value::Int2(r)) => l.cmp(r),
475			(Value::Int4(l), Value::Int4(r)) => l.cmp(r),
476			(Value::Int8(l), Value::Int8(r)) => l.cmp(r),
477			(Value::Int16(l), Value::Int16(r)) => l.cmp(r),
478			(Value::Utf8(l), Value::Utf8(r)) => l.cmp(r),
479			(Value::Uint1(l), Value::Uint1(r)) => l.cmp(r),
480			(Value::Uint2(l), Value::Uint2(r)) => l.cmp(r),
481			(Value::Uint4(l), Value::Uint4(r)) => l.cmp(r),
482			(Value::Uint8(l), Value::Uint8(r)) => l.cmp(r),
483			(Value::Uint16(l), Value::Uint16(r)) => l.cmp(r),
484			(Value::Date(l), Value::Date(r)) => l.cmp(r),
485			(Value::DateTime(l), Value::DateTime(r)) => l.cmp(r),
486			(Value::Time(l), Value::Time(r)) => l.cmp(r),
487			(Value::Duration(l), Value::Duration(r)) => l.cmp(r),
488			(Value::IdentityId(l), Value::IdentityId(r)) => l.cmp(r),
489			(Value::Uuid4(l), Value::Uuid4(r)) => l.cmp(r),
490			(Value::Uuid7(l), Value::Uuid7(r)) => l.cmp(r),
491			(Value::Blob(l), Value::Blob(r)) => l.cmp(r),
492			(Value::Int(l), Value::Int(r)) => l.cmp(r),
493			(Value::Uint(l), Value::Uint(r)) => l.cmp(r),
494			(Value::Decimal(l), Value::Decimal(r)) => l.cmp(r),
495			(Value::DictionaryId(l), Value::DictionaryId(r)) => l.to_u128().cmp(&r.to_u128()),
496			(Value::Type(l), Value::Type(r)) => l.cmp(r),
497			(Value::List(_), Value::List(_)) => unreachable!("List values are not orderable"),
498			(Value::Record(_), Value::Record(_)) => unreachable!("Record values are not orderable"),
499			(Value::Tuple(_), Value::Tuple(_)) => unreachable!("Tuple values are not orderable"),
500			(Value::Any(_), Value::Any(_)) => unreachable!("Any values are not orderable"),
501			_ => unimplemented!(),
502		}
503	}
504}
505
506impl Display for Value {
507	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
508		match self {
509			Value::Boolean(true) => f.write_str("true"),
510			Value::Boolean(false) => f.write_str("false"),
511			Value::Float4(value) => Display::fmt(value, f),
512			Value::Float8(value) => Display::fmt(value, f),
513			Value::Int1(value) => Display::fmt(value, f),
514			Value::Int2(value) => Display::fmt(value, f),
515			Value::Int4(value) => Display::fmt(value, f),
516			Value::Int8(value) => Display::fmt(value, f),
517			Value::Int16(value) => Display::fmt(value, f),
518			Value::Utf8(value) => Display::fmt(value, f),
519			Value::Uint1(value) => Display::fmt(value, f),
520			Value::Uint2(value) => Display::fmt(value, f),
521			Value::Uint4(value) => Display::fmt(value, f),
522			Value::Uint8(value) => Display::fmt(value, f),
523			Value::Uint16(value) => Display::fmt(value, f),
524			Value::Date(value) => Display::fmt(value, f),
525			Value::DateTime(value) => Display::fmt(value, f),
526			Value::Time(value) => Display::fmt(value, f),
527			Value::Duration(value) => Display::fmt(value, f),
528			Value::IdentityId(value) => Display::fmt(value, f),
529			Value::Uuid4(value) => Display::fmt(value, f),
530			Value::Uuid7(value) => Display::fmt(value, f),
531			Value::Blob(value) => Display::fmt(value, f),
532			Value::Int(value) => Display::fmt(value, f),
533			Value::Uint(value) => Display::fmt(value, f),
534			Value::Decimal(value) => Display::fmt(value, f),
535			Value::Any(value) => Display::fmt(value, f),
536			Value::DictionaryId(value) => Display::fmt(value, f),
537			Value::Type(value) => Display::fmt(value, f),
538			Value::List(items) => {
539				f.write_str("[")?;
540				for (i, item) in items.iter().enumerate() {
541					if i > 0 {
542						f.write_str(", ")?;
543					}
544					Display::fmt(item, f)?;
545				}
546				f.write_str("]")
547			}
548			Value::Record(fields) => {
549				f.write_str("{")?;
550				for (i, (key, value)) in fields.iter().enumerate() {
551					if i > 0 {
552						f.write_str(", ")?;
553					}
554					write!(f, "{}: {}", key, value)?;
555				}
556				f.write_str("}")
557			}
558			Value::Tuple(items) => {
559				f.write_str("(")?;
560				for (i, item) in items.iter().enumerate() {
561					if i > 0 {
562						f.write_str(", ")?;
563					}
564					Display::fmt(item, f)?;
565				}
566				f.write_str(")")
567			}
568			Value::None {
569				..
570			} => f.write_str("none"),
571		}
572	}
573}
574
575impl Value {
576	pub fn get_type(&self) -> ValueType {
577		match self {
578			Value::None {
579				inner,
580			} => ValueType::Option(Box::new(inner.clone())),
581			Value::Boolean(_) => ValueType::Boolean,
582			Value::Float4(_) => ValueType::Float4,
583			Value::Float8(_) => ValueType::Float8,
584			Value::Int1(_) => ValueType::Int1,
585			Value::Int2(_) => ValueType::Int2,
586			Value::Int4(_) => ValueType::Int4,
587			Value::Int8(_) => ValueType::Int8,
588			Value::Int16(_) => ValueType::Int16,
589			Value::Utf8(_) => ValueType::Utf8,
590			Value::Uint1(_) => ValueType::Uint1,
591			Value::Uint2(_) => ValueType::Uint2,
592			Value::Uint4(_) => ValueType::Uint4,
593			Value::Uint8(_) => ValueType::Uint8,
594			Value::Uint16(_) => ValueType::Uint16,
595			Value::Date(_) => ValueType::Date,
596			Value::DateTime(_) => ValueType::DateTime,
597			Value::Time(_) => ValueType::Time,
598			Value::Duration(_) => ValueType::Duration,
599			Value::IdentityId(_) => ValueType::IdentityId,
600			Value::Uuid4(_) => ValueType::Uuid4,
601			Value::Uuid7(_) => ValueType::Uuid7,
602			Value::Blob(_) => ValueType::Blob,
603			Value::Int(_) => ValueType::Int,
604			Value::Uint(_) => ValueType::Uint,
605			Value::Decimal(_) => ValueType::Decimal,
606			Value::Any(_) => ValueType::Any,
607			Value::DictionaryId(_) => ValueType::DictionaryId,
608			Value::Type(t) => t.clone(),
609			Value::List(items) => {
610				let element_type = items.first().map(|v| v.get_type()).unwrap_or(ValueType::Any);
611				ValueType::list_of(element_type)
612			}
613			Value::Record(fields) => {
614				ValueType::Record(fields.iter().map(|(k, v)| (k.clone(), v.get_type())).collect())
615			}
616			Value::Tuple(items) => ValueType::Tuple(items.iter().map(|v| v.get_type()).collect()),
617		}
618	}
619
620	pub fn unwrap_any(&self) -> &Value {
621		match self {
622			Value::Any(inner) => inner.unwrap_any(),
623			other => other,
624		}
625	}
626}
627
628#[cfg(test)]
629mod tests {
630	use std::str::FromStr;
631
632	use ::uuid::Uuid as StdUuid;
633	use bigdecimal::BigDecimal;
634	use num_bigint::BigInt;
635	use postcard::{from_bytes, to_allocvec};
636
637	use super::*;
638	use crate::value::{
639		blob::Blob,
640		date::Date,
641		datetime::DateTime,
642		decimal::Decimal,
643		dictionary::DictionaryEntryId,
644		duration::Duration,
645		identity::IdentityId,
646		int::Int,
647		ordered_f32::OrderedF32,
648		ordered_f64::OrderedF64,
649		time::Time,
650		uint::Uint,
651		uuid::{Uuid4, Uuid7},
652	};
653
654	#[test]
655	fn to_usize_uint1() {
656		assert_eq!(Value::uint1(42u8).to_usize(), Some(42));
657	}
658
659	#[test]
660	fn to_usize_uint2() {
661		assert_eq!(Value::uint2(1000u16).to_usize(), Some(1000));
662	}
663
664	#[test]
665	fn to_usize_uint4() {
666		assert_eq!(Value::uint4(100_000u32).to_usize(), Some(100_000));
667	}
668
669	#[test]
670	fn to_usize_uint8() {
671		assert_eq!(Value::uint8(1_000_000u64).to_usize(), Some(1_000_000));
672	}
673
674	#[test]
675	fn to_usize_uint16() {
676		assert_eq!(Value::Uint16(500u128).to_usize(), Some(500));
677	}
678
679	#[test]
680	fn to_usize_int1() {
681		assert_eq!(Value::int1(100i8).to_usize(), Some(100));
682	}
683
684	#[test]
685	fn to_usize_int2() {
686		assert_eq!(Value::int2(5000i16).to_usize(), Some(5000));
687	}
688
689	#[test]
690	fn to_usize_int4() {
691		assert_eq!(Value::int4(50_000i32).to_usize(), Some(50_000));
692	}
693
694	#[test]
695	fn to_usize_int8() {
696		assert_eq!(Value::int8(1_000_000i64).to_usize(), Some(1_000_000));
697	}
698
699	#[test]
700	fn to_usize_int16() {
701		assert_eq!(Value::Int16(999i128).to_usize(), Some(999));
702	}
703
704	#[test]
705	fn to_usize_float4() {
706		assert_eq!(Value::float4(42.0f32).to_usize(), Some(42));
707	}
708
709	#[test]
710	fn to_usize_float8() {
711		assert_eq!(Value::float8(42.0f64).to_usize(), Some(42));
712	}
713
714	#[test]
715	fn to_usize_int_bigint() {
716		assert_eq!(Value::Int(Int::from_i64(42)).to_usize(), Some(42));
717	}
718
719	#[test]
720	fn to_usize_uint_bigint() {
721		assert_eq!(Value::Uint(Uint::from_u64(42)).to_usize(), Some(42));
722	}
723
724	#[test]
725	fn to_usize_decimal() {
726		assert_eq!(Value::Decimal(Decimal::from_i64(42)).to_usize(), Some(42));
727	}
728
729	#[test]
730	fn to_usize_int1_negative() {
731		assert_eq!(Value::int1(-1i8).to_usize(), None);
732	}
733
734	#[test]
735	fn to_usize_int2_negative() {
736		assert_eq!(Value::int2(-100i16).to_usize(), None);
737	}
738
739	#[test]
740	fn to_usize_int4_negative() {
741		assert_eq!(Value::int4(-1i32).to_usize(), None);
742	}
743
744	#[test]
745	fn to_usize_int8_negative() {
746		assert_eq!(Value::int8(-1i64).to_usize(), None);
747	}
748
749	#[test]
750	fn to_usize_int16_negative() {
751		assert_eq!(Value::Int16(-1i128).to_usize(), None);
752	}
753
754	#[test]
755	fn to_usize_float4_negative() {
756		assert_eq!(Value::float4(-1.0f32).to_usize(), None);
757	}
758
759	#[test]
760	fn to_usize_float8_negative() {
761		assert_eq!(Value::float8(-1.0f64).to_usize(), None);
762	}
763
764	#[test]
765	fn to_usize_int_bigint_negative() {
766		assert_eq!(Value::Int(Int::from_i64(-5)).to_usize(), None);
767	}
768
769	#[test]
770	fn to_usize_zero() {
771		assert_eq!(Value::uint1(0u8).to_usize(), Some(0));
772	}
773
774	#[test]
775	fn to_usize_int1_zero() {
776		assert_eq!(Value::int1(0i8).to_usize(), Some(0));
777	}
778
779	#[test]
780	fn to_usize_float4_zero() {
781		assert_eq!(Value::float4(0.0f32).to_usize(), Some(0));
782	}
783
784	#[test]
785	fn to_usize_boolean_none() {
786		assert_eq!(Value::bool(true).to_usize(), None);
787	}
788
789	#[test]
790	fn to_usize_utf8_integer() {
791		assert_eq!(Value::utf8("42").to_usize(), Some(42));
792	}
793
794	#[test]
795	fn to_usize_utf8_float() {
796		assert_eq!(Value::utf8("3.7").to_usize(), Some(3));
797	}
798
799	#[test]
800	fn to_usize_utf8_negative() {
801		assert_eq!(Value::utf8("-5").to_usize(), None);
802	}
803
804	#[test]
805	fn to_usize_utf8_negative_float() {
806		assert_eq!(Value::utf8("-1.5").to_usize(), None);
807	}
808
809	#[test]
810	fn to_usize_utf8_whitespace() {
811		assert_eq!(Value::utf8("  42  ").to_usize(), Some(42));
812	}
813
814	#[test]
815	fn to_usize_utf8_zero() {
816		assert_eq!(Value::utf8("0").to_usize(), Some(0));
817	}
818
819	#[test]
820	fn to_usize_utf8_non_numeric() {
821		assert_eq!(Value::utf8("hello").to_usize(), None);
822	}
823
824	#[test]
825	fn to_usize_utf8_empty() {
826		assert_eq!(Value::utf8("").to_usize(), None);
827	}
828
829	#[test]
830	fn to_usize_none_none() {
831		assert_eq!(Value::none().to_usize(), None);
832	}
833
834	#[test]
835	fn to_usize_float8_fractional() {
836		assert_eq!(Value::float8(3.7f64).to_usize(), Some(3));
837	}
838
839	#[test]
840	fn to_usize_decimal_fractional() {
841		assert_eq!(Value::Decimal(Decimal::from_str("3.7").unwrap()).to_usize(), Some(3));
842	}
843
844	#[test]
845	fn test_none_with_same_inner_type_are_equal() {
846		// A none carries its inner type, so equality must compare it: a round trip through Any
847		// encoding relies on `==` catching an inner type that was lost or changed.
848		assert_eq!(Value::none_of(ValueType::Duration), Value::none_of(ValueType::Duration));
849	}
850
851	#[test]
852	fn test_none_with_different_inner_type_are_not_equal() {
853		assert_ne!(Value::none_of(ValueType::Duration), Value::none_of(ValueType::Boolean));
854	}
855
856	#[test]
857	fn test_none_with_different_nesting_depth_are_not_equal() {
858		let option_duration = Value::none_of(ValueType::Option(Box::new(ValueType::Duration)));
859		let duration = Value::none_of(ValueType::Duration);
860		assert_ne!(option_duration, duration);
861	}
862
863	#[test]
864	fn test_none_any_is_not_equal_to_none_of_concrete_type() {
865		assert_ne!(Value::none(), Value::none_of(ValueType::Duration));
866	}
867
868	#[test]
869	fn test_value_every_arm_round_trips() {
870		// A lossy arm silently corrupts any persisted operator state built from that variant.
871		let values = vec![
872			Value::None {
873				inner: ValueType::Utf8,
874			},
875			Value::Boolean(true),
876			Value::Float4(OrderedF32::try_from(1.5f32).unwrap()),
877			Value::Float8(OrderedF64::try_from(-2.25f64).unwrap()),
878			Value::Int1(-8),
879			Value::Int2(-1_600),
880			Value::Int4(-320_000),
881			Value::Int8(-64_000_000_000),
882			Value::Int16(i128::MIN),
883			Value::Utf8("state".to_string()),
884			Value::Uint1(8),
885			Value::Uint2(1_600),
886			Value::Uint4(320_000),
887			Value::Uint8(64_000_000_000),
888			Value::Uint16(u128::MAX),
889			Value::Date(Date::new(2026, 7, 20).unwrap()),
890			Value::DateTime(DateTime::new(2026, 7, 20, 12, 34, 56, 789).unwrap()),
891			Value::Time(Time::new(23, 59, 59, 1).unwrap()),
892			Value::Duration(Duration::new(1, 2, 3).unwrap()),
893			Value::IdentityId(IdentityId(Uuid7(StdUuid::from_bytes([
894				0x01, 0x8F, 0x2A, 0x3B, 0x4C, 0x5D, 0x70, 0x07, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00,
895				0x00, 0x07,
896			])))),
897			Value::Uuid4(Uuid4(StdUuid::from_u128(4))),
898			Value::Uuid7(Uuid7(StdUuid::from_u128(77))),
899			Value::Blob(Blob::new(vec![1, 2, 3])),
900			Value::Int(Int::from_i128(i128::MIN)),
901			Value::Uint(Uint::from_u128(u128::MAX)),
902			Value::Decimal(Decimal(BigDecimal::new(BigInt::from(-12345), 3))),
903			Value::Any(Box::new(Value::Boolean(false))),
904			Value::DictionaryId(DictionaryEntryId::U16(u128::MAX)),
905			Value::Type(ValueType::Record(vec![("k".to_string(), ValueType::Int4)])),
906			Value::List(vec![Value::Int4(1), Value::Utf8("x".to_string())]),
907			Value::Record(vec![("k".to_string(), Value::Int8(9))]),
908			Value::Tuple(vec![
909				Value::Boolean(true),
910				Value::None {
911					inner: ValueType::Any,
912				},
913			]),
914		];
915
916		let bytes = to_allocvec(&values).unwrap();
917		let restored: Vec<Value> = from_bytes(&bytes).unwrap();
918		assert_eq!(restored, values);
919	}
920}