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