Skip to content

Commit

Permalink
Merge pull request #3274 from prisma/feat/remove-simulated-noaction-f…
Browse files Browse the repository at this point in the history
…or-postgres-and-sqlite

feat(relation-mode): remove emulated `NoAction` for `postgres` and `sqlite`
  • Loading branch information
jkomyno committed Oct 11, 2022
2 parents 0813826 + 5755b7a commit f6d71ec
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 58 deletions.
3 changes: 1 addition & 2 deletions prisma-fmt/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ pub(crate) fn run(schema: &str) -> String {
if validated_configuration.datasources.len() != 1 {
"[]".to_string()
} else if let Some(datasource) = validated_configuration.datasources.first() {
let relation_mode = datasource.relation_mode();
let available_referential_actions = datasource
.active_connector
.referential_actions(&relation_mode)
.referential_actions()
.iter()
.map(|act| format!("{:?}", act))
.collect::<Vec<_>>();
Expand Down
4 changes: 2 additions & 2 deletions prisma-fmt/src/text_document_completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub(crate) fn completion(schema: String, params: CompletionParams) -> Completion
fn push_ast_completions(
completion_list: &mut CompletionList,
connector: &'static dyn Connector,
relation_mode: RelationMode,
_relation_mode: RelationMode,
db: &ParserDatabase,
position: usize,
) {
Expand All @@ -62,7 +62,7 @@ fn push_ast_completions(
_model_id,
ast::ModelPosition::Field(_, ast::FieldPosition::Attribute("relation", _, Some(attr_name))),
) if attr_name == "onDelete" || attr_name == "onUpdate" => {
for referential_action in connector.referential_actions(&relation_mode).iter() {
for referential_action in connector.referential_actions().iter() {
completion_list.items.push(CompletionItem {
label: referential_action.as_str().to_owned(),
kind: Some(CompletionItemKind::ENUM),
Expand Down
7 changes: 3 additions & 4 deletions psl/builtin-connectors/src/cockroach_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ use native_types::{CockroachType, NativeType};
use psl_core::{
datamodel_connector::{
helper::{arg_vec_from_opt, args_vec_from_opt, parse_one_opt_u32, parse_two_opt_u32},
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, RelationMode,
StringFilter,
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, StringFilter,
},
diagnostics::{DatamodelError, Diagnostics},
parser_database::{
Expand Down Expand Up @@ -133,10 +132,10 @@ impl Connector for CockroachDatamodelConnector {
63
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

relation_mode.allowed_referential_actions(NoAction | Restrict | Cascade | SetNull | SetDefault)
NoAction | Restrict | Cascade | SetNull | SetDefault
}

fn scalar_type_for_native_type(&self, native_type: serde_json::Value) -> ScalarType {
Expand Down
4 changes: 2 additions & 2 deletions psl/builtin-connectors/src/mongodb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ impl Connector for MongoDbDatamodelConnector {
&[ConstraintScope::ModelKeyIndex]
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
relation_mode.allowed_referential_actions(BitFlags::empty())
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
BitFlags::empty()
}

fn validate_model(&self, model: ModelWalker<'_>, errors: &mut Diagnostics) {
Expand Down
6 changes: 3 additions & 3 deletions psl/builtin-connectors/src/mssql_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
use psl_core::{
datamodel_connector::{
helper::{arg_vec_from_opt, args_vec_from_opt, parse_one_opt_u32, parse_two_opt_u32},
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, RelationMode,
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance,
},
diagnostics::{DatamodelError, Diagnostics, Span},
parser_database::{self, ast, ParserDatabase, ReferentialAction, ScalarType},
Expand Down Expand Up @@ -171,10 +171,10 @@ impl Connector for MsSqlDatamodelConnector {
128
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

relation_mode.allowed_referential_actions(NoAction | Cascade | SetNull | SetDefault)
NoAction | Cascade | SetNull | SetDefault
}

fn scalar_type_for_native_type(&self, native_type: serde_json::Value) -> ScalarType {
Expand Down
6 changes: 3 additions & 3 deletions psl/builtin-connectors/src/mysql_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use native_types::{
use psl_core::{
datamodel_connector::{
helper::{args_vec_from_opt, parse_one_opt_u32, parse_one_u32, parse_two_opt_u32},
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, RelationMode,
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance,
},
diagnostics::{DatamodelError, Diagnostics, Span},
parser_database::{walkers, ReferentialAction, ScalarType},
Expand Down Expand Up @@ -160,10 +160,10 @@ impl Connector for MySqlDatamodelConnector {
64
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

relation_mode.allowed_referential_actions(Restrict | Cascade | SetNull | NoAction | SetDefault)
Restrict | Cascade | SetNull | NoAction | SetDefault
}

fn scalar_type_for_native_type(&self, native_type: serde_json::Value) -> ScalarType {
Expand Down
13 changes: 9 additions & 4 deletions psl/builtin-connectors/src/postgres_datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use psl_core::{
common::preview_features::PreviewFeature,
datamodel_connector::{
helper::{arg_vec_from_opt, args_vec_from_opt, parse_one_opt_u32, parse_two_opt_u32},
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, RelationMode,
StringFilter,
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, StringFilter,
},
diagnostics::{DatamodelError, Diagnostics},
parser_database::{ast, walkers, IndexAlgorithm, OperatorClass, ParserDatabase, ReferentialAction, ScalarType},
Expand Down Expand Up @@ -258,10 +257,16 @@ impl Connector for PostgresDatamodelConnector {
63
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

relation_mode.allowed_referential_actions(NoAction | Restrict | Cascade | SetNull | SetDefault)
NoAction | Restrict | Cascade | SetNull | SetDefault
}

fn emulated_referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

Restrict | SetNull | Cascade
}

fn scalar_type_for_native_type(&self, native_type: serde_json::Value) -> ScalarType {
Expand Down
14 changes: 9 additions & 5 deletions psl/builtin-connectors/src/sqlite_datamodel_connector.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use enumflags2::BitFlags;
use psl_core::{
datamodel_connector::{
Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance, RelationMode,
},
datamodel_connector::{Connector, ConnectorCapability, ConstraintScope, NativeTypeConstructor, NativeTypeInstance},
diagnostics::{DatamodelError, Span},
parser_database::{ReferentialAction, ScalarType},
};
Expand Down Expand Up @@ -43,10 +41,16 @@ impl Connector for SqliteDatamodelConnector {
10000
}

fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

relation_mode.allowed_referential_actions(SetNull | SetDefault | Cascade | Restrict | NoAction)
SetNull | SetDefault | Cascade | Restrict | NoAction
}

fn emulated_referential_actions(&self) -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

Restrict | SetNull | Cascade
}

fn scalar_type_for_native_type(&self, _native_type: serde_json::Value) -> ScalarType {
Expand Down
17 changes: 15 additions & 2 deletions psl/psl-core/src/datamodel_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,17 @@ pub trait Connector: Send + Sync {
}

/// The referential actions supported by the connector.
fn referential_actions(&self, relation_mode: &RelationMode) -> BitFlags<ReferentialAction>;
fn referential_actions(&self) -> BitFlags<ReferentialAction>;

/// The referential actions supported when using relationMode = "prisma" by the connector.
/// There are in fact scenarios in which the set of emulated referential actions supported may change
/// depending on the connector. For example, Postgres' NoAction mode behaves similarly to Restrict
/// (raising an error if any referencing rows still exist when the constraint is checked), but with
/// a subtle twist we decided not to emulate: NO ACTION allows the check to be deferred until later
/// in the transaction, whereas RESTRICT does not.
fn emulated_referential_actions(&self) -> BitFlags<ReferentialAction> {
RelationMode::allowed_emulated_referential_actions_default()
}

fn supports_composite_types(&self) -> bool {
self.has_capability(ConnectorCapability::CompositeTypes)
Expand All @@ -101,7 +111,10 @@ pub trait Connector: Send + Sync {
}

fn supports_referential_action(&self, relation_mode: &RelationMode, action: ReferentialAction) -> bool {
self.referential_actions(relation_mode).contains(action)
match relation_mode {
RelationMode::ForeignKeys => self.referential_actions().contains(action),
RelationMode::Prisma => self.emulated_referential_actions().contains(action),
}
}

/// This is used by the query engine schema builder.
Expand Down
2 changes: 1 addition & 1 deletion psl/psl-core/src/datamodel_connector/empty_connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl Connector for EmptyDatamodelConnector {
std::any::type_name::<EmptyDatamodelConnector>()
}

fn referential_actions(&self, _relation_mode: &RelationMode) -> BitFlags<ReferentialAction> {
fn referential_actions(&self) -> BitFlags<ReferentialAction> {
BitFlags::all()
}

Expand Down
13 changes: 2 additions & 11 deletions psl/psl-core/src/datamodel_connector/relation_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,10 @@ pub enum RelationMode {
}

impl RelationMode {
/// Returns either the given actions if foreign keys are used, or the
/// allowed emulated actions if referential integrity happens in Prisma.
pub fn allowed_referential_actions(
&self,
from_connector: BitFlags<ReferentialAction>,
) -> BitFlags<ReferentialAction> {
pub fn allowed_emulated_referential_actions_default() -> BitFlags<ReferentialAction> {
use ReferentialAction::*;

match self {
Self::ForeignKeys => from_connector,
// The emulated modes should be listed here.
Self::Prisma => Restrict | SetNull | NoAction | Cascade,
}
Restrict | SetNull | NoAction | Cascade
}

pub fn is_prisma(&self) -> bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use super::{database_name::validate_db_name, names::Names};
use crate::{
ast::{self, WithName, WithSpan},
datamodel_connector::RelationMode,
diagnostics::DatamodelError,
validate::validation_pipeline::context::Context,
};
use enumflags2::BitFlags;
use itertools::Itertools;
use parser_database::{
walkers::{ModelWalker, RelationFieldWalker, RelationName},
Expand Down Expand Up @@ -140,39 +142,81 @@ pub(super) fn ignored_related_model(field: RelationFieldWalker<'_>, ctx: &mut Co
pub(super) fn referential_actions(field: RelationFieldWalker<'_>, ctx: &mut Context<'_>) {
let connector = ctx.connector;
let relation_mode = ctx.relation_mode;
let msg = |action: ReferentialAction| {
let allowed_values = connector
.referential_actions(&relation_mode)
.iter()
.map(|f| format!("`{}`", f.as_str()))
.join(", ");

fn fmt_allowed_actions(allowed_actions: BitFlags<ReferentialAction>) -> String {
allowed_actions.iter().map(|f| format!("`{}`", f.as_str())).join(", ")
}

// validation template for relationMode = "foreignKeys"
let msg_foreign_keys = |action: ReferentialAction| {
let allowed_actions = connector.referential_actions();

format!(
"Invalid referential action: `{}`. Allowed values: ({})",
action.as_str(),
allowed_values,
fmt_allowed_actions(allowed_actions),
)
};

// validation template for relationMode = "prisma"
let msg_prisma = |action: ReferentialAction| {
let allowed_actions = connector.emulated_referential_actions();

let additional_info = match action {
ReferentialAction::NoAction => {
// we don't want to suggest the users to use Restrict instead, if the connector doesn't support it
if ctx
.connector
.emulated_referential_actions()
.contains(ReferentialAction::Restrict)
{
Some(format!(
". `{}` is not implemented for {} when using `relationMode = \"prisma\"`, you could try using `{}` instead. Learn more at https://pris.ly/d/relationMode",
ReferentialAction::NoAction.as_str(),
connector.name(),
ReferentialAction::Restrict.as_str(),
))
} else {
None
}
}
_ => None,
};
let additional_info = additional_info.unwrap_or_default();

format!(
"Invalid referential action: `{}`. Allowed values: ({}){additional_info}",
action.as_str(),
fmt_allowed_actions(allowed_actions),
)
};

let msg_template = |action: ReferentialAction| -> String {
match relation_mode {
RelationMode::ForeignKeys => msg_foreign_keys(action),
RelationMode::Prisma => msg_prisma(action),
}
};

if let Some(on_delete) = field.explicit_on_delete() {
if !ctx.connector.supports_referential_action(&ctx.relation_mode, on_delete) {
let span = field
.ast_field()
.span_for_argument("relation", "onDelete")
.unwrap_or_else(|| field.ast_field().span());
let span = field
.ast_field()
.span_for_argument("relation", "onDelete")
.unwrap_or_else(|| field.ast_field().span());

ctx.push_error(DatamodelError::new_validation_error(&msg(on_delete), span));
if !ctx.connector.supports_referential_action(&ctx.relation_mode, on_delete) {
ctx.push_error(DatamodelError::new_validation_error(&msg_template(on_delete), span));
}
}

if let Some(on_update) = field.explicit_on_update() {
if !ctx.connector.supports_referential_action(&ctx.relation_mode, on_update) {
let span = field
.ast_field()
.span_for_argument("relation", "onUpdate")
.unwrap_or_else(|| field.ast_field().span());
let span = field
.ast_field()
.span_for_argument("relation", "onUpdate")
.unwrap_or_else(|| field.ast_field().span());

ctx.push_error(DatamodelError::new_validation_error(&msg(on_update), span));
if !ctx.connector.supports_referential_action(&ctx.relation_mode, on_update) {
ctx.push_error(DatamodelError::new_validation_error(&msg_template(on_update), span));
}
}
}
Expand Down

0 comments on commit f6d71ec

Please sign in to comment.