tests_fuzz/translator/mysql/
create_expr.rs1use datatypes::data_type::ConcreteDataType;
16use datatypes::value::Value;
17use partition::partition::PartitionBound;
18use sql::statements::concrete_data_type_to_sql_data_type;
19
20use crate::error::{Error, Result};
21use crate::ir::create_expr::ColumnOption;
22use crate::ir::{Column, CreateDatabaseExpr, CreateTableExpr};
23use crate::translator::DslTranslator;
24
25pub struct CreateTableExprTranslator;
26
27impl DslTranslator<CreateTableExpr, String> for CreateTableExprTranslator {
28 type Error = Error;
29
30 fn translate(&self, input: &CreateTableExpr) -> Result<String> {
31 Ok(format!(
32 "CREATE TABLE{}{}(\n{}\n)\n{}{};",
33 Self::create_if_not_exists(input),
34 input.table_name,
35 Self::format_columns(input),
36 Self::format_table_options(input),
37 Self::format_with_clause(input),
38 ))
39 }
40}
41
42impl CreateTableExprTranslator {
43 fn create_if_not_exists(input: &CreateTableExpr) -> &str {
44 if input.if_not_exists {
45 " IF NOT EXISTS "
46 } else {
47 " "
48 }
49 }
50
51 fn format_columns(input: &CreateTableExpr) -> String {
52 let mut output =
53 Vec::with_capacity(input.columns.len() + (!input.primary_keys.is_empty()) as usize);
54 for column in &input.columns {
55 output.push(Self::format_column(column));
56 }
57 if let Some(primary_keys) = Self::format_primary_keys(input) {
58 output.push(primary_keys);
59 }
60 output.join(",\n")
61 }
62
63 fn format_column(column: &Column) -> String {
64 vec![
65 column.name.to_string(),
66 Self::format_column_type(&column.column_type),
67 Self::format_column_options(&column.options),
68 ]
69 .into_iter()
70 .filter(|s| !s.is_empty())
71 .collect::<Vec<_>>()
72 .join(" ")
73 }
74
75 fn format_partition(input: &CreateTableExpr) -> Option<String> {
76 input.partition.as_ref().map(|partition| {
77 format!(
78 "PARTITION ON COLUMNS({}) (\n{}\n)",
79 partition.partition_columns().join(", "),
80 partition
81 .partition_bounds()
82 .iter()
83 .map(Self::format_partition_bound)
84 .collect::<Vec<_>>()
85 .join(",\n")
86 )
87 })
88 }
89
90 fn format_partition_bound(bound: &PartitionBound) -> String {
91 match bound {
92 PartitionBound::Value(v) => match v {
93 Value::String(v) => format!("'{}'", v.as_utf8()),
94 _ => format!("{v}"),
95 },
96 PartitionBound::MaxValue => "MAXVALUE".to_string(),
97 PartitionBound::Expr(expr) => expr.to_parser_expr().to_string(),
98 }
99 }
100
101 fn format_column_type(column_type: &ConcreteDataType) -> String {
102 concrete_data_type_to_sql_data_type(column_type)
104 .unwrap()
105 .to_string()
106 }
107
108 fn format_column_options(options: &[ColumnOption]) -> String {
109 let mut output = Vec::with_capacity(options.len());
110 for option in options {
111 if option != &ColumnOption::PrimaryKey {
112 output.push(option.to_string());
113 }
114 }
115 output.join(" ")
116 }
117
118 fn format_primary_keys(input: &CreateTableExpr) -> Option<String> {
119 if input.primary_keys.is_empty() {
120 None
121 } else {
122 Some(format!(
123 "PRIMARY KEY({})",
124 input
125 .primary_keys
126 .iter()
127 .map(|idx| input.columns[*idx].name.to_string())
128 .collect::<Vec<_>>()
129 .join(", ")
130 ))
131 }
132 }
133
134 fn format_table_options(input: &CreateTableExpr) -> String {
135 let mut output = vec![];
136 if let Some(partition) = Self::format_partition(input) {
137 output.push(partition);
138 }
139 if !input.engine.is_empty() {
140 output.push(format!("ENGINE={}", input.engine));
141 }
142
143 output.join("\n")
144 }
145
146 fn format_with_clause(input: &CreateTableExpr) -> String {
147 if input.options.is_empty() {
148 String::new()
149 } else {
150 let mut output = vec![];
151 for (key, value) in &input.options {
152 output.push(format!("\"{key}\" = \"{value}\""));
153 }
154 format!(" with ({})", output.join(",\n"))
155 }
156 }
157}
158
159pub struct CreateDatabaseExprTranslator;
160
161impl DslTranslator<CreateDatabaseExpr, String> for CreateDatabaseExprTranslator {
162 type Error = Error;
163
164 fn translate(&self, input: &CreateDatabaseExpr) -> Result<String> {
165 Ok(format!(
166 "CREATE DATABASE{}{};",
167 Self::create_if_not_exists(input),
168 input.database_name
169 ))
170 }
171}
172
173impl CreateDatabaseExprTranslator {
174 fn create_if_not_exists(input: &CreateDatabaseExpr) -> &str {
175 if input.if_not_exists {
176 " IF NOT EXISTS "
177 } else {
178 " "
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use partition::expr::{Operand, PartitionExpr, RestrictedOp};
186 use partition::partition::{PartitionBound, PartitionDef};
187
188 use super::CreateTableExprTranslator;
189 use crate::ir::create_expr::{CreateDatabaseExprBuilder, CreateTableExprBuilder};
190 use crate::test_utils;
191 use crate::translator::DslTranslator;
192
193 #[test]
194 fn test_create_table_expr_translator() {
195 let test_ctx = test_utils::new_test_ctx();
196 let create_table_expr = CreateTableExprBuilder::default()
197 .columns(test_ctx.columns.clone())
198 .table_name("system_metrics")
199 .engine("mito")
200 .primary_keys(vec![0, 1])
201 .partition(PartitionDef::new(
202 vec!["idc".to_string()],
203 vec![
204 PartitionBound::Expr(PartitionExpr::new(
205 Operand::Column("idc".to_string()),
206 RestrictedOp::Lt,
207 Operand::Value(datatypes::value::Value::Int32(10)),
208 )),
209 PartitionBound::Expr(PartitionExpr::new(
210 Operand::Expr(PartitionExpr::new(
211 Operand::Column("idc".to_string()),
212 RestrictedOp::GtEq,
213 Operand::Value(datatypes::value::Value::Int32(10)),
214 )),
215 RestrictedOp::And,
216 Operand::Expr(PartitionExpr::new(
217 Operand::Column("idc".to_string()),
218 RestrictedOp::Lt,
219 Operand::Value(datatypes::value::Value::Int32(50)),
220 )),
221 )),
222 PartitionBound::Expr(PartitionExpr::new(
223 Operand::Column("idc".to_string()),
224 RestrictedOp::GtEq,
225 Operand::Value(datatypes::value::Value::Int32(50)),
226 )),
227 ],
228 ))
229 .build()
230 .unwrap();
231
232 let output = CreateTableExprTranslator
233 .translate(&create_table_expr)
234 .unwrap();
235 assert_eq!(
236 "CREATE TABLE system_metrics(
237host STRING,
238idc STRING,
239cpu_util DOUBLE,
240memory_util DOUBLE,
241disk_util DOUBLE,
242ts TIMESTAMP(3) TIME INDEX,
243PRIMARY KEY(host, idc)
244)
245PARTITION ON COLUMNS(idc) (
246idc < 10,
247idc >= 10 AND idc < 50,
248idc >= 50
249)
250ENGINE=mito;",
251 output
252 );
253 }
254
255 #[test]
256 fn test_create_database_expr_translator() {
257 let create_database_expr = CreateDatabaseExprBuilder::default()
258 .database_name("all_metrics")
259 .if_not_exists(true)
260 .build()
261 .unwrap();
262
263 let output = super::CreateDatabaseExprTranslator
264 .translate(&create_database_expr)
265 .unwrap();
266
267 assert_eq!("CREATE DATABASE IF NOT EXISTS all_metrics;", output);
268 }
269}