diff --git a/psl/psl-core/src/builtin_connectors/mongodb.rs b/psl/psl-core/src/builtin_connectors/mongodb.rs index bc18956201ad..2ab5d557296e 100644 --- a/psl/psl-core/src/builtin_connectors/mongodb.rs +++ b/psl/psl-core/src/builtin_connectors/mongodb.rs @@ -145,7 +145,7 @@ impl Connector for MongoDbDatamodelConnector { } fn allowed_relation_mode_settings(&self) -> enumflags2::BitFlags { - RelationMode::Prisma.into() + RelationMode::Prisma | RelationMode::PrismaSkipIntegrity } /// Avoid checking whether the fields appearing in a `@relation` attribute are included in an index. diff --git a/psl/psl-core/src/datamodel_connector.rs b/psl/psl-core/src/datamodel_connector.rs index d256225c1a9f..4964438ada0d 100644 --- a/psl/psl-core/src/datamodel_connector.rs +++ b/psl/psl-core/src/datamodel_connector.rs @@ -87,6 +87,8 @@ pub trait Connector: Send + Sync { match relation_mode { RelationMode::ForeignKeys => self.foreign_key_referential_actions(), RelationMode::Prisma => self.emulated_referential_actions(), + // PrismaSkipIntegrity does not support any referential action, cause it is used to skip any integrity check. + RelationMode::PrismaSkipIntegrity => BitFlags::empty(), } } diff --git a/psl/psl-core/src/datamodel_connector/relation_mode.rs b/psl/psl-core/src/datamodel_connector/relation_mode.rs index 632824ada680..9a034d615c52 100644 --- a/psl/psl-core/src/datamodel_connector/relation_mode.rs +++ b/psl/psl-core/src/datamodel_connector/relation_mode.rs @@ -13,6 +13,11 @@ pub enum RelationMode { /// Enforced in Prisma. Slower, but for databases that do not support /// foreign keys. Prisma, + /// Enforced in Prisma. Currently allowed only for MongoDB. + /// Skips any referential integrity checks (emulated relations) after an update or delete operation. + /// Using this mode, Prisma will STOP ensuring that every relation-field is valid (left intact), thus stricter checks + /// must be done at the PrismaClient side before executing a query or with a try-catch pattern. + PrismaSkipIntegrity, } impl RelationMode { @@ -23,7 +28,11 @@ impl RelationMode { } pub fn is_prisma(&self) -> bool { - matches!(self, Self::Prisma) + matches!(self, Self::Prisma) || matches!(self, Self::PrismaSkipIntegrity) + } + + pub fn should_skip_emulated_referential_integrity(&self) -> bool { + self.uses_foreign_keys() || matches!(self, Self::PrismaSkipIntegrity) } /// True, if integrity is in database foreign keys @@ -43,6 +52,7 @@ impl fmt::Display for RelationMode { match self { RelationMode::ForeignKeys => write!(f, "foreignKeys"), RelationMode::Prisma => write!(f, "prisma"), + RelationMode::PrismaSkipIntegrity => write!(f, "prismaSkipIntegrity"), } } } diff --git a/psl/psl-core/src/validate/datasource_loader.rs b/psl/psl-core/src/validate/datasource_loader.rs index 6ec62d6d10e3..31a448d908fe 100644 --- a/psl/psl-core/src/validate/datasource_loader.rs +++ b/psl/psl-core/src/validate/datasource_loader.rs @@ -265,9 +265,10 @@ fn get_relation_mode( let relation_mode = match coerce::string(rm, diagnostics)? { "prisma" => RelationMode::Prisma, "foreignKeys" => RelationMode::ForeignKeys, + "prismaSkipIntegrity" => RelationMode::PrismaSkipIntegrity, other => { let message = format!( - "Invalid relation mode setting: \"{other}\". Supported values: \"prisma\", \"foreignKeys\"", + "Invalid relation mode setting: \"{other}\". Supported values: \"prisma\", \"foreignKeys\", \"prismaSkipIntegrity\"", ); let error = DatamodelError::new_source_validation_error(&message, "relationMode", source.span); diagnostics.push_error(error); diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/relation_fields.rs b/psl/psl-core/src/validate/validation_pipeline/validations/relation_fields.rs index d3380c7ab017..3b671fac6c25 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/relation_fields.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/relation_fields.rs @@ -195,10 +195,17 @@ pub(super) fn referential_actions(field: RelationFieldWalker<'_>, ctx: &mut Cont ) }; + // validation template for relationMode = "prismaSkipIntegrity" + let msg_prisma_skip_integrity = |action: ReferentialAction| { + let additional_info = "When using `relationMode = \"prismaSkipIntegrity\"`, you cannot specify any referential action. Learn more at https://pris.ly/d/relation-mode"; + format!("Invalid referential action: `{}`. {additional_info}", action.as_str()) + }; + let msg_template = |action: ReferentialAction| -> String { match relation_mode { RelationMode::ForeignKeys => msg_foreign_keys(action), RelationMode::Prisma => msg_prisma(action), + RelationMode::PrismaSkipIntegrity => msg_prisma_skip_integrity(action), } }; diff --git a/psl/psl-core/src/validate/validation_pipeline/validations/relations.rs b/psl/psl-core/src/validate/validation_pipeline/validations/relations.rs index e834fe3b54ea..7f579fabbc2c 100644 --- a/psl/psl-core/src/validate/validation_pipeline/validations/relations.rs +++ b/psl/psl-core/src/validate/validation_pipeline/validations/relations.rs @@ -225,7 +225,7 @@ pub(super) fn cycles(relation: CompleteInlineRelationWalker<'_>, ctx: &mut Conte if !ctx.has_capability(ConnectorCapability::ReferenceCycleDetection) && ctx .datasource - .map(|ds| ds.relation_mode().uses_foreign_keys()) + .map(|ds| ds.relation_mode().should_skip_emulated_referential_integrity()) .unwrap_or(true) { return; diff --git a/psl/psl/tests/attributes/relations/referential_actions.rs b/psl/psl/tests/attributes/relations/referential_actions.rs index d1c234ec83d8..dbc23408cb63 100644 --- a/psl/psl/tests/attributes/relations/referential_actions.rs +++ b/psl/psl/tests/attributes/relations/referential_actions.rs @@ -380,7 +380,7 @@ fn foreign_keys_not_allowed_on_mongo() { "#}; let expected = expect![[r#" - error: Error validating datasource `relationMode`: Invalid relation mode setting: "foreignKeys". Supported values: "prisma" + error: Error validating datasource `relationMode`: Invalid relation mode setting: "foreignKeys". Supported values: "prisma", "prismaSkipIntegrity" --> schema.prisma:3  |   2 |  provider = "mongodb" @@ -912,3 +912,36 @@ fn on_update_cannot_be_defined_on_the_wrong_side_1_1() { expect.assert_eq(&parse_unwrap_err(dml)); } + +#[test] +fn mongo_prisma_skip_integrity_relation_mode_parses_correctly() { + let dml = indoc! {r#" + datasource db { + provider = "mongodb" + relationMode = "prismaSkipIntegrity" + url = "mongodb://" + } + + generator client { + provider = "prisma-client-js" + } + + model A { + id String @id @map("_id") @db.ObjectId + bs B[] + } + + model B { + id String @id @map("_id") @db.ObjectId + aId String @db.ObjectId + a A @relation(fields: [aId], references: [id]) + } + "#}; + + let schema = parse_schema(&dml); + + schema.assert_has_model("A"); + schema.assert_has_model("B").assert_has_relation_field("a"); + + assert_eq!(schema.relation_mode(), RelationMode::PrismaSkipIntegrity) +} diff --git a/psl/psl/tests/attributes/relations/relations_negative.rs b/psl/psl/tests/attributes/relations/relations_negative.rs index 6ce8192f1a91..c03467384008 100644 --- a/psl/psl/tests/attributes/relations/relations_negative.rs +++ b/psl/psl/tests/attributes/relations/relations_negative.rs @@ -1105,3 +1105,61 @@ fn multiple_relation_validation_errors_do_not_prevent_each_other_across_models() expect_error(schema, &expected_error) } + +#[test] +fn mongo_prisma_skip_integrity_relation_mode_shouldnt_allow_any_referential_action() { + let dml = indoc! {r#" + datasource db { + provider = "mongodb" + relationMode = "prismaSkipIntegrity" + url = "mongodb://" + } + + generator client { + provider = "prisma-client-js" + } + + model A { + id String @id @map("_id") @db.ObjectId + bs B[] + } + + model B { + id String @id @map("_id") @db.ObjectId + aId String @db.ObjectId + a A @relation(fields: [aId], references: [id], onUpdate: Restrict, onDelete: Restrict) + } + "#}; + + assert!(parse_unwrap_err(dml).contains("Invalid referential action: `Restrict`. When using `relationMode = \"prismaSkipIntegrity\"`, you cannot specify any referential action.")); +} + +#[test] +fn sql_should_fail_with_prisma_skip_integrity_relation_mode() { + let dml = indoc! {r#" + datasource db { + provider = "mysql" + relationMode = "prismaSkipIntegrity" + url = env("PLACEHOLDER") + } + + generator client { + provider = "prisma-client-js" + } + + model A { + id Int @id + bs B[] + } + + model B { + id Int @id + aId Int + a A @relation(fields: [aId], references: [id], onUpdate: Restrict, onDelete: Restrict) + } + "#}; + + assert!(parse_unwrap_err(dml).contains( + "Error validating datasource `relationMode`: Invalid relation mode setting: \"prismaSkipIntegrity\"." + )); +} diff --git a/query-engine/core/src/query_graph_builder/write/utils.rs b/query-engine/core/src/query_graph_builder/write/utils.rs index a931fa1edc30..1063ffee978e 100644 --- a/query-engine/core/src/query_graph_builder/write/utils.rs +++ b/query-engine/core/src/query_graph_builder/write/utils.rs @@ -385,8 +385,11 @@ pub(crate) fn insert_emulated_on_delete( model_to_delete: &Model, node_providing_ids: &NodeRef, ) -> QueryGraphBuilderResult> { - // If the connector uses the `RelationMode::ForeignKeys` mode, we do not do any checks / emulation. - if query_schema.relation_mode().uses_foreign_keys() { + // If the connector uses the `RelationMode::ForeignKeys` or `RelationMode::PrismaSkipIntegrity` mode, we do not do any checks / emulation. + if query_schema + .relation_mode() + .should_skip_emulated_referential_integrity() + { return Ok(vec![]); } @@ -915,8 +918,11 @@ pub fn insert_emulated_on_update_with_intermediary_node( parent_node: &NodeRef, child_node: &NodeRef, ) -> QueryGraphBuilderResult> { - // If the connector uses the `RelationMode::ForeignKeys` mode, we do not do any checks / emulation. - if query_schema.relation_mode().uses_foreign_keys() { + // If the connector uses the `RelationMode::ForeignKeys` mode or the `RelationMode::PrismaSkipIntegrity`, we do not do any checks / emulation. + if query_schema + .relation_mode() + .should_skip_emulated_referential_integrity() + { return Ok(None); } @@ -962,8 +968,11 @@ pub fn insert_emulated_on_update( parent_node: &NodeRef, child_node: &NodeRef, ) -> QueryGraphBuilderResult<()> { - // If the connector uses the `RelationMode::ForeignKeys` mode, we do not do any checks / emulation. - if query_schema.relation_mode().uses_foreign_keys() { + // If the connector uses the `RelationMode::ForeignKeys` mode or the `RelationMode::PrismaSkipIntegrity` mode, we do not do any checks / emulation. + if query_schema + .relation_mode() + .should_skip_emulated_referential_integrity() + { return Ok(()); }