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