Skip to main content

reifydb_type/value/type/
mod.rs

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