Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: re-introspection with @@map and relationMode #3335

Merged
merged 12 commits into from
Oct 27, 2022
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(())
}
}