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