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		match (self, other) {
399			(Value::Boolean(l), Value::Boolean(r)) => l.partial_cmp(r),
400			(Value::Float4(l), Value::Float4(r)) => l.partial_cmp(r),
401			(Value::Float8(l), Value::Float8(r)) => l.partial_cmp(r),
402			(Value::Int1(l), Value::Int1(r)) => l.partial_cmp(r),
403			(Value::Int2(l), Value::Int2(r)) => l.partial_cmp(r),
404			(Value::Int4(l), Value::Int4(r)) => l.partial_cmp(r),
405			(Value::Int8(l), Value::Int8(r)) => l.partial_cmp(r),
406			(Value::Int16(l), Value::Int16(r)) => l.partial_cmp(r),
407			(Value::Utf8(l), Value::Utf8(r)) => l.partial_cmp(r),
408			(Value::Uint1(l), Value::Uint1(r)) => l.partial_cmp(r),
409			(Value::Uint2(l), Value::Uint2(r)) => l.partial_cmp(r),
410			(Value::Uint4(l), Value::Uint4(r)) => l.partial_cmp(r),
411			(Value::Uint8(l), Value::Uint8(r)) => l.partial_cmp(r),
412			(Value::Uint16(l), Value::Uint16(r)) => l.partial_cmp(r),
413			(Value::Date(l), Value::Date(r)) => l.partial_cmp(r),
414			(Value::DateTime(l), Value::DateTime(r)) => l.partial_cmp(r),
415			(Value::Time(l), Value::Time(r)) => l.partial_cmp(r),
416			(Value::Duration(l), Value::Duration(r)) => l.partial_cmp(r),
417			(Value::IdentityId(l), Value::IdentityId(r)) => l.partial_cmp(r),
418			(Value::Uuid4(l), Value::Uuid4(r)) => l.partial_cmp(r),
419			(Value::Uuid7(l), Value::Uuid7(r)) => l.partial_cmp(r),
420			(Value::Blob(l), Value::Blob(r)) => l.partial_cmp(r),
421			(Value::Int(l), Value::Int(r)) => l.partial_cmp(r),
422			(Value::Uint(l), Value::Uint(r)) => l.partial_cmp(r),
423			(Value::Decimal(l), Value::Decimal(r)) => l.partial_cmp(r),
424			(Value::DictionaryId(l), Value::DictionaryId(r)) => l.to_u128().partial_cmp(&r.to_u128()),
425			(Value::Type(l), Value::Type(r)) => l.partial_cmp(r),
426			(Value::List(_), Value::List(_)) => None,     // Lists are not orderable
427			(Value::Record(_), Value::Record(_)) => None, // Records are not orderable
428			(Value::Tuple(_), Value::Tuple(_)) => None,   // Tuples are not orderable
429			(Value::Any(_), Value::Any(_)) => None,       // Any values are not comparable
430			(
431				Value::None {
432					..
433				},
434				Value::None {
435					..
436				},
437			) => Some(Ordering::Equal),
438			// None sorts after all other values (similar to NULL in SQL)
439			(
440				Value::None {
441					..
442				},
443				_,
444			) => Some(Ordering::Greater),
445			(
446				_,
447				Value::None {
448					..
449				},
450			) => Some(Ordering::Less),
451			(left, right) => {
452				unimplemented!("partial cmp {left:?} {right:?}")
453			}
454		}
455	}
456}
457
458impl Ord for Value {
459	fn cmp(&self, other: &Self) -> Ordering {
460		match (self, other) {
461			(
462				Value::None {
463					..
464				},
465				Value::None {
466					..
467				},
468			) => Ordering::Equal,
469			(
470				Value::None {
471					..
472				},
473				_,
474			) => Ordering::Greater,
475			(
476				_,
477				Value::None {
478					..
479				},
480			) => Ordering::Less,
481			(Value::Boolean(l), Value::Boolean(r)) => l.cmp(r),
482			(Value::Float4(l), Value::Float4(r)) => l.cmp(r),
483			(Value::Float8(l), Value::Float8(r)) => l.cmp(r),
484			(Value::Int1(l), Value::Int1(r)) => l.cmp(r),
485			(Value::Int2(l), Value::Int2(r)) => l.cmp(r),
486			(Value::Int4(l), Value::Int4(r)) => l.cmp(r),
487			(Value::Int8(l), Value::Int8(r)) => l.cmp(r),
488			(Value::Int16(l), Value::Int16(r)) => l.cmp(r),
489			(Value::Utf8(l), Value::Utf8(r)) => l.cmp(r),
490			(Value::Uint1(l), Value::Uint1(r)) => l.cmp(r),
491			(Value::Uint2(l), Value::Uint2(r)) => l.cmp(r),
492			(Value::Uint4(l), Value::Uint4(r)) => l.cmp(r),
493			(Value::Uint8(l), Value::Uint8(r)) => l.cmp(r),
494			(Value::Uint16(l), Value::Uint16(r)) => l.cmp(r),
495			(Value::Date(l), Value::Date(r)) => l.cmp(r),
496			(Value::DateTime(l), Value::DateTime(r)) => l.cmp(r),
497			(Value::Time(l), Value::Time(r)) => l.cmp(r),
498			(Value::Duration(l), Value::Duration(r)) => l.cmp(r),
499			(Value::IdentityId(l), Value::IdentityId(r)) => l.cmp(r),
500			(Value::Uuid4(l), Value::Uuid4(r)) => l.cmp(r),
501			(Value::Uuid7(l), Value::Uuid7(r)) => l.cmp(r),
502			(Value::Blob(l), Value::Blob(r)) => l.cmp(r),
503			(Value::Int(l), Value::Int(r)) => l.cmp(r),
504			(Value::Uint(l), Value::Uint(r)) => l.cmp(r),
505			(Value::Decimal(l), Value::Decimal(r)) => l.cmp(r),
506			(Value::DictionaryId(l), Value::DictionaryId(r)) => l.to_u128().cmp(&r.to_u128()),
507			(Value::Type(l), Value::Type(r)) => l.cmp(r),
508			(Value::List(_), Value::List(_)) => unreachable!("List values are not orderable"),
509			(Value::Record(_), Value::Record(_)) => unreachable!("Record values are not orderable"),
510			(Value::Tuple(_), Value::Tuple(_)) => unreachable!("Tuple values are not orderable"),
511			(Value::Any(_), Value::Any(_)) => unreachable!("Any values are not orderable"),
512			_ => unimplemented!(),
513		}
514	}
515}
516
517impl Display for Value {
518	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
519		match self {
520			Value::Boolean(true) => f.write_str("true"),
521			Value::Boolean(false) => f.write_str("false"),
522			Value::Float4(value) => Display::fmt(value, f),
523			Value::Float8(value) => Display::fmt(value, f),
524			Value::Int1(value) => Display::fmt(value, f),
525			Value::Int2(value) => Display::fmt(value, f),
526			Value::Int4(value) => Display::fmt(value, f),
527			Value::Int8(value) => Display::fmt(value, f),
528			Value::Int16(value) => Display::fmt(value, f),
529			Value::Utf8(value) => Display::fmt(value, f),
530			Value::Uint1(value) => Display::fmt(value, f),
531			Value::Uint2(value) => Display::fmt(value, f),
532			Value::Uint4(value) => Display::fmt(value, f),
533			Value::Uint8(value) => Display::fmt(value, f),
534			Value::Uint16(value) => Display::fmt(value, f),
535			Value::Date(value) => Display::fmt(value, f),
536			Value::DateTime(value) => Display::fmt(value, f),
537			Value::Time(value) => Display::fmt(value, f),
538			Value::Duration(value) => Display::fmt(value, f),
539			Value::IdentityId(value) => Display::fmt(value, f),
540			Value::Uuid4(value) => Display::fmt(value, f),
541			Value::Uuid7(value) => Display::fmt(value, f),
542			Value::Blob(value) => Display::fmt(value, f),
543			Value::Int(value) => Display::fmt(value, f),
544			Value::Uint(value) => Display::fmt(value, f),
545			Value::Decimal(value) => Display::fmt(value, f),
546			Value::Any(value) => Display::fmt(value, f),
547			Value::DictionaryId(value) => Display::fmt(value, f),
548			Value::Type(value) => Display::fmt(value, f),
549			Value::List(items) => {
550				f.write_str("[")?;
551				for (i, item) in items.iter().enumerate() {
552					if i > 0 {
553						f.write_str(", ")?;
554					}
555					Display::fmt(item, f)?;
556				}
557				f.write_str("]")
558			}
559			Value::Record(fields) => {
560				f.write_str("{")?;
561				for (i, (key, value)) in fields.iter().enumerate() {
562					if i > 0 {
563						f.write_str(", ")?;
564					}
565					write!(f, "{}: {}", key, value)?;
566				}
567				f.write_str("}")
568			}
569			Value::Tuple(items) => {
570				f.write_str("(")?;
571				for (i, item) in items.iter().enumerate() {
572					if i > 0 {
573						f.write_str(", ")?;
574					}
575					Display::fmt(item, f)?;
576				}
577				f.write_str(")")
578			}
579			Value::None {
580				..
581			} => f.write_str("none"),
582		}
583	}
584}
585
586impl Value {
587	pub fn get_type(&self) -> Type {
588		match self {
589			Value::None {
590				inner,
591			} => Type::Option(Box::new(inner.clone())),
592			Value::Boolean(_) => Type::Boolean,
593			Value::Float4(_) => Type::Float4,
594			Value::Float8(_) => Type::Float8,
595			Value::Int1(_) => Type::Int1,
596			Value::Int2(_) => Type::Int2,
597			Value::Int4(_) => Type::Int4,
598			Value::Int8(_) => Type::Int8,
599			Value::Int16(_) => Type::Int16,
600			Value::Utf8(_) => Type::Utf8,
601			Value::Uint1(_) => Type::Uint1,
602			Value::Uint2(_) => Type::Uint2,
603			Value::Uint4(_) => Type::Uint4,
604			Value::Uint8(_) => Type::Uint8,
605			Value::Uint16(_) => Type::Uint16,
606			Value::Date(_) => Type::Date,
607			Value::DateTime(_) => Type::DateTime,
608			Value::Time(_) => Type::Time,
609			Value::Duration(_) => Type::Duration,
610			Value::IdentityId(_) => Type::IdentityId,
611			Value::Uuid4(_) => Type::Uuid4,
612			Value::Uuid7(_) => Type::Uuid7,
613			Value::Blob(_) => Type::Blob,
614			Value::Int(_) => Type::Int,
615			Value::Uint(_) => Type::Uint,
616			Value::Decimal(_) => Type::Decimal,
617			Value::Any(_) => Type::Any,
618			Value::DictionaryId(_) => Type::DictionaryId,
619			Value::Type(t) => t.clone(),
620			Value::List(items) => {
621				let element_type = items.first().map(|v| v.get_type()).unwrap_or(Type::Any);
622				Type::list_of(element_type)
623			}
624			Value::Record(fields) => {
625				Type::Record(fields.iter().map(|(k, v)| (k.clone(), v.get_type())).collect())
626			}
627			Value::Tuple(items) => Type::Tuple(items.iter().map(|v| v.get_type()).collect()),
628		}
629	}
630}
631
632#[cfg(test)]
633mod tests {
634	use std::str::FromStr;
635
636	use super::*;
637
638	// Happy path — one per numeric type
639
640	#[test]
641	fn to_usize_uint1() {
642		assert_eq!(Value::uint1(42u8).to_usize(), Some(42));
643	}
644
645	#[test]
646	fn to_usize_uint2() {
647		assert_eq!(Value::uint2(1000u16).to_usize(), Some(1000));
648	}
649
650	#[test]
651	fn to_usize_uint4() {
652		assert_eq!(Value::uint4(100_000u32).to_usize(), Some(100_000));
653	}
654
655	#[test]
656	fn to_usize_uint8() {
657		assert_eq!(Value::uint8(1_000_000u64).to_usize(), Some(1_000_000));
658	}
659
660	#[test]
661	fn to_usize_uint16() {
662		assert_eq!(Value::Uint16(500u128).to_usize(), Some(500));
663	}
664
665	#[test]
666	fn to_usize_int1() {
667		assert_eq!(Value::int1(100i8).to_usize(), Some(100));
668	}
669
670	#[test]
671	fn to_usize_int2() {
672		assert_eq!(Value::int2(5000i16).to_usize(), Some(5000));
673	}
674
675	#[test]
676	fn to_usize_int4() {
677		assert_eq!(Value::int4(50_000i32).to_usize(), Some(50_000));
678	}
679
680	#[test]
681	fn to_usize_int8() {
682		assert_eq!(Value::int8(1_000_000i64).to_usize(), Some(1_000_000));
683	}
684
685	#[test]
686	fn to_usize_int16() {
687		assert_eq!(Value::Int16(999i128).to_usize(), Some(999));
688	}
689
690	#[test]
691	fn to_usize_float4() {
692		assert_eq!(Value::float4(42.0f32).to_usize(), Some(42));
693	}
694
695	#[test]
696	fn to_usize_float8() {
697		assert_eq!(Value::float8(42.0f64).to_usize(), Some(42));
698	}
699
700	#[test]
701	fn to_usize_int_bigint() {
702		assert_eq!(Value::Int(Int::from_i64(42)).to_usize(), Some(42));
703	}
704
705	#[test]
706	fn to_usize_uint_bigint() {
707		assert_eq!(Value::Uint(Uint::from_u64(42)).to_usize(), Some(42));
708	}
709
710	#[test]
711	fn to_usize_decimal() {
712		assert_eq!(Value::Decimal(Decimal::from_i64(42)).to_usize(), Some(42));
713	}
714
715	// Edge cases & errors — negative numbers
716
717	#[test]
718	fn to_usize_int1_negative() {
719		assert_eq!(Value::int1(-1i8).to_usize(), None);
720	}
721
722	#[test]
723	fn to_usize_int2_negative() {
724		assert_eq!(Value::int2(-100i16).to_usize(), None);
725	}
726
727	#[test]
728	fn to_usize_int4_negative() {
729		assert_eq!(Value::int4(-1i32).to_usize(), None);
730	}
731
732	#[test]
733	fn to_usize_int8_negative() {
734		assert_eq!(Value::int8(-1i64).to_usize(), None);
735	}
736
737	#[test]
738	fn to_usize_int16_negative() {
739		assert_eq!(Value::Int16(-1i128).to_usize(), None);
740	}
741
742	#[test]
743	fn to_usize_float4_negative() {
744		assert_eq!(Value::float4(-1.0f32).to_usize(), None);
745	}
746
747	#[test]
748	fn to_usize_float8_negative() {
749		assert_eq!(Value::float8(-1.0f64).to_usize(), None);
750	}
751
752	#[test]
753	fn to_usize_int_bigint_negative() {
754		assert_eq!(Value::Int(Int::from_i64(-5)).to_usize(), None);
755	}
756
757	// Edge cases — zero boundary
758
759	#[test]
760	fn to_usize_zero() {
761		assert_eq!(Value::uint1(0u8).to_usize(), Some(0));
762	}
763
764	#[test]
765	fn to_usize_int1_zero() {
766		assert_eq!(Value::int1(0i8).to_usize(), Some(0));
767	}
768
769	#[test]
770	fn to_usize_float4_zero() {
771		assert_eq!(Value::float4(0.0f32).to_usize(), Some(0));
772	}
773
774	// Edge cases — non-numeric types return None
775
776	#[test]
777	fn to_usize_boolean_none() {
778		assert_eq!(Value::bool(true).to_usize(), None);
779	}
780
781	#[test]
782	fn to_usize_utf8_integer() {
783		assert_eq!(Value::utf8("42").to_usize(), Some(42));
784	}
785
786	#[test]
787	fn to_usize_utf8_float() {
788		assert_eq!(Value::utf8("3.7").to_usize(), Some(3));
789	}
790
791	#[test]
792	fn to_usize_utf8_negative() {
793		assert_eq!(Value::utf8("-5").to_usize(), None);
794	}
795
796	#[test]
797	fn to_usize_utf8_negative_float() {
798		assert_eq!(Value::utf8("-1.5").to_usize(), None);
799	}
800
801	#[test]
802	fn to_usize_utf8_whitespace() {
803		assert_eq!(Value::utf8("  42  ").to_usize(), Some(42));
804	}
805
806	#[test]
807	fn to_usize_utf8_zero() {
808		assert_eq!(Value::utf8("0").to_usize(), Some(0));
809	}
810
811	#[test]
812	fn to_usize_utf8_non_numeric() {
813		assert_eq!(Value::utf8("hello").to_usize(), None);
814	}
815
816	#[test]
817	fn to_usize_utf8_empty() {
818		assert_eq!(Value::utf8("").to_usize(), None);
819	}
820
821	#[test]
822	fn to_usize_none_none() {
823		assert_eq!(Value::none().to_usize(), None);
824	}
825
826	// Edge cases — fractional truncation
827
828	#[test]
829	fn to_usize_float8_fractional() {
830		assert_eq!(Value::float8(3.7f64).to_usize(), Some(3));
831	}
832
833	#[test]
834	fn to_usize_decimal_fractional() {
835		assert_eq!(Value::Decimal(Decimal::from_str("3.7").unwrap()).to_usize(), Some(3));
836	}
837}