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