1use reifydb_core::interface::catalog::{
5 column::Column,
6 dictionary::Dictionary,
7 namespace::Namespace,
8 ringbuffer::RingBuffer,
9 series::{Series, SeriesKey, TimestampPrecision},
10 sumtype::{Field, SumType},
11 table::Table,
12};
13
14use crate::{
15 error::ExportError,
16 model::NameResolver,
17 render::{
18 layout::{EnumColumn, LayoutColumn, build_layout},
19 typ::{render_column_type, render_value_type},
20 },
21};
22
23pub fn qualified_name(
24 resolver: &NameResolver,
25 namespace_id: u64,
26 name: &str,
27 context: &str,
28) -> Result<String, ExportError> {
29 let ns = resolver.namespaces.get(&namespace_id).ok_or_else(|| ExportError::UnresolvedReference {
30 kind: "namespace",
31 id: namespace_id,
32 shape: context.to_string(),
33 })?;
34 Ok(format!("{}::{}", ns, name))
35}
36
37fn keyword_prefix(if_not_exists: bool) -> &'static str {
38 if if_not_exists {
39 " IF NOT EXISTS"
40 } else {
41 ""
42 }
43}
44
45fn render_column(col: &Column, resolver: &NameResolver, shape: &str) -> Result<String, ExportError> {
46 let rendered = render_column_type(&col.constraint, resolver, shape)?;
47 let mut type_text = rendered.type_text;
48 let mut dictionary = rendered.dictionary;
49
50 if let (true, Some(dict_id)) = (dictionary.is_none(), &col.dictionary_id) {
51 let id = dict_id.to_u64();
52 let resolved = resolver.dictionary(id).ok_or_else(|| ExportError::UnresolvedReference {
53 kind: "dictionary",
54 id,
55 shape: shape.to_string(),
56 })?;
57 type_text = render_value_type(&resolved.value_type, shape)?;
58 dictionary = Some(resolved.qualified_name.clone());
59 }
60
61 let mut properties = Vec::new();
62 if let Some(dict) = dictionary {
63 properties.push(format!("dictionary: {}", dict));
64 }
65 if col.auto_increment {
66 properties.push("auto_increment".to_string());
67 }
68
69 let mut out = format!("{}: {}", col.name, type_text);
70 if !properties.is_empty() {
71 out.push_str(&format!(" with {{ {} }}", properties.join(", ")));
72 }
73 Ok(out)
74}
75
76fn render_field(field: &Field, resolver: &NameResolver, shape: &str) -> Result<String, ExportError> {
77 let rendered = render_column_type(&field.field_type, resolver, shape)?;
78 let mut out = format!("{}: {}", field.name, rendered.type_text);
79 if let Some(dict) = rendered.dictionary {
80 out.push_str(&format!(" with {{ dictionary: {} }}", dict));
81 }
82 Ok(out)
83}
84
85fn render_columns_block(columns: &[Column], resolver: &NameResolver, shape: &str) -> Result<String, ExportError> {
86 let mut rendered = Vec::new();
87 for column in build_layout(columns, resolver) {
88 rendered.push(match column {
89 LayoutColumn::Plain(c) => render_column(c, resolver, shape)?,
90 LayoutColumn::Enum(e) => render_enum_column(&e, resolver, shape)?,
91 });
92 }
93 Ok(format!("{{ {} }}", rendered.join(", ")))
94}
95
96fn render_enum_column(column: &EnumColumn, resolver: &NameResolver, shape: &str) -> Result<String, ExportError> {
97 let resolved = resolver.sumtype(column.sumtype_id).ok_or_else(|| ExportError::UnresolvedReference {
98 kind: "sumtype",
99 id: column.sumtype_id,
100 shape: shape.to_string(),
101 })?;
102 Ok(format!("{}: {}", column.logical_name, resolved.qualified_name))
103}
104
105pub fn render_namespace(namespace: &Namespace, if_not_exists: bool) -> String {
106 format!("CREATE NAMESPACE{} {};", keyword_prefix(if_not_exists), namespace.name())
107}
108
109pub fn render_enum(sumtype: &SumType, resolver: &NameResolver, if_not_exists: bool) -> Result<String, ExportError> {
110 let name = qualified_name(resolver, sumtype.namespace.0, &sumtype.name, &sumtype.name)?;
111 let mut variants = sumtype.variants.clone();
112 variants.sort_by_key(|v| v.tag);
113
114 let mut rendered_variants = Vec::new();
115 for variant in &variants {
116 if variant.fields.is_empty() {
117 rendered_variants.push(variant.name.clone());
118 } else {
119 let fields: Result<Vec<_>, _> =
120 variant.fields.iter().map(|f| render_field(f, resolver, &sumtype.name)).collect();
121 rendered_variants.push(format!("{} {{ {} }}", variant.name, fields?.join(", ")));
122 }
123 }
124
125 Ok(format!("CREATE ENUM{} {} {{ {} }};", keyword_prefix(if_not_exists), name, rendered_variants.join(", ")))
126}
127
128pub fn render_dictionary(
129 dictionary: &Dictionary,
130 resolver: &NameResolver,
131 if_not_exists: bool,
132) -> Result<String, ExportError> {
133 let name = qualified_name(resolver, dictionary.namespace.0, &dictionary.name, &dictionary.name)?;
134 let value_type = render_value_type(&dictionary.value_type, &dictionary.name)?;
135 let id_type = render_value_type(&dictionary.id_type, &dictionary.name)?;
136 Ok(format!("CREATE DICTIONARY{} {} FOR {} AS {};", keyword_prefix(if_not_exists), name, value_type, id_type))
137}
138
139pub fn render_table(table: &Table, resolver: &NameResolver, if_not_exists: bool) -> Result<String, ExportError> {
140 let name = qualified_name(resolver, table.namespace.0, &table.name, &table.name)?;
141 let columns = render_columns_block(&table.columns, resolver, &table.name)?;
142 Ok(format!("CREATE TABLE{} {} {};", keyword_prefix(if_not_exists), name, columns))
143}
144
145pub fn render_ringbuffer(ringbuffer: &RingBuffer, resolver: &NameResolver) -> Result<String, ExportError> {
146 let name = qualified_name(resolver, ringbuffer.namespace.0, &ringbuffer.name, &ringbuffer.name)?;
147 let columns = render_columns_block(&ringbuffer.columns, resolver, &ringbuffer.name)?;
148
149 let mut with = format!("capacity: {}", ringbuffer.capacity);
150 if !ringbuffer.partition_by.is_empty() {
151 with.push_str(&format!(", partition_by: {{ {} }}", ringbuffer.partition_by.join(", ")));
152 }
153
154 Ok(format!("CREATE RINGBUFFER {} {} WITH {{ {} }};", name, columns, with))
155}
156
157pub fn render_series(series: &Series, resolver: &NameResolver) -> Result<String, ExportError> {
158 let name = qualified_name(resolver, series.namespace.0, &series.name, &series.name)?;
159 let columns = render_columns_block(&series.columns, resolver, &series.name)?;
160
161 let mut with = format!("key: {}", series.key.column());
162
163 if let Some(tag_id) = &series.tag {
164 let id = tag_id.to_u64();
165 let resolved = resolver.sumtype(id).ok_or_else(|| ExportError::UnresolvedReference {
166 kind: "sumtype",
167 id,
168 shape: series.name.clone(),
169 })?;
170 with.push_str(&format!(", tag: {}", resolved.qualified_name));
171 }
172
173 if let SeriesKey::DateTime {
174 precision,
175 ..
176 } = &series.key
177 {
178 with.push_str(&format!(", precision: {}", render_precision(precision)));
179 }
180
181 Ok(format!("CREATE SERIES {} {} WITH {{ {} }};", name, columns, with))
182}
183
184fn render_precision(precision: &TimestampPrecision) -> &'static str {
185 match precision {
186 TimestampPrecision::Second => "second",
187 TimestampPrecision::Millisecond => "millisecond",
188 TimestampPrecision::Microsecond => "microsecond",
189 TimestampPrecision::Nanosecond => "nanosecond",
190 }
191}