diff --git a/.github/workflows/test-query-compiler.yml b/.github/workflows/test-query-compiler.yml index 57aa800aebd0..34498ae0f0ee 100644 --- a/.github/workflows/test-query-compiler.yml +++ b/.github/workflows/test-query-compiler.yml @@ -49,6 +49,12 @@ jobs: should_fail_tests_list: query-compiler/query-engine-tests-todo/planetscale/fail relation_load_strategy: '["query"]' + - name: mysql2 + setup_task: dev-mysql2-qc + ignored_tests_list: query-compiler/query-engine-tests-todo/mysql2/skip + should_fail_tests_list: query-compiler/query-engine-tests-todo/mysql2/fail + relation_load_strategy: '["query"]' + - name: d1 setup_task: dev-d1-qc ignored_tests_list: query-compiler/query-engine-tests-todo/d1/skip diff --git a/.github/workflows/test-query-engine.yml b/.github/workflows/test-query-engine.yml index 437d856ef679..bf9b12f652d5 100644 --- a/.github/workflows/test-query-engine.yml +++ b/.github/workflows/test-query-engine.yml @@ -219,6 +219,8 @@ jobs: adapter: - name: 'planetscale (wasm)' setup_task: 'dev-planetscale-wasm' + - name: 'mysql2 (wasm)' + setup_task: 'dev-mysql2-wasm' - name: 'pg (wasm)' setup_task: 'dev-pg-wasm' - name: 'neon (wasm)' diff --git a/Makefile b/Makefile index 7eed1e149105..df2713157db1 100644 --- a/Makefile +++ b/Makefile @@ -436,6 +436,17 @@ test-planetscale-qc: dev-planetscale-qc test-qe-st test-driver-adapter-planetscale: test-planetscale-js test-driver-adapter-planetscale-wasm: test-planetscale-wasm +start-mysql2: + docker compose -f docker-compose.yml up -d --remove-orphans mysql-8-0 + +dev-mysql2-wasm: start-mysql2 build-qe-wasm build-driver-adapters-kit-qe + cp $(CONFIG_PATH)/mysql2-wasm $(CONFIG_FILE) + +dev-mysql2-qc: start-mysql2 build-qc-wasm build-driver-adapters-kit-qc + cp $(CONFIG_PATH)/mysql2-qc $(CONFIG_FILE) + +test-mysql2-qc: dev-mysql2-qc test-qe-st + ###################### # Local dev commands # ###################### diff --git a/libs/driver-adapters/executor/package.json b/libs/driver-adapters/executor/package.json index 948822332517..f7fcb2d99001 100644 --- a/libs/driver-adapters/executor/package.json +++ b/libs/driver-adapters/executor/package.json @@ -25,6 +25,7 @@ "dependencies": { "@effect/schema": "0.64.20", "@prisma/adapter-better-sqlite3": "workspace:*", + "@prisma/adapter-mysql2": "workspace:*", "@prisma/adapter-d1": "workspace:*", "@prisma/adapter-libsql": "workspace:*", "@prisma/adapter-neon": "workspace:*", diff --git a/libs/driver-adapters/executor/src/driver-adapters-manager/mysql2.ts b/libs/driver-adapters/executor/src/driver-adapters-manager/mysql2.ts new file mode 100644 index 000000000000..7240865ece82 --- /dev/null +++ b/libs/driver-adapters/executor/src/driver-adapters-manager/mysql2.ts @@ -0,0 +1,43 @@ +import { PrismaMySQL2 } from '@prisma/adapter-mysql2' +import type { + SqlDriverAdapter, + SqlMigrationAwareDriverAdapterFactory, +} from '@prisma/driver-adapter-utils' +import type { DriverAdaptersManager, SetupDriverAdaptersInput } from './index' +import type { DriverAdapterTag, EnvForAdapter } from '../types' + +const TAG = 'mysql2' as const satisfies DriverAdapterTag +type TAG = typeof TAG + +export class MySQL2Manager implements DriverAdaptersManager { + #factory: SqlMigrationAwareDriverAdapterFactory + #adapter?: SqlDriverAdapter + + private constructor( + private env: EnvForAdapter, + { url }: SetupDriverAdaptersInput, + ) { + const database = new URL(url).pathname.split('/').pop() + this.#factory = new PrismaMySQL2({ + uri: url, + database, + }) + } + + static async setup(env: EnvForAdapter, input: SetupDriverAdaptersInput) { + return new MySQL2Manager(env, input) + } + + factory() { + return this.#factory + } + + async connect() { + this.#adapter = await this.#factory.connect() + return this.#adapter + } + + async teardown() { + await this.#adapter?.dispose() + } +} diff --git a/libs/driver-adapters/executor/src/setup.ts b/libs/driver-adapters/executor/src/setup.ts index 75ba284dc225..e884b1e2f1ec 100644 --- a/libs/driver-adapters/executor/src/setup.ts +++ b/libs/driver-adapters/executor/src/setup.ts @@ -10,6 +10,7 @@ import { LibSQLManager } from './driver-adapters-manager/libsql' import { PlanetScaleManager } from './driver-adapters-manager/planetscale' import { D1Manager } from './driver-adapters-manager/d1' import { BetterSQLite3Manager } from './driver-adapters-manager/better-sqlite3' +import { MySQL2Manager } from './driver-adapters-manager/mysql2' export async function setupDriverAdaptersManager( env: Env, @@ -40,5 +41,9 @@ export async function setupDriverAdaptersManager( { DRIVER_ADAPTER: 'better-sqlite3' }, async (env) => await BetterSQLite3Manager.setup(env, input), ) + .with( + { DRIVER_ADAPTER: 'mysql2' }, + async (env) => await MySQL2Manager.setup(env, input), + ) .exhaustive() } diff --git a/libs/driver-adapters/executor/src/types/env.ts b/libs/driver-adapters/executor/src/types/env.ts index b298791124f2..a793985073c7 100644 --- a/libs/driver-adapters/executor/src/types/env.ts +++ b/libs/driver-adapters/executor/src/types/env.ts @@ -47,7 +47,13 @@ export const Env = S.extend( EnvPlanetScale, EnvNeonWS, S.struct({ - DRIVER_ADAPTER: S.literal('pg', 'libsql', 'd1', 'better-sqlite3'), + DRIVER_ADAPTER: S.literal( + 'pg', + 'libsql', + 'd1', + 'better-sqlite3', + 'mysql2', + ), }), ), S.union( diff --git a/libs/driver-adapters/pnpm-workspace.yaml b/libs/driver-adapters/pnpm-workspace.yaml index 3626be09c869..70f00d85016b 100644 --- a/libs/driver-adapters/pnpm-workspace.yaml +++ b/libs/driver-adapters/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - '../../../prisma/packages/adapter-d1' - '../../../prisma/packages/adapter-better-sqlite3' + - '../../../prisma/packages/adapter-mysql2' - '../../../prisma/packages/adapter-libsql' - '../../../prisma/packages/adapter-neon' - '../../../prisma/packages/adapter-pg' diff --git a/quaint/src/connector/external.rs b/quaint/src/connector/external.rs index 7c103f2ff1b4..c70c20a77358 100644 --- a/quaint/src/connector/external.rs +++ b/quaint/src/connector/external.rs @@ -23,6 +23,7 @@ pub enum AdapterName { LibSQL, BetterSQLite3, Planetscale, + MySQL2, Mssql, Unknown, } @@ -38,6 +39,7 @@ impl FromStr for AdapterName { "d1-http" => Ok(Self::D1(AdapterD1::HTTP)), "libsql" => Ok(Self::LibSQL), "better-sqlite3" => Ok(Self::BetterSQLite3), + "mysql2" => Ok(Self::MySQL2), "planetscale" => Ok(Self::Planetscale), "mssql" => Ok(Self::Mssql), _ => Ok(Self::Unknown), diff --git a/query-compiler/query-engine-tests-todo/mysql2/fail/query b/query-compiler/query-engine-tests-todo/mysql2/fail/query new file mode 100644 index 000000000000..811899c6c7fe --- /dev/null +++ b/query-compiler/query-engine-tests-todo/mysql2/fail/query @@ -0,0 +1,6 @@ +new::interactive_tx::interactive_tx::batch_queries_failure +new::regressions::prisma_7434::not_in_chunking::not_in_batch_filter +queries::aggregation::group_by::aggregation_group_by::group_by_ordering_sum_aggregation +queries::chunking::chunking::order_by_aggregation_should_fail +queries::filters::self_relation_regression::sr_regression::all_categories +writes::nested_mutations::already_converted::nested_connect_inside_update::connect_inside_update::p1_c1req_rel_child_idempotent diff --git a/query-compiler/query-engine-tests-todo/mysql2/skip/query b/query-compiler/query-engine-tests-todo/mysql2/skip/query new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/query-compiler/query-engine-tests-todo/mysql2/skip/query @@ -0,0 +1 @@ + diff --git a/query-engine/connector-test-kit-rs/qe-setup/src/driver_adapters.rs b/query-engine/connector-test-kit-rs/qe-setup/src/driver_adapters.rs index 9a31d5a8d861..33e880c92334 100644 --- a/query-engine/connector-test-kit-rs/qe-setup/src/driver_adapters.rs +++ b/query-engine/connector-test-kit-rs/qe-setup/src/driver_adapters.rs @@ -21,6 +21,9 @@ pub enum DriverAdapter { #[serde(rename = "better-sqlite3")] BetterSQLite3, + #[serde(rename = "mysql2")] + MySQL2, + #[serde(rename = "mssql")] Mssql, } 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 c98837df2149..593a4321175f 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 @@ -135,13 +135,17 @@ pub(crate) fn connection_string( ConnectorVersion::MySql(v) => match v { Some(MySqlVersion::V5_6) if is_ci => format!("mysql://root:prisma@test-db-mysql-5-6:3306/{database}"), Some(MySqlVersion::V5_7) if is_ci => format!("mysql://root:prisma@test-db-mysql-5-7:3306/{database}"), - Some(MySqlVersion::V8) if is_ci => format!("mysql://root:prisma@test-db-mysql-8:3306/{database}"), + Some(MySqlVersion::V8 | MySqlVersion::Mysql2JsWasm) if is_ci => { + format!("mysql://root:prisma@test-db-mysql-8:3306/{database}") + } Some(MySqlVersion::MariaDb) if is_ci => { format!("mysql://root:prisma@test-db-mysql-mariadb:3306/{database}") } Some(MySqlVersion::V5_6) => format!("mysql://root:prisma@127.0.0.1:3309/{database}"), Some(MySqlVersion::V5_7) => format!("mysql://root:prisma@127.0.0.1:3306/{database}"), - Some(MySqlVersion::V8) => format!("mysql://root:prisma@127.0.0.1:3307/{database}"), + Some(MySqlVersion::V8 | MySqlVersion::Mysql2JsWasm) => { + format!("mysql://root:prisma@127.0.0.1:3307/{database}") + } Some(MySqlVersion::MariaDb) => { format!("mysql://root:prisma@127.0.0.1:3308/{database}") } diff --git a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mysql.rs b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mysql.rs index d08462391d86..2cafe314100a 100644 --- a/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mysql.rs +++ b/query-engine/connector-test-kit-rs/query-tests-setup/src/connector_tag/mysql.rs @@ -34,6 +34,7 @@ pub enum MySqlVersion { V5_7, V8, MariaDb, + Mysql2JsWasm, } impl TryFrom<&str> for MySqlVersion { @@ -45,6 +46,7 @@ impl TryFrom<&str> for MySqlVersion { "5.7" => Self::V5_7, "8" => Self::V8, "mariadb" => Self::MariaDb, + "mysql2.js.wasm" => Self::Mysql2JsWasm, _ => return Err(TestError::parse_error(format!("Unknown MySQL version `{s}`"))), }; @@ -59,6 +61,7 @@ impl Display for MySqlVersion { MySqlVersion::V5_7 => f.write_str("5.7"), MySqlVersion::V8 => f.write_str("8"), MySqlVersion::MariaDb => f.write_str("mariadb"), + MySqlVersion::Mysql2JsWasm => f.write_str("mysql2.js.wasm"), } } } diff --git a/query-engine/connector-test-kit-rs/test-configs/mysql2-js b/query-engine/connector-test-kit-rs/test-configs/mysql2-js new file mode 100644 index 000000000000..2c1ec188d947 --- /dev/null +++ b/query-engine/connector-test-kit-rs/test-configs/mysql2-js @@ -0,0 +1,6 @@ +{ + "connector": "mysql", + "version": "mysql2.js", + "driver_adapter": "mysql2", + "external_test_executor": "Napi" +} diff --git a/query-engine/connector-test-kit-rs/test-configs/mysql2-qc b/query-engine/connector-test-kit-rs/test-configs/mysql2-qc new file mode 100644 index 000000000000..340a70dd8f8f --- /dev/null +++ b/query-engine/connector-test-kit-rs/test-configs/mysql2-qc @@ -0,0 +1,6 @@ +{ + "connector": "mysql", + "version": "mysql2.js.wasm", + "driver_adapter": "mysql2", + "external_test_executor": "QueryCompiler" +} diff --git a/query-engine/core/src/query_graph_builder/write/update.rs b/query-engine/core/src/query_graph_builder/write/update.rs index 153fd3331490..ba4f3e1c612c 100644 --- a/query-engine/core/src/query_graph_builder/write/update.rs +++ b/query-engine/core/src/query_graph_builder/write/update.rs @@ -39,32 +39,16 @@ pub(crate) fn update_record( Some(&field), )?; - if query_schema.relation_mode().is_prisma() { - let read_parent_node = graph.create_node(utils::read_id_infallible( - model.clone(), - model.shard_aware_primary_identifier(), - filter, - )); + let read_parent_node = graph.create_node(utils::read_id_infallible( + model.clone(), + model.shard_aware_primary_identifier(), + filter, + )); - utils::insert_emulated_on_update(graph, query_schema, &model, &read_parent_node, &update_node)?; + let needs_read_parent = query_schema.relation_mode().is_prisma() || !can_use_atomic_update; - graph.create_edge( - &read_parent_node, - &update_node, - QueryGraphDependency::ProjectedDataDependency( - model.shard_aware_primary_identifier(), - Box::new(move |mut update_node, parent_ids| { - if let Node::Query(Query::Write(WriteQuery::UpdateRecord(ref mut ur))) = update_node { - ur.set_record_filter(parent_ids.into()); - } - - Ok(update_node) - }), - Some(DataExpectation::non_empty_rows( - MissingRecord::builder().operation(DataOperation::Update).build(), - )), - ), - )?; + if query_schema.relation_mode().is_prisma() { + utils::insert_emulated_on_update(graph, query_schema, &model, &read_parent_node, &update_node)?; } // If the update can be done in a single operation (which includes getting the result back), @@ -107,6 +91,26 @@ pub(crate) fn update_record( )?; } + if needs_read_parent { + graph.create_edge( + &read_parent_node, + &update_node, + QueryGraphDependency::ProjectedDataDependency( + model.shard_aware_primary_identifier(), + Box::new(move |mut update_node, parent_ids| { + if let Node::Query(Query::Write(WriteQuery::UpdateRecord(ref mut ur))) = update_node { + ur.set_record_filter(parent_ids.into()); + } + + Ok(update_node) + }), + Some(DataExpectation::non_empty_rows( + MissingRecord::builder().operation(DataOperation::Update).build(), + )), + ), + )?; + } + Ok(()) }