Skip to main content

reifydb_value/value/
mod.rs

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