Skip to main content

reifydb_type/value/
mod.rs

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