Skip to main content

reifydb_export/render/
typ.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::value::{
5	constraint::{Constraint, TypeConstraint},
6	value_type::ValueType,
7};
8
9use crate::{error::ExportError, model::NameResolver};
10
11pub struct RenderedColumnType {
12	pub type_text: String,
13	pub dictionary: Option<String>,
14}
15
16pub fn render_column_type(
17	constraint: &TypeConstraint,
18	resolver: &NameResolver,
19	object: &str,
20) -> Result<RenderedColumnType, ExportError> {
21	match constraint.constraint() {
22		Some(Constraint::Dictionary(dict_id, _)) => {
23			let id = dict_id.to_u64();
24			let resolved = resolver.dictionary(id).ok_or_else(|| ExportError::UnresolvedReference {
25				kind: "dictionary",
26				id,
27				object: object.to_string(),
28			})?;
29			Ok(RenderedColumnType {
30				type_text: render_value_type(&resolved.value_type, object)?,
31				dictionary: Some(resolved.qualified_name.clone()),
32			})
33		}
34		Some(Constraint::SumType(sum_id)) => {
35			let id = sum_id.to_u64();
36			let resolved = resolver.sumtype(id).ok_or_else(|| ExportError::UnresolvedReference {
37				kind: "sumtype",
38				id,
39				object: object.to_string(),
40			})?;
41			Ok(RenderedColumnType {
42				type_text: resolved.qualified_name.clone(),
43				dictionary: None,
44			})
45		}
46		Some(Constraint::MaxBytes(max)) => {
47			let base = render_value_type(&constraint.get_type(), object)?;
48			Ok(RenderedColumnType {
49				type_text: format!("{}({})", base, max),
50				dictionary: None,
51			})
52		}
53		Some(Constraint::PrecisionScale(precision, scale)) => Ok(RenderedColumnType {
54			type_text: format!("decimal({},{})", precision, scale),
55			dictionary: None,
56		}),
57		None => Ok(RenderedColumnType {
58			type_text: render_value_type(&constraint.get_type(), object)?,
59			dictionary: None,
60		}),
61	}
62}
63
64pub fn render_value_type(ty: &ValueType, object: &str) -> Result<String, ExportError> {
65	let text = match ty {
66		ValueType::Boolean => "bool".to_string(),
67		ValueType::Float4 => "float4".to_string(),
68		ValueType::Float8 => "float8".to_string(),
69		ValueType::Int1 => "int1".to_string(),
70		ValueType::Int2 => "int2".to_string(),
71		ValueType::Int4 => "int4".to_string(),
72		ValueType::Int8 => "int8".to_string(),
73		ValueType::Int16 => "int16".to_string(),
74		ValueType::Uint1 => "uint1".to_string(),
75		ValueType::Uint2 => "uint2".to_string(),
76		ValueType::Uint4 => "uint4".to_string(),
77		ValueType::Uint8 => "uint8".to_string(),
78		ValueType::Uint16 => "uint16".to_string(),
79		ValueType::Utf8 => "utf8".to_string(),
80		ValueType::Date => "date".to_string(),
81		ValueType::DateTime => "datetime".to_string(),
82		ValueType::Time => "time".to_string(),
83		ValueType::Duration => "duration".to_string(),
84		ValueType::IdentityId => "identityid".to_string(),
85		ValueType::Uuid4 => "uuid4".to_string(),
86		ValueType::Uuid7 => "uuid7".to_string(),
87		ValueType::Blob => "blob".to_string(),
88		ValueType::Int => "int".to_string(),
89		ValueType::Uint => "uint".to_string(),
90		ValueType::Decimal => "decimal".to_string(),
91		ValueType::Option(inner) => format!("option({})", render_value_type(inner, object)?),
92		ValueType::Any
93		| ValueType::DictionaryId
94		| ValueType::List(_)
95		| ValueType::Record(_)
96		| ValueType::Tuple(_) => {
97			return Err(ExportError::UnsupportedType {
98				object: object.to_string(),
99				value_type: format!("{}", ty),
100			});
101		}
102	};
103	Ok(text)
104}
105
106#[cfg(test)]
107mod tests {
108	use reifydb_value::value::constraint::{bytes::MaxBytes, precision::Precision, scale::Scale};
109
110	use super::*;
111
112	#[test]
113	fn plain_types_render_lowercase() {
114		let r = NameResolver::empty();
115		assert_eq!(
116			render_column_type(&TypeConstraint::unconstrained(ValueType::Int4), &r, "s").unwrap().type_text,
117			"int4"
118		);
119		assert_eq!(
120			render_column_type(&TypeConstraint::unconstrained(ValueType::Utf8), &r, "s").unwrap().type_text,
121			"utf8"
122		);
123	}
124
125	#[test]
126	fn option_wraps_inner() {
127		let r = NameResolver::empty();
128		let tc = TypeConstraint::unconstrained(ValueType::Option(Box::new(ValueType::Int4)));
129		assert_eq!(render_column_type(&tc, &r, "s").unwrap().type_text, "option(int4)");
130	}
131
132	#[test]
133	fn constrained_utf8_and_decimal() {
134		let r = NameResolver::empty();
135		let utf8 = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(255)));
136		assert_eq!(render_column_type(&utf8, &r, "s").unwrap().type_text, "utf8(255)");
137
138		let dec = TypeConstraint::with_constraint(
139			ValueType::Decimal,
140			Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
141		);
142		assert_eq!(render_column_type(&dec, &r, "s").unwrap().type_text, "decimal(10,2)");
143	}
144
145	#[test]
146	fn unsupported_base_type_fails_loud() {
147		let r = NameResolver::empty();
148		let tc = TypeConstraint::unconstrained(ValueType::Any);
149		assert!(matches!(render_column_type(&tc, &r, "s"), Err(ExportError::UnsupportedType { .. })));
150	}
151}