1use sea_query::{Alias, ForeignKey, Query, Table};
2
3use vespertide_core::{TableConstraint, TableDef};
4
5use super::helpers::{build_sqlite_temp_table_create, recreate_indexes_after_rebuild};
6use super::rename_table::build_rename_table;
7use super::types::{BuiltQuery, DatabaseBackend};
8use crate::error::QueryError;
9use crate::sql::RawSql;
10
11pub fn build_remove_constraint(
12 backend: &DatabaseBackend,
13 table: &str,
14 constraint: &TableConstraint,
15 current_schema: &[TableDef],
16 pending_constraints: &[TableConstraint],
17) -> Result<Vec<BuiltQuery>, QueryError> {
18 match constraint {
19 TableConstraint::PrimaryKey { .. } => {
20 if *backend == DatabaseBackend::Sqlite {
21 let table_def = current_schema.iter().find(|t| t.name == table).ok_or_else(|| QueryError::Other(format!("Table '{}' not found in current schema. SQLite requires current schema information to remove constraints.", table)))?;
23
24 let mut new_constraints = table_def.constraints.clone();
26 new_constraints.retain(|c| !matches!(c, TableConstraint::PrimaryKey { .. }));
27
28 let temp_table = format!("{}_temp", table);
29
30 let create_query = build_sqlite_temp_table_create(
32 backend,
33 &temp_table,
34 table,
35 &table_def.columns,
36 &new_constraints,
37 );
38
39 let column_aliases: Vec<Alias> = table_def
41 .columns
42 .iter()
43 .map(|c| Alias::new(&c.name))
44 .collect();
45 let mut select_query = Query::select();
46 for col_alias in &column_aliases {
47 select_query = select_query.column(col_alias.clone()).to_owned();
48 }
49 select_query = select_query.from(Alias::new(table)).to_owned();
50
51 let insert_stmt = Query::insert()
52 .into_table(Alias::new(&temp_table))
53 .columns(column_aliases.clone())
54 .select_from(select_query)
55 .unwrap()
56 .to_owned();
57 let insert_query = BuiltQuery::Insert(Box::new(insert_stmt));
58
59 let drop_table = Table::drop().table(Alias::new(table)).to_owned();
61 let drop_query = BuiltQuery::DropTable(Box::new(drop_table));
62
63 let rename_query = build_rename_table(&temp_table, table);
65
66 let index_queries = recreate_indexes_after_rebuild(
68 table,
69 &table_def.constraints,
70 pending_constraints,
71 );
72
73 let mut queries = vec![create_query, insert_query, drop_query, rename_query];
74 queries.extend(index_queries);
75 Ok(queries)
76 } else {
77 let pg_sql = format!(
79 "ALTER TABLE \"{}\" DROP CONSTRAINT \"{}_pkey\"",
80 table, table
81 );
82 let mysql_sql = format!("ALTER TABLE `{}` DROP PRIMARY KEY", table);
83 Ok(vec![BuiltQuery::Raw(RawSql::per_backend(
84 pg_sql.clone(),
85 mysql_sql,
86 pg_sql,
87 ))])
88 }
89 }
90 TableConstraint::Unique { name, columns } => {
91 if *backend == DatabaseBackend::Sqlite {
93 let table_def = current_schema.iter().find(|t| t.name == table).ok_or_else(|| QueryError::Other(format!("Table '{}' not found in current schema. SQLite requires current schema information to remove constraints.", table)))?;
95
96 let mut new_constraints = table_def.constraints.clone();
98 new_constraints.retain(|c| {
99 match (c, constraint) {
100 (
101 TableConstraint::Unique {
102 name: c_name,
103 columns: c_cols,
104 },
105 TableConstraint::Unique {
106 name: r_name,
107 columns: r_cols,
108 },
109 ) => {
110 if let (Some(cn), Some(rn)) = (c_name, r_name) {
112 cn != rn
113 } else {
114 c_cols != r_cols
115 }
116 }
117 _ => true,
118 }
119 });
120
121 let temp_table = format!("{}_temp", table);
122
123 let create_query = build_sqlite_temp_table_create(
125 backend,
126 &temp_table,
127 table,
128 &table_def.columns,
129 &new_constraints,
130 );
131
132 let column_aliases: Vec<Alias> = table_def
134 .columns
135 .iter()
136 .map(|c| Alias::new(&c.name))
137 .collect();
138 let mut select_query = Query::select();
139 for col_alias in &column_aliases {
140 select_query = select_query.column(col_alias.clone()).to_owned();
141 }
142 select_query = select_query.from(Alias::new(table)).to_owned();
143
144 let insert_stmt = Query::insert()
145 .into_table(Alias::new(&temp_table))
146 .columns(column_aliases.clone())
147 .select_from(select_query)
148 .unwrap()
149 .to_owned();
150 let insert_query = BuiltQuery::Insert(Box::new(insert_stmt));
151
152 let drop_table = Table::drop().table(Alias::new(table)).to_owned();
154 let drop_query = BuiltQuery::DropTable(Box::new(drop_table));
155
156 let rename_query = build_rename_table(&temp_table, table);
158
159 let index_queries =
161 recreate_indexes_after_rebuild(table, &new_constraints, pending_constraints);
162
163 let mut queries = vec![create_query, insert_query, drop_query, rename_query];
164 queries.extend(index_queries);
165 Ok(queries)
166 } else {
167 let constraint_name = vespertide_naming::build_unique_constraint_name(
170 table,
171 columns,
172 name.as_deref(),
173 );
174 let pg_sql = format!("DROP INDEX \"{}\"", constraint_name);
177 let mysql_sql = format!("ALTER TABLE `{}` DROP INDEX `{}`", table, constraint_name);
178 let sqlite_sql = format!("DROP INDEX \"{}\"", constraint_name);
179 Ok(vec![BuiltQuery::Raw(RawSql::per_backend(
180 pg_sql, mysql_sql, sqlite_sql,
181 ))])
182 }
183 }
184 TableConstraint::ForeignKey { name, columns, .. } => {
185 if *backend == DatabaseBackend::Sqlite {
187 let table_def = current_schema.iter().find(|t| t.name == table).ok_or_else(|| QueryError::Other(format!("Table '{}' not found in current schema. SQLite requires current schema information to remove constraints.", table)))?;
189
190 let mut new_constraints = table_def.constraints.clone();
192 new_constraints.retain(|c| {
193 match (c, constraint) {
194 (
195 TableConstraint::ForeignKey {
196 name: c_name,
197 columns: c_cols,
198 ..
199 },
200 TableConstraint::ForeignKey {
201 name: r_name,
202 columns: r_cols,
203 ..
204 },
205 ) => {
206 if let (Some(cn), Some(rn)) = (c_name, r_name) {
208 cn != rn
209 } else {
210 c_cols != r_cols
211 }
212 }
213 _ => true,
214 }
215 });
216
217 let temp_table = format!("{}_temp", table);
218
219 let create_query = build_sqlite_temp_table_create(
221 backend,
222 &temp_table,
223 table,
224 &table_def.columns,
225 &new_constraints,
226 );
227
228 let column_aliases: Vec<Alias> = table_def
230 .columns
231 .iter()
232 .map(|c| Alias::new(&c.name))
233 .collect();
234 let mut select_query = Query::select();
235 for col_alias in &column_aliases {
236 select_query = select_query.column(col_alias.clone()).to_owned();
237 }
238 select_query = select_query.from(Alias::new(table)).to_owned();
239
240 let insert_stmt = Query::insert()
241 .into_table(Alias::new(&temp_table))
242 .columns(column_aliases.clone())
243 .select_from(select_query)
244 .unwrap()
245 .to_owned();
246 let insert_query = BuiltQuery::Insert(Box::new(insert_stmt));
247
248 let drop_table = Table::drop().table(Alias::new(table)).to_owned();
250 let drop_query = BuiltQuery::DropTable(Box::new(drop_table));
251
252 let rename_query = build_rename_table(&temp_table, table);
254
255 let index_queries = recreate_indexes_after_rebuild(
257 table,
258 &table_def.constraints,
259 pending_constraints,
260 );
261
262 let mut queries = vec![create_query, insert_query, drop_query, rename_query];
263 queries.extend(index_queries);
264 Ok(queries)
265 } else {
266 let constraint_name =
268 vespertide_naming::build_foreign_key_name(table, columns, name.as_deref());
269 let fk_drop = ForeignKey::drop()
270 .name(&constraint_name)
271 .table(Alias::new(table))
272 .to_owned();
273 Ok(vec![BuiltQuery::DropForeignKey(Box::new(fk_drop))])
274 }
275 }
276 TableConstraint::Index { name, columns } => {
277 let index_name = if let Some(n) = name {
279 vespertide_naming::build_index_name(table, columns, Some(n))
281 } else {
282 vespertide_naming::build_index_name(table, columns, None)
284 };
285 let idx_drop = sea_query::Index::drop()
286 .table(Alias::new(table))
287 .name(&index_name)
288 .to_owned();
289 Ok(vec![BuiltQuery::DropIndex(Box::new(idx_drop))])
290 }
291 TableConstraint::Check { name, .. } => {
292 if *backend == DatabaseBackend::Sqlite {
294 let table_def = current_schema.iter().find(|t| t.name == table).ok_or_else(|| QueryError::Other(format!("Table '{}' not found in current schema. SQLite requires current schema information to remove constraints.", table)))?;
296
297 let mut new_constraints = table_def.constraints.clone();
299 new_constraints.retain(|c| match (c, constraint) {
300 (
301 TableConstraint::Check { name: c_name, .. },
302 TableConstraint::Check { name: r_name, .. },
303 ) => c_name != r_name,
304 _ => true,
305 });
306
307 let temp_table = format!("{}_temp", table);
308
309 let create_query = build_sqlite_temp_table_create(
311 backend,
312 &temp_table,
313 table,
314 &table_def.columns,
315 &new_constraints,
316 );
317
318 let column_aliases: Vec<Alias> = table_def
320 .columns
321 .iter()
322 .map(|c| Alias::new(&c.name))
323 .collect();
324 let mut select_query = Query::select();
325 for col_alias in &column_aliases {
326 select_query = select_query.column(col_alias.clone()).to_owned();
327 }
328 select_query = select_query.from(Alias::new(table)).to_owned();
329
330 let insert_stmt = Query::insert()
331 .into_table(Alias::new(&temp_table))
332 .columns(column_aliases.clone())
333 .select_from(select_query)
334 .unwrap()
335 .to_owned();
336 let insert_query = BuiltQuery::Insert(Box::new(insert_stmt));
337
338 let drop_table = Table::drop().table(Alias::new(table)).to_owned();
340 let drop_query = BuiltQuery::DropTable(Box::new(drop_table));
341
342 let rename_query = build_rename_table(&temp_table, table);
344
345 let index_queries = recreate_indexes_after_rebuild(
347 table,
348 &table_def.constraints,
349 pending_constraints,
350 );
351
352 let mut queries = vec![create_query, insert_query, drop_query, rename_query];
353 queries.extend(index_queries);
354 Ok(queries)
355 } else {
356 let pg_sql = format!("ALTER TABLE \"{}\" DROP CONSTRAINT \"{}\"", table, name);
357 let mysql_sql = format!("ALTER TABLE `{}` DROP CHECK `{}`", table, name);
358 Ok(vec![BuiltQuery::Raw(RawSql::per_backend(
359 pg_sql.clone(),
360 mysql_sql,
361 pg_sql,
362 ))])
363 }
364 }
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use crate::sql::types::DatabaseBackend;
372 use insta::{assert_snapshot, with_settings};
373 use rstest::rstest;
374 use vespertide_core::{ColumnDef, ColumnType, SimpleColumnType, TableConstraint, TableDef};
375
376 #[rstest]
377 #[case::remove_constraint_primary_key_postgres(
378 "remove_constraint_primary_key_postgres",
379 DatabaseBackend::Postgres,
380 &["DROP CONSTRAINT \"users_pkey\""]
381 )]
382 #[case::remove_constraint_primary_key_mysql(
383 "remove_constraint_primary_key_mysql",
384 DatabaseBackend::MySql,
385 &["DROP PRIMARY KEY"]
386 )]
387 #[case::remove_constraint_primary_key_sqlite(
388 "remove_constraint_primary_key_sqlite",
389 DatabaseBackend::Sqlite,
390 &["CREATE TABLE \"users_temp\""]
391 )]
392 #[case::remove_constraint_unique_named_postgres(
393 "remove_constraint_unique_named_postgres",
394 DatabaseBackend::Postgres,
395 &["DROP INDEX \"uq_users__uq_email\""]
396 )]
397 #[case::remove_constraint_unique_named_mysql(
398 "remove_constraint_unique_named_mysql",
399 DatabaseBackend::MySql,
400 &["DROP INDEX `uq_users__uq_email`"]
401 )]
402 #[case::remove_constraint_unique_named_sqlite(
403 "remove_constraint_unique_named_sqlite",
404 DatabaseBackend::Sqlite,
405 &["CREATE TABLE \"users_temp\""]
406 )]
407 #[case::remove_constraint_foreign_key_named_postgres(
408 "remove_constraint_foreign_key_named_postgres",
409 DatabaseBackend::Postgres,
410 &["DROP CONSTRAINT \"fk_users__fk_user\""]
411 )]
412 #[case::remove_constraint_foreign_key_named_mysql(
413 "remove_constraint_foreign_key_named_mysql",
414 DatabaseBackend::MySql,
415 &["DROP FOREIGN KEY `fk_users__fk_user`"]
416 )]
417 #[case::remove_constraint_foreign_key_named_sqlite(
418 "remove_constraint_foreign_key_named_sqlite",
419 DatabaseBackend::Sqlite,
420 &["CREATE TABLE \"users_temp\""]
421 )]
422 #[case::remove_constraint_check_named_postgres(
423 "remove_constraint_check_named_postgres",
424 DatabaseBackend::Postgres,
425 &["DROP CONSTRAINT \"chk_age\""]
426 )]
427 #[case::remove_constraint_check_named_mysql(
428 "remove_constraint_check_named_mysql",
429 DatabaseBackend::MySql,
430 &["DROP CHECK `chk_age`"]
431 )]
432 #[case::remove_constraint_check_named_sqlite(
433 "remove_constraint_check_named_sqlite",
434 DatabaseBackend::Sqlite,
435 &["CREATE TABLE \"users_temp\""]
436 )]
437 fn test_remove_constraint(
438 #[case] title: &str,
439 #[case] backend: DatabaseBackend,
440 #[case] expected: &[&str],
441 ) {
442 let constraint = if title.contains("primary_key") {
443 TableConstraint::PrimaryKey {
444 columns: vec!["id".into()],
445 auto_increment: false,
446 }
447 } else if title.contains("unique") {
448 TableConstraint::Unique {
449 name: Some("uq_email".into()),
450 columns: vec!["email".into()],
451 }
452 } else if title.contains("foreign_key") {
453 TableConstraint::ForeignKey {
454 name: Some("fk_user".into()),
455 columns: vec!["user_id".into()],
456 ref_table: "users".into(),
457 ref_columns: vec!["id".into()],
458 on_delete: None,
459 on_update: None,
460 }
461 } else {
462 TableConstraint::Check {
463 name: "chk_age".into(),
464 expr: "age > 0".into(),
465 }
466 };
467
468 let current_schema = vec![TableDef {
470 name: "users".into(),
471 description: None,
472 columns: if title.contains("check") {
473 vec![
474 ColumnDef {
475 name: "id".into(),
476 r#type: ColumnType::Simple(SimpleColumnType::Integer),
477 nullable: false,
478 default: None,
479 comment: None,
480 primary_key: None,
481 unique: None,
482 index: None,
483 foreign_key: None,
484 },
485 ColumnDef {
486 name: "age".into(),
487 r#type: ColumnType::Simple(SimpleColumnType::Integer),
488 nullable: true,
489 default: None,
490 comment: None,
491 primary_key: None,
492 unique: None,
493 index: None,
494 foreign_key: None,
495 },
496 ]
497 } else if title.contains("foreign_key") {
498 vec![
499 ColumnDef {
500 name: "id".into(),
501 r#type: ColumnType::Simple(SimpleColumnType::Integer),
502 nullable: false,
503 default: None,
504 comment: None,
505 primary_key: None,
506 unique: None,
507 index: None,
508 foreign_key: None,
509 },
510 ColumnDef {
511 name: "user_id".into(),
512 r#type: ColumnType::Simple(SimpleColumnType::Integer),
513 nullable: true,
514 default: None,
515 comment: None,
516 primary_key: None,
517 unique: None,
518 index: None,
519 foreign_key: None,
520 },
521 ]
522 } else {
523 vec![ColumnDef {
525 name: "id".into(),
526 r#type: ColumnType::Simple(SimpleColumnType::Integer),
527 nullable: false,
528 default: None,
529 comment: None,
530 primary_key: None,
531 unique: None,
532 index: None,
533 foreign_key: None,
534 }]
535 },
536 constraints: vec![constraint.clone()],
537 }];
538
539 let result =
540 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
541 let sql = result[0].build(backend);
542 for exp in expected {
543 assert!(
544 sql.contains(exp),
545 "Expected SQL to contain '{}', got: {}",
546 exp,
547 sql
548 );
549 }
550
551 with_settings!({ snapshot_suffix => format!("remove_constraint_{}", title) }, {
552 assert_snapshot!(result.iter().map(|q| q.build(backend)).collect::<Vec<String>>().join("\n"));
553 });
554 }
555
556 #[test]
557 fn test_remove_constraint_primary_key_sqlite_table_not_found() {
558 let constraint = TableConstraint::PrimaryKey {
560 columns: vec!["id".into()],
561 auto_increment: false,
562 };
563 let result = build_remove_constraint(
564 &DatabaseBackend::Sqlite,
565 "nonexistent_table",
566 &constraint,
567 &[], &[],
569 );
570 assert!(result.is_err());
571 let err_msg = result.unwrap_err().to_string();
572 assert!(err_msg.contains("Table 'nonexistent_table' not found in current schema"));
573 }
574
575 #[rstest]
576 #[case::remove_primary_key_with_index_postgres(DatabaseBackend::Postgres)]
577 #[case::remove_primary_key_with_index_mysql(DatabaseBackend::MySql)]
578 #[case::remove_primary_key_with_index_sqlite(DatabaseBackend::Sqlite)]
579 fn test_remove_constraint_primary_key_with_index(#[case] backend: DatabaseBackend) {
580 let constraint = TableConstraint::PrimaryKey {
582 columns: vec!["id".into()],
583 auto_increment: false,
584 };
585 let current_schema = vec![TableDef {
586 name: "users".into(),
587 description: None,
588 columns: vec![ColumnDef {
589 name: "id".into(),
590 r#type: ColumnType::Simple(SimpleColumnType::Integer),
591 nullable: false,
592 default: None,
593 comment: None,
594 primary_key: None,
595 unique: None,
596 index: None,
597 foreign_key: None,
598 }],
599 constraints: vec![
600 constraint.clone(),
601 TableConstraint::Index {
602 name: Some("idx_id".into()),
603 columns: vec!["id".into()],
604 },
605 ],
606 }];
607
608 let result =
609 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
610 let sql = result
611 .iter()
612 .map(|q| q.build(backend))
613 .collect::<Vec<String>>()
614 .join("\n");
615
616 if matches!(backend, DatabaseBackend::Sqlite) {
617 assert!(sql.contains("CREATE INDEX"));
618 assert!(sql.contains("ix_users__idx_id"));
619 }
620
621 with_settings!({ snapshot_suffix => format!("remove_primary_key_with_index_{:?}", backend) }, {
622 assert_snapshot!(sql);
623 });
624 }
625
626 #[rstest]
627 #[case::remove_primary_key_with_unique_constraint_postgres(DatabaseBackend::Postgres)]
628 #[case::remove_primary_key_with_unique_constraint_mysql(DatabaseBackend::MySql)]
629 #[case::remove_primary_key_with_unique_constraint_sqlite(DatabaseBackend::Sqlite)]
630 fn test_remove_constraint_primary_key_with_unique_constraint(#[case] backend: DatabaseBackend) {
631 let constraint = TableConstraint::PrimaryKey {
633 columns: vec!["id".into()],
634 auto_increment: false,
635 };
636 let current_schema = vec![TableDef {
637 name: "users".into(),
638 description: None,
639 columns: vec![ColumnDef {
640 name: "id".into(),
641 r#type: ColumnType::Simple(SimpleColumnType::Integer),
642 nullable: false,
643 default: None,
644 comment: None,
645 primary_key: None,
646 unique: None,
647 index: None,
648 foreign_key: None,
649 }],
650 constraints: vec![
651 constraint.clone(),
652 TableConstraint::Unique {
653 name: Some("uq_email".into()),
654 columns: vec!["email".into()],
655 },
656 ],
657 }];
658
659 let result =
660 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
661 let sql = result
662 .iter()
663 .map(|q| q.build(backend))
664 .collect::<Vec<String>>()
665 .join("\n");
666
667 if matches!(backend, DatabaseBackend::Sqlite) {
668 assert!(sql.contains("CREATE TABLE"));
670 }
671
672 with_settings!({ snapshot_suffix => format!("remove_primary_key_with_unique_constraint_{:?}", backend) }, {
673 assert_snapshot!(sql);
674 });
675 }
676
677 #[test]
678 fn test_remove_constraint_unique_sqlite_table_not_found() {
679 let constraint = TableConstraint::Unique {
681 name: Some("uq_email".into()),
682 columns: vec!["email".into()],
683 };
684 let result = build_remove_constraint(
685 &DatabaseBackend::Sqlite,
686 "nonexistent_table",
687 &constraint,
688 &[], &[],
690 );
691 assert!(result.is_err());
692 let err_msg = result.unwrap_err().to_string();
693 assert!(err_msg.contains("Table 'nonexistent_table' not found in current schema"));
694 }
695
696 #[rstest]
697 #[case::remove_unique_without_name_postgres(DatabaseBackend::Postgres)]
698 #[case::remove_unique_without_name_mysql(DatabaseBackend::MySql)]
699 #[case::remove_unique_without_name_sqlite(DatabaseBackend::Sqlite)]
700 fn test_remove_constraint_unique_without_name(#[case] backend: DatabaseBackend) {
701 let constraint = TableConstraint::Unique {
703 name: None,
704 columns: vec!["email".into()],
705 };
706 let current_schema = vec![TableDef {
707 name: "users".into(),
708 description: None,
709 columns: vec![
710 ColumnDef {
711 name: "id".into(),
712 r#type: ColumnType::Simple(SimpleColumnType::Integer),
713 nullable: false,
714 default: None,
715 comment: None,
716 primary_key: None,
717 unique: None,
718 index: None,
719 foreign_key: None,
720 },
721 ColumnDef {
722 name: "email".into(),
723 r#type: ColumnType::Simple(SimpleColumnType::Text),
724 nullable: true,
725 default: None,
726 comment: None,
727 primary_key: None,
728 unique: None,
729 index: None,
730 foreign_key: None,
731 },
732 ],
733 constraints: vec![constraint.clone()],
734 }];
735
736 let result =
737 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
738 let sql = result
739 .iter()
740 .map(|q| q.build(backend))
741 .collect::<Vec<String>>()
742 .join("\n");
743
744 if !matches!(backend, DatabaseBackend::Sqlite) {
746 assert!(sql.contains("users_email_key") || sql.contains("email"));
747 }
748
749 with_settings!({ snapshot_suffix => format!("remove_unique_without_name_{:?}", backend) }, {
750 assert_snapshot!(sql);
751 });
752 }
753
754 #[rstest]
755 #[case::remove_unique_with_index_postgres(DatabaseBackend::Postgres)]
756 #[case::remove_unique_with_index_mysql(DatabaseBackend::MySql)]
757 #[case::remove_unique_with_index_sqlite(DatabaseBackend::Sqlite)]
758 fn test_remove_constraint_unique_with_index(#[case] backend: DatabaseBackend) {
759 let constraint = TableConstraint::Unique {
761 name: Some("uq_email".into()),
762 columns: vec!["email".into()],
763 };
764 let current_schema = vec![TableDef {
765 name: "users".into(),
766 description: None,
767 columns: vec![
768 ColumnDef {
769 name: "id".into(),
770 r#type: ColumnType::Simple(SimpleColumnType::Integer),
771 nullable: false,
772 default: None,
773 comment: None,
774 primary_key: None,
775 unique: None,
776 index: None,
777 foreign_key: None,
778 },
779 ColumnDef {
780 name: "email".into(),
781 r#type: ColumnType::Simple(SimpleColumnType::Text),
782 nullable: true,
783 default: None,
784 comment: None,
785 primary_key: None,
786 unique: None,
787 index: None,
788 foreign_key: None,
789 },
790 ],
791 constraints: vec![
792 constraint.clone(),
793 TableConstraint::Index {
794 name: Some("idx_id".into()),
795 columns: vec!["id".into()],
796 },
797 ],
798 }];
799
800 let result =
801 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
802 let sql = result
803 .iter()
804 .map(|q| q.build(backend))
805 .collect::<Vec<String>>()
806 .join("\n");
807
808 if matches!(backend, DatabaseBackend::Sqlite) {
809 assert!(sql.contains("CREATE INDEX"));
810 assert!(sql.contains("ix_users__idx_id"));
811 }
812
813 with_settings!({ snapshot_suffix => format!("remove_unique_with_index_{:?}", backend) }, {
814 assert_snapshot!(sql);
815 });
816 }
817
818 #[rstest]
819 #[case::remove_unique_with_other_unique_constraint_postgres(DatabaseBackend::Postgres)]
820 #[case::remove_unique_with_other_unique_constraint_mysql(DatabaseBackend::MySql)]
821 #[case::remove_unique_with_other_unique_constraint_sqlite(DatabaseBackend::Sqlite)]
822 fn test_remove_constraint_unique_with_other_unique_constraint(
823 #[case] backend: DatabaseBackend,
824 ) {
825 let constraint = TableConstraint::Unique {
827 name: Some("uq_email".into()),
828 columns: vec!["email".into()],
829 };
830 let current_schema = vec![TableDef {
831 name: "users".into(),
832 description: None,
833 columns: vec![
834 ColumnDef {
835 name: "id".into(),
836 r#type: ColumnType::Simple(SimpleColumnType::Integer),
837 nullable: false,
838 default: None,
839 comment: None,
840 primary_key: None,
841 unique: None,
842 index: None,
843 foreign_key: None,
844 },
845 ColumnDef {
846 name: "email".into(),
847 r#type: ColumnType::Simple(SimpleColumnType::Text),
848 nullable: true,
849 default: None,
850 comment: None,
851 primary_key: None,
852 unique: None,
853 index: None,
854 foreign_key: None,
855 },
856 ],
857 constraints: vec![
858 constraint.clone(),
859 TableConstraint::Unique {
860 name: Some("uq_name".into()),
861 columns: vec!["name".into()],
862 },
863 ],
864 }];
865
866 let result =
867 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
868 let sql = result
869 .iter()
870 .map(|q| q.build(backend))
871 .collect::<Vec<String>>()
872 .join("\n");
873
874 if matches!(backend, DatabaseBackend::Sqlite) {
875 assert!(sql.contains("CREATE TABLE"));
877 }
878
879 with_settings!({ snapshot_suffix => format!("remove_unique_with_other_unique_constraint_{:?}", backend) }, {
880 assert_snapshot!(sql);
881 });
882 }
883
884 #[test]
885 fn test_remove_constraint_foreign_key_sqlite_table_not_found() {
886 let constraint = TableConstraint::ForeignKey {
888 name: Some("fk_user".into()),
889 columns: vec!["user_id".into()],
890 ref_table: "users".into(),
891 ref_columns: vec!["id".into()],
892 on_delete: None,
893 on_update: None,
894 };
895 let result = build_remove_constraint(
896 &DatabaseBackend::Sqlite,
897 "nonexistent_table",
898 &constraint,
899 &[], &[],
901 );
902 assert!(result.is_err());
903 let err_msg = result.unwrap_err().to_string();
904 assert!(err_msg.contains("Table 'nonexistent_table' not found in current schema"));
905 }
906
907 #[rstest]
908 #[case::remove_foreign_key_without_name_postgres(DatabaseBackend::Postgres)]
909 #[case::remove_foreign_key_without_name_mysql(DatabaseBackend::MySql)]
910 #[case::remove_foreign_key_without_name_sqlite(DatabaseBackend::Sqlite)]
911 fn test_remove_constraint_foreign_key_without_name(#[case] backend: DatabaseBackend) {
912 let constraint = TableConstraint::ForeignKey {
914 name: None,
915 columns: vec!["user_id".into()],
916 ref_table: "users".into(),
917 ref_columns: vec!["id".into()],
918 on_delete: None,
919 on_update: None,
920 };
921 let current_schema = vec![TableDef {
922 name: "posts".into(),
923 description: None,
924 columns: vec![
925 ColumnDef {
926 name: "id".into(),
927 r#type: ColumnType::Simple(SimpleColumnType::Integer),
928 nullable: false,
929 default: None,
930 comment: None,
931 primary_key: None,
932 unique: None,
933 index: None,
934 foreign_key: None,
935 },
936 ColumnDef {
937 name: "user_id".into(),
938 r#type: ColumnType::Simple(SimpleColumnType::Integer),
939 nullable: true,
940 default: None,
941 comment: None,
942 primary_key: None,
943 unique: None,
944 index: None,
945 foreign_key: None,
946 },
947 ],
948 constraints: vec![constraint.clone()],
949 }];
950
951 let result =
952 build_remove_constraint(&backend, "posts", &constraint, ¤t_schema, &[]).unwrap();
953 let sql = result
954 .iter()
955 .map(|q| q.build(backend))
956 .collect::<Vec<String>>()
957 .join("\n");
958
959 if !matches!(backend, DatabaseBackend::Sqlite) {
961 assert!(sql.contains("posts_user_id_fkey") || sql.contains("user_id"));
962 }
963
964 with_settings!({ snapshot_suffix => format!("remove_foreign_key_without_name_{:?}", backend) }, {
965 assert_snapshot!(sql);
966 });
967 }
968
969 #[rstest]
970 #[case::remove_foreign_key_with_index_postgres(DatabaseBackend::Postgres)]
971 #[case::remove_foreign_key_with_index_mysql(DatabaseBackend::MySql)]
972 #[case::remove_foreign_key_with_index_sqlite(DatabaseBackend::Sqlite)]
973 fn test_remove_constraint_foreign_key_with_index(#[case] backend: DatabaseBackend) {
974 let constraint = TableConstraint::ForeignKey {
976 name: Some("fk_user".into()),
977 columns: vec!["user_id".into()],
978 ref_table: "users".into(),
979 ref_columns: vec!["id".into()],
980 on_delete: None,
981 on_update: None,
982 };
983 let current_schema = vec![TableDef {
984 name: "posts".into(),
985 description: None,
986 columns: vec![
987 ColumnDef {
988 name: "id".into(),
989 r#type: ColumnType::Simple(SimpleColumnType::Integer),
990 nullable: false,
991 default: None,
992 comment: None,
993 primary_key: None,
994 unique: None,
995 index: None,
996 foreign_key: None,
997 },
998 ColumnDef {
999 name: "user_id".into(),
1000 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1001 nullable: true,
1002 default: None,
1003 comment: None,
1004 primary_key: None,
1005 unique: None,
1006 index: None,
1007 foreign_key: None,
1008 },
1009 ],
1010 constraints: vec![
1011 constraint.clone(),
1012 TableConstraint::Index {
1013 name: Some("idx_user_id".into()),
1014 columns: vec!["user_id".into()],
1015 },
1016 ],
1017 }];
1018
1019 let result =
1020 build_remove_constraint(&backend, "posts", &constraint, ¤t_schema, &[]).unwrap();
1021 let sql = result
1022 .iter()
1023 .map(|q| q.build(backend))
1024 .collect::<Vec<String>>()
1025 .join("\n");
1026
1027 if matches!(backend, DatabaseBackend::Sqlite) {
1028 assert!(sql.contains("CREATE INDEX"));
1029 assert!(sql.contains("idx_user_id"));
1030 }
1031
1032 with_settings!({ snapshot_suffix => format!("remove_foreign_key_with_index_{:?}", backend) }, {
1033 assert_snapshot!(sql);
1034 });
1035 }
1036
1037 #[rstest]
1038 #[case::remove_foreign_key_with_unique_constraint_postgres(DatabaseBackend::Postgres)]
1039 #[case::remove_foreign_key_with_unique_constraint_mysql(DatabaseBackend::MySql)]
1040 #[case::remove_foreign_key_with_unique_constraint_sqlite(DatabaseBackend::Sqlite)]
1041 fn test_remove_constraint_foreign_key_with_unique_constraint(#[case] backend: DatabaseBackend) {
1042 let constraint = TableConstraint::ForeignKey {
1044 name: Some("fk_user".into()),
1045 columns: vec!["user_id".into()],
1046 ref_table: "users".into(),
1047 ref_columns: vec!["id".into()],
1048 on_delete: None,
1049 on_update: None,
1050 };
1051 let current_schema = vec![TableDef {
1052 name: "posts".into(),
1053 description: None,
1054 columns: vec![
1055 ColumnDef {
1056 name: "id".into(),
1057 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1058 nullable: false,
1059 default: None,
1060 comment: None,
1061 primary_key: None,
1062 unique: None,
1063 index: None,
1064 foreign_key: None,
1065 },
1066 ColumnDef {
1067 name: "user_id".into(),
1068 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1069 nullable: true,
1070 default: None,
1071 comment: None,
1072 primary_key: None,
1073 unique: None,
1074 index: None,
1075 foreign_key: None,
1076 },
1077 ],
1078 constraints: vec![
1079 constraint.clone(),
1080 TableConstraint::Unique {
1081 name: Some("uq_user_id".into()),
1082 columns: vec!["user_id".into()],
1083 },
1084 ],
1085 }];
1086
1087 let result =
1088 build_remove_constraint(&backend, "posts", &constraint, ¤t_schema, &[]).unwrap();
1089 let sql = result
1090 .iter()
1091 .map(|q| q.build(backend))
1092 .collect::<Vec<String>>()
1093 .join("\n");
1094
1095 if matches!(backend, DatabaseBackend::Sqlite) {
1096 assert!(sql.contains("CREATE TABLE"));
1098 }
1099
1100 with_settings!({ snapshot_suffix => format!("remove_foreign_key_with_unique_constraint_{:?}", backend) }, {
1101 assert_snapshot!(sql);
1102 });
1103 }
1104
1105 #[test]
1106 fn test_remove_constraint_check_sqlite_table_not_found() {
1107 let constraint = TableConstraint::Check {
1109 name: "chk_age".into(),
1110 expr: "age > 0".into(),
1111 };
1112 let result = build_remove_constraint(
1113 &DatabaseBackend::Sqlite,
1114 "nonexistent_table",
1115 &constraint,
1116 &[], &[],
1118 );
1119 assert!(result.is_err());
1120 let err_msg = result.unwrap_err().to_string();
1121 assert!(err_msg.contains("Table 'nonexistent_table' not found in current schema"));
1122 }
1123
1124 #[rstest]
1125 #[case::remove_check_with_index_postgres(DatabaseBackend::Postgres)]
1126 #[case::remove_check_with_index_mysql(DatabaseBackend::MySql)]
1127 #[case::remove_check_with_index_sqlite(DatabaseBackend::Sqlite)]
1128 fn test_remove_constraint_check_with_index(#[case] backend: DatabaseBackend) {
1129 let constraint = TableConstraint::Check {
1131 name: "chk_age".into(),
1132 expr: "age > 0".into(),
1133 };
1134 let current_schema = vec![TableDef {
1135 name: "users".into(),
1136 description: None,
1137 columns: vec![
1138 ColumnDef {
1139 name: "id".into(),
1140 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1141 nullable: false,
1142 default: None,
1143 comment: None,
1144 primary_key: None,
1145 unique: None,
1146 index: None,
1147 foreign_key: None,
1148 },
1149 ColumnDef {
1150 name: "age".into(),
1151 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1152 nullable: true,
1153 default: None,
1154 comment: None,
1155 primary_key: None,
1156 unique: None,
1157 index: None,
1158 foreign_key: None,
1159 },
1160 ],
1161 constraints: vec![
1162 constraint.clone(),
1163 TableConstraint::Index {
1164 name: Some("idx_age".into()),
1165 columns: vec!["age".into()],
1166 },
1167 ],
1168 }];
1169
1170 let result =
1171 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
1172 let sql = result
1173 .iter()
1174 .map(|q| q.build(backend))
1175 .collect::<Vec<String>>()
1176 .join("\n");
1177
1178 if matches!(backend, DatabaseBackend::Sqlite) {
1179 assert!(sql.contains("CREATE INDEX"));
1180 assert!(sql.contains("idx_age"));
1181 }
1182
1183 with_settings!({ snapshot_suffix => format!("remove_check_with_index_{:?}", backend) }, {
1184 assert_snapshot!(sql);
1185 });
1186 }
1187
1188 #[rstest]
1189 #[case::remove_check_with_unique_constraint_postgres(DatabaseBackend::Postgres)]
1190 #[case::remove_check_with_unique_constraint_mysql(DatabaseBackend::MySql)]
1191 #[case::remove_check_with_unique_constraint_sqlite(DatabaseBackend::Sqlite)]
1192 fn test_remove_constraint_check_with_unique_constraint(#[case] backend: DatabaseBackend) {
1193 let constraint = TableConstraint::Check {
1195 name: "chk_age".into(),
1196 expr: "age > 0".into(),
1197 };
1198 let current_schema = vec![TableDef {
1199 name: "users".into(),
1200 description: None,
1201 columns: vec![
1202 ColumnDef {
1203 name: "id".into(),
1204 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1205 nullable: false,
1206 default: None,
1207 comment: None,
1208 primary_key: None,
1209 unique: None,
1210 index: None,
1211 foreign_key: None,
1212 },
1213 ColumnDef {
1214 name: "age".into(),
1215 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1216 nullable: true,
1217 default: None,
1218 comment: None,
1219 primary_key: None,
1220 unique: None,
1221 index: None,
1222 foreign_key: None,
1223 },
1224 ],
1225 constraints: vec![
1226 constraint.clone(),
1227 TableConstraint::Unique {
1228 name: Some("uq_age".into()),
1229 columns: vec!["age".into()],
1230 },
1231 ],
1232 }];
1233
1234 let result =
1235 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
1236 let sql = result
1237 .iter()
1238 .map(|q| q.build(backend))
1239 .collect::<Vec<String>>()
1240 .join("\n");
1241
1242 if matches!(backend, DatabaseBackend::Sqlite) {
1243 assert!(sql.contains("CREATE TABLE"));
1245 }
1246
1247 with_settings!({ snapshot_suffix => format!("remove_check_with_unique_constraint_{:?}", backend) }, {
1248 assert_snapshot!(sql);
1249 });
1250 }
1251
1252 #[rstest]
1253 #[case::remove_unique_with_other_constraints_postgres(DatabaseBackend::Postgres)]
1254 #[case::remove_unique_with_other_constraints_mysql(DatabaseBackend::MySql)]
1255 #[case::remove_unique_with_other_constraints_sqlite(DatabaseBackend::Sqlite)]
1256 fn test_remove_constraint_unique_with_other_constraints(#[case] backend: DatabaseBackend) {
1257 let constraint = TableConstraint::Unique {
1259 name: Some("uq_email".into()),
1260 columns: vec!["email".into()],
1261 };
1262 let current_schema = vec![TableDef {
1263 name: "users".into(),
1264 description: None,
1265 columns: vec![
1266 ColumnDef {
1267 name: "id".into(),
1268 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1269 nullable: false,
1270 default: None,
1271 comment: None,
1272 primary_key: None,
1273 unique: None,
1274 index: None,
1275 foreign_key: None,
1276 },
1277 ColumnDef {
1278 name: "email".into(),
1279 r#type: ColumnType::Simple(SimpleColumnType::Text),
1280 nullable: true,
1281 default: None,
1282 comment: None,
1283 primary_key: None,
1284 unique: None,
1285 index: None,
1286 foreign_key: None,
1287 },
1288 ],
1289 constraints: vec![
1290 TableConstraint::PrimaryKey {
1291 columns: vec!["id".into()],
1292 auto_increment: false,
1293 },
1294 constraint.clone(),
1295 TableConstraint::Check {
1296 name: "chk_email".into(),
1297 expr: "email IS NOT NULL".into(),
1298 },
1299 ],
1300 }];
1301
1302 let result =
1303 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
1304 let sql = result
1305 .iter()
1306 .map(|q| q.build(backend))
1307 .collect::<Vec<String>>()
1308 .join("\n");
1309
1310 assert!(sql.contains("DROP") || sql.contains("CREATE TABLE"));
1312
1313 with_settings!({ snapshot_suffix => format!("remove_unique_with_other_constraints_{:?}", backend) }, {
1314 assert_snapshot!(sql);
1315 });
1316 }
1317
1318 #[rstest]
1319 #[case::remove_foreign_key_with_other_constraints_postgres(DatabaseBackend::Postgres)]
1320 #[case::remove_foreign_key_with_other_constraints_mysql(DatabaseBackend::MySql)]
1321 #[case::remove_foreign_key_with_other_constraints_sqlite(DatabaseBackend::Sqlite)]
1322 fn test_remove_constraint_foreign_key_with_other_constraints(#[case] backend: DatabaseBackend) {
1323 let constraint = TableConstraint::ForeignKey {
1325 name: Some("fk_user".into()),
1326 columns: vec!["user_id".into()],
1327 ref_table: "users".into(),
1328 ref_columns: vec!["id".into()],
1329 on_delete: None,
1330 on_update: None,
1331 };
1332 let current_schema = vec![TableDef {
1333 name: "posts".into(),
1334 description: None,
1335 columns: vec![
1336 ColumnDef {
1337 name: "id".into(),
1338 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1339 nullable: false,
1340 default: None,
1341 comment: None,
1342 primary_key: None,
1343 unique: None,
1344 index: None,
1345 foreign_key: None,
1346 },
1347 ColumnDef {
1348 name: "user_id".into(),
1349 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1350 nullable: true,
1351 default: None,
1352 comment: None,
1353 primary_key: None,
1354 unique: None,
1355 index: None,
1356 foreign_key: None,
1357 },
1358 ],
1359 constraints: vec![
1360 TableConstraint::PrimaryKey {
1361 columns: vec!["id".into()],
1362 auto_increment: false,
1363 },
1364 constraint.clone(),
1365 TableConstraint::Unique {
1366 name: Some("uq_user_id".into()),
1367 columns: vec!["user_id".into()],
1368 },
1369 TableConstraint::Check {
1370 name: "chk_user_id".into(),
1371 expr: "user_id > 0".into(),
1372 },
1373 ],
1374 }];
1375
1376 let result =
1377 build_remove_constraint(&backend, "posts", &constraint, ¤t_schema, &[]).unwrap();
1378 let sql = result
1379 .iter()
1380 .map(|q| q.build(backend))
1381 .collect::<Vec<String>>()
1382 .join("\n");
1383
1384 assert!(sql.contains("DROP") || sql.contains("CREATE TABLE"));
1386
1387 with_settings!({ snapshot_suffix => format!("remove_foreign_key_with_other_constraints_{:?}", backend) }, {
1388 assert_snapshot!(sql);
1389 });
1390 }
1391
1392 #[rstest]
1393 #[case::remove_check_with_other_constraints_postgres(DatabaseBackend::Postgres)]
1394 #[case::remove_check_with_other_constraints_mysql(DatabaseBackend::MySql)]
1395 #[case::remove_check_with_other_constraints_sqlite(DatabaseBackend::Sqlite)]
1396 fn test_remove_constraint_check_with_other_constraints(#[case] backend: DatabaseBackend) {
1397 let constraint = TableConstraint::Check {
1399 name: "chk_age".into(),
1400 expr: "age > 0".into(),
1401 };
1402 let current_schema = vec![TableDef {
1403 name: "users".into(),
1404 description: None,
1405 columns: vec![
1406 ColumnDef {
1407 name: "id".into(),
1408 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1409 nullable: false,
1410 default: None,
1411 comment: None,
1412 primary_key: None,
1413 unique: None,
1414 index: None,
1415 foreign_key: None,
1416 },
1417 ColumnDef {
1418 name: "age".into(),
1419 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1420 nullable: true,
1421 default: None,
1422 comment: None,
1423 primary_key: None,
1424 unique: None,
1425 index: None,
1426 foreign_key: None,
1427 },
1428 ],
1429 constraints: vec![
1430 TableConstraint::PrimaryKey {
1431 columns: vec!["id".into()],
1432 auto_increment: false,
1433 },
1434 TableConstraint::Unique {
1435 name: Some("uq_age".into()),
1436 columns: vec!["age".into()],
1437 },
1438 constraint.clone(),
1439 ],
1440 }];
1441
1442 let result =
1443 build_remove_constraint(&backend, "users", &constraint, ¤t_schema, &[]).unwrap();
1444 let sql = result
1445 .iter()
1446 .map(|q| q.build(backend))
1447 .collect::<Vec<String>>()
1448 .join("\n");
1449
1450 assert!(sql.contains("DROP") || sql.contains("CREATE TABLE"));
1452
1453 with_settings!({ snapshot_suffix => format!("remove_check_with_other_constraints_{:?}", backend) }, {
1454 assert_snapshot!(sql);
1455 });
1456 }
1457
1458 #[test]
1459 fn test_remove_constraint_primary_key_postgres_direct() {
1460 let constraint = TableConstraint::PrimaryKey {
1462 columns: vec!["id".into()],
1463 auto_increment: false,
1464 };
1465 let schema = vec![TableDef {
1466 name: "orders".into(),
1467 description: None,
1468 columns: vec![ColumnDef {
1469 name: "id".into(),
1470 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1471 nullable: false,
1472 default: None,
1473 comment: None,
1474 primary_key: None,
1475 unique: None,
1476 index: None,
1477 foreign_key: None,
1478 }],
1479 constraints: vec![constraint.clone()],
1480 }];
1481 let result = build_remove_constraint(
1482 &DatabaseBackend::Postgres,
1483 "orders",
1484 &constraint,
1485 &schema,
1486 &[],
1487 )
1488 .unwrap();
1489 assert_eq!(result.len(), 1);
1490 let sql = result[0].build(DatabaseBackend::Postgres);
1491 assert!(sql.contains("ALTER TABLE \"orders\" DROP CONSTRAINT \"orders_pkey\""));
1492 }
1493
1494 #[test]
1495 fn test_remove_constraint_primary_key_mysql_direct() {
1496 let constraint = TableConstraint::PrimaryKey {
1498 columns: vec!["id".into()],
1499 auto_increment: false,
1500 };
1501 let schema = vec![TableDef {
1502 name: "orders".into(),
1503 description: None,
1504 columns: vec![ColumnDef {
1505 name: "id".into(),
1506 r#type: ColumnType::Simple(SimpleColumnType::Integer),
1507 nullable: false,
1508 default: None,
1509 comment: None,
1510 primary_key: None,
1511 unique: None,
1512 index: None,
1513 foreign_key: None,
1514 }],
1515 constraints: vec![constraint.clone()],
1516 }];
1517 let result =
1518 build_remove_constraint(&DatabaseBackend::MySql, "orders", &constraint, &schema, &[])
1519 .unwrap();
1520 assert_eq!(result.len(), 1);
1521 let sql = result[0].build(DatabaseBackend::MySql);
1522 assert!(sql.contains("ALTER TABLE `orders` DROP PRIMARY KEY"));
1523 }
1524
1525 #[rstest]
1526 #[case::remove_index_with_custom_inline_name_postgres(DatabaseBackend::Postgres)]
1527 #[case::remove_index_with_custom_inline_name_mysql(DatabaseBackend::MySql)]
1528 #[case::remove_index_with_custom_inline_name_sqlite(DatabaseBackend::Sqlite)]
1529 fn test_remove_constraint_index_with_custom_inline_name(#[case] backend: DatabaseBackend) {
1530 let constraint = TableConstraint::Index {
1533 name: Some("custom_idx_email".into()),
1534 columns: vec!["email".into()],
1535 };
1536
1537 let schema = vec![TableDef {
1538 name: "users".to_string(),
1539 description: None,
1540 columns: vec![ColumnDef {
1541 name: "email".to_string(),
1542 r#type: ColumnType::Simple(SimpleColumnType::Text),
1543 nullable: true,
1544 default: None,
1545 comment: None,
1546 primary_key: None,
1547 unique: None,
1548 index: Some(vespertide_core::StrOrBoolOrArray::Str(
1549 "custom_idx_email".into(),
1550 )),
1551 foreign_key: None,
1552 }],
1553 constraints: vec![],
1554 }];
1555
1556 let result = build_remove_constraint(&backend, "users", &constraint, &schema, &[]);
1557 assert!(result.is_ok());
1558 let sql = result
1559 .unwrap()
1560 .iter()
1561 .map(|q| q.build(backend))
1562 .collect::<Vec<String>>()
1563 .join("\n");
1564
1565 assert!(sql.contains("custom_idx_email"));
1567
1568 with_settings!({ snapshot_suffix => format!("remove_index_custom_name_{:?}", backend) }, {
1569 assert_snapshot!(sql);
1570 });
1571 }
1572}