Skip to content

Commit

Permalink
Prefer unique constraints over indexes on SQL Server
Browse files Browse the repository at this point in the history
Unique constraint and index do not differ in SQL Server today. The
problem is if we create an index and try to drop a constraint, the
drop fails.

Currently if the unique is added when the table is created, we add
constraint. If the table is altered with a unique constraint we create
an index instead. Dropping this does not work.

The PR makes altering the table with an `@unique` to use constraints
instead of an index.

Closes: prisma/prisma#14051
  • Loading branch information
Julius de Bruijn committed Jun 29, 2022
1 parent a460085 commit c68ecc8
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 8 deletions.
Expand Up @@ -125,12 +125,6 @@ impl SqlRenderer for MssqlFlavour {

fn render_create_index(&self, index: IndexWalker<'_>) -> String {
let mssql_schema_ext: &MssqlSchemaExt = index.schema.downcast_connector_data().unwrap_or_default();
let index_type = match index.index_type() {
IndexType::Unique => "UNIQUE ",
IndexType::Normal => "",
IndexType::Fulltext => unreachable!("Fulltext index on SQL Server"),
};

let index_name = self.quote(index.name());
let table_reference = self.quote_with_schema(index.table().name()).to_string();

Expand All @@ -153,7 +147,17 @@ impl SqlRenderer for MssqlFlavour {

let columns = columns.join(", ");

format!("CREATE {index_type}{clustering}INDEX {index_name} ON {table_reference}({columns})",)
match index.index_type() {
IndexType::Unique => {
let constraint_name = self.quote(index.name());

format!("ALTER TABLE {table_reference} ADD CONSTRAINT {constraint_name} UNIQUE {clustering}({columns})")
}
IndexType::Normal => {
format!("CREATE {clustering}INDEX {index_name} ON {table_reference}({columns})",)
}
IndexType::Fulltext => todo!("SQL Server full-text indices..."),
}
}

fn render_create_table_as(&self, table: TableWalker<'_>, table_name: &str) -> String {
Expand Down
@@ -1,4 +1,4 @@
use indoc::formatdoc;
use indoc::{formatdoc, indoc};
use migration_engine_tests::test_api::*;

#[test_connector(tags(Mssql))]
Expand Down Expand Up @@ -184,3 +184,82 @@ fn clustered_to_non_clustered_index(api: TestApi) {
table.assert_index_on_columns(&["og"], |index| index.assert_non_clustered())
});
}

#[test_connector(tags(Mssql))]
fn creating_and_dropping_unique_constraint_works(api: TestApi) {
let dm1 = indoc! {r#"
model Logbook {
id Int @id
categoryId String? @db.UniqueIdentifier
date DateTime @db.Date()
}
"#};

api.schema_push_w_datasource(dm1).send().assert_green();

api.assert_schema()
.assert_table("Logbook", |cat| cat.assert_indexes_count(0));

let dm1 = indoc! {r#"
model Logbook {
id Int @id
categoryId String? @db.UniqueIdentifier
date DateTime @db.Date()
@@unique([categoryId, date])
}
"#};

api.schema_push_w_datasource(dm1).force(true).send();

api.assert_schema().assert_table("Logbook", |cat| {
cat.assert_indexes_count(1)
.assert_index_on_columns(&["categoryId", "date"], |idx| idx.assert_is_unique())
});

let dm2 = indoc! {r#"
model Logbook {
id Int @id
categoryId String? @db.UniqueIdentifier
date DateTime @db.Date()
}
"#};

api.schema_push_w_datasource(dm2).send().assert_green();

api.assert_schema()
.assert_table("Logbook", |cat| cat.assert_indexes_count(0));
}

#[test_connector(tags(Mssql))]
fn dropping_unique_constraint_works(api: TestApi) {
let dm1 = indoc! {r#"
model Logbook {
id Int @id
categoryId String? @db.UniqueIdentifier
date DateTime @db.Date()
@@unique([categoryId, date])
}
"#};

api.schema_push_w_datasource(dm1).force(true).send();

api.assert_schema().assert_table("Logbook", |cat| {
cat.assert_indexes_count(1)
.assert_index_on_columns(&["categoryId", "date"], |idx| idx.assert_is_unique())
});

let dm2 = indoc! {r#"
model Logbook {
id Int @id
categoryId String? @db.UniqueIdentifier
date DateTime @db.Date()
}
"#};

api.schema_push_w_datasource(dm2).send().assert_green();

api.assert_schema()
.assert_table("Logbook", |cat| cat.assert_indexes_count(0));
}

0 comments on commit c68ecc8

Please sign in to comment.