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(qe): NoAction should be alias of Restrict when relationMode = "prisma" #3276

Merged
2 changes: 2 additions & 0 deletions libs/dml/src/relation_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ pub enum ReferentialAction {
/// deferred, this makes it possible to temporarily violate integrity in a
/// transaction while making sure that subsequent operations in the
/// transaction restore integrity.
/// When using referential integrity with relation mode "prisma", NoAction
/// becomes an alias of the simulated Restrict when supported.
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
NoAction,
/// Sets relation scalar fields to null if the relation is deleted or
/// updated. This will always result in a runtime error if one or more of the
Expand Down
11 changes: 9 additions & 2 deletions query-engine/prisma-models/src/builders/internal_dm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use crate::{
IndexType, InlineRelation, InternalEnum, InternalEnumValue, RelationLinkManifestation, RelationSide, RelationTable,
TypeIdentifier,
};
use psl::dml::{self, CompositeTypeFieldType, Datamodel, Ignorable, WithDatabaseName};
use psl::{
datamodel_connector::RelationMode,
dml::{self, CompositeTypeFieldType, Datamodel, Ignorable, WithDatabaseName},
};

pub(crate) fn model_builders(
datamodel: &Datamodel,
Expand Down Expand Up @@ -136,7 +139,10 @@ fn composite_field_builders(datamodel: &Datamodel, composite: &dml::CompositeTyp
.collect()
}

pub(crate) fn relation_builders(placeholders: &[RelationPlaceholder]) -> Vec<RelationBuilder> {
pub(crate) fn relation_builders(
placeholders: &[RelationPlaceholder],
relation_mode: RelationMode,
) -> Vec<RelationBuilder> {
placeholders
.iter()
.filter(|r| r.model_a.is_relation_supported(&r.field_a) && r.model_b.is_relation_supported(&r.field_b))
Expand All @@ -145,6 +151,7 @@ pub(crate) fn relation_builders(placeholders: &[RelationPlaceholder]) -> Vec<Rel
manifestation: r.manifestation(),
model_a_name: r.model_a.name.clone(),
model_b_name: r.model_b.name.clone(),
relation_mode,
})
.collect()
}
Expand Down
3 changes: 3 additions & 0 deletions query-engine/prisma-models/src/builders/relation_builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;

use once_cell::sync::OnceCell;
use psl::datamodel_connector::RelationMode;

use crate::{InternalDataModelWeakRef, Relation, RelationLinkManifestation, RelationRef};

Expand All @@ -10,6 +11,7 @@ pub struct RelationBuilder {
pub manifestation: RelationLinkManifestation,
pub model_a_name: String,
pub model_b_name: String,
pub relation_mode: RelationMode,
}

impl RelationBuilder {
Expand All @@ -19,6 +21,7 @@ impl RelationBuilder {
manifestation: self.manifestation,
model_a_name: self.model_a_name,
model_b_name: self.model_b_name,
relation_mode: self.relation_mode,
model_a: OnceCell::new(),
model_b: OnceCell::new(),
field_a: OnceCell::new(),
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
7 changes: 6 additions & 1 deletion query-engine/prisma-models/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ use std::sync::Arc;

pub fn convert(schema: &psl::ValidatedSchema, db_name: String) -> InternalDataModelRef {
let datamodel = psl::lift(schema);
let relation_mode = schema.relation_mode();

let relation_placeholders = builders::relation_placeholders(&datamodel);
let models = builders::model_builders(&datamodel, &relation_placeholders);
let relations = builders::relation_builders(&relation_placeholders);

// relations can be influenced by the relation mode, e.g., to let an action conditionally become an alias
// for another action.
let relations = builders::relation_builders(&relation_placeholders, relation_mode);

let enums = builders::convert_enums(&datamodel);
let composite_types = builders::composite_type_builders(&datamodel);
let internal_data_model = Arc::new(InternalDataModel {
Expand Down
24 changes: 20 additions & 4 deletions query-engine/prisma-models/src/relation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::prelude::*;
use dml::ReferentialAction;
use once_cell::sync::OnceCell;
use psl::datamodel_connector::RelationMode;
use std::{
fmt::Debug,
sync::{Arc, Weak},
Expand All @@ -25,6 +26,7 @@ pub struct Relation {

pub manifestation: RelationLinkManifestation,
pub internal_data_model: InternalDataModelWeakRef,
pub relation_mode: RelationMode,
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
}

impl Relation {
Expand Down Expand Up @@ -124,20 +126,34 @@ impl Relation {

/// Retrieves the onDelete policy for this relation.
pub fn on_delete(&self) -> ReferentialAction {
self.field_a()
let action = self
.field_a()
.on_delete()
.cloned()
.or_else(|| self.field_b().on_delete().cloned())
.unwrap_or(self.field_a().on_delete_default)
.unwrap_or(self.field_a().on_delete_default);

match (action, self.relation_mode) {
// NoAction is an alias for Restrict when relationMode = "prisma"
(ReferentialAction::NoAction, RelationMode::Prisma) => ReferentialAction::Restrict,
(action, _) => action,
}
}

/// Retrieves the onUpdate policy for this relation.
pub fn on_update(&self) -> ReferentialAction {
self.field_a()
let action = self
.field_a()
.on_update()
.cloned()
.or_else(|| self.field_b().on_update().cloned())
.unwrap_or(self.field_a().on_update_default)
.unwrap_or(self.field_a().on_update_default);

match (action, self.relation_mode) {
// NoAction is an alias for Restrict when relationMode = "prisma"
(ReferentialAction::NoAction, RelationMode::Prisma) => ReferentialAction::Restrict,
(action, _) => action,
}
}
}

Expand Down
74 changes: 73 additions & 1 deletion query-engine/prisma-models/tests/datamodel_converter_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,79 @@
#![allow(non_snake_case)]
use prisma_models::*;
use prisma_models::{dml::ReferentialAction, *};
use std::sync::Arc;

#[test]
fn no_action_is_alias_for_restrict_when_prisma_relation_mode() {
let datamodel = convert(
r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}

datasource db {
provider = "mysql"
url = "mysql://"
relationMode = "prisma"
}

model A {
id Int @id
bs B[]
}

model B {
id Int @id
aId Int
a A @relation(fields: [aId], references: [id], onUpdate: NoAction, onDelete: NoAction)
}
"#,
);

let relations = datamodel.relations();
assert_eq!(relations.len(), 1);

let relation = &relations[0];
assert_eq!(relation.on_update(), ReferentialAction::Restrict);
assert_eq!(relation.on_delete(), ReferentialAction::Restrict);
}

#[test]
fn no_action_is_not_alias_for_restrict_when_foreign_keys_relation_mode() {
let datamodel = convert(
r#"
generator client {
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}

datasource db {
provider = "mysql"
url = "mysql://"
relationMode = "foreignKeys"
}

model A {
id Int @id
bs B[]
}

model B {
id Int @id
aId Int
a A @relation(fields: [aId], references: [id], onUpdate: NoAction, onDelete: NoAction)
}
"#,
);

let relations = datamodel.relations();
assert_eq!(relations.len(), 1);

let relation = &relations[0];
assert_eq!(relation.on_update(), ReferentialAction::NoAction);
assert_eq!(relation.on_delete(), ReferentialAction::NoAction);
}

#[test]
fn an_empty_datamodel_must_work() {
let datamodel = convert("");
Expand Down