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#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
21pub enum Type {
22	Boolean,
23
24	Float4,
25
26	Float8,
27
28	Int1,
29
30	Int2,
31
32	Int4,
33
34	Int8,
35
36	Int16,
37
38	Utf8,
39
40	Uint1,
41
42	Uint2,
43
44	Uint4,
45
46	Uint8,
47
48	Uint16,
49
50	Date,
51
52	DateTime,
53
54	Time,
55
56	Duration,
57
58	IdentityId,
59
60	Uuid4,
61
62	Uuid7,
63
64	Blob,
65
66	Int,
67
68	Uint,
69
70	Decimal,
71
72	Option(Box<Type>),
73
74	Any,
75
76	DictionaryId,
77
78	List(Box<Type>),
79
80	Record(Vec<(String, Type)>),
81
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	pub fn inner_type(&self) -> &Type {
176		match self {
177			Type::Option(inner) => inner,
178			other => other,
179		}
180	}
181}
182
183impl Type {
184	pub fn to_u8(&self) -> u8 {
185		match self {
186			Type::Option(inner) => 0x80 | inner.to_u8(),
187			Type::Float4 => 1,
188			Type::Float8 => 2,
189			Type::Int1 => 3,
190			Type::Int2 => 4,
191			Type::Int4 => 5,
192			Type::Int8 => 6,
193			Type::Int16 => 7,
194			Type::Utf8 => 8,
195			Type::Uint1 => 9,
196			Type::Uint2 => 10,
197			Type::Uint4 => 11,
198			Type::Uint8 => 12,
199			Type::Uint16 => 13,
200			Type::Boolean => 14,
201			Type::Date => 15,
202			Type::DateTime => 16,
203			Type::Time => 17,
204			Type::Duration => 18,
205			Type::IdentityId => 19,
206			Type::Uuid4 => 20,
207			Type::Uuid7 => 21,
208			Type::Blob => 22,
209			Type::Int => 23,
210			Type::Decimal => 24,
211			Type::Uint => 25,
212			Type::Any => 26,
213			Type::DictionaryId => 27,
214			Type::List(_) => 28,
215			Type::Record(_) => 29,
216			Type::Tuple(_) => 30,
217		}
218	}
219}
220
221impl Type {
222	pub fn from_u8(value: u8) -> Self {
223		if value & 0x80 != 0 {
224			return Type::Option(Box::new(Type::from_u8(value & 0x7F)));
225		}
226		match value {
227			1 => Type::Float4,
228			2 => Type::Float8,
229			3 => Type::Int1,
230			4 => Type::Int2,
231			5 => Type::Int4,
232			6 => Type::Int8,
233			7 => Type::Int16,
234			8 => Type::Utf8,
235			9 => Type::Uint1,
236			10 => Type::Uint2,
237			11 => Type::Uint4,
238			12 => Type::Uint8,
239			13 => Type::Uint16,
240			14 => Type::Boolean,
241			15 => Type::Date,
242			16 => Type::DateTime,
243			17 => Type::Time,
244			18 => Type::Duration,
245			19 => Type::IdentityId,
246			20 => Type::Uuid4,
247			21 => Type::Uuid7,
248			22 => Type::Blob,
249			23 => Type::Int,
250			24 => Type::Decimal,
251			25 => Type::Uint,
252			26 => Type::Any,
253			27 => Type::DictionaryId,
254			28 => Type::list_of(Type::Any),
255			29 => Type::Record(Vec::new()),
256			30 => Type::Tuple(Vec::new()),
257			_ => unreachable!(),
258		}
259	}
260}
261
262impl Type {
263	pub fn size(&self) -> usize {
264		match self {
265			Type::Boolean => 1,
266			Type::Float4 => 4,
267			Type::Float8 => 8,
268			Type::Int1 => 1,
269			Type::Int2 => 2,
270			Type::Int4 => 4,
271			Type::Int8 => 8,
272			Type::Int16 => 16,
273			Type::Utf8 => 8,
274			Type::Uint1 => 1,
275			Type::Uint2 => 2,
276			Type::Uint4 => 4,
277			Type::Uint8 => 8,
278			Type::Uint16 => 16,
279			Type::Date => 4,
280			Type::DateTime => 8,
281			Type::Time => 8,
282			Type::Duration => 16,
283			Type::IdentityId => 16,
284			Type::Uuid4 => 16,
285			Type::Uuid7 => 16,
286			Type::Blob => 8,
287			Type::Int => 16,
288
289			Type::Uint => 16,
290
291			Type::Decimal => 16,
292
293			Type::Option(inner) => inner.size(),
294			Type::Any => 8,
295			Type::List(_) => 8,
296			Type::Record(_) => 8,
297			Type::Tuple(_) => 8,
298			Type::DictionaryId => 16,
299		}
300	}
301
302	pub fn alignment(&self) -> usize {
303		match self {
304			Type::Boolean => 1,
305			Type::Float4 => 4,
306			Type::Float8 => 8,
307			Type::Int1 => 1,
308			Type::Int2 => 2,
309			Type::Int4 => 4,
310			Type::Int8 => 8,
311			Type::Int16 => 16,
312			Type::Utf8 => 4,
313			Type::Uint1 => 1,
314			Type::Uint2 => 2,
315			Type::Uint4 => 4,
316			Type::Uint8 => 8,
317			Type::Uint16 => 16,
318			Type::Date => 4,
319			Type::DateTime => 8,
320			Type::Time => 8,
321			Type::Duration => 8,
322			Type::IdentityId => 8,
323			Type::Uuid4 => 8,
324			Type::Uuid7 => 8,
325			Type::Blob => 4,
326			Type::Int => 16,
327
328			Type::Uint => 16,
329
330			Type::Decimal => 16,
331
332			Type::Option(inner) => inner.alignment(),
333			Type::Any => 8,
334			Type::DictionaryId => 16,
335			Type::List(_) => 8,
336			Type::Record(_) => 8,
337			Type::Tuple(_) => 8,
338		}
339	}
340}
341
342impl Display for Type {
343	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
344		match self {
345			Type::Boolean => f.write_str("Boolean"),
346			Type::Float4 => f.write_str("Float4"),
347			Type::Float8 => f.write_str("Float8"),
348			Type::Int1 => f.write_str("Int1"),
349			Type::Int2 => f.write_str("Int2"),
350			Type::Int4 => f.write_str("Int4"),
351			Type::Int8 => f.write_str("Int8"),
352			Type::Int16 => f.write_str("Int16"),
353			Type::Utf8 => f.write_str("Utf8"),
354			Type::Uint1 => f.write_str("Uint1"),
355			Type::Uint2 => f.write_str("Uint2"),
356			Type::Uint4 => f.write_str("Uint4"),
357			Type::Uint8 => f.write_str("Uint8"),
358			Type::Uint16 => f.write_str("Uint16"),
359			Type::Date => f.write_str("Date"),
360			Type::DateTime => f.write_str("DateTime"),
361			Type::Time => f.write_str("Time"),
362			Type::Duration => f.write_str("Duration"),
363			Type::IdentityId => f.write_str("IdentityId"),
364			Type::Uuid4 => f.write_str("Uuid4"),
365			Type::Uuid7 => f.write_str("Uuid7"),
366			Type::Blob => f.write_str("Blob"),
367			Type::Int => f.write_str("Int"),
368			Type::Uint => f.write_str("Uint"),
369			Type::Decimal => f.write_str("Decimal"),
370			Type::Option(inner) => write!(f, "Option({inner})"),
371			Type::Any => f.write_str("Any"),
372			Type::DictionaryId => f.write_str("DictionaryId"),
373			Type::List(inner) => write!(f, "List({inner})"),
374			Type::Record(fields) => {
375				f.write_str("Record(")?;
376				for (i, (name, ty)) in fields.iter().enumerate() {
377					if i > 0 {
378						f.write_str(", ")?;
379					}
380					write!(f, "{}: {}", name, ty)?;
381				}
382				f.write_str(")")
383			}
384			Type::Tuple(types) => {
385				f.write_str("Tuple(")?;
386				for (i, ty) in types.iter().enumerate() {
387					if i > 0 {
388						f.write_str(", ")?;
389					}
390					write!(f, "{}", ty)?;
391				}
392				f.write_str(")")
393			}
394		}
395	}
396}
397
398impl From<&Value> for Type {
399	fn from(value: &Value) -> Self {
400		match value {
401			Value::None {
402				inner,
403			} => Type::Option(Box::new(inner.clone())),
404			Value::Boolean(_) => Type::Boolean,
405			Value::Float4(_) => Type::Float4,
406			Value::Float8(_) => Type::Float8,
407			Value::Int1(_) => Type::Int1,
408			Value::Int2(_) => Type::Int2,
409			Value::Int4(_) => Type::Int4,
410			Value::Int8(_) => Type::Int8,
411			Value::Int16(_) => Type::Int16,
412			Value::Utf8(_) => Type::Utf8,
413			Value::Uint1(_) => Type::Uint1,
414			Value::Uint2(_) => Type::Uint2,
415			Value::Uint4(_) => Type::Uint4,
416			Value::Uint8(_) => Type::Uint8,
417			Value::Uint16(_) => Type::Uint16,
418			Value::Date(_) => Type::Date,
419			Value::DateTime(_) => Type::DateTime,
420			Value::Time(_) => Type::Time,
421			Value::Duration(_) => Type::Duration,
422			Value::IdentityId(_) => Type::IdentityId,
423			Value::Uuid4(_) => Type::Uuid4,
424			Value::Uuid7(_) => Type::Uuid7,
425			Value::Blob(_) => Type::Blob,
426			Value::Int(_) => Type::Int,
427			Value::Uint(_) => Type::Uint,
428			Value::Decimal(_) => Type::Decimal,
429			Value::Any(_) => Type::Any,
430			Value::DictionaryId(_) => Type::DictionaryId,
431			Value::Type(t) => t.clone(),
432			Value::List(items) => {
433				let element_type = items.first().map(Type::from).unwrap_or(Type::Any);
434				Type::list_of(element_type)
435			}
436			Value::Record(fields) => {
437				Type::Record(fields.iter().map(|(k, v)| (k.clone(), Type::from(v))).collect())
438			}
439			Value::Tuple(items) => Type::Tuple(items.iter().map(Type::from).collect()),
440		}
441	}
442}
443
444impl FromStr for Type {
445	type Err = ();
446
447	fn from_str(s: &str) -> Result<Self, Self::Err> {
448		let upper = s.to_uppercase();
449
450		if upper.starts_with("OPTION<") && upper.ends_with('>') {
451			let inner_str = &s[7..s.len() - 1];
452			let inner = Type::from_str(inner_str)?;
453			return Ok(Type::Option(Box::new(inner)));
454		}
455		match upper.as_str() {
456			"BOOL" | "BOOLEAN" => Ok(Type::Boolean),
457			"FLOAT4" => Ok(Type::Float4),
458			"FLOAT8" => Ok(Type::Float8),
459			"INT1" => Ok(Type::Int1),
460			"INT2" => Ok(Type::Int2),
461			"INT4" => Ok(Type::Int4),
462			"INT8" => Ok(Type::Int8),
463			"INT16" => Ok(Type::Int16),
464			"UTF8" | "TEXT" => Ok(Type::Utf8),
465			"UINT1" => Ok(Type::Uint1),
466			"UINT2" => Ok(Type::Uint2),
467			"UINT4" => Ok(Type::Uint4),
468			"UINT8" => Ok(Type::Uint8),
469			"UINT16" => Ok(Type::Uint16),
470			"DATE" => Ok(Type::Date),
471			"DATETIME" => Ok(Type::DateTime),
472			"TIME" => Ok(Type::Time),
473			"DURATION" | "INTERVAL" => Ok(Type::Duration),
474			"IDENTITYID" | "IDENTITY_ID" => Ok(Type::IdentityId),
475			"UUID4" => Ok(Type::Uuid4),
476			"UUID7" => Ok(Type::Uuid7),
477			"BLOB" => Ok(Type::Blob),
478			"INT" => Ok(Type::Int),
479			"UINT" => Ok(Type::Uint),
480			"DECIMAL" => Ok(Type::Decimal),
481			"ANY" => Ok(Type::Any),
482			"DICTIONARYID" | "DICTIONARY_ID" => Ok(Type::DictionaryId),
483			_ => Err(()),
484		}
485	}
486}