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