toolu_orm_core/sql/
gen.rs1use crate::dialect::Dialect;
4use crate::diff::Operation;
5use crate::ordering::order_operations;
6
7use super::ddl::{add_column_sql, create_index_sql, create_table_sql, recreation_sql};
8use super::postgres::{alter_column_statements_postgres, needs_recreation_sqlite};
9
10pub fn generate_sql(operations: &[Operation]) -> String {
11 generate_sql_for(operations, Dialect::CURRENT)
12}
13
14pub fn generate_sql_for(operations: &[Operation], dialect: Dialect) -> String {
15 let ordered = order_operations(operations.to_vec());
16 let mut parts: Vec<String> = Vec::new();
17 for op in ordered {
18 let chunk = operation_sql(&op, dialect);
19 if !chunk.trim().is_empty() {
20 parts.push(chunk);
21 }
22 }
23 parts.join("\n\n--> statement-breakpoint\n\n")
24}
25
26fn operation_sql(op: &Operation, dialect: Dialect) -> String {
27 match op {
28 Operation::CreateEnum { name, variants } => match dialect {
29 Dialect::Postgres => {
30 let vals = variants
31 .iter()
32 .map(|v| format!("'{v}'"))
33 .collect::<Vec<_>>()
34 .join(", ");
35 format!("CREATE TYPE \"{name}\" AS ENUM ({vals});")
36 },
37 Dialect::Sqlite => format!("-- enum \"{name}\" (SQLite: TEXT + CHECK)"),
38 },
39 Operation::AlterEnum {
40 name,
41 added,
42 removed,
43 } => {
44 let mut s = String::new();
45 if !added.is_empty() {
46 match dialect {
47 Dialect::Postgres => {
48 let stmts: Vec<String> = added
49 .iter()
50 .map(|v| format!("ALTER TYPE \"{name}\" ADD VALUE '{v}';"))
51 .collect();
52 s.push_str(&stmts.join("\n\n--> statement-breakpoint\n\n"));
53 },
54 Dialect::Sqlite => {
55 s.push_str(&format!(
56 "-- ALTER ENUM \"{name}\" add variants (SQLite: adjust CHECK)\n",
57 ));
58 },
59 }
60 }
61 if !removed.is_empty() {
62 match dialect {
63 Dialect::Postgres => {
64 s.push_str("-- TODO: enum variant removal requires type recreation on Postgres for ");
65 s.push_str(name);
66 s.push('\n');
67 },
68 Dialect::Sqlite => {
69 s.push_str("-- SQLite: adjust CHECK for enum variant removal\n");
70 },
71 }
72 }
73 s.trim_end().to_owned()
74 },
75 Operation::DropEnum { name } => match dialect {
76 Dialect::Postgres => format!("DROP TYPE IF EXISTS \"{name}\";"),
77 Dialect::Sqlite => format!("-- drop enum \"{name}\" (no-op on SQLite)"),
78 },
79 Operation::CreateTable { table } => create_table_sql(table, dialect),
80 Operation::DropTable { name } => format!("DROP TABLE IF EXISTS \"{name}\";"),
81 Operation::RenameTable { old, new } => {
82 format!("ALTER TABLE \"{old}\" RENAME TO \"{new}\";")
83 },
84 Operation::RenameColumn { table, old, new } => {
85 format!("ALTER TABLE \"{table}\" RENAME COLUMN \"{old}\" TO \"{new}\";")
86 },
87 Operation::AddColumn { table, column } => add_column_sql(table, column, dialect),
88 Operation::DropColumn { table, column } => match dialect {
89 Dialect::Postgres => format!("ALTER TABLE \"{table}\" DROP COLUMN IF EXISTS \"{column}\";"),
90 Dialect::Sqlite => format!("ALTER TABLE \"{table}\" DROP COLUMN \"{column}\";"),
91 },
92 Operation::AlterColumn {
93 table,
94 changes,
95 table_def,
96 } => {
97 if changes.is_empty() {
98 return String::new();
99 }
100 match dialect {
101 Dialect::Postgres => alter_column_statements_postgres(table, changes, table_def)
102 .join("\n\n--> statement-breakpoint\n\n"),
103 Dialect::Sqlite => {
104 if needs_recreation_sqlite(changes) {
105 recreation_sql(table, table_def)
106 } else {
107 String::new()
108 }
109 },
110 }
111 },
112 Operation::CreateIndex { table, index } => create_index_sql(table, index),
113 Operation::DropIndex { name } => format!("DROP INDEX IF EXISTS \"{name}\";"),
114 Operation::AddForeignKey { table, fk } => match dialect {
115 Dialect::Postgres => {
116 let cols = fk
117 .columns
118 .iter()
119 .map(|c| format!("\"{c}\""))
120 .collect::<Vec<_>>()
121 .join(", ");
122 let ref_cols = fk
123 .references_columns
124 .iter()
125 .map(|c| format!("\"{c}\""))
126 .collect::<Vec<_>>()
127 .join(", ");
128 let mut s = format!(
129 "ALTER TABLE \"{table}\" ADD CONSTRAINT \"{}\" FOREIGN KEY ({cols}) REFERENCES \"{}\" ({ref_cols})",
130 fk.name, fk.references_table
131 );
132 if let Some(a) = fk.on_delete {
133 s.push_str(&format!(" ON DELETE {}", a.as_sql()));
134 }
135 if let Some(a) = fk.on_update {
136 s.push_str(&format!(" ON UPDATE {}", a.as_sql()));
137 }
138 s.push(';');
139 s
140 },
141 Dialect::Sqlite => format!(
142 "-- FOREIGN KEY \"{}\" on \"{table}\" (SQLite: rebuild table to attach constraint)",
143 fk.name
144 ),
145 },
146 Operation::DropForeignKey { table, name } => match dialect {
147 Dialect::Postgres => {
148 format!("ALTER TABLE \"{table}\" DROP CONSTRAINT IF EXISTS \"{name}\";")
149 },
150 Dialect::Sqlite => {
151 format!("-- drop FOREIGN KEY \"{name}\" on \"{table}\" (SQLite: rebuild table)")
152 },
153 },
154 Operation::AddCheckConstraint { table, name, expr } => match dialect {
155 Dialect::Postgres => {
156 format!("ALTER TABLE \"{table}\" ADD CONSTRAINT \"{name}\" CHECK ({expr});")
157 },
158 Dialect::Sqlite => format!(
159 "-- ADD CHECK \"{name}\" on \"{table}\" ({expr}) — SQLite may require table rebuild"
160 ),
161 },
162 Operation::DropCheckConstraint { table, name } => match dialect {
163 Dialect::Postgres => {
164 format!("ALTER TABLE \"{table}\" DROP CONSTRAINT IF EXISTS \"{name}\";")
165 },
166 Dialect::Sqlite => {
167 format!("-- DROP CHECK \"{name}\" on \"{table}\" (SQLite may require table rebuild)")
168 },
169 },
170 }
171}