Skip to main content

reifydb_type/value/type/
mod.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	fmt::{Display, Formatter},
6	str::FromStr,
7};
8
9use serde::{Deserialize, Serialize};
10
11pub mod get;
12pub mod promote;
13
14use std::fmt;
15
16use crate::value::Value;
17
18/// All possible RQL data types
19#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
20pub enum Type {
21	/// A boolean: true or false.
22	Boolean,
23	/// A 4-byte floating point
24	Float4,
25	/// An 8-byte floating point
26	Float8,
27	/// A 1-byte signed integer
28	Int1,
29	/// A 2-byte signed integer
30	Int2,
31	/// A 4-byte signed integer
32	Int4,
33	/// An 8-byte signed integer
34	Int8,
35	/// A 16-byte signed integer
36	Int16,
37	/// A UTF-8 encoded text.
38	Utf8,
39	/// A 1-byte unsigned integer
40	Uint1,
41	/// A 2-byte unsigned integer
42	Uint2,
43	/// A 4-byte unsigned integer
44	Uint4,
45	/// A 8-byte unsigned integer
46	Uint8,
47	/// A 16-byte unsigned integer
48	Uint16,
49	/// A date value (year, month, day)
50	Date,
51	/// A date and time value with nanosecond precision in SVTC
52	DateTime,
53	/// A time value (hour, minute, second, nanosecond)
54	Time,
55	/// A duration representing a duration
56	Duration,
57	/// An identity identifier (UUID v7)
58	IdentityId,
59	/// A UUID version 4 (random)
60	Uuid4,
61	/// A UUID version 7 (timestamp-based)
62	Uuid7,
63	/// A binary large object (BLOB)
64	Blob,
65	/// An arbitrary-precision signed integer
66	Int,
67	/// An arbitrary-precision unsigned integer
68	Uint,
69	/// An arbitrary-precision decimal with precision and scale
70	Decimal,
71	/// An optional type that can hold None or a value of the inner type
72	Option(Box<Type>),
73	/// A container that can hold any value type
74	Any,
75	/// A dictionary entry identifier
76	DictionaryId,
77	/// An ordered list of values of a given element type
78	List(Box<Type>),
79}
80
81impl Type {
82	pub fn list_of(ty: Type) -> Self {
83		Type::List(Box::new(ty))
84	}
85
86	pub fn is_number(&self) -> bool {
87		match self {
88			Type::Option(inner) => inner.is_number(),
89			_ => matches!(
90				self,
91				Type::Float4
92					| Type::Float8 | Type::Int1 | Type::Int2
93					| Type::Int4 | Type::Int8 | Type::Int16
94					| Type::Uint1 | Type::Uint2 | Type::Uint4
95					| Type::Uint8 | Type::Uint16 | Type::Int
96					| Type::Uint | Type::Decimal
97			),
98		}
99	}
100
101	pub fn is_bool(&self) -> bool {
102		match self {
103			Type::Option(inner) => inner.is_bool(),
104			_ => matches!(self, Type::Boolean),
105		}
106	}
107
108	pub fn is_signed_integer(&self) -> bool {
109		match self {
110			Type::Option(inner) => inner.is_signed_integer(),
111			_ => matches!(
112				self,
113				Type::Int1 | Type::Int2 | Type::Int4 | Type::Int8 | Type::Int16 | Type::Int
114			),
115		}
116	}
117
118	pub fn is_unsigned_integer(&self) -> bool {
119		match self {
120			Type::Option(inner) => inner.is_unsigned_integer(),
121			_ => matches!(
122				self,
123				Type::Uint1 | Type::Uint2 | Type::Uint4 | Type::Uint8 | Type::Uint16 | Type::Uint
124			),
125		}
126	}
127
128	pub fn is_integer(&self) -> bool {
129		self.is_signed_integer() || self.is_unsigned_integer()
130	}
131
132	pub fn is_floating_point(&self) -> bool {
133		match self {
134			Type::Option(inner) => inner.is_floating_point(),
135			_ => matches!(self, Type::Float4 | Type::Float8),
136		}
137	}
138
139	pub fn is_utf8(&self) -> bool {
140		match self {
141			Type::Option(inner) => inner.is_utf8(),
142			_ => matches!(self, Type::Utf8),
143		}
144	}
145
146	pub fn is_temporal(&self) -> bool {
147		match self {
148			Type::Option(inner) => inner.is_temporal(),
149			_ => matches!(self, Type::Date | Type::DateTime | Type::Time | Type::Duration),
150		}
151	}
152
153	pub fn is_uuid(&self) -> bool {
154		match self {
155			Type::Option(inner) => inner.is_uuid(),
156			_ => matches!(self, Type::Uuid4 | Type::Uuid7),
157		}
158	}
159
160	pub fn is_blob(&self) -> bool {
161		match self {
162			Type::Option(inner) => inner.is_blob(),
163			_ => matches!(self, Type::Blob),
164		}
165	}
166
167	pub fn is_option(&self) -> bool {
168		matches!(self, Type::Option(_))
169	}
170
171	/// Returns the inner type if this is an Option type, otherwise returns self
172	pub fn inner_type(&self) -> &Type {
173		match self {
174			Type::Option(inner) => inner,
175			other => other,
176		}
177	}
178}
179
180impl Type {
181	pub fn to_u8(&self) -> u8 {
182		match self {
183			Type::Option(inner) => 0x80 | inner.to_u8(),
184			Type::Float4 => 1,
185			Type::Float8 => 2,
186			Type::Int1 => 3,
187			Type::Int2 => 4,
188			Type::Int4 => 5,
189			Type::Int8 => 6,
190			Type::Int16 => 7,
191			Type::Utf8 => 8,
192			Type::Uint1 => 9,
193			Type::Uint2 => 10,
194			Type::Uint4 => 11,
195			Type::Uint8 => 12,
196			Type::Uint16 => 13,
197			Type::Boolean => 14,
198			Type::Date => 15,
199			Type::DateTime => 16,
200			Type::Time => 17,
201			Type::Duration => 18,
202			Type::IdentityId => 19,
203			Type::Uuid4 => 20,
204			Type::Uuid7 => 21,
205			Type::Blob => 22,
206			Type::Int => 23,
207			Type::Decimal {
208				..
209			} => 24,
210			Type::Uint => 25,
211			Type::Any => 26,
212			Type::DictionaryId => 27,
213			Type::List(_) => 28,
214		}
215	}
216}
217
218impl Type {
219	pub fn from_u8(value: u8) -> Self {
220		if value & 0x80 != 0 {
221			return Type::Option(Box::new(Type::from_u8(value & 0x7F)));
222		}
223		match value {
224			1 => Type::Float4,
225			2 => Type::Float8,
226			3 => Type::Int1,
227			4 => Type::Int2,
228			5 => Type::Int4,
229			6 => Type::Int8,
230			7 => Type::Int16,
231			8 => Type::Utf8,
232			9 => Type::Uint1,
233			10 => Type::Uint2,
234			11 => Type::Uint4,
235			12 => Type::Uint8,
236			13 => Type::Uint16,
237			14 => Type::Boolean,
238			15 => Type::Date,
239			16 => Type::DateTime,
240			17 => Type::Time,
241			18 => Type::Duration,
242			19 => Type::IdentityId,
243			20 => Type::Uuid4,
244			21 => Type::Uuid7,
245			22 => Type::Blob,
246			23 => Type::Int,
247			24 => Type::Decimal,
248			25 => Type::Uint,
249			26 => Type::Any,
250			27 => Type::DictionaryId,
251			28 => Type::list_of(Type::Any),
252			_ => unreachable!(),
253		}
254	}
255}
256
257impl Type {
258	pub fn size(&self) -> usize {
259		match self {
260			Type::Boolean => 1,
261			Type::Float4 => 4,
262			Type::Float8 => 8,
263			Type::Int1 => 1,
264			Type::Int2 => 2,
265			Type::Int4 => 4,
266			Type::Int8 => 8,
267			Type::Int16 => 16,
268			Type::Utf8 => 8, // offset: u32 + length: u32
269			Type::Uint1 => 1,
270			Type::Uint2 => 2,
271			Type::Uint4 => 4,
272			Type::Uint8 => 8,
273			Type::Uint16 => 16,
274			Type::Date => 4,
275			Type::DateTime => 12, // seconds: i64 + nanos: u32
276			Type::Time => 8,
277			Type::Duration => 16, // months: i32 + days: i32 +
278			// nanos: i64
279			Type::IdentityId => 16, // UUID v7 is 16 bytes
280			Type::Uuid4 => 16,
281			Type::Uuid7 => 16,
282			Type::Blob => 8, // offset: u32 + length: u32
283			Type::Int => 16, // i128 inline or dynamic
284			// storage with offset + length
285			Type::Uint => 16, // u128 inline or dynamic
286			// storage with offset + length
287			Type::Decimal {
288				..
289			} => 16, // i128 inline or dynamic
290			// storage with offset + length
291			Type::Option(inner) => inner.size(), // size determined by inner type
292			Type::Any => 8,                      // pointer size on 64-bit systems
293			Type::List(_) => 8,                  // pointer size (Vec is heap-allocated)
294			Type::DictionaryId => 16,            /* max possible; actual size determined by constraint's
295			                                       * id_type */
296		}
297	}
298
299	pub fn alignment(&self) -> usize {
300		match self {
301			Type::Boolean => 1,
302			Type::Float4 => 4,
303			Type::Float8 => 8,
304			Type::Int1 => 1,
305			Type::Int2 => 2,
306			Type::Int4 => 4,
307			Type::Int8 => 8,
308			Type::Int16 => 16,
309			Type::Utf8 => 4, // u32 alignment
310			Type::Uint1 => 1,
311			Type::Uint2 => 2,
312			Type::Uint4 => 4,
313			Type::Uint8 => 8,
314			Type::Uint16 => 16,
315			Type::Date => 4,
316			Type::DateTime => 8,
317			Type::Time => 8,
318			Type::Duration => 8,
319			Type::IdentityId => 8, // Same alignment as UUID
320			Type::Uuid4 => 8,
321			Type::Uuid7 => 8,
322			Type::Blob => 4, // u32 alignment
323			Type::Int => 16, // i128 alignment for
324			// inline storage
325			Type::Uint => 16, // u128 alignment for
326			// inline storage
327			Type::Decimal {
328				..
329			} => 16, // i128 alignment for
330			// inline storage
331			Type::Option(inner) => inner.alignment(),
332			Type::Any => 8, // pointer alignment
333			Type::DictionaryId => 16,
334			Type::List(_) => 8, // pointer alignment
335		}
336	}
337}
338
339impl Display for Type {
340	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
341		match self {
342			Type::Boolean => f.write_str("Boolean"),
343			Type::Float4 => f.write_str("Float4"),
344			Type::Float8 => f.write_str("Float8"),
345			Type::Int1 => f.write_str("Int1"),
346			Type::Int2 => f.write_str("Int2"),
347			Type::Int4 => f.write_str("Int4"),
348			Type::Int8 => f.write_str("Int8"),
349			Type::Int16 => f.write_str("Int16"),
350			Type::Utf8 => f.write_str("Utf8"),
351			Type::Uint1 => f.write_str("Uint1"),
352			Type::Uint2 => f.write_str("Uint2"),
353			Type::Uint4 => f.write_str("Uint4"),
354			Type::Uint8 => f.write_str("Uint8"),
355			Type::Uint16 => f.write_str("Uint16"),
356			Type::Date => f.write_str("Date"),
357			Type::DateTime => f.write_str("DateTime"),
358			Type::Time => f.write_str("Time"),
359			Type::Duration => f.write_str("Duration"),
360			Type::IdentityId => f.write_str("IdentityId"),
361			Type::Uuid4 => f.write_str("Uuid4"),
362			Type::Uuid7 => f.write_str("Uuid7"),
363			Type::Blob => f.write_str("Blob"),
364			Type::Int => f.write_str("Int"),
365			Type::Uint => f.write_str("Uint"),
366			Type::Decimal => f.write_str("Decimal"),
367			Type::Option(inner) => write!(f, "Option({inner})"),
368			Type::Any => f.write_str("Any"),
369			Type::DictionaryId => f.write_str("DictionaryId"),
370			Type::List(inner) => write!(f, "List({inner})"),
371		}
372	}
373}
374
375impl From<&Value> for Type {
376	fn from(value: &Value) -> Self {
377		match value {
378			Value::None {
379				inner,
380			} => Type::Option(Box::new(inner.clone())),
381			Value::Boolean(_) => Type::Boolean,
382			Value::Float4(_) => Type::Float4,
383			Value::Float8(_) => Type::Float8,
384			Value::Int1(_) => Type::Int1,
385			Value::Int2(_) => Type::Int2,
386			Value::Int4(_) => Type::Int4,
387			Value::Int8(_) => Type::Int8,
388			Value::Int16(_) => Type::Int16,
389			Value::Utf8(_) => Type::Utf8,
390			Value::Uint1(_) => Type::Uint1,
391			Value::Uint2(_) => Type::Uint2,
392			Value::Uint4(_) => Type::Uint4,
393			Value::Uint8(_) => Type::Uint8,
394			Value::Uint16(_) => Type::Uint16,
395			Value::Date(_) => Type::Date,
396			Value::DateTime(_) => Type::DateTime,
397			Value::Time(_) => Type::Time,
398			Value::Duration(_) => Type::Duration,
399			Value::IdentityId(_) => Type::IdentityId,
400			Value::Uuid4(_) => Type::Uuid4,
401			Value::Uuid7(_) => Type::Uuid7,
402			Value::Blob(_) => Type::Blob,
403			Value::Int(_) => Type::Int,
404			Value::Uint(_) => Type::Uint,
405			Value::Decimal(_) => Type::Decimal,
406			Value::Any(_) => Type::Any,
407			Value::DictionaryId(_) => Type::DictionaryId,
408			Value::Type(t) => t.clone(),
409			Value::List(items) => {
410				let element_type = items.first().map(|v| Type::from(v)).unwrap_or(Type::Any);
411				Type::list_of(element_type)
412			}
413		}
414	}
415}
416
417impl FromStr for Type {
418	type Err = ();
419
420	fn from_str(s: &str) -> Result<Self, Self::Err> {
421		let upper = s.to_uppercase();
422		// Handle Option<T> syntax
423		if upper.starts_with("OPTION<") && upper.ends_with('>') {
424			let inner_str = &s[7..s.len() - 1]; // extract between "OPTION<" and ">"
425			let inner = Type::from_str(inner_str)?;
426			return Ok(Type::Option(Box::new(inner)));
427		}
428		match upper.as_str() {
429			"BOOL" | "BOOLEAN" => Ok(Type::Boolean),
430			"FLOAT4" => Ok(Type::Float4),
431			"FLOAT8" => Ok(Type::Float8),
432			"INT1" => Ok(Type::Int1),
433			"INT2" => Ok(Type::Int2),
434			"INT4" => Ok(Type::Int4),
435			"INT8" => Ok(Type::Int8),
436			"INT16" => Ok(Type::Int16),
437			"UTF8" | "TEXT" => Ok(Type::Utf8),
438			"UINT1" => Ok(Type::Uint1),
439			"UINT2" => Ok(Type::Uint2),
440			"UINT4" => Ok(Type::Uint4),
441			"UINT8" => Ok(Type::Uint8),
442			"UINT16" => Ok(Type::Uint16),
443			"DATE" => Ok(Type::Date),
444			"DATETIME" => Ok(Type::DateTime),
445			"TIME" => Ok(Type::Time),
446			"DURATION" | "INTERVAL" => Ok(Type::Duration),
447			"IDENTITYID" | "IDENTITY_ID" => Ok(Type::IdentityId),
448			"UUID4" => Ok(Type::Uuid4),
449			"UUID7" => Ok(Type::Uuid7),
450			"BLOB" => Ok(Type::Blob),
451			"INT" => Ok(Type::Int),
452			"UINT" => Ok(Type::Uint),
453			"DECIMAL" => Ok(Type::Decimal),
454			"ANY" => Ok(Type::Any),
455			"DICTIONARYID" | "DICTIONARY_ID" => Ok(Type::DictionaryId),
456			_ => Err(()),
457		}
458	}
459}