diff --git a/prisma-fmt/src/code_actions.rs b/prisma-fmt/src/code_actions.rs index b9dbdc58067d..1037192b1a93 100644 --- a/prisma-fmt/src/code_actions.rs +++ b/prisma-fmt/src/code_actions.rs @@ -99,6 +99,16 @@ pub(crate) fn available_actions(schema: String, params: CodeActionParams) -> Vec complete_relation.referencing_field(), ); } + + if validated_schema.relation_mode().uses_foreign_keys() { + relation_mode::replace_set_default_mysql( + &mut actions, + ¶ms, + validated_schema.db.source(), + complete_relation, + config, + ) + } } } diff --git a/prisma-fmt/src/code_actions/relation_mode.rs b/prisma-fmt/src/code_actions/relation_mode.rs index 28d9018220e7..751fb956073b 100644 --- a/prisma-fmt/src/code_actions/relation_mode.rs +++ b/prisma-fmt/src/code_actions/relation_mode.rs @@ -1,5 +1,5 @@ use lsp_types::{CodeAction, CodeActionKind, CodeActionOrCommand}; -use psl::schema_ast::ast::SourceConfig; +use psl::{parser_database::walkers::CompleteInlineRelationWalker, schema_ast::ast::SourceConfig, Configuration}; pub(crate) fn edit_referential_integrity( actions: &mut Vec, @@ -35,3 +35,51 @@ pub(crate) fn edit_referential_integrity( actions.push(CodeActionOrCommand::CodeAction(action)) } + +pub(crate) fn replace_set_default_mysql( + actions: &mut Vec, + params: &lsp_types::CodeActionParams, + schema: &str, + relation: CompleteInlineRelationWalker<'_>, + config: &Configuration, +) { + let datasource = match config.datasources.first() { + Some(ds) => ds, + None => return, + }; + + if datasource.active_connector.provider_name() != "mysql" { + return; + } + + let span = match relation.on_update_span() { + Some(span) => span, + None => return, + }; + + let span_diagnostics = match super::diagnostics_for_span(schema, ¶ms.context.diagnostics, span) { + Some(sd) => sd, + None => return, + }; + + let diagnostics = match + super::filter_diagnostics( + span_diagnostics, + "MySQL does not actually support the `SetDefault` referential action, so using it may result in unexpected errors.") { + Some(value) => value, + None => return, + }; + + let edit = super::create_text_edit(schema, "NoAction".to_owned(), false, span, params); + + let action = CodeAction { + title: r#"Replace SetDefault with NoAction"#.to_owned(), + + kind: Some(CodeActionKind::QUICKFIX), + edit: Some(edit), + diagnostics: Some(diagnostics), + ..Default::default() + }; + + actions.push(CodeActionOrCommand::CodeAction(action)) +} diff --git a/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/result.json b/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/result.json new file mode 100644 index 000000000000..d31f54355c36 --- /dev/null +++ b/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/result.json @@ -0,0 +1,41 @@ +[ + { + "title": "Replace SetDefault with NoAction", + "kind": "quickfix", + "diagnostics": [ + { + "range": { + "start": { + "line": 14, + "character": 62 + }, + "end": { + "line": 14, + "character": 82 + } + }, + "severity": 2, + "message": "MySQL does not actually support the `SetDefault` referential action, so using it may result in unexpected errors. Read more at https://pris.ly/d/mysql-set-default " + } + ], + "edit": { + "changes": { + "file:///path/to/schema.prisma": [ + { + "range": { + "start": { + "line": 14, + "character": 72 + }, + "end": { + "line": 14, + "character": 82 + } + }, + "newText": "NoAction" + } + ] + } + } + } +] \ No newline at end of file diff --git a/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/schema.prisma b/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/schema.prisma new file mode 100644 index 000000000000..b13952553002 --- /dev/null +++ b/prisma-fmt/tests/code_actions/scenarios/relation_mode_mysql_foreign_keys_set_default/schema.prisma @@ -0,0 +1,29 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") + relationMode = "foreignKeys" +} + +/// multi line +/// commennttt +model Foo { + id Int @id + bar Bar @relation(fields: [bar_id], references: [id], onUpdate: SetDefault) + bar_id Int @unique + t Test +} + +model Bar { + id Int @id + foo Foo? +} + +// This is a test enum. +enum Test { + TestUno + TestDue +} diff --git a/prisma-fmt/tests/code_actions/tests.rs b/prisma-fmt/tests/code_actions/tests.rs index 41035ed65d93..e76179204d92 100644 --- a/prisma-fmt/tests/code_actions/tests.rs +++ b/prisma-fmt/tests/code_actions/tests.rs @@ -25,6 +25,7 @@ scenarios! { one_to_one_referencing_side_misses_unique_compound_field_indentation_four_spaces relation_mode_prisma_missing_index relation_mode_referential_integrity + relation_mode_mysql_foreign_keys_set_default multi_schema_one_model multi_schema_one_model_one_enum multi_schema_two_models diff --git a/psl/parser-database/src/walkers/relation/inline/complete.rs b/psl/parser-database/src/walkers/relation/inline/complete.rs index 1c5536e948a6..3f7b1b67dc60 100644 --- a/psl/parser-database/src/walkers/relation/inline/complete.rs +++ b/psl/parser-database/src/walkers/relation/inline/complete.rs @@ -2,6 +2,7 @@ use crate::{ walkers::{ModelWalker, RelationFieldId, RelationFieldWalker, ScalarFieldWalker}, ParserDatabase, ReferentialAction, }; +use diagnostics::Span; use schema_ast::ast; /// Represents a relation that has fields and references defined in one of the @@ -65,6 +66,10 @@ impl<'db> CompleteInlineRelationWalker<'db> { .unwrap_or(Cascade) } + pub fn on_update_span(self) -> Option { + self.referencing_field().attributes().on_update.map(|(_, span)| span) + } + /// Prisma allows setting the relation field as optional, even if one of the /// underlying scalar fields is required. For the purpose of referential /// actions, we count the relation field required if any of the underlying diff --git a/query-engine/connectors/sql-query-connector/src/database/js.rs b/query-engine/connectors/sql-query-connector/src/database/js.rs index 1dced9453fa3..5b22653647f8 100644 --- a/query-engine/connectors/sql-query-connector/src/database/js.rs +++ b/query-engine/connectors/sql-query-connector/src/database/js.rs @@ -11,40 +11,25 @@ use quaint::{ connector::{IsolationLevel, Transaction}, prelude::{Queryable as QuaintQueryable, *}, }; -use std::{ - collections::{hash_map::Entry, HashMap}, - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; -/// Registry is the type for the global registry of driver adapters. -type Registry = HashMap; +static ACTIVE_DRIVER_ADAPTER: Lazy>> = Lazy::new(|| Mutex::new(None)); -/// REGISTRY is the global registry of Driver Adapters. -static REGISTRY: Lazy> = Lazy::new(|| Mutex::new(HashMap::new())); +fn active_driver_adapter(provider: &str) -> connector::Result { + let lock = ACTIVE_DRIVER_ADAPTER.lock().unwrap(); -fn registered_driver_adapter(provider: &str) -> connector::Result { - let lock = REGISTRY.lock().unwrap(); - lock.get(provider) + lock.as_ref() + .map(|conn_ref| conn_ref.to_owned()) .ok_or(ConnectorError::from_kind(ErrorKind::UnsupportedConnector(format!( "A driver adapter for {} was not registered", provider )))) - .map(|conn_ref| conn_ref.to_owned()) } -pub fn register_driver_adapter(provider: &str, connector: Arc) -> Result<(), String> { - let mut lock = REGISTRY.lock().unwrap(); - let entry = lock.entry(provider.to_string()); - match entry { - Entry::Occupied(_) => Err(format!( - "A driver adapter for {} was already registered, and cannot be overridden.", - provider - )), - Entry::Vacant(v) => { - v.insert(DriverAdapter { connector }); - Ok(()) - } - } +pub fn activate_driver_adapter(connector: Arc) { + let mut lock = ACTIVE_DRIVER_ADAPTER.lock().unwrap(); + + *lock = Some(DriverAdapter { connector }); } pub struct Js { @@ -69,7 +54,7 @@ impl FromSource for Js { url: &str, features: psl::PreviewFeatures, ) -> connector_interface::Result { - let connector = registered_driver_adapter(source.active_provider)?; + let connector = active_driver_adapter(source.active_provider)?; let connection_info = get_connection_info(url)?; Ok(Js { diff --git a/query-engine/connectors/sql-query-connector/src/lib.rs b/query-engine/connectors/sql-query-connector/src/lib.rs index 06aa1e376c4a..d98f87d9a92e 100644 --- a/query-engine/connectors/sql-query-connector/src/lib.rs +++ b/query-engine/connectors/sql-query-connector/src/lib.rs @@ -23,7 +23,7 @@ use self::{column_metadata::*, context::Context, filter_conversion::*, query_ext use quaint::prelude::Queryable; #[cfg(feature = "driver-adapters")] -pub use database::{register_driver_adapter, Js}; +pub use database::{activate_driver_adapter, Js}; pub use database::{FromSource, Mssql, Mysql, PostgreSql, Sqlite}; pub use error::SqlError; diff --git a/query-engine/query-engine-node-api/src/engine.rs b/query-engine/query-engine-node-api/src/engine.rs index 37baeaee2c60..8b53f9dfc8c5 100644 --- a/query-engine/query-engine-node-api/src/engine.rs +++ b/query-engine/query-engine-node-api/src/engine.rs @@ -183,15 +183,12 @@ impl QueryEngine { #[cfg(feature = "driver-adapters")] if let Some(driver) = maybe_driver { let js_queryable = driver_adapters::from_napi(driver); - let provider_name = schema.connector.provider_name(); - match sql_connector::register_driver_adapter(provider_name, Arc::new(js_queryable)) { - Ok(_) => { - connector_mode = ConnectorMode::Js; - tracing::info!("Registered driver adapter for {provider_name}.") - } - Err(err) => tracing::error!("Failed to register driver adapter for {provider_name}. {err}"), - } + sql_connector::activate_driver_adapter(Arc::new(js_queryable)); + connector_mode = ConnectorMode::Js; + + let provider_name = schema.connector.provider_name(); + tracing::info!("Registered driver adapter for {provider_name}."); } }