Skip to main content

reifydb_export/render/
ddl.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use 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}