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