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