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