diff --git a/Cargo.lock b/Cargo.lock index 446a80ea2891..025ea5a9789e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,7 +360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -1624,15 +1624,6 @@ dependencies = [ "ahash 0.7.6", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.7", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -2266,9 +2257,9 @@ checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -2554,7 +2545,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "mysql_async" version = "0.31.3" -source = "git+https://github.com/prisma/mysql_async?branch=vendored-openssl#dad187b50dc7e8ce2b61fec126822e8e172a9c8a" +source = "git+https://github.com/prisma/mysql_async?branch=vendored-openssl#0d40d0d2c332fc97512bff81e82e62002f03c224" dependencies = [ "bytes", "crossbeam", @@ -2563,6 +2554,7 @@ dependencies = [ "futures-sink", "futures-util", "lazy_static", + "lexical", "lru 0.8.1", "mio", "mysql_common", @@ -2572,6 +2564,7 @@ dependencies = [ "percent-encoding", "pin-project", "priority-queue", + "regex", "serde", "serde_json", "socket2 0.4.9", @@ -4131,14 +4124,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick 1.0.3", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.4.5", + "regex-syntax 0.8.2", ] [[package]] @@ -4152,13 +4145,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick 1.0.3", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] @@ -4169,9 +4162,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rend" @@ -5847,7 +5840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "rand 0.8.5", + "rand 0.7.3", "static_assertions", ] diff --git a/Makefile b/Makefile index b6921cc24f04..e4764c48b9a5 100644 --- a/Makefile +++ b/Makefile @@ -287,9 +287,15 @@ dev-mysql8: start-mysql_8 start-mysql_mariadb: docker compose -f docker-compose.yml up --wait -d --remove-orphans mariadb-10-0 +start-mysql_mariadb_11: + docker compose -f docker-compose.yml up --wait -d --remove-orphans mariadb-11-0 + dev-mariadb: start-mysql_mariadb cp $(CONFIG_PATH)/mariadb $(CONFIG_FILE) +dev-mariadb11: start-mysql_mariadb_11 + cp $(CONFIG_PATH)/mariadb $(CONFIG_FILE) + start-mssql_2019: docker compose -f docker-compose.yml up --wait -d --remove-orphans mssql-2019 diff --git a/docker-compose.yml b/docker-compose.yml index 46873b432357..f71231ad89de 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -294,6 +294,19 @@ services: - databases tmpfs: /var/lib/mariadb + mariadb-11-0: + image: mariadb:11 + restart: unless-stopped + environment: + MYSQL_USER: root + MYSQL_ROOT_PASSWORD: prisma + MYSQL_DATABASE: prisma + ports: + - '3308:3306' + networks: + - databases + tmpfs: /var/lib/mariadb + vitess-test-8_0: image: vitess/vttestserver:mysql80@sha256:53a2d2f58ecf8e6cf984c725612f7651c4fc7ac9bc7d198dbd9964d50e28b9a2 restart: unless-stopped diff --git a/psl/psl-core/src/builtin_connectors/mysql_datamodel_connector.rs b/psl/psl-core/src/builtin_connectors/mysql_datamodel_connector.rs index a44a2639e430..75db8d2c870b 100644 --- a/psl/psl-core/src/builtin_connectors/mysql_datamodel_connector.rs +++ b/psl/psl-core/src/builtin_connectors/mysql_datamodel_connector.rs @@ -8,8 +8,8 @@ use prisma_value::{decode_bytes, PrismaValueResult}; use super::completions; use crate::{ datamodel_connector::{ - Connector, ConnectorCapabilities, ConnectorCapability, ConstraintScope, Flavour, NativeTypeConstructor, - NativeTypeInstance, RelationMode, + Connector, ConnectorCapabilities, ConnectorCapability, ConstraintScope, Flavour, JoinStrategySupport, + NativeTypeConstructor, NativeTypeInstance, RelationMode, }, diagnostics::{DatamodelError, Diagnostics, Span}, parser_database::{walkers, ReferentialAction, ScalarType}, @@ -316,4 +316,13 @@ impl Connector for MySqlDatamodelConnector { fn parse_json_bytes(&self, str: &str, _nt: Option) -> PrismaValueResult> { decode_bytes(str) } + + fn runtime_join_strategy_support(&self) -> JoinStrategySupport { + match self.static_join_strategy_support() { + // Prior to MySQL 8.0.14 and for MariaDB, a derived table cannot contain outer references. + // Source: https://dev.mysql.com/doc/refman/8.0/en/derived-tables.html. + true => JoinStrategySupport::UnknownYet, + false => JoinStrategySupport::No, + } + } } diff --git a/psl/psl-core/src/datamodel_connector.rs b/psl/psl-core/src/datamodel_connector.rs index 107abd24710f..b2c539036a9f 100644 --- a/psl/psl-core/src/datamodel_connector.rs +++ b/psl/psl-core/src/datamodel_connector.rs @@ -329,6 +329,20 @@ pub trait Connector: Send + Sync { ) -> prisma_value::PrismaValueResult> { unreachable!("This method is only implemented on connectors with lateral join support.") } + + fn static_join_strategy_support(&self) -> bool { + self.has_capability(ConnectorCapability::LateralJoin) + || self.has_capability(ConnectorCapability::CorrelatedSubqueries) + } + + /// Returns whether the connector supports the `RelationLoadStrategy::Join`. + /// On some connectors, this might return `UnknownYet`. + fn runtime_join_strategy_support(&self) -> JoinStrategySupport { + match self.static_join_strategy_support() { + true => JoinStrategySupport::Yes, + false => JoinStrategySupport::No, + } + } } #[derive(Copy, Clone, Debug, PartialEq)] @@ -406,3 +420,19 @@ impl ConstraintScope { } } } + +/// Describes whether a connector supports relation join strategy. +#[derive(Debug, Copy, Clone)] +pub enum JoinStrategySupport { + /// The connector supports it. + Yes, + /// The connector supports it but the specific database version does not. + /// This state can only be known at runtime by checking the actual database version. + UnsupportedDbVersion, + /// The connector does not support it. + No, + /// The connector may or may not support it. Additional runtime informations are required to determine the support. + /// This state is used when the connector does not have a static capability to determine the support. + /// For example, the MySQL connector supports relation join strategy, but only for versions >= 8.0.14. + UnknownYet, +} diff --git a/quaint/src/connector/connection_info.rs b/quaint/src/connector/connection_info.rs index ec41e07a0b24..90b123106d89 100644 --- a/quaint/src/connector/connection_info.rs +++ b/quaint/src/connector/connection_info.rs @@ -255,6 +255,26 @@ impl ConnectionInfo { ConnectionInfo::External(_) => "external".into(), } } + + #[allow(unused_variables)] + pub fn set_version(&mut self, version: Option) { + match self { + #[cfg(not(target_arch = "wasm32"))] + ConnectionInfo::Native(native) => native.set_version(version), + ConnectionInfo::External(_) => (), + } + } + + pub fn version(&self) -> Option<&str> { + match self { + #[cfg(not(target_arch = "wasm32"))] + ConnectionInfo::Native(nt) => match nt { + NativeConnectionInfo::Mysql(m) => m.version(), + _ => None, + }, + ConnectionInfo::External(_) => None, + } + } } /// One of the supported SQL variants. diff --git a/quaint/src/connector/mysql/native/mod.rs b/quaint/src/connector/mysql/native/mod.rs index 98feb2649763..4ffdfc88b4cf 100644 --- a/quaint/src/connector/mysql/native/mod.rs +++ b/quaint/src/connector/mysql/native/mod.rs @@ -266,14 +266,12 @@ impl Queryable for Mysql { } async fn version(&self) -> crate::Result> { - let query = r#"SELECT @@GLOBAL.version version"#; - let rows = timeout::socket(self.socket_timeout, self.query_raw(query, &[])).await?; + let guard = self.conn.lock().await; + let (major, minor, patch) = guard.server_version(); + let flavour = if guard.is_mariadb() { "MariaDB" } else { "MySQL" }; + drop(guard); - let version_string = rows - .first() - .and_then(|row| row.get("version").and_then(|version| version.typed.to_string())); - - Ok(version_string) + Ok(Some(format!("{major}.{minor}.{patch}-{flavour}"))) } fn is_healthy(&self) -> bool { diff --git a/quaint/src/connector/mysql/url.rs b/quaint/src/connector/mysql/url.rs index 512f2ba50662..9bbb11c0cb6a 100644 --- a/quaint/src/connector/mysql/url.rs +++ b/quaint/src/connector/mysql/url.rs @@ -13,6 +13,7 @@ use url::{Host, Url}; #[derive(Debug, Clone)] pub struct MysqlUrl { url: Url, + version: Option, pub(crate) query_params: MysqlUrlQueryParams, } @@ -22,7 +23,15 @@ impl MysqlUrl { pub fn new(url: Url) -> Result { let query_params = Self::parse_query_params(&url)?; - Ok(Self { url, query_params }) + Ok(Self { + url, + query_params, + version: None, + }) + } + + pub fn set_version(&mut self, version: Option) { + self.version = version; } /// The bare `Url` to the database. @@ -298,6 +307,10 @@ impl MysqlUrl { pub(crate) fn connection_limit(&self) -> Option { self.query_params.connection_limit } + + pub fn version(&self) -> Option<&str> { + self.version.as_deref() + } } #[derive(Debug, Clone)] diff --git a/quaint/src/connector/native.rs b/quaint/src/connector/native.rs index b9cf4b9858e6..4a1a12a2733b 100644 --- a/quaint/src/connector/native.rs +++ b/quaint/src/connector/native.rs @@ -29,3 +29,12 @@ pub enum NativeConnectionInfo { #[cfg(feature = "sqlite")] InMemorySqlite { db_name: String }, } + +impl NativeConnectionInfo { + pub fn set_version(&mut self, version: Option) { + #[cfg(feature = "mysql")] + if let NativeConnectionInfo::Mysql(c) = self { + c.set_version(version); + } + } +} diff --git a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/relation_load_strategy.rs b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/relation_load_strategy.rs index a44a2c2c9308..55acc7b30521 100644 --- a/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/relation_load_strategy.rs +++ b/query-engine/connector-test-kit-rs/query-engine-tests/tests/new/relation_load_strategy.rs @@ -32,7 +32,7 @@ mod relation_load_strategy { .to_owned() } - async fn seed(runner: &mut Runner) -> TestResult<()> { + async fn seed(runner: &Runner) -> TestResult<()> { run_query!( runner, r#" @@ -123,27 +123,43 @@ mod relation_load_strategy { }; } - macro_rules! relation_load_strategy_tests_pair { + macro_rules! relation_load_strategy_tests { ($name:ident, $query:expr, $result:literal) => { - relation_load_strategy_test!( - $name, - join, - $query, - $result, - only(Postgres, CockroachDb, Mysql(8)) - ); - // TODO: Remove Mysql & Vitess exclusions once we are able to have version speficic preview features. - relation_load_strategy_test!( - $name, - query, - $query, - $result, - exclude(Mysql("5.6", "5.7", "mariadb")) - ); + paste::paste! { + relation_load_strategy_test!( + [<$name _lateral>], + join, + $query, + $result, + capabilities(LateralJoin) + ); + relation_load_strategy_test!( + [<$name _subquery>], + join, + $query, + $result, + capabilities(CorrelatedSubqueries), + exclude(Mysql("5.6", "5.7", "mariadb")) + ); + relation_load_strategy_test!( + [<$name _lateral>], + query, + $query, + $result, + capabilities(LateralJoin) + ); + relation_load_strategy_test!( + [<$name _subquery>], + query, + $query, + $result, + capabilities(CorrelatedSubqueries) + ); + } }; } - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( find_many, r#" query { @@ -162,7 +178,7 @@ mod relation_load_strategy { r#"{"data":{"findManyUser":[{"login":"author","posts":[{"title":"first post","comments":[{"author":{"login":"commenter"},"body":"a comment"}]}]},{"login":"commenter","posts":[]}]}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( find_first, r#" query { @@ -186,7 +202,7 @@ mod relation_load_strategy { r#"{"data":{"findFirstUser":{"login":"author","posts":[{"title":"first post","comments":[{"author":{"login":"commenter"},"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( find_first_or_throw, r#" query { @@ -210,7 +226,7 @@ mod relation_load_strategy { r#"{"data":{"findFirstUserOrThrow":{"login":"author","posts":[{"title":"first post","comments":[{"author":{"login":"commenter"},"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( find_unique, r#" query { @@ -234,7 +250,7 @@ mod relation_load_strategy { r#"{"data":{"findUniqueUser":{"login":"author","posts":[{"title":"first post","comments":[{"author":{"login":"commenter"},"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( find_unique_or_throw, r#" query { @@ -258,7 +274,7 @@ mod relation_load_strategy { r#"{"data":{"findUniqueUserOrThrow":{"login":"author","posts":[{"title":"first post","comments":[{"author":{"login":"commenter"},"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( create, r#" mutation { @@ -289,7 +305,7 @@ mod relation_load_strategy { r#"{"data":{"createOneUser":{"login":"reader","comments":[{"post":{"title":"first post"},"body":"most insightful indeed!"}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( update, r#" mutation { @@ -313,7 +329,7 @@ mod relation_load_strategy { r#"{"data":{"updateOneUser":{"login":"distinguished author","posts":[{"title":"first post","comments":[{"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( delete, r#" mutation { @@ -334,7 +350,7 @@ mod relation_load_strategy { r#"{"data":{"deleteOneUser":{"login":"author","posts":[{"title":"first post","comments":[{"body":"a comment"}]}]}}}"# ); - relation_load_strategy_tests_pair!( + relation_load_strategy_tests!( upsert, r#" mutation { @@ -450,4 +466,25 @@ mod relation_load_strategy { } "# ); + + #[connector_test(schema(schema), only(Mysql(5.6, 5.7, "mariadb")))] + async fn unsupported_join_strategy(runner: Runner) -> TestResult<()> { + seed(&runner).await?; + + assert_error!( + &runner, + r#"{ findManyUser(relationLoadStrategy: join) { id } }"#, + 2019, + "`relationLoadStrategy: join` is not available for MySQL < 8.0.14 and MariaDB." + ); + + assert_error!( + &runner, + r#"{ findFirstUser(relationLoadStrategy: join) { id } }"#, + 2019, + "`relationLoadStrategy: join` is not available for MySQL < 8.0.14 and MariaDB." + ); + + Ok(()) + } } diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs index ac07d9b71546..5a2c49eb21c4 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mod.rs @@ -326,11 +326,6 @@ impl ConnectorVersion { | Self::Sqlite(Some(SqliteVersion::LibsqlJsWasm)) ) } - - /// Returns `true` if the connector version is [`MySql`]. - pub(crate) fn is_mysql(&self) -> bool { - matches!(self, Self::MySql(..)) - } } impl fmt::Display for ConnectorVersion { diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/datamodel_rendering/mod.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/datamodel_rendering/mod.rs index 5390ee975d89..7295972f9812 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/datamodel_rendering/mod.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/datamodel_rendering/mod.rs @@ -4,13 +4,11 @@ mod sql_renderer; pub use mongodb_renderer::*; pub use sql_renderer::*; -use crate::{ - connection_string, templating, ConnectorVersion, DatamodelFragment, IdFragment, M2mFragment, MySqlVersion, CONFIG, -}; +use crate::{connection_string, templating, DatamodelFragment, IdFragment, M2mFragment, CONFIG}; use indoc::indoc; use itertools::Itertools; use once_cell::sync::Lazy; -use psl::{PreviewFeature, ALL_PREVIEW_FEATURES}; +use psl::ALL_PREVIEW_FEATURES; use regex::Regex; /// Test configuration, loaded once at runtime. @@ -39,7 +37,7 @@ pub fn render_test_datamodel( isolation_level: Option<&'static str>, ) -> String { let (tag, version) = CONFIG.test_connector().unwrap(); - let preview_features = render_preview_features(excluded_features, &version); + let preview_features = render_preview_features(excluded_features); let is_multi_schema = !db_schemas.is_empty(); @@ -91,13 +89,8 @@ fn process_template(template: String, renderer: Box) -> S }) } -fn render_preview_features(excluded_features: &[&str], version: &ConnectorVersion) -> String { - let mut excluded_features: Vec<_> = excluded_features.iter().map(|f| format!(r#""{f}""#)).collect(); - - // TODO: Remove this once we are able to have version speficic preview features. - if version.is_mysql() && !matches!(version, ConnectorVersion::MySql(Some(MySqlVersion::V8))) { - excluded_features.push(format!(r#""{}""#, PreviewFeature::RelationJoins)); - } +fn render_preview_features(excluded_features: &[&str]) -> String { + let excluded_features: Vec<_> = excluded_features.iter().map(|f| format!(r#""{f}""#)).collect(); ALL_PREVIEW_FEATURES .active_features() diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/mod.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/mod.rs index 1dc4eb1a0c44..6fa095a1a15b 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/mod.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/runner/mod.rs @@ -10,6 +10,7 @@ use crate::{ use colored::Colorize; use query_core::{ protocol::EngineProtocol, + relation_load_strategy, schema::{self, QuerySchemaRef}, QueryExecutor, TransactionOptions, TxId, }; @@ -126,25 +127,34 @@ impl Runner { let datasource = schema.configuration.datasources.first().unwrap(); let url = datasource.load_url(|key| env::var(key).ok()).unwrap(); - let executor = match crate::CONFIG.external_test_executor() { - Some(_) => RunnerExecutor::new_external(&url, &datamodel).await?, - None => RunnerExecutor::Builtin( - request_handlers::load_executor( + let (executor, db_version) = match crate::CONFIG.external_test_executor() { + Some(_) => (RunnerExecutor::new_external(&url, &datamodel).await?, None), + None => { + let executor = request_handlers::load_executor( ConnectorKind::Rust { url: url.to_owned(), datasource, }, schema.configuration.preview_features(), ) - .await?, - ), + .await?; + + let connector = executor.primary_connector(); + let conn = connector.get_connection().await.unwrap(); + let database_version = conn.version().await; + + (RunnerExecutor::Builtin(executor), database_version) + } }; - let query_schema: QuerySchemaRef = Arc::new(schema::build(Arc::new(schema), true)); + + let query_schema = schema::build(Arc::new(schema), true).with_db_version_supports_join_strategy( + relation_load_strategy::db_version_supports_joins_strategy(db_version)?, + ); Ok(Self { version: connector_version, executor, - query_schema, + query_schema: Arc::new(query_schema), connector_tag, connection_url: url, current_tx_id: None, diff --git a/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs b/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs index 09cb46eae6fa..ef3d580b9ac8 100644 --- a/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs +++ b/query-engine/connectors/mongodb-query-connector/src/interface/connection.rs @@ -40,6 +40,10 @@ impl Connection for MongoDbConnection { Ok(tx as Box) } + async fn version(&self) -> Option { + None + } + fn as_connection_like(&mut self) -> &mut dyn ConnectionLike { self } diff --git a/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs b/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs index 4c3b1dfec68f..0f882ee3d6be 100644 --- a/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs +++ b/query-engine/connectors/mongodb-query-connector/src/interface/transaction.rs @@ -60,6 +60,10 @@ impl<'conn> Transaction for MongoDbTransaction<'conn> { Ok(()) } + async fn version(&self) -> Option { + None + } + fn as_connection_like(&mut self) -> &mut dyn ConnectionLike { self } diff --git a/query-engine/connectors/query-connector/src/error.rs b/query-engine/connectors/query-connector/src/error.rs index 1d9937ee55aa..d9f7e99688ac 100644 --- a/query-engine/connectors/query-connector/src/error.rs +++ b/query-engine/connectors/query-connector/src/error.rs @@ -287,6 +287,9 @@ pub enum ErrorKind { #[error("Too many DB connections opened: {}", _0)] TooManyConnections(Box), + + #[error("Failed to parse database version: {}. Reason: {}", version, reason)] + UnexpectedDatabaseVersion { version: String, reason: String }, } impl From for ConnectorError { diff --git a/query-engine/connectors/query-connector/src/interface.rs b/query-engine/connectors/query-connector/src/interface.rs index d42d6f0524b7..1368b8a7c247 100644 --- a/query-engine/connectors/query-connector/src/interface.rs +++ b/query-engine/connectors/query-connector/src/interface.rs @@ -24,6 +24,8 @@ pub trait Connection: ConnectionLike { isolation_level: Option, ) -> crate::Result>; + async fn version(&self) -> Option; + /// Explicit upcast. fn as_connection_like(&mut self) -> &mut dyn ConnectionLike; } @@ -33,6 +35,8 @@ pub trait Transaction: ConnectionLike { async fn commit(&mut self) -> crate::Result<()>; async fn rollback(&mut self) -> crate::Result<()>; + async fn version(&self) -> Option; + /// Explicit upcast of self reference. Rusts current vtable layout doesn't allow for an upcast if /// `trait A`, `trait B: A`, so that `Box as Box` works. This is a simple, explicit workaround. fn as_connection_like(&mut self) -> &mut dyn ConnectionLike; diff --git a/query-engine/connectors/sql-query-connector/src/database/connection.rs b/query-engine/connectors/sql-query-connector/src/database/connection.rs index 457fb6136b52..ae4ceb5933de 100644 --- a/query-engine/connectors/sql-query-connector/src/database/connection.rs +++ b/query-engine/connectors/sql-query-connector/src/database/connection.rs @@ -26,9 +26,7 @@ impl SqlConnection where C: TransactionCapable + Send + Sync + 'static, { - pub fn new(inner: C, connection_info: &ConnectionInfo, features: psl::PreviewFeatures) -> Self { - let connection_info = connection_info.clone(); - + pub fn new(inner: C, connection_info: ConnectionInfo, features: psl::PreviewFeatures) -> Self { Self { inner, connection_info, @@ -71,6 +69,10 @@ where .await } + async fn version(&self) -> Option { + self.connection_info.version().map(|v| v.to_string()) + } + fn as_connection_like(&mut self) -> &mut dyn ConnectionLike { self } 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 a40af53613b1..9badc8659738 100644 --- a/query-engine/connectors/sql-query-connector/src/database/js.rs +++ b/query-engine/connectors/sql-query-connector/src/database/js.rs @@ -41,7 +41,7 @@ impl Js { impl Connector for Js { async fn get_connection<'a>(&'a self) -> connector::Result> { super::catch(&self.connection_info, async move { - let sql_conn = SqlConnection::new(self.connector.clone(), &self.connection_info, self.features); + let sql_conn = SqlConnection::new(self.connector.clone(), self.connection_info.clone(), self.features); Ok(Box::new(sql_conn) as Box) }) .await diff --git a/query-engine/connectors/sql-query-connector/src/database/native/mssql.rs b/query-engine/connectors/sql-query-connector/src/database/native/mssql.rs index daf7cac4baed..9cceeaf32942 100644 --- a/query-engine/connectors/sql-query-connector/src/database/native/mssql.rs +++ b/query-engine/connectors/sql-query-connector/src/database/native/mssql.rs @@ -62,7 +62,7 @@ impl Connector for Mssql { async fn get_connection<'a>(&'a self) -> connector::Result> { catch(&self.connection_info, async move { let conn = self.pool.check_out().await.map_err(SqlError::from)?; - let conn = SqlConnection::new(conn, &self.connection_info, self.features); + let conn = SqlConnection::new(conn, self.connection_info.clone(), self.features); Ok(Box::new(conn) as Box) }) diff --git a/query-engine/connectors/sql-query-connector/src/database/native/mysql.rs b/query-engine/connectors/sql-query-connector/src/database/native/mysql.rs index 023c68dc5943..cb290ee0a261 100644 --- a/query-engine/connectors/sql-query-connector/src/database/native/mysql.rs +++ b/query-engine/connectors/sql-query-connector/src/database/native/mysql.rs @@ -6,6 +6,7 @@ use connector_interface::{ error::{ConnectorError, ErrorKind}, Connection, Connector, }; +use quaint::connector::Queryable; use quaint::{pooled::Quaint, prelude::ConnectionInfo}; use std::time::Duration; @@ -69,7 +70,12 @@ impl Connector for Mysql { let runtime_conn = self.pool.check_out().await?; // Note: `runtime_conn` must be `Sized`, as that's required by `TransactionCapable` - let sql_conn = SqlConnection::new(runtime_conn, &self.connection_info, self.features); + let mut conn_info = self.connection_info.clone(); + let db_version = runtime_conn.version().await.unwrap(); + // MySQL has its version grabbed at connection time. We know it's infaillible. + conn_info.set_version(db_version); + + let sql_conn = SqlConnection::new(runtime_conn, conn_info, self.features); Ok(Box::new(sql_conn) as Box) }) diff --git a/query-engine/connectors/sql-query-connector/src/database/native/postgresql.rs b/query-engine/connectors/sql-query-connector/src/database/native/postgresql.rs index 7323f4027759..701fa9a1a0c5 100644 --- a/query-engine/connectors/sql-query-connector/src/database/native/postgresql.rs +++ b/query-engine/connectors/sql-query-connector/src/database/native/postgresql.rs @@ -69,7 +69,7 @@ impl Connector for PostgreSql { async fn get_connection<'a>(&'a self) -> connector_interface::Result> { catch(&self.connection_info, async move { let conn = self.pool.check_out().await.map_err(SqlError::from)?; - let conn = SqlConnection::new(conn, &self.connection_info, self.features); + let conn = SqlConnection::new(conn, self.connection_info.clone(), self.features); Ok(Box::new(conn) as Box) }) .await diff --git a/query-engine/connectors/sql-query-connector/src/database/native/sqlite.rs b/query-engine/connectors/sql-query-connector/src/database/native/sqlite.rs index c711ce891736..79b547a82ac8 100644 --- a/query-engine/connectors/sql-query-connector/src/database/native/sqlite.rs +++ b/query-engine/connectors/sql-query-connector/src/database/native/sqlite.rs @@ -82,7 +82,7 @@ impl Connector for Sqlite { async fn get_connection<'a>(&'a self) -> connector::Result> { catch(self.connection_info(), async move { let conn = self.pool.check_out().await.map_err(SqlError::from)?; - let conn = SqlConnection::new(conn, self.connection_info(), self.features); + let conn = SqlConnection::new(conn, self.connection_info().clone(), self.features); Ok(Box::new(conn) as Box) }) diff --git a/query-engine/connectors/sql-query-connector/src/database/transaction.rs b/query-engine/connectors/sql-query-connector/src/database/transaction.rs index c85185c16466..4273e48e745b 100644 --- a/query-engine/connectors/sql-query-connector/src/database/transaction.rs +++ b/query-engine/connectors/sql-query-connector/src/database/transaction.rs @@ -56,6 +56,10 @@ impl<'tx> Transaction for SqlConnectorTransaction<'tx> { .await } + async fn version(&self) -> Option { + self.connection_info.version().map(|v| v.to_string()) + } + fn as_connection_like(&mut self) -> &mut dyn ConnectionLike { self } diff --git a/query-engine/core/src/lib.rs b/query-engine/core/src/lib.rs index 219b78753277..bf993d6bce18 100644 --- a/query-engine/core/src/lib.rs +++ b/query-engine/core/src/lib.rs @@ -8,6 +8,7 @@ pub mod executor; pub mod protocol; pub mod query_document; pub mod query_graph_builder; +pub mod relation_load_strategy; pub mod response_ir; pub mod telemetry; diff --git a/query-engine/core/src/query_graph_builder/read/many.rs b/query-engine/core/src/query_graph_builder/read/many.rs index edadeb8814df..75ff04276a79 100644 --- a/query-engine/core/src/query_graph_builder/read/many.rs +++ b/query-engine/core/src/query_graph_builder/read/many.rs @@ -43,7 +43,7 @@ fn find_many_with_options( args.distinct.as_ref(), &nested, query_schema, - ); + )?; Ok(ReadQuery::ManyRecordsQuery(ManyRecordsQuery { name, diff --git a/query-engine/core/src/query_graph_builder/read/one.rs b/query-engine/core/src/query_graph_builder/read/one.rs index a2dd291f6760..a091b6154ec1 100644 --- a/query-engine/core/src/query_graph_builder/read/one.rs +++ b/query-engine/core/src/query_graph_builder/read/one.rs @@ -51,7 +51,7 @@ fn find_unique_with_options( let selected_fields = utils::merge_relation_selections(selected_fields, None, &nested); let relation_load_strategy = - get_relation_load_strategy(requested_rel_load_strategy, None, None, &nested, query_schema); + get_relation_load_strategy(requested_rel_load_strategy, None, None, &nested, query_schema)?; Ok(ReadQuery::RecordQuery(RecordQuery { name, diff --git a/query-engine/core/src/query_graph_builder/read/utils.rs b/query-engine/core/src/query_graph_builder/read/utils.rs index 19222baebf7d..42d06b38b0c4 100644 --- a/query-engine/core/src/query_graph_builder/read/utils.rs +++ b/query-engine/core/src/query_graph_builder/read/utils.rs @@ -1,5 +1,6 @@ use super::*; use crate::{ArgumentListLookup, FieldPair, ParsedField, ReadQuery}; +use psl::datamodel_connector::JoinStrategySupport; use query_structure::{prelude::*, RelationLoadStrategy}; use schema::{ constants::{aggregations::*, args}, @@ -256,18 +257,46 @@ pub(crate) fn get_relation_load_strategy( distinct: Option<&FieldSelection>, nested_queries: &[ReadQuery], query_schema: &QuerySchema, -) -> RelationLoadStrategy { - if query_schema.can_resolve_relation_with_joins() - && cursor.is_none() +) -> QueryGraphBuilderResult { + match query_schema.join_strategy_support() { + // Connector and database version supports the `Join` strategy... + JoinStrategySupport::Yes => match requested_strategy { + // But incoming query cannot be resolved with joins. + _ if !query_can_be_resolved_with_joins(cursor, distinct, nested_queries) => { + // So we fallback to the `Query` one. + Ok(RelationLoadStrategy::Query) + } + // But requested strategy is `Query`. + Some(RelationLoadStrategy::Query) => Ok(RelationLoadStrategy::Query), + // And requested strategy is `Join` or there's none selected, in which case the default is still `Join`. + Some(RelationLoadStrategy::Join) | None => Ok(RelationLoadStrategy::Join), + }, + // Connector supports `Join` strategy but database version does not... + JoinStrategySupport::UnsupportedDbVersion => match requested_strategy { + // So we error out if the requested strategy is `Join`. + Some(RelationLoadStrategy::Join) => Err(QueryGraphBuilderError::InputError( + "`relationLoadStrategy: join` is not available for MySQL < 8.0.14 and MariaDB.".into(), + )), + // Otherwise we fallback to the `Query` one. (This makes the default relation load strategy `Query` for database versions that do not support joins.) + Some(RelationLoadStrategy::Query) | None => Ok(RelationLoadStrategy::Query), + }, + // Connectors does not support the join strategy so we always fallback to the `Query` one. + JoinStrategySupport::No => Ok(RelationLoadStrategy::Query), + JoinStrategySupport::UnknownYet => { + unreachable!("Connector should have resolved the join strategy support by now.") + } + } +} + +fn query_can_be_resolved_with_joins( + cursor: Option<&SelectionResult>, + distinct: Option<&FieldSelection>, + nested_queries: &[ReadQuery], +) -> bool { + cursor.is_none() && distinct.is_none() && !nested_queries.iter().any(|q| match q { ReadQuery::RelatedRecordsQuery(q) => q.has_cursor() || q.has_distinct(), _ => false, }) - && requested_strategy != Some(RelationLoadStrategy::Query) - { - RelationLoadStrategy::Join - } else { - RelationLoadStrategy::Query - } } diff --git a/query-engine/core/src/relation_load_strategy.rs b/query-engine/core/src/relation_load_strategy.rs new file mode 100644 index 000000000000..b515126f504f --- /dev/null +++ b/query-engine/core/src/relation_load_strategy.rs @@ -0,0 +1,70 @@ +use connector::error::{ConnectorError, ErrorKind}; + +use crate::CoreError; + +/// Returns whether the database supports joins given its version. +/// Only versions of the MySQL connector are currently parsed at runtime. +pub fn db_version_supports_joins_strategy(db_version: Option) -> crate::Result { + DatabaseVersion::try_from(db_version.as_deref()).map(|version| version.supports_join_relation_load_strategy()) +} + +/// Parsed database version. +#[derive(Debug)] +enum DatabaseVersion { + Mysql(u16, u16, u16), + Mariadb, + Unknown, +} + +impl DatabaseVersion { + /// Returns whether the database supports joins given its version. + /// Only versions of the MySQL connector are currently parsed at runtime. + pub(crate) fn supports_join_relation_load_strategy(&self) -> bool { + match self { + // Prior to MySQL 8.0.14, a derived table cannot contain outer references. + // Source: https://dev.mysql.com/doc/refman/8.0/en/derived-tables.html + DatabaseVersion::Mysql(major, minor, patch) => (*major, *minor, *patch) >= (8, 0, 14), + DatabaseVersion::Mariadb => false, + DatabaseVersion::Unknown => true, + } + } +} + +impl TryFrom> for DatabaseVersion { + type Error = crate::CoreError; + + fn try_from(version: Option<&str>) -> crate::Result { + match version { + Some(version) => { + let build_err = |reason: &str| { + CoreError::ConnectorError(ConnectorError::from_kind(ErrorKind::UnexpectedDatabaseVersion { + version: version.into(), + reason: reason.into(), + })) + }; + + let mut iter = version.split('-'); + + let version = iter.next().ok_or_else(|| build_err("Missing version"))?; + let is_mariadb = iter.next().map(|s| s.contains("MariaDB")).unwrap_or(false); + + if is_mariadb { + return Ok(DatabaseVersion::Mariadb); + } + + let mut version_iter = version.split('.'); + + let major = version_iter.next().ok_or_else(|| build_err("Missing major version"))?; + let minor = version_iter.next().ok_or_else(|| build_err("Missing minor version"))?; + let patch = version_iter.next().ok_or_else(|| build_err("Missing patch version"))?; + + let parsed_major = major.parse().map_err(|_| build_err("Major version is not a number"))?; + let parsed_minor = minor.parse().map_err(|_| build_err("Minor version is not a number"))?; + let parsed_patch = patch.parse().map_err(|_| build_err("Patch version is not a number"))?; + + Ok(DatabaseVersion::Mysql(parsed_major, parsed_minor, parsed_patch)) + } + None => Ok(DatabaseVersion::Unknown), + } + } +} diff --git a/query-engine/query-engine-node-api/src/engine.rs b/query-engine/query-engine-node-api/src/engine.rs index 31df595785a3..4ca524af699c 100644 --- a/query-engine/query-engine-node-api/src/engine.rs +++ b/query-engine/query-engine-node-api/src/engine.rs @@ -4,11 +4,7 @@ use napi::{threadsafe_function::ThreadSafeCallContext, Env, JsFunction, JsObject use napi_derive::napi; use psl::PreviewFeature; use quaint::connector::ExternalConnector; -use query_core::{ - protocol::EngineProtocol, - schema::{self}, - telemetry, TransactionOptions, TxId, -}; +use query_core::{protocol::EngineProtocol, relation_load_strategy, schema, telemetry, TransactionOptions, TxId}; use query_engine_common::engine::{ map_known_error, stringify_env_values, ConnectedEngine, ConnectedEngineNative, ConstructorOptions, ConstructorOptionsNative, EngineBuilder, EngineBuilderNative, Inner, @@ -228,9 +224,10 @@ impl QueryEngine { "db.type" = connector.name(), ); - connector.get_connection().instrument(conn_span).await?; + let conn = connector.get_connection().instrument(conn_span).await?; + let database_version = conn.version().await; - crate::Result::<_>::Ok(executor) + crate::Result::<_>::Ok((executor, database_version)) }; let query_schema_span = tracing::info_span!("prisma:engine:schema"); @@ -241,12 +238,17 @@ impl QueryEngine { }) .instrument(query_schema_span); - let (query_schema, executor) = tokio::join!(query_schema_fut, executor_fut); + let (query_schema, executor_with_db_version) = tokio::join!(query_schema_fut, executor_fut); + let (executor, db_version) = executor_with_db_version?; + + let query_schema = query_schema.unwrap().with_db_version_supports_join_strategy( + relation_load_strategy::db_version_supports_joins_strategy(db_version)?, + ); Ok(ConnectedEngine { schema: builder.schema.clone(), - query_schema: Arc::new(query_schema.unwrap()), - executor: executor?, + query_schema: Arc::new(query_schema), + executor, engine_protocol: builder.engine_protocol, native: ConnectedEngineNative { config_dir: builder.native.config_dir.clone(), diff --git a/query-engine/query-engine-wasm/src/wasm/engine.rs b/query-engine/query-engine-wasm/src/wasm/engine.rs index 57a1d469d5a6..ae6fe40f8728 100644 --- a/query-engine/query-engine-wasm/src/wasm/engine.rs +++ b/query-engine/query-engine-wasm/src/wasm/engine.rs @@ -11,6 +11,7 @@ use psl::ConnectorRegistry; use quaint::connector::ExternalConnector; use query_core::{ protocol::EngineProtocol, + relation_load_strategy, schema::{self}, telemetry, TransactionOptions, TxId, }; @@ -113,10 +114,16 @@ impl QueryEngine { "db.type" = connector.name(), ); - connector.get_connection().instrument(conn_span).await?; + let conn = connector.get_connection().instrument(conn_span).await?; + let db_version = conn.version().await; let query_schema_span = tracing::info_span!("prisma:engine:schema"); - let query_schema = query_schema_span.in_scope(|| schema::build(arced_schema, true)); + + let query_schema = query_schema_span + .in_scope(|| schema::build(arced_schema, true)) + .with_db_version_supports_join_strategy( + relation_load_strategy::db_version_supports_joins_strategy(db_version)?, + ); Ok(ConnectedEngine { schema: builder.schema.clone(), diff --git a/query-engine/query-engine/src/context.rs b/query-engine/query-engine/src/context.rs index 4fd20bc61f99..7a1138c411e5 100644 --- a/query-engine/query-engine/src/context.rs +++ b/query-engine/query-engine/src/context.rs @@ -4,6 +4,7 @@ use crate::{PrismaError, PrismaResult}; use psl::PreviewFeature; use query_core::{ protocol::EngineProtocol, + relation_load_strategy, schema::{self, QuerySchemaRef}, QueryExecutor, }; @@ -46,10 +47,7 @@ impl PrismaContext { let query_schema_fut = tokio::runtime::Handle::current().spawn_blocking(move || { // Construct query schema - Arc::new(schema::build( - arced_schema, - enabled_features.contains(Feature::RawQueries), - )) + schema::build(arced_schema, enabled_features.contains(Feature::RawQueries)) }); let executor_fut = tokio::spawn(async move { let config = &arced_schema_2.configuration; @@ -64,15 +62,22 @@ impl PrismaContext { let url = datasource.load_url(|key| env::var(key).ok())?; // Load executor let executor = load_executor(ConnectorKind::Rust { url, datasource }, preview_features).await?; - executor.primary_connector().get_connection().await?; - PrismaResult::<_>::Ok(executor) + let conn = executor.primary_connector().get_connection().await?; + let db_version = conn.version().await; + + PrismaResult::<_>::Ok((executor, db_version)) }); - let (query_schema, executor) = tokio::join!(query_schema_fut, executor_fut); + let (query_schema, executor_with_db_version) = tokio::join!(query_schema_fut, executor_fut); + let (executor, db_version) = executor_with_db_version.unwrap()?; + + let query_schema = query_schema.unwrap().with_db_version_supports_join_strategy( + relation_load_strategy::db_version_supports_joins_strategy(db_version)?, + ); let context = Self { - query_schema: query_schema.unwrap(), - executor: executor.unwrap()?, + query_schema: Arc::new(query_schema), + executor, metrics: metrics.unwrap_or_default(), engine_protocol: protocol, enabled_features, diff --git a/query-engine/query-structure/src/query_arguments.rs b/query-engine/query-structure/src/query_arguments.rs index 3cbd3c0164e5..abe23ab4de4e 100644 --- a/query-engine/query-structure/src/query_arguments.rs +++ b/query-engine/query-structure/src/query_arguments.rs @@ -33,6 +33,7 @@ pub enum RelationLoadStrategy { Join, Query, } + impl RelationLoadStrategy { pub fn is_query(&self) -> bool { matches!(self, RelationLoadStrategy::Query) diff --git a/query-engine/schema/src/build.rs b/query-engine/schema/src/build.rs index 3c589989f21e..2970be408b59 100644 --- a/query-engine/schema/src/build.rs +++ b/query-engine/schema/src/build.rs @@ -20,6 +20,7 @@ use query_structure::{ast, Field as ModelField, Model, RelationFieldRef, TypeIde pub fn build(schema: Arc, enable_raw_queries: bool) -> QuerySchema { let preview_features = schema.configuration.preview_features(); + build_with_features(schema, preview_features, enable_raw_queries) } @@ -30,5 +31,6 @@ pub fn build_with_features( ) -> QuerySchema { let connector = schema.connector; let internal_data_model = query_structure::convert(schema); + QuerySchema::new(enable_raw_queries, connector, preview_features, internal_data_model) } diff --git a/query-engine/schema/src/build/enum_types.rs b/query-engine/schema/src/build/enum_types.rs index c878226e76bf..b0ddc66a638d 100644 --- a/query-engine/schema/src/build/enum_types.rs +++ b/query-engine/schema/src/build/enum_types.rs @@ -111,7 +111,7 @@ pub fn itx_isolation_levels(ctx: &'_ QuerySchema) -> Option { } pub(crate) fn relation_load_strategy(ctx: &QuerySchema) -> Option { - if !ctx.has_feature(psl::PreviewFeature::RelationJoins) { + if !ctx.can_resolve_relation_with_joins() { return None; } diff --git a/query-engine/schema/src/query_schema.rs b/query-engine/schema/src/query_schema.rs index 4859984d11a6..dbd96dd4ab77 100644 --- a/query-engine/schema/src/query_schema.rs +++ b/query-engine/schema/src/query_schema.rs @@ -1,6 +1,6 @@ use crate::{IdentifierType, ObjectType, OutputField}; use psl::{ - datamodel_connector::{Connector, ConnectorCapabilities, ConnectorCapability, RelationMode}, + datamodel_connector::{Connector, ConnectorCapabilities, ConnectorCapability, JoinStrategySupport, RelationMode}, PreviewFeature, PreviewFeatures, }; use query_structure::{ast, InternalDataModel}; @@ -36,6 +36,12 @@ pub struct QuerySchema { /// Relation mode in the datasource. relation_mode: RelationMode, + + /// Whether the database supports `RelationLoadStrategy::Join`. + /// By the time the `QuerySchema`` is created, we don't have all the evidence yet to determine + /// whether the database supports the join strategy (eg: database version). + // Hack: Ideally, this shoud be known statically and live in the PSL connector entirely. + join_strategy_support: JoinStrategySupport, } impl QuerySchema { @@ -57,6 +63,11 @@ impl QuerySchema { relation_mode, mutation_fields: Default::default(), query_fields: Default::default(), + join_strategy_support: if preview_features.contains(PreviewFeature::RelationJoins) { + connector.runtime_join_strategy_support() + } else { + JoinStrategySupport::No + }, }; query_schema.query_fields = crate::build::query_type::query_fields(&query_schema); @@ -96,10 +107,31 @@ impl QuerySchema { || self.has_capability(ConnectorCapability::FullTextSearchWithIndex)) } + /// Returns whether the loaded connector supports the join strategy. pub fn can_resolve_relation_with_joins(&self) -> bool { - self.has_feature(PreviewFeature::RelationJoins) - && (self.has_capability(ConnectorCapability::LateralJoin) - || self.has_capability(ConnectorCapability::CorrelatedSubqueries)) + !matches!(self.join_strategy_support, JoinStrategySupport::No) + } + + /// Returns whether the database version of the loaded connector supports the join strategy. + pub fn join_strategy_support(&self) -> JoinStrategySupport { + self.join_strategy_support + } + + /// Augments the join strategy support with the runtime database version knowledge. + /// This is specifically designed for the MySQL connector, which does not support the join strategy for versions < 8.0.14 and MariaDB. + pub fn with_db_version_supports_join_strategy(self, db_version_supports_joins_strategy: bool) -> Self { + let augmented_support = match self.join_strategy_support { + JoinStrategySupport::UnknownYet => match db_version_supports_joins_strategy { + true => JoinStrategySupport::Yes, + false => JoinStrategySupport::UnsupportedDbVersion, + }, + x => x, + }; + + Self { + join_strategy_support: augmented_support, + ..self + } } pub fn has_feature(&self, feature: PreviewFeature) -> bool {