1use {
2 crate::{
3 ast::{ColumnDef, Expr, ForeignKey, OrderByExpr, Statement, ToSql},
4 prelude::{parse, translate},
5 result::Result,
6 },
7 chrono::{NaiveDateTime, Utc},
8 serde::{Deserialize, Serialize},
9 std::{fmt::Debug, iter},
10 strum_macros::Display,
11 thiserror::Error as ThisError,
12};
13
14#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Display)]
15#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
16pub enum SchemaIndexOrd {
17 Asc,
18 Desc,
19 Both,
20}
21
22#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
23pub struct SchemaIndex {
24 pub name: String,
25 pub expr: Expr,
26 pub order: SchemaIndexOrd,
27 pub created: NaiveDateTime,
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
31pub struct Schema {
32 pub table_name: String,
33 pub column_defs: Option<Vec<ColumnDef>>,
34 pub indexes: Vec<SchemaIndex>,
35 pub engine: Option<String>,
36 pub foreign_keys: Vec<ForeignKey>,
37 pub comment: Option<String>,
38}
39
40impl Schema {
41 pub fn to_ddl(&self) -> String {
42 let Schema {
43 table_name,
44 column_defs,
45 indexes,
46 engine,
47 foreign_keys,
48 comment,
49 } = self;
50
51 let create_table = Statement::CreateTable {
52 if_not_exists: false,
53 name: table_name.to_owned(),
54 columns: column_defs.to_owned(),
55 engine: engine.to_owned(),
56 comment: comment.to_owned(),
57 source: None,
58 foreign_keys: foreign_keys.to_owned(),
59 }
60 .to_sql();
61
62 let create_indexes = indexes.iter().map(|SchemaIndex { name, expr, .. }| {
63 let expr = expr.to_sql();
64
65 format!(r#"CREATE INDEX "{name}" ON "{table_name}" ({expr});"#)
66 });
67
68 iter::once(create_table)
69 .chain(create_indexes)
70 .collect::<Vec<_>>()
71 .join("\n")
72 }
73
74 pub fn from_ddl(ddl: &str) -> Result<Schema> {
75 let created = Utc::now().naive_utc();
76 let statements = parse(ddl)?;
77
78 let indexes = statements
79 .iter()
80 .skip(1)
81 .map(|create_index| {
82 let create_index = translate(create_index)?;
83 match create_index {
84 Statement::CreateIndex {
85 name,
86 column: OrderByExpr { expr, asc },
87 ..
88 } => {
89 let order = asc
90 .and_then(|bool| bool.then_some(SchemaIndexOrd::Asc))
91 .unwrap_or(SchemaIndexOrd::Both);
92
93 let index = SchemaIndex {
94 name,
95 expr,
96 order,
97 created,
98 };
99
100 Ok(index)
101 }
102 _ => Err(SchemaParseError::CannotParseDDL.into()),
103 }
104 })
105 .collect::<Result<Vec<_>>>()?;
106
107 let create_table = statements.first().ok_or(SchemaParseError::CannotParseDDL)?;
108 let create_table = translate(create_table)?;
109
110 match create_table {
111 Statement::CreateTable {
112 name,
113 columns,
114 engine,
115 foreign_keys,
116 comment,
117 ..
118 } => Ok(Schema {
119 table_name: name,
120 column_defs: columns,
121 indexes,
122 engine,
123 foreign_keys,
124 comment,
125 }),
126 _ => Err(SchemaParseError::CannotParseDDL.into()),
127 }
128 }
129}
130
131#[derive(ThisError, Debug, PartialEq, Serialize)]
132pub enum SchemaParseError {
133 #[error("cannot parse ddl")]
134 CannotParseDDL,
135}
136
137#[cfg(test)]
138mod tests {
139 use {
140 super::SchemaParseError,
141 crate::{
142 ast::{AstLiteral, ColumnDef, ColumnUniqueOption, Expr},
143 chrono::Utc,
144 data::{Schema, SchemaIndex, SchemaIndexOrd},
145 prelude::DataType,
146 },
147 };
148
149 fn assert_schema(actual: Schema, expected: Schema) {
150 let Schema {
151 table_name,
152 column_defs,
153 indexes,
154 engine,
155 foreign_keys,
156 comment,
157 } = actual;
158
159 let Schema {
160 table_name: table_name_e,
161 column_defs: column_defs_e,
162 indexes: indexes_e,
163 engine: engine_e,
164 foreign_keys: foreign_keys_e,
165 comment: comment_e,
166 } = expected;
167
168 assert_eq!(table_name, table_name_e);
169 assert_eq!(column_defs, column_defs_e);
170 assert_eq!(engine, engine_e);
171 assert_eq!(foreign_keys, foreign_keys_e);
172 assert_eq!(comment, comment_e);
173 indexes
174 .into_iter()
175 .zip(indexes_e)
176 .for_each(|(actual, expected)| assert_index(actual, expected));
177 }
178
179 fn assert_index(actual: SchemaIndex, expected: SchemaIndex) {
180 let SchemaIndex {
181 name, expr, order, ..
182 } = actual;
183 let SchemaIndex {
184 name: name_e,
185 expr: expr_e,
186 order: order_e,
187 ..
188 } = expected;
189
190 assert_eq!(name, name_e);
191 assert_eq!(expr, expr_e);
192 assert_eq!(order, order_e);
193 }
194
195 #[test]
196 fn table_basic() {
197 let schema = Schema {
198 table_name: "User".to_owned(),
199 column_defs: Some(vec![
200 ColumnDef {
201 name: "id".to_owned(),
202 data_type: DataType::Int,
203 nullable: false,
204 default: None,
205 unique: None,
206 comment: None,
207 },
208 ColumnDef {
209 name: "name".to_owned(),
210 data_type: DataType::Text,
211 nullable: true,
212 default: Some(Expr::Literal(AstLiteral::QuotedString("glue".to_owned()))),
213 unique: None,
214 comment: None,
215 },
216 ]),
217 indexes: Vec::new(),
218 engine: None,
219 foreign_keys: Vec::new(),
220 comment: None,
221 };
222
223 let ddl = r#"CREATE TABLE "User" ("id" INT NOT NULL, "name" TEXT NULL DEFAULT 'glue');"#;
224 assert_eq!(schema.to_ddl(), ddl);
225
226 let actual = Schema::from_ddl(ddl).unwrap();
227 assert_schema(actual, schema);
228
229 let schema = Schema {
230 table_name: "Test".to_owned(),
231 column_defs: None,
232 indexes: Vec::new(),
233 engine: None,
234 foreign_keys: Vec::new(),
235 comment: None,
236 };
237 let ddl = r#"CREATE TABLE "Test";"#;
238 assert_eq!(schema.to_ddl(), ddl);
239
240 let actual = Schema::from_ddl(ddl).unwrap();
241 assert_schema(actual, schema);
242 }
243
244 #[test]
245 fn table_primary() {
246 let schema = Schema {
247 table_name: "User".to_owned(),
248 column_defs: Some(vec![ColumnDef {
249 name: "id".to_owned(),
250 data_type: DataType::Int,
251 nullable: false,
252 default: None,
253 unique: Some(ColumnUniqueOption { is_primary: true }),
254 comment: None,
255 }]),
256 indexes: Vec::new(),
257 engine: None,
258 foreign_keys: Vec::new(),
259 comment: None,
260 };
261
262 let ddl = r#"CREATE TABLE "User" ("id" INT NOT NULL PRIMARY KEY);"#;
263 assert_eq!(schema.to_ddl(), ddl);
264
265 let actual = Schema::from_ddl(ddl).unwrap();
266 assert_schema(actual, schema);
267 }
268
269 #[test]
270 fn invalid_ddl() {
271 let invalid_ddl = r#"DROP TABLE "Users";"#;
273 let actual = Schema::from_ddl(invalid_ddl);
274 assert_eq!(actual, Err(SchemaParseError::CannotParseDDL.into()));
275 }
276
277 #[test]
278 fn table_with_index() {
279 let schema = Schema {
280 table_name: "User".to_owned(),
281 column_defs: Some(vec![
282 ColumnDef {
283 name: "id".to_owned(),
284 data_type: DataType::Int,
285 nullable: false,
286 default: None,
287 unique: None,
288 comment: None,
289 },
290 ColumnDef {
291 name: "name".to_owned(),
292 data_type: DataType::Text,
293 nullable: false,
294 default: None,
295 unique: None,
296 comment: None,
297 },
298 ]),
299 indexes: vec![
300 SchemaIndex {
301 name: "User_id".to_owned(),
302 expr: Expr::Identifier("id".to_owned()),
303 order: SchemaIndexOrd::Both,
304 created: Utc::now().naive_utc(),
305 },
306 SchemaIndex {
307 name: "User_name".to_owned(),
308 expr: Expr::Identifier("name".to_owned()),
309 order: SchemaIndexOrd::Both,
310 created: Utc::now().naive_utc(),
311 },
312 ],
313 engine: None,
314 foreign_keys: Vec::new(),
315 comment: None,
316 };
317 let ddl = r#"CREATE TABLE "User" ("id" INT NOT NULL, "name" TEXT NOT NULL);
318CREATE INDEX "User_id" ON "User" ("id");
319CREATE INDEX "User_name" ON "User" ("name");"#;
320 assert_eq!(schema.to_ddl(), ddl);
321
322 let actual = Schema::from_ddl(ddl).unwrap();
323 assert_schema(actual, schema);
324
325 let index_should_not_be_first = r#"CREATE INDEX "User_id" ON "User" ("id");
326CREATE TABLE "User" ("id" INT NOT NULL, "name" TEXT NOT NULL);"#;
327 let actual = Schema::from_ddl(index_should_not_be_first);
328 assert_eq!(actual, Err(SchemaParseError::CannotParseDDL.into()));
329 }
330
331 #[test]
332 fn non_word_identifier() {
333 let schema = Schema {
334 table_name: 1.to_string(),
335 column_defs: Some(vec![
336 ColumnDef {
337 name: 2.to_string(),
338 data_type: DataType::Int,
339 nullable: true,
340 default: None,
341 unique: None,
342 comment: None,
343 },
344 ColumnDef {
345 name: ";".to_owned(),
346 data_type: DataType::Int,
347 nullable: true,
348 default: None,
349 unique: None,
350 comment: None,
351 },
352 ]),
353 indexes: vec![SchemaIndex {
354 name: ".".to_owned(),
355 expr: Expr::Identifier(";".to_owned()),
356 order: SchemaIndexOrd::Both,
357 created: Utc::now().naive_utc(),
358 }],
359 engine: None,
360 foreign_keys: Vec::new(),
361 comment: None,
362 };
363 let ddl = r#"CREATE TABLE "1" ("2" INT NULL, ";" INT NULL);
364CREATE INDEX "." ON "1" (";");"#;
365 assert_eq!(schema.to_ddl(), ddl);
366
367 let actual = Schema::from_ddl(ddl).unwrap();
368 assert_schema(actual, schema);
369 }
370}