1use ruest_db_schema::{Attribute, FieldKind, Schema};
2
3use crate::naming::{column_name, default_sql, pg_type, table_columns, table_name};
4
5pub fn generate_create_all(schema: &Schema) -> String {
7 let mut parts = Vec::new();
8 for model in &schema.models {
9 parts.push(generate_create_table(model));
10 }
11 parts.join("\n\n")
12}
13
14pub fn generate_migration_sql(schema: &Schema) -> String {
15 format!(
16 "-- RuestDB migration\n-- Generated from schema.ruest\n\n{}\n",
17 generate_create_all(schema)
18 )
19}
20
21fn generate_create_table(model: &ruest_db_schema::Model) -> String {
22 let table = table_name(&model.name);
23 let mut lines = Vec::new();
24
25 for field in table_columns(model) {
26 let col = column_name(&field.name);
27 let mut line = format!(" \"{col}\" {}", pg_type(field));
28 if field
29 .attributes
30 .iter()
31 .any(|a| matches!(a, Attribute::Id))
32 {
33 line.push_str(" PRIMARY KEY");
34 }
35 if field
36 .attributes
37 .iter()
38 .any(|a| matches!(a, Attribute::Unique))
39 {
40 line.push_str(" UNIQUE");
41 }
42 if !field.optional {
43 line.push_str(" NOT NULL");
44 }
45 if let Some(def) = default_sql(field) {
46 line.push_str(&format!(" DEFAULT {def}"));
47 }
48 lines.push(line);
49 }
50
51 for field in &model.fields {
53 let FieldKind::Model(target) = &field.kind else {
54 continue;
55 };
56 let Some(rel) = field.attributes.iter().find_map(|a| match a {
57 Attribute::Relation(r) => Some(r),
58 _ => None,
59 }) else {
60 continue;
61 };
62 let fk_col = column_name(&rel.fields[0]);
63 let ref_table = table_name(target);
64 let ref_col = column_name(&rel.references[0]);
65 lines.push(format!(
66 " CONSTRAINT \"fk_{table}_{fk_col}\" FOREIGN KEY (\"{fk_col}\") REFERENCES \"{ref_table}\" (\"{ref_col}\") ON DELETE CASCADE"
67 ));
68 }
69
70 format!(
71 "CREATE TABLE IF NOT EXISTS \"{table}\" (\n{}\n);",
72 lines.join(",\n")
73 )
74}
75
76#[cfg(test)]
77mod tests {
78 use ruest_db_parser::parse_schema;
79 use super::*;
80
81 const SAMPLE: &str = r#"
82model User {
83 id String @id @default(uuid())
84 email String @unique
85 name String
86 posts Post[]
87}
88
89model Post {
90 id String @id @default(uuid())
91 title String
92 userId String
93 user User @relation(fields: [userId], references: [id])
94}
95"#;
96
97 #[test]
98 fn generates_postgres_ddl() {
99 let schema = parse_schema(SAMPLE).unwrap();
100 let sql = generate_create_all(&schema);
101 assert!(sql.contains("CREATE TABLE IF NOT EXISTS \"users\""));
102 assert!(sql.contains("CREATE TABLE IF NOT EXISTS \"posts\""));
103 assert!(sql.contains("FOREIGN KEY"));
104 }
105}