elif_orm/migrations/
schema_builder.rs1pub struct SchemaBuilder {
8 statements: Vec<String>,
9}
10
11impl SchemaBuilder {
12 pub fn new() -> Self {
14 Self {
15 statements: Vec::new(),
16 }
17 }
18
19 pub fn create_table<F>(&mut self, table_name: &str, callback: F) -> &mut Self
21 where
22 F: FnOnce(&mut TableBuilder),
23 {
24 let mut table_builder = TableBuilder::new(table_name);
25 callback(&mut table_builder);
26
27 let sql = table_builder.to_sql();
28 self.statements.push(sql);
29 self
30 }
31
32 pub fn drop_table(&mut self, table_name: &str) -> &mut Self {
34 self.statements
35 .push(format!("DROP TABLE IF EXISTS {};", table_name));
36 self
37 }
38
39 pub fn add_column(
41 &mut self,
42 table_name: &str,
43 column_name: &str,
44 column_type: &str,
45 ) -> &mut Self {
46 self.statements.push(format!(
47 "ALTER TABLE {} ADD COLUMN {} {};",
48 table_name, column_name, column_type
49 ));
50 self
51 }
52
53 pub fn drop_column(&mut self, table_name: &str, column_name: &str) -> &mut Self {
55 self.statements.push(format!(
56 "ALTER TABLE {} DROP COLUMN {};",
57 table_name, column_name
58 ));
59 self
60 }
61
62 pub fn create_index(
64 &mut self,
65 table_name: &str,
66 column_names: &[&str],
67 index_name: Option<&str>,
68 ) -> &mut Self {
69 let default_name = format!("idx_{}_{}", table_name, column_names.join("_"));
70 let index_name = index_name.unwrap_or(&default_name);
71 self.statements.push(format!(
72 "CREATE INDEX {} ON {} ({});",
73 index_name,
74 table_name,
75 column_names.join(", ")
76 ));
77 self
78 }
79
80 pub fn drop_index(&mut self, index_name: &str) -> &mut Self {
82 self.statements
83 .push(format!("DROP INDEX IF EXISTS {};", index_name));
84 self
85 }
86
87 pub fn to_sql(&self) -> Vec<String> {
89 self.statements.clone()
90 }
91
92 pub fn build(&self) -> String {
94 self.statements.join("\n")
95 }
96}
97
98pub struct TableBuilder {
100 table_name: String,
101 columns: Vec<String>,
102 constraints: Vec<String>,
103}
104
105impl TableBuilder {
106 pub fn new(table_name: &str) -> Self {
107 Self {
108 table_name: table_name.to_string(),
109 columns: Vec::new(),
110 constraints: Vec::new(),
111 }
112 }
113
114 pub fn column(&mut self, name: &str, column_type: &str) -> &mut Self {
116 self.columns.push(format!("{} {}", name, column_type));
117 self
118 }
119
120 pub fn id(&mut self, name: &str) -> &mut Self {
122 self.columns.push(format!("{} SERIAL PRIMARY KEY", name));
123 self
124 }
125
126 pub fn uuid(&mut self, name: &str) -> &mut Self {
128 self.columns
129 .push(format!("{} UUID DEFAULT gen_random_uuid()", name));
130 self
131 }
132
133 pub fn string(&mut self, name: &str, length: Option<u32>) -> &mut Self {
135 let column_type = match length {
136 Some(len) => format!("VARCHAR({})", len),
137 None => "TEXT".to_string(),
138 };
139 self.columns.push(format!("{} {}", name, column_type));
140 self
141 }
142
143 pub fn integer(&mut self, name: &str) -> &mut Self {
145 self.columns.push(format!("{} INTEGER", name));
146 self
147 }
148
149 pub fn boolean(&mut self, name: &str) -> &mut Self {
151 self.columns.push(format!("{} BOOLEAN", name));
152 self
153 }
154
155 pub fn timestamps(&mut self) -> &mut Self {
157 self.columns
158 .push("created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP".to_string());
159 self.columns
160 .push("updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP".to_string());
161 self
162 }
163
164 pub fn primary_key(&mut self, columns: &[&str]) -> &mut Self {
166 self.constraints
167 .push(format!("PRIMARY KEY ({})", columns.join(", ")));
168 self
169 }
170
171 pub fn foreign_key(
173 &mut self,
174 column: &str,
175 references_table: &str,
176 references_column: &str,
177 ) -> &mut Self {
178 self.constraints.push(format!(
179 "FOREIGN KEY ({}) REFERENCES {} ({})",
180 column, references_table, references_column
181 ));
182 self
183 }
184
185 pub fn unique(&mut self, columns: &[&str]) -> &mut Self {
187 self.constraints
188 .push(format!("UNIQUE ({})", columns.join(", ")));
189 self
190 }
191
192 pub fn to_sql(&self) -> String {
194 let mut parts = self.columns.clone();
195 parts.extend(self.constraints.clone());
196
197 format!(
198 "CREATE TABLE {} (\n {}\n);",
199 self.table_name,
200 parts.join(",\n ")
201 )
202 }
203}
204
205impl Default for SchemaBuilder {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_schema_builder() {
217 let mut builder = SchemaBuilder::new();
218 builder.create_table("users", |table| {
219 table.id("id");
220 table.string("name", Some(255));
221 table.string("email", Some(255));
222 table.timestamps();
223 table.unique(&["email"]);
224 });
225
226 let sql = builder.build();
227 assert!(sql.contains("CREATE TABLE users"));
228 assert!(sql.contains("id SERIAL PRIMARY KEY"));
229 assert!(sql.contains("name VARCHAR(255)"));
230 assert!(sql.contains("email VARCHAR(255)"));
231 assert!(sql.contains("created_at TIMESTAMP"));
232 assert!(sql.contains("UNIQUE (email)"));
233 }
234
235 #[test]
236 fn test_table_builder() {
237 let mut table = TableBuilder::new("posts");
238 table.id("id");
239 table.string("title", Some(255));
240 table.string("content", None);
241 table.integer("user_id");
242 table.timestamps();
243 table.foreign_key("user_id", "users", "id");
244
245 let sql = table.to_sql();
246 assert!(sql.contains("CREATE TABLE posts"));
247 assert!(sql.contains("id SERIAL PRIMARY KEY"));
248 assert!(sql.contains("title VARCHAR(255)"));
249 assert!(sql.contains("content TEXT"));
250 assert!(sql.contains("user_id INTEGER"));
251 assert!(sql.contains("FOREIGN KEY (user_id) REFERENCES users (id)"));
252 }
253}