Skip to content

Commit

Permalink
Merge pull request #3335 from prisma/fix/relation-mode-re-introspecti…
Browse files Browse the repository at this point in the history
…on-map

fix: re-introspection with `@@map` and `relationMode`
  • Loading branch information
jkomyno committed Oct 27, 2022
2 parents 3d0f4a0 + abe4999 commit f90920b
Show file tree
Hide file tree
Showing 5 changed files with 1,378 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::{
SqlFamilyTrait,
};
use introspection_connector::Warning;
use psl::dml::{self, Datamodel, DefaultValue, Field, FieldType, Ignorable, PrismaValue, ValueGenerator, WithName};
use psl::dml::{
self, Datamodel, DefaultValue, Field, FieldType, Ignorable, PrismaValue, ValueGenerator, WithDatabaseName, WithName,
};
use std::collections::{BTreeSet, HashMap};

pub(crate) fn enrich(
Expand Down Expand Up @@ -127,10 +129,28 @@ fn keep_index_ordering(old_data_model: &Datamodel, new_data_model: &mut Datamode
fn merge_relation_fields(old_data_model: &Datamodel, new_data_model: &mut Datamodel, warnings: &mut Vec<Warning>) {
let mut changed_models = BTreeSet::new();

// Maps a model name to the table name it was introspected to. This is helpful when @@map is used.
// E.g., for
//
// ```prisma
// model Foo {
// id Int @id
// bar Bar @relation(fields: [bar_id], references: [id])
// bar_id Int @unique
// @@map("foo_table")
// }
//
// the map would be {"Foo" -> "foo_table"}.
// ```
let old_model_name_to_final_database_name: HashMap<&str, &str> = old_data_model
.models()
.map(|m| (m.name.as_str(), m.final_database_name()))
.collect();

for old_model in old_data_model.models() {
let modifications = new_data_model
.models()
.find(|m| m.name == *old_model.name())
.find(|m| *m.final_database_name() == *old_model.final_database_name())
.map(|new_model| {
let mut ordering: HashMap<String, usize> = old_model
.fields()
Expand All @@ -147,10 +167,12 @@ fn merge_relation_fields(old_data_model: &Datamodel, new_data_model: &mut Datamo
let mut fields = Vec::new();

for field in old_model.relation_fields() {
if new_data_model
.models()
.any(|m| m.name == field.relation_info.referenced_model)
{
if new_data_model.models().any(|m| {
m.name.as_str()
== *old_model_name_to_final_database_name
.get(field.relation_info.referenced_model.as_str())
.unwrap() // as the old datamodel is guaranteed to be valid at this point, this unwrap is safe
}) {
fields.push(Field::RelationField(field.clone()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,337 @@ async fn relation_mode_foreign_keys(api: &TestApi) -> TestResult {

Ok(())
}

// @@map
mod at_at_map {
use indoc::indoc;
use introspection_engine_tests::test_api::*;

// referentialIntegrity="prisma" is renamed as relationMode="prisma", and @relations are preserved.
#[test_connector(tags(Mssql))]
async fn referential_integrity_prisma_at_map_map(api: &TestApi) -> TestResult {
let init = indoc! {r#"
CREATE TABLE [dbo].[foo_table] (
[id] INT NOT NULL,
[bar_id] INT NOT NULL,
CONSTRAINT [foo_table_pkey] PRIMARY KEY CLUSTERED ([id]),
CONSTRAINT [foo_table_bar_id_key] UNIQUE NONCLUSTERED ([bar_id])
);
CREATE TABLE [dbo].[bar_table] (
[id] INT NOT NULL,
CONSTRAINT [bar_table_pkey] PRIMARY KEY CLUSTERED ([id])
);
"#};

api.raw_cmd(&init).await;

let input = indoc! {r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
referentialIntegrity = "prisma"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#};

let expected = expect![[r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "prisma"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#]];

let result = api.re_introspect_config(input).await?;
expected.assert_eq(&result);

Ok(())
}

// referentialIntegrity="foreignKeys" is renamed as relationMode="foreignKeys", and @relations are preserved.
#[test_connector(tags(Mssql))]
async fn referential_integrity_foreign_keys_at_map_map(api: &TestApi) -> TestResult {
let init = indoc! {r#"
CREATE TABLE [dbo].[foo_table] (
[id] INT NOT NULL,
[bar_id] INT NOT NULL,
CONSTRAINT [foo_table_pkey] PRIMARY KEY CLUSTERED ([id]),
CONSTRAINT [foo_table_bar_id_key] UNIQUE NONCLUSTERED ([bar_id])
);
CREATE TABLE [dbo].[bar_table] (
[id] INT NOT NULL,
CONSTRAINT [bar_table_pkey] PRIMARY KEY CLUSTERED ([id])
);
ALTER TABLE [dbo].[foo_table] ADD CONSTRAINT [foo_table_bar_id_fkey] FOREIGN KEY ([bar_id]) REFERENCES [dbo].[bar_table]([id]) ON DELETE NO ACTION ON UPDATE CASCADE;
"#};

api.raw_cmd(&init).await;

let input = indoc! {r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
referentialIntegrity = "foreignKeys"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#};

let expected = expect![[r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "foreignKeys"
}
model Foo {
id Int @id
bar_id Int @unique
bar Bar @relation(fields: [bar_id], references: [id])
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#]];

let result = api.re_introspect_config(input).await?;
expected.assert_eq(&result);

Ok(())
}

// relationMode="prisma" preserves the relation policy ("prisma") as well as @relations.
#[test_connector(tags(Mssql))]
async fn relation_mode_prisma_at_map_map(api: &TestApi) -> TestResult {
let init = indoc! {r#"
CREATE TABLE [dbo].[foo_table] (
[id] INT NOT NULL,
[bar_id] INT NOT NULL,
CONSTRAINT [foo_table_pkey] PRIMARY KEY CLUSTERED ([id]),
CONSTRAINT [foo_table_bar_id_key] UNIQUE NONCLUSTERED ([bar_id])
);
CREATE TABLE [dbo].[bar_table] (
[id] INT NOT NULL,
CONSTRAINT [bar_table_pkey] PRIMARY KEY CLUSTERED ([id])
);
"#};

api.raw_cmd(&init).await;

let input = indoc! {r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "prisma"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#};

let expected = expect![[r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "prisma"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#]];

let result = api.re_introspect_config(input).await?;
expected.assert_eq(&result);

Ok(())
}

// relationMode="foreignKeys" preserves the relation policy ("foreignKeys") as well as @relations., which are moved to the bottom.
#[test_connector(tags(Mssql))]
async fn relation_mode_foreign_keys_at_map_map(api: &TestApi) -> TestResult {
let init = indoc! {r#"
CREATE TABLE [dbo].[foo_table] (
[id] INT NOT NULL,
[bar_id] INT NOT NULL,
CONSTRAINT [foo_table_pkey] PRIMARY KEY CLUSTERED ([id]),
CONSTRAINT [foo_table_bar_id_key] UNIQUE NONCLUSTERED ([bar_id])
);
CREATE TABLE [dbo].[bar_table] (
[id] INT NOT NULL,
CONSTRAINT [bar_table_pkey] PRIMARY KEY CLUSTERED ([id])
);
ALTER TABLE [dbo].[foo_table] ADD CONSTRAINT [foo_table_bar_id_fkey] FOREIGN KEY ([bar_id]) REFERENCES [dbo].[bar_table]([id]) ON DELETE NO ACTION ON UPDATE CASCADE;
"#};

api.raw_cmd(&init).await;

let input = indoc! {r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "foreignKeys"
}
model Foo {
id Int @id
bar Bar @relation(fields: [bar_id], references: [id])
bar_id Int @unique
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#};

let expected = expect![[r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}
datasource db {
provider = "sqlserver"
url = env("TEST_DATABASE_URL")
relationMode = "foreignKeys"
}
model Foo {
id Int @id
bar_id Int @unique
bar Bar @relation(fields: [bar_id], references: [id])
@@map("foo_table")
}
model Bar {
id Int @id
foo Foo?
@@map("bar_table")
}
"#]];

let result = api.re_introspect_config(input).await?;
expected.assert_eq(&result);

Ok(())
}
}

0 comments on commit f90920b

Please sign in to comment.