Skip to main content

reifydb_type/value/
mod.rs

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