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 => 24,
213			Type::Uint => 25,
214			Type::Any => 26,
215			Type::DictionaryId => 27,
216			Type::List(_) => 28,
217			Type::Record(_) => 29,
218			Type::Tuple(_) => 30,
219		}
220	}
221}
222
223impl Type {
224	pub fn from_u8(value: u8) -> Self {
225		if value & 0x80 != 0 {
226			return Type::Option(Box::new(Type::from_u8(value & 0x7F)));
227		}
228		match value {
229			1 => Type::Float4,
230			2 => Type::Float8,
231			3 => Type::Int1,
232			4 => Type::Int2,
233			5 => Type::Int4,
234			6 => Type::Int8,
235			7 => Type::Int16,
236			8 => Type::Utf8,
237			9 => Type::Uint1,
238			10 => Type::Uint2,
239			11 => Type::Uint4,
240			12 => Type::Uint8,
241			13 => Type::Uint16,
242			14 => Type::Boolean,
243			15 => Type::Date,
244			16 => Type::DateTime,
245			17 => Type::Time,
246			18 => Type::Duration,
247			19 => Type::IdentityId,
248			20 => Type::Uuid4,
249			21 => Type::Uuid7,
250			22 => Type::Blob,
251			23 => Type::Int,
252			24 => Type::Decimal,
253			25 => Type::Uint,
254			26 => Type::Any,
255			27 => Type::DictionaryId,
256			28 => Type::list_of(Type::Any),
257			29 => Type::Record(Vec::new()),
258			30 => Type::Tuple(Vec::new()),
259			_ => unreachable!(),
260		}
261	}
262}
263
264impl Type {
265	pub fn size(&self) -> usize {
266		match self {
267			Type::Boolean => 1,
268			Type::Float4 => 4,
269			Type::Float8 => 8,
270			Type::Int1 => 1,
271			Type::Int2 => 2,
272			Type::Int4 => 4,
273			Type::Int8 => 8,
274			Type::Int16 => 16,
275			Type::Utf8 => 8, // offset: u32 + length: u32
276			Type::Uint1 => 1,
277			Type::Uint2 => 2,
278			Type::Uint4 => 4,
279			Type::Uint8 => 8,
280			Type::Uint16 => 16,
281			Type::Date => 4,
282			Type::DateTime => 8, // nanos: u64
283			Type::Time => 8,
284			Type::Duration => 16,   // months: i32 + days: i32 + nanos: i64
285			Type::IdentityId => 16, // UUID v7 is 16 bytes
286			Type::Uuid4 => 16,
287			Type::Uuid7 => 16,
288			Type::Blob => 8, // offset: u32 + length: u32
289			Type::Int => 16, // i128 inline or dynamic
290			// storage with offset + length
291			Type::Uint => 16, // u128 inline or dynamic
292			// storage with offset + length
293			Type::Decimal => 16, // i128 inline or dynamic
294			// storage with offset + length
295			Type::Option(inner) => inner.size(), // size determined by inner type
296			Type::Any => 8,                      // pointer size on 64-bit systems
297			Type::List(_) => 8,                  // pointer size (Vec is heap-allocated)
298			Type::Record(_) => 8,                // pointer size (Vec is heap-allocated)
299			Type::Tuple(_) => 8,                 // pointer size (Vec is heap-allocated)
300			Type::DictionaryId => 16,            /* max possible; actual size determined by constraint's
301			                                       * id_type */
302		}
303	}
304
305	pub fn alignment(&self) -> usize {
306		match self {
307			Type::Boolean => 1,
308			Type::Float4 => 4,
309			Type::Float8 => 8,
310			Type::Int1 => 1,
311			Type::Int2 => 2,
312			Type::Int4 => 4,
313			Type::Int8 => 8,
314			Type::Int16 => 16,
315			Type::Utf8 => 4, // u32 alignment
316			Type::Uint1 => 1,
317			Type::Uint2 => 2,
318			Type::Uint4 => 4,
319			Type::Uint8 => 8,
320			Type::Uint16 => 16,
321			Type::Date => 4,
322			Type::DateTime => 8,
323			Type::Time => 8,
324			Type::Duration => 8,
325			Type::IdentityId => 8, // Same alignment as UUID
326			Type::Uuid4 => 8,
327			Type::Uuid7 => 8,
328			Type::Blob => 4, // u32 alignment
329			Type::Int => 16, // i128 alignment for
330			// inline storage
331			Type::Uint => 16, // u128 alignment for
332			// inline storage
333			Type::Decimal => 16, // i128 alignment for
334			// inline storage
335			Type::Option(inner) => inner.alignment(),
336			Type::Any => 8, // pointer alignment
337			Type::DictionaryId => 16,
338			Type::List(_) => 8,   // pointer alignment
339			Type::Record(_) => 8, // pointer alignment
340			Type::Tuple(_) => 8,  // pointer alignment
341		}
342	}
343}
344
345impl Display for Type {
346	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
347		match self {
348			Type::Boolean => f.write_str("Boolean"),
349			Type::Float4 => f.write_str("Float4"),
350			Type::Float8 => f.write_str("Float8"),
351			Type::Int1 => f.write_str("Int1"),
352			Type::Int2 => f.write_str("Int2"),
353			Type::Int4 => f.write_str("Int4"),
354			Type::Int8 => f.write_str("Int8"),
355			Type::Int16 => f.write_str("Int16"),
356			Type::Utf8 => f.write_str("Utf8"),
357			Type::Uint1 => f.write_str("Uint1"),
358			Type::Uint2 => f.write_str("Uint2"),
359			Type::Uint4 => f.write_str("Uint4"),
360			Type::Uint8 => f.write_str("Uint8"),
361			Type::Uint16 => f.write_str("Uint16"),
362			Type::Date => f.write_str("Date"),
363			Type::DateTime => f.write_str("DateTime"),
364			Type::Time => f.write_str("Time"),
365			Type::Duration => f.write_str("Duration"),
366			Type::IdentityId => f.write_str("IdentityId"),
367			Type::Uuid4 => f.write_str("Uuid4"),
368			Type::Uuid7 => f.write_str("Uuid7"),
369			Type::Blob => f.write_str("Blob"),
370			Type::Int => f.write_str("Int"),
371			Type::Uint => f.write_str("Uint"),
372			Type::Decimal => f.write_str("Decimal"),
373			Type::Option(inner) => write!(f, "Option({inner})"),
374			Type::Any => f.write_str("Any"),
375			Type::DictionaryId => f.write_str("DictionaryId"),
376			Type::List(inner) => write!(f, "List({inner})"),
377			Type::Record(fields) => {
378				f.write_str("Record(")?;
379				for (i, (name, ty)) in fields.iter().enumerate() {
380					if i > 0 {
381						f.write_str(", ")?;
382					}
383					write!(f, "{}: {}", name, ty)?;
384				}
385				f.write_str(")")
386			}
387			Type::Tuple(types) => {
388				f.write_str("Tuple(")?;
389				for (i, ty) in types.iter().enumerate() {
390					if i > 0 {
391						f.write_str(", ")?;
392					}
393					write!(f, "{}", ty)?;
394				}
395				f.write_str(")")
396			}
397		}
398	}
399}
400
401impl From<&Value> for Type {
402	fn from(value: &Value) -> Self {
403		match value {
404			Value::None {
405				inner,
406			} => Type::Option(Box::new(inner.clone())),
407			Value::Boolean(_) => Type::Boolean,
408			Value::Float4(_) => Type::Float4,
409			Value::Float8(_) => Type::Float8,
410			Value::Int1(_) => Type::Int1,
411			Value::Int2(_) => Type::Int2,
412			Value::Int4(_) => Type::Int4,
413			Value::Int8(_) => Type::Int8,
414			Value::Int16(_) => Type::Int16,
415			Value::Utf8(_) => Type::Utf8,
416			Value::Uint1(_) => Type::Uint1,
417			Value::Uint2(_) => Type::Uint2,
418			Value::Uint4(_) => Type::Uint4,
419			Value::Uint8(_) => Type::Uint8,
420			Value::Uint16(_) => Type::Uint16,
421			Value::Date(_) => Type::Date,
422			Value::DateTime(_) => Type::DateTime,
423			Value::Time(_) => Type::Time,
424			Value::Duration(_) => Type::Duration,
425			Value::IdentityId(_) => Type::IdentityId,
426			Value::Uuid4(_) => Type::Uuid4,
427			Value::Uuid7(_) => Type::Uuid7,
428			Value::Blob(_) => Type::Blob,
429			Value::Int(_) => Type::Int,
430			Value::Uint(_) => Type::Uint,
431			Value::Decimal(_) => Type::Decimal,
432			Value::Any(_) => Type::Any,
433			Value::DictionaryId(_) => Type::DictionaryId,
434			Value::Type(t) => t.clone(),
435			Value::List(items) => {
436				let element_type = items.first().map(Type::from).unwrap_or(Type::Any);
437				Type::list_of(element_type)
438			}
439			Value::Record(fields) => {
440				Type::Record(fields.iter().map(|(k, v)| (k.clone(), Type::from(v))).collect())
441			}
442			Value::Tuple(items) => Type::Tuple(items.iter().map(Type::from).collect()),
443		}
444	}
445}
446
447impl FromStr for Type {
448	type Err = ();
449
450	fn from_str(s: &str) -> Result<Self, Self::Err> {
451		let upper = s.to_uppercase();
452		// Handle Option<T> syntax
453		if upper.starts_with("OPTION<") && upper.ends_with('>') {
454			let inner_str = &s[7..s.len() - 1]; // extract between "OPTION<" and ">"
455			let inner = Type::from_str(inner_str)?;
456			return Ok(Type::Option(Box::new(inner)));
457		}
458		match upper.as_str() {
459			"BOOL" | "BOOLEAN" => Ok(Type::Boolean),
460			"FLOAT4" => Ok(Type::Float4),
461			"FLOAT8" => Ok(Type::Float8),
462			"INT1" => Ok(Type::Int1),
463			"INT2" => Ok(Type::Int2),
464			"INT4" => Ok(Type::Int4),
465			"INT8" => Ok(Type::Int8),
466			"INT16" => Ok(Type::Int16),
467			"UTF8" | "TEXT" => Ok(Type::Utf8),
468			"UINT1" => Ok(Type::Uint1),
469			"UINT2" => Ok(Type::Uint2),
470			"UINT4" => Ok(Type::Uint4),
471			"UINT8" => Ok(Type::Uint8),
472			"UINT16" => Ok(Type::Uint16),
473			"DATE" => Ok(Type::Date),
474			"DATETIME" => Ok(Type::DateTime),
475			"TIME" => Ok(Type::Time),
476			"DURATION" | "INTERVAL" => Ok(Type::Duration),
477			"IDENTITYID" | "IDENTITY_ID" => Ok(Type::IdentityId),
478			"UUID4" => Ok(Type::Uuid4),
479			"UUID7" => Ok(Type::Uuid7),
480			"BLOB" => Ok(Type::Blob),
481			"INT" => Ok(Type::Int),
482			"UINT" => Ok(Type::Uint),
483			"DECIMAL" => Ok(Type::Decimal),
484			"ANY" => Ok(Type::Any),
485			"DICTIONARYID" | "DICTIONARY_ID" => Ok(Type::DictionaryId),
486			_ => Err(()),
487		}
488	}
489}