1use 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}