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 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	/// A record type with named fields
80	Record(Vec<(String, Type)>),
81	/// A tuple of heterogeneous types
82	Tuple(Vec<Type>),
83}
84
85impl Type {
86	pub fn list_of(ty: Type) -> Self {
87		Type::List(Box::new(ty))
88	}
89
90	pub fn is_number(&self) -> bool {
91		match self {
92			Type::Option(inner) => inner.is_number(),
93			_ => matches!(
94				self,
95				Type::Float4
96					| Type::Float8 | Type::Int1 | Type::Int2
97					| Type::Int4 | Type::Int8 | Type::Int16
98					| Type::Uint1 | Type::Uint2 | Type::Uint4
99					| Type::Uint8 | Type::Uint16 | Type::Int
100					| Type::Uint | Type::Decimal
101			),
102		}
103	}
104
105	pub fn is_bool(&self) -> bool {
106		match self {
107			Type::Option(inner) => inner.is_bool(),
108			_ => matches!(self, Type::Boolean),
109		}
110	}
111
112	pub fn is_signed_integer(&self) -> bool {
113		match self {
114			Type::Option(inner) => inner.is_signed_integer(),
115			_ => matches!(
116				self,
117				Type::Int1 | Type::Int2 | Type::Int4 | Type::Int8 | Type::Int16 | Type::Int
118			),
119		}
120	}
121
122	pub fn is_unsigned_integer(&self) -> bool {
123		match self {
124			Type::Option(inner) => inner.is_unsigned_integer(),
125			_ => matches!(
126				self,
127				Type::Uint1 | Type::Uint2 | Type::Uint4 | Type::Uint8 | Type::Uint16 | Type::Uint
128			),
129		}
130	}
131
132	pub fn is_integer(&self) -> bool {
133		self.is_signed_integer() || self.is_unsigned_integer()
134	}
135
136	pub fn is_floating_point(&self) -> bool {
137		match self {
138			Type::Option(inner) => inner.is_floating_point(),
139			_ => matches!(self, Type::Float4 | Type::Float8),
140		}
141	}
142
143	pub fn is_utf8(&self) -> bool {
144		match self {
145			Type::Option(inner) => inner.is_utf8(),
146			_ => matches!(self, Type::Utf8),
147		}
148	}
149
150	pub fn is_temporal(&self) -> bool {
151		match self {
152			Type::Option(inner) => inner.is_temporal(),
153			_ => matches!(self, Type::Date | Type::DateTime | Type::Time | Type::Duration),
154		}
155	}
156
157	pub fn is_uuid(&self) -> bool {
158		match self {
159			Type::Option(inner) => inner.is_uuid(),
160			_ => matches!(self, Type::Uuid4 | Type::Uuid7),
161		}
162	}
163
164	pub fn is_blob(&self) -> bool {
165		match self {
166			Type::Option(inner) => inner.is_blob(),
167			_ => matches!(self, Type::Blob),
168		}
169	}
170
171	pub fn is_option(&self) -> bool {
172		matches!(self, Type::Option(_))
173	}
174
175	/// Returns the inner type if this is an Option type, otherwise returns self
176	pub fn inner_type(&self) -> &Type {
177		match self {
178			Type::Option(inner) => inner,
179			other => other,
180		}
181	}
182}
183
184impl Type {
185	pub fn to_u8(&self) -> u8 {
186		match self {
187			Type::Option(inner) => 0x80 | inner.to_u8(),
188			Type::Float4 => 1,
189			Type::Float8 => 2,
190			Type::Int1 => 3,
191			Type::Int2 => 4,
192			Type::Int4 => 5,
193			Type::Int8 => 6,
194			Type::Int16 => 7,
195			Type::Utf8 => 8,
196			Type::Uint1 => 9,
197			Type::Uint2 => 10,
198			Type::Uint4 => 11,
199			Type::Uint8 => 12,
200			Type::Uint16 => 13,
201			Type::Boolean => 14,
202			Type::Date => 15,
203			Type::DateTime => 16,
204			Type::Time => 17,
205			Type::Duration => 18,
206			Type::IdentityId => 19,
207			Type::Uuid4 => 20,
208			Type::Uuid7 => 21,
209			Type::Blob => 22,
210			Type::Int => 23,
211			Type::Decimal {
212				..
213			} => 24,
214			Type::Uint => 25,
215			Type::Any => 26,
216			Type::DictionaryId => 27,
217			Type::List(_) => 28,
218			Type::Record(_) => 29,
219			Type::Tuple(_) => 30,
220		}
221	}
222}
223
224impl Type {
225	pub fn from_u8(value: u8) -> Self {
226		if value & 0x80 != 0 {
227			return Type::Option(Box::new(Type::from_u8(value & 0x7F)));
228		}
229		match value {
230			1 => Type::Float4,
231			2 => Type::Float8,
232			3 => Type::Int1,
233			4 => Type::Int2,
234			5 => Type::Int4,
235			6 => Type::Int8,
236			7 => Type::Int16,
237			8 => Type::Utf8,
238			9 => Type::Uint1,
239			10 => Type::Uint2,
240			11 => Type::Uint4,
241			12 => Type::Uint8,
242			13 => Type::Uint16,
243			14 => Type::Boolean,
244			15 => Type::Date,
245			16 => Type::DateTime,
246			17 => Type::Time,
247			18 => Type::Duration,
248			19 => Type::IdentityId,
249			20 => Type::Uuid4,
250			21 => Type::Uuid7,
251			22 => Type::Blob,
252			23 => Type::Int,
253			24 => Type::Decimal,
254			25 => Type::Uint,
255			26 => Type::Any,
256			27 => Type::DictionaryId,
257			28 => Type::list_of(Type::Any),
258			29 => Type::Record(Vec::new()),
259			30 => Type::Tuple(Vec::new()),
260			_ => unreachable!(),
261		}
262	}
263}
264
265impl Type {
266	pub fn size(&self) -> usize {
267		match self {
268			Type::Boolean => 1,
269			Type::Float4 => 4,
270			Type::Float8 => 8,
271			Type::Int1 => 1,
272			Type::Int2 => 2,
273			Type::Int4 => 4,
274			Type::Int8 => 8,
275			Type::Int16 => 16,
276			Type::Utf8 => 8, // offset: u32 + length: u32
277			Type::Uint1 => 1,
278			Type::Uint2 => 2,
279			Type::Uint4 => 4,
280			Type::Uint8 => 8,
281			Type::Uint16 => 16,
282			Type::Date => 4,
283			Type::DateTime => 12, // seconds: i64 + nanos: u32
284			Type::Time => 8,
285			Type::Duration => 16, // months: i32 + days: i32 +
286			// 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}