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