1use super::column_builder::ColumnDefinition;
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum Operation {
10 CreateTable(CreateTableOp),
12 DropTable(DropTableOp),
14 RenameTable(RenameTableOp),
16 AddColumn(AddColumnOp),
18 DropColumn(DropColumnOp),
20 AlterColumn(AlterColumnOp),
22 RenameColumn(RenameColumnOp),
24 CreateIndex(CreateIndexOp),
26 DropIndex(DropIndexOp),
28 AddForeignKey(AddForeignKeyOp),
30 DropForeignKey(DropForeignKeyOp),
32 RunSql(RawSqlOp),
34}
35
36impl Operation {
37 #[must_use]
39 pub fn drop_table(name: impl Into<String>) -> Self {
40 Self::DropTable(DropTableOp {
41 name: name.into(),
42 if_exists: false,
43 cascade: false,
44 })
45 }
46
47 #[must_use]
49 pub fn drop_table_if_exists(name: impl Into<String>) -> Self {
50 Self::DropTable(DropTableOp {
51 name: name.into(),
52 if_exists: true,
53 cascade: false,
54 })
55 }
56
57 #[must_use]
59 pub fn rename_table(old_name: impl Into<String>, new_name: impl Into<String>) -> Self {
60 Self::RenameTable(RenameTableOp {
61 old_name: old_name.into(),
62 new_name: new_name.into(),
63 })
64 }
65
66 #[must_use]
68 pub fn add_column(table: impl Into<String>, column: ColumnDefinition) -> Self {
69 Self::AddColumn(AddColumnOp {
70 table: table.into(),
71 column,
72 })
73 }
74
75 #[must_use]
77 pub fn drop_column(table: impl Into<String>, column: impl Into<String>) -> Self {
78 Self::DropColumn(DropColumnOp {
79 table: table.into(),
80 column: column.into(),
81 })
82 }
83
84 #[must_use]
86 pub fn rename_column(
87 table: impl Into<String>,
88 old_name: impl Into<String>,
89 new_name: impl Into<String>,
90 ) -> Self {
91 Self::RenameColumn(RenameColumnOp {
92 table: table.into(),
93 old_name: old_name.into(),
94 new_name: new_name.into(),
95 })
96 }
97
98 #[must_use]
100 pub fn run_sql(sql: impl Into<String>) -> Self {
101 Self::RunSql(RawSqlOp {
102 up_sql: sql.into(),
103 down_sql: None,
104 })
105 }
106
107 #[must_use]
109 pub fn run_sql_reversible(up_sql: impl Into<String>, down_sql: impl Into<String>) -> Self {
110 Self::RunSql(RawSqlOp {
111 up_sql: up_sql.into(),
112 down_sql: Some(down_sql.into()),
113 })
114 }
115
116 #[must_use]
120 pub fn reverse(&self) -> Option<Self> {
121 match self {
122 Self::CreateTable(op) => Some(Self::drop_table(&op.name)),
123 Self::DropTable(_) => None, Self::RenameTable(op) => {
125 Some(Self::rename_table(op.new_name.clone(), op.old_name.clone()))
126 }
127 Self::AddColumn(op) => Some(Self::drop_column(&op.table, &op.column.name)),
128 Self::DropColumn(_) => None, Self::AlterColumn(_) => None, Self::RenameColumn(op) => Some(Self::rename_column(
131 &op.table,
132 op.new_name.clone(),
133 op.old_name.clone(),
134 )),
135 Self::CreateIndex(op) => Some(Self::DropIndex(DropIndexOp {
136 name: op.name.clone(),
137 table: Some(op.table.clone()),
138 if_exists: false,
139 })),
140 Self::DropIndex(_) => None, Self::AddForeignKey(op) => op.name.as_ref().map(|name| {
142 Self::DropForeignKey(DropForeignKeyOp {
143 table: op.table.clone(),
144 name: name.clone(),
145 })
146 }),
147 Self::DropForeignKey(_) => None, Self::RunSql(op) => op.down_sql.as_ref().map(|down| Self::run_sql(down.clone())),
149 }
150 }
151
152 #[must_use]
154 pub fn is_reversible(&self) -> bool {
155 self.reverse().is_some()
156 }
157}
158
159#[derive(Debug, Clone, PartialEq)]
161pub struct CreateTableOp {
162 pub name: String,
164 pub columns: Vec<ColumnDefinition>,
166 pub constraints: Vec<TableConstraint>,
168 pub if_not_exists: bool,
170}
171
172impl From<CreateTableOp> for Operation {
173 fn from(op: CreateTableOp) -> Self {
174 Self::CreateTable(op)
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub enum TableConstraint {
181 PrimaryKey {
183 name: Option<String>,
185 columns: Vec<String>,
187 },
188 Unique {
190 name: Option<String>,
192 columns: Vec<String>,
194 },
195 ForeignKey {
197 name: Option<String>,
199 columns: Vec<String>,
201 references_table: String,
203 references_columns: Vec<String>,
205 on_delete: Option<super::column_builder::ForeignKeyAction>,
207 on_update: Option<super::column_builder::ForeignKeyAction>,
209 },
210 Check {
212 name: Option<String>,
214 expression: String,
216 },
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct DropTableOp {
222 pub name: String,
224 pub if_exists: bool,
226 pub cascade: bool,
228}
229
230impl From<DropTableOp> for Operation {
231 fn from(op: DropTableOp) -> Self {
232 Self::DropTable(op)
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct RenameTableOp {
239 pub old_name: String,
241 pub new_name: String,
243}
244
245impl From<RenameTableOp> for Operation {
246 fn from(op: RenameTableOp) -> Self {
247 Self::RenameTable(op)
248 }
249}
250
251#[derive(Debug, Clone, PartialEq)]
253pub struct AddColumnOp {
254 pub table: String,
256 pub column: ColumnDefinition,
258}
259
260impl From<AddColumnOp> for Operation {
261 fn from(op: AddColumnOp) -> Self {
262 Self::AddColumn(op)
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct DropColumnOp {
269 pub table: String,
271 pub column: String,
273}
274
275impl From<DropColumnOp> for Operation {
276 fn from(op: DropColumnOp) -> Self {
277 Self::DropColumn(op)
278 }
279}
280
281#[derive(Debug, Clone, PartialEq)]
283pub enum AlterColumnChange {
284 SetDataType(crate::ast::DataType),
286 SetNullable(bool),
288 SetDefault(super::column_builder::DefaultValue),
290 DropDefault,
292}
293
294#[derive(Debug, Clone, PartialEq)]
296pub struct AlterColumnOp {
297 pub table: String,
299 pub column: String,
301 pub change: AlterColumnChange,
303}
304
305impl From<AlterColumnOp> for Operation {
306 fn from(op: AlterColumnOp) -> Self {
307 Self::AlterColumn(op)
308 }
309}
310
311#[derive(Debug, Clone, PartialEq, Eq)]
313pub struct RenameColumnOp {
314 pub table: String,
316 pub old_name: String,
318 pub new_name: String,
320}
321
322impl From<RenameColumnOp> for Operation {
323 fn from(op: RenameColumnOp) -> Self {
324 Self::RenameColumn(op)
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
330pub enum IndexType {
331 #[default]
333 BTree,
334 Hash,
336 Gist,
338 Gin,
340}
341
342#[derive(Debug, Clone, PartialEq, Eq)]
344pub struct CreateIndexOp {
345 pub name: String,
347 pub table: String,
349 pub columns: Vec<String>,
351 pub unique: bool,
353 pub index_type: IndexType,
355 pub if_not_exists: bool,
357 pub condition: Option<String>,
359}
360
361impl From<CreateIndexOp> for Operation {
362 fn from(op: CreateIndexOp) -> Self {
363 Self::CreateIndex(op)
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct DropIndexOp {
370 pub name: String,
372 pub table: Option<String>,
374 pub if_exists: bool,
376}
377
378impl From<DropIndexOp> for Operation {
379 fn from(op: DropIndexOp) -> Self {
380 Self::DropIndex(op)
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq)]
386pub struct AddForeignKeyOp {
387 pub table: String,
389 pub name: Option<String>,
391 pub columns: Vec<String>,
393 pub references_table: String,
395 pub references_columns: Vec<String>,
397 pub on_delete: Option<super::column_builder::ForeignKeyAction>,
399 pub on_update: Option<super::column_builder::ForeignKeyAction>,
401}
402
403impl From<AddForeignKeyOp> for Operation {
404 fn from(op: AddForeignKeyOp) -> Self {
405 Self::AddForeignKey(op)
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
411pub struct DropForeignKeyOp {
412 pub table: String,
414 pub name: String,
416}
417
418impl From<DropForeignKeyOp> for Operation {
419 fn from(op: DropForeignKeyOp) -> Self {
420 Self::DropForeignKey(op)
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
426pub struct RawSqlOp {
427 pub up_sql: String,
429 pub down_sql: Option<String>,
431}
432
433impl From<RawSqlOp> for Operation {
434 fn from(op: RawSqlOp) -> Self {
435 Self::RunSql(op)
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442 use crate::migrations::column_builder::{bigint, varchar, ForeignKeyAction};
443
444 #[test]
445 fn test_drop_table_operation() {
446 let op = Operation::drop_table("users");
447 match op {
448 Operation::DropTable(drop) => {
449 assert_eq!(drop.name, "users");
450 assert!(!drop.if_exists);
451 assert!(!drop.cascade);
452 }
453 _ => panic!("Expected DropTable operation"),
454 }
455 }
456
457 #[test]
458 fn test_rename_table_operation() {
459 let op = Operation::rename_table("old_name", "new_name");
460 match op {
461 Operation::RenameTable(rename) => {
462 assert_eq!(rename.old_name, "old_name");
463 assert_eq!(rename.new_name, "new_name");
464 }
465 _ => panic!("Expected RenameTable operation"),
466 }
467 }
468
469 #[test]
470 fn test_add_column_operation() {
471 let col = varchar("email", 255).not_null().build();
472 let op = Operation::add_column("users", col);
473 match op {
474 Operation::AddColumn(add) => {
475 assert_eq!(add.table, "users");
476 assert_eq!(add.column.name, "email");
477 }
478 _ => panic!("Expected AddColumn operation"),
479 }
480 }
481
482 #[test]
483 fn test_reverse_operations() {
484 let create = CreateTableOp {
486 name: "users".to_string(),
487 columns: vec![bigint("id").primary_key().build()],
488 constraints: vec![],
489 if_not_exists: false,
490 };
491 let op = Operation::CreateTable(create);
492 let reversed = op.reverse().expect("Should be reversible");
493 match reversed {
494 Operation::DropTable(drop) => assert_eq!(drop.name, "users"),
495 _ => panic!("Expected DropTable"),
496 }
497
498 let rename = Operation::rename_table("old", "new");
500 let reversed = rename.reverse().expect("Should be reversible");
501 match reversed {
502 Operation::RenameTable(r) => {
503 assert_eq!(r.old_name, "new");
504 assert_eq!(r.new_name, "old");
505 }
506 _ => panic!("Expected RenameTable"),
507 }
508
509 let add = Operation::add_column("users", varchar("email", 255).build());
511 let reversed = add.reverse().expect("Should be reversible");
512 match reversed {
513 Operation::DropColumn(drop) => {
514 assert_eq!(drop.table, "users");
515 assert_eq!(drop.column, "email");
516 }
517 _ => panic!("Expected DropColumn"),
518 }
519
520 let drop = Operation::drop_table("users");
522 assert!(drop.reverse().is_none());
523 }
524
525 #[test]
526 fn test_raw_sql_reversibility() {
527 let op = Operation::run_sql("INSERT INTO config VALUES ('key', 'value')");
529 assert!(!op.is_reversible());
530
531 let op = Operation::run_sql_reversible(
533 "INSERT INTO config VALUES ('key', 'value')",
534 "DELETE FROM config WHERE key = 'key'",
535 );
536 assert!(op.is_reversible());
537 }
538
539 #[test]
540 fn test_table_constraint() {
541 let pk = TableConstraint::PrimaryKey {
542 name: Some("pk_users".to_string()),
543 columns: vec!["id".to_string()],
544 };
545 match pk {
546 TableConstraint::PrimaryKey { name, columns } => {
547 assert_eq!(name, Some("pk_users".to_string()));
548 assert_eq!(columns, vec!["id"]);
549 }
550 _ => panic!("Expected PrimaryKey"),
551 }
552
553 let fk = TableConstraint::ForeignKey {
554 name: Some("fk_user_company".to_string()),
555 columns: vec!["company_id".to_string()],
556 references_table: "companies".to_string(),
557 references_columns: vec!["id".to_string()],
558 on_delete: Some(ForeignKeyAction::Cascade),
559 on_update: None,
560 };
561 match fk {
562 TableConstraint::ForeignKey {
563 references_table,
564 on_delete,
565 ..
566 } => {
567 assert_eq!(references_table, "companies");
568 assert_eq!(on_delete, Some(ForeignKeyAction::Cascade));
569 }
570 _ => panic!("Expected ForeignKey"),
571 }
572 }
573}