Repository: prisma/prisma-engines
Pull request #5291: feat(qc): Transactional graphs
Author: login: viktor-ferenczi
Labels: name: PR: Feature
description: A PR That introduces a new feature
Created: 2025-03-24T14:54:58Z | Merged: 2025-03-28T11:06:00Z

- Introduced `Expression::Transaction` to Query Compiler

Implements [ORM-742](https://linear.app/prisma-company/issue/ORM-742/run-transactional-graphs-in-transactions)

[Prisma PR](https://github.com/prisma/prisma/pull/26720)


Diff:
diff --git a/libs/driver-adapters/executor/src/qc-test-worker/worker-query.ts b/libs/driver-adapters/executor/src/qc-test-worker/worker-query.ts
index cc180e23c60e..8547f438cf74 100644
--- a/libs/driver-adapters/executor/src/qc-test-worker/worker-query.ts
+++ b/libs/driver-adapters/executor/src/qc-test-worker/worker-query.ts
@@ -8,7 +8,8 @@ import type { State } from './worker'
 import { debug } from '../utils'
 import {
   QueryInterpreter,
-  TransactionManager,
+  type QueryInterpreterTransactionManager,
+  type TransactionManager,
 } from '@prisma/client-engine-runtime'
 import { QueryCompiler } from '../query-compiler'
 import { parseIsolationLevel } from './worker-transaction'
@@ -43,9 +44,9 @@ class QueryPipeline {
 
       const results = transaction
         ? await this.executeTransactionalBatch(
-            batch,
-            parseIsolationLevel(transaction.isolationLevel),
-          )
+          batch,
+          parseIsolationLevel(transaction.isolationLevel),
+        )
         : await this.executeIndependentBatch(batch)
 
       debug('🟢 Batch query results: ', results)
@@ -66,7 +67,7 @@ class QueryPipeline {
         )
       }
 
-      const result = await this.executeQuery(queryable, query)
+      const result = await this.executeQuery(queryable, query, !txId)
 
       debug('🟢 Query result: ', result)
 
@@ -77,6 +78,7 @@ class QueryPipeline {
   private async executeQuery(
     queryable: SqlQueryable,
     query: JsonProtocolQuery,
+    allowTransaction: boolean,
   ) {
     const queryPlanString = withLocalPanicHandler(() =>
       this.compiler.compile(JSON.stringify(query)),
@@ -86,20 +88,24 @@ class QueryPipeline {
 
     debug('🟢 Query plan: ', util.inspect(queryPlan, false, null, true))
 
+    const qiTransactionManager = (
+      allowTransaction ? { enabled: true, manager: this.transactionManager } : { enabled: false }
+    ) satisfies QueryInterpreterTransactionManager
+
     const interpreter = new QueryInterpreter({
-      queryable,
+      transactionManager: qiTransactionManager,
       placeholderValues: {},
       onQuery: (event) => {
         this.logs.push(JSON.stringify(event))
       },
     })
 
-    return interpreter.run(queryPlan)
+    return interpreter.run(queryPlan, queryable)
   }
 
   private async executeIndependentBatch(queries: readonly JsonProtocolQuery[]) {
     return Promise.all(
-      queries.map((query) => this.executeQuery(this.driverAdapter, query)),
+      queries.map((query) => this.executeQuery(this.driverAdapter, query, true)),
     )
   }
 
@@ -111,7 +117,7 @@ class QueryPipeline {
       isolationLevel,
     })
 
-    const queryable = this.transactionManager.getTransaction(
+    const transaction = this.transactionManager.getTransaction(
       txInfo,
       'batch transaction query',
     )
@@ -120,7 +126,7 @@ class QueryPipeline {
       const results: unknown[] = []
 
       for (const query of queries) {
-        const result = await this.executeQuery(queryable, query)
+        const result = await this.executeQuery(transaction, query, false)
         results.push(result)
       }
 
diff --git a/libs/driver-adapters/executor/src/qc-test-worker/worker.ts b/libs/driver-adapters/executor/src/qc-test-worker/worker.ts
index 99f8dd874b9d..d2f79001b762 100644
--- a/libs/driver-adapters/executor/src/qc-test-worker/worker.ts
+++ b/libs/driver-adapters/executor/src/qc-test-worker/worker.ts
@@ -2,7 +2,7 @@ import {Schema as S} from '@effect/schema'
 import type {ConnectionInfo, SqlDriverAdapter} from '@prisma/driver-adapter-utils'
 import {DriverAdaptersManager} from '../driver-adapters-manager'
 import * as qc from '../query-compiler'
-import {TransactionManager} from '@prisma/client-engine-runtime'
+import {TransactionManager, type TransactionOptions} from '@prisma/client-engine-runtime'
 import {parentPort} from 'worker_threads'
 import {
     CommitTxParams,
@@ -161,6 +161,12 @@ async function initializeSchema(
 
     const transactionManager = new TransactionManager({
         driverAdapter: adapter,
+        // Transaction timeouts matching `TransactionManager.test.ts` in the `prisma` repo
+        transactionOptions: {
+            maxWait: 200,
+            timeout: 500,
+            isolationLevel: 'SERIALIZABLE'
+        } satisfies TransactionOptions,
     })
 
     state = {
diff --git a/query-compiler/query-compiler/src/expression.rs b/query-compiler/query-compiler/src/expression.rs
index 4ba6f22a46b0..1659fa181429 100644
--- a/query-compiler/query-compiler/src/expression.rs
+++ b/query-compiler/query-compiler/src/expression.rs
@@ -77,6 +77,9 @@ pub enum Expression {
     /// Get a field from a record or records. If the argument is a list of records,
     /// returns a list of values of this field.
     MapField { field: String, records: Box<Expression> },
+
+    /// Run the query inside a transaction
+    Transaction(Box<Expression>),
 }
 
 #[derive(Debug, Clone)]
@@ -137,6 +140,7 @@ impl Expression {
             Expression::Required(expression) => expression.r#type(),
             Expression::Join { parent, .. } => parent.r#type(),
             Expression::MapField { records, .. } => records.r#type(),
+            Expression::Transaction(expression) => expression.r#type(),
         }
     }
 }
diff --git a/query-compiler/query-compiler/src/expression/format.rs b/query-compiler/query-compiler/src/expression/format.rs
index 2f68c4ad3e03..a4ed2a9c1d1e 100644
--- a/query-compiler/query-compiler/src/expression/format.rs
+++ b/query-compiler/query-compiler/src/expression/format.rs
@@ -55,6 +55,7 @@ where
             Expression::Required(expression) => self.unary_function("required", expression),
             Expression::Join { parent, children } => self.join(parent, children),
             Expression::MapField { field, records } => self.map_field(field, records),
+            Expression::Transaction(expression) => self.transaction(expression),
         }
     }
 
@@ -252,6 +253,15 @@ where
             .append(self.space())
             .append(self.expression(records).parens())
     }
+
+    fn transaction(&'a self, expr: &'a Expression) -> DocBuilder<'a, PrettyPrinter<'a, D>, ColorSpec> {
+        self.keyword("transaction")
+            .append(self.line())
+            .append(self.softline())
+            .append(self.softline())
+            .append(self.softline())
+            .append(self.expression(expr).align())
+    }
 }
 
 impl<'a, D, A> DocAllocator<'a, A> for PrettyPrinter<'a, D>
diff --git a/query-compiler/query-compiler/src/translate.rs b/query-compiler/query-compiler/src/translate.rs
index d02131a546c6..c0c4f1cbb350 100644
--- a/query-compiler/query-compiler/src/translate.rs
+++ b/query-compiler/query-compiler/src/translate.rs
@@ -1,5 +1,7 @@
 mod query;
 
+use super::expression::{Binding, Expression};
+use crate::Expression::Transaction;
 use itertools::{Either, Itertools};
 use query::translate_query;
 use query_builder::QueryBuilder;
@@ -7,8 +9,6 @@ use query_core::{EdgeRef, Node, NodeRef, Query, QueryGraph, QueryGraphBuilderErr
 use query_structure::{PrismaValue, PrismaValueType, SelectedField, SelectionResult};
 use thiserror::Error;
 
-use super::expression::{Binding, Expression};
-
 #[derive(Debug, Error)]
 pub enum TranslateError {
     #[error("node {0} has no content")]
@@ -24,12 +24,18 @@ pub enum TranslateError {
 pub type TranslateResult<T> = Result<T, TranslateError>;
 
 pub fn translate(mut graph: QueryGraph, builder: &dyn QueryBuilder) -> TranslateResult<Expression> {
-    graph
+    let root = graph
         .root_nodes()
         .into_iter()
         .map(|node| NodeTranslator::new(&mut graph, node, &[], builder).translate())
         .collect::<TranslateResult<Vec<_>>>()
-        .map(Expression::Seq)
+        .map(Expression::Seq)?;
+
+    if graph.needs_transaction() {
+        return Ok(Transaction(Box::new(root)));
+    }
+
+    Ok(root)
 }
 
 struct NodeTranslator<'a, 'b> {
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-m2m.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-m2m.json.snap
index 3b78d3b4f56b..8cf524a28d0f 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-m2m.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-m2m.json.snap
@@ -1,38 +1,42 @@
 ---
 source: query-compiler/query-compiler/tests/queries.rs
+assertion_line: 46
 expression: pretty
 input_file: query-compiler/query-compiler/tests/data/create-m2m.json
+snapshot_kind: text
 ---
-let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
-                       RETURNING "public"."User"."id"»
-                params [const(String("user.1737556027349@prisma.io"))])
-in let 0$id = mapField id (get 0)
-   in let 1 = unique (query «INSERT INTO "public"."Post" ("title","userId")
-                             VALUES ($1,$2) RETURNING "public"."Post"."id"»
-                      params [const(String("How to make an omelette")),
-                              var(0$id as Int)])
-      in let 2 = unique (query «INSERT INTO "public"."Category" ("name") VALUES
-                                ($1) RETURNING "public"."Category"."id"»
-                         params [const(String("cooking"))])
-         in let 1$id = mapField id (get 1);
-                2$id = mapField id (get 2)
-            in execute «INSERT INTO "public"."_CategoryToPost" ("B","A") VALUES
-                        ($1,$2) ON CONFLICT DO NOTHING»
-               params [var(1$id as Int), var(2$id as Int)];
-   let 4 = let 0$id = mapField id (get 0)
-       in let @parent = unique (query «SELECT "public"."User"."id",
-                                       "public"."User"."email" FROM
-                                       "public"."User" WHERE
-                                       "public"."User"."id" = $1 LIMIT $2 OFFSET
-                                       $3»
-                                params [var(0$id as Int), const(BigInt(1)),
-                                        const(BigInt(0))])
-          in let @parent$id = mapField id (get @parent)
-             in join (get @parent)
-                with (query «SELECT "public"."Post"."id",
-                             "public"."Post"."title", "public"."Post"."userId"
-                             FROM "public"."Post" WHERE "public"."Post"."userId"
-                             = $1 OFFSET $2»
-                      params [var(@parent$id as Int),
-                              const(BigInt(0))]) on left.(id) = right.(userId) as posts
-   in get 4
+transaction
+   let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
+                          RETURNING "public"."User"."id"»
+                   params [const(String("user.1737556027349@prisma.io"))])
+   in let 0$id = mapField id (get 0)
+      in let 1 = unique (query «INSERT INTO "public"."Post" ("title","userId")
+                                VALUES ($1,$2) RETURNING "public"."Post"."id"»
+                         params [const(String("How to make an omelette")),
+                                 var(0$id as Int)])
+         in let 2 = unique (query «INSERT INTO "public"."Category" ("name")
+                                   VALUES ($1) RETURNING
+                                   "public"."Category"."id"»
+                            params [const(String("cooking"))])
+            in let 1$id = mapField id (get 1);
+                   2$id = mapField id (get 2)
+               in execute «INSERT INTO "public"."_CategoryToPost" ("B","A")
+                           VALUES ($1,$2) ON CONFLICT DO NOTHING»
+                  params [var(1$id as Int), var(2$id as Int)];
+      let 4 = let 0$id = mapField id (get 0)
+          in let @parent = unique (query «SELECT "public"."User"."id",
+                                          "public"."User"."email" FROM
+                                          "public"."User" WHERE
+                                          "public"."User"."id" = $1 LIMIT $2
+                                          OFFSET $3»
+                                   params [var(0$id as Int), const(BigInt(1)),
+                                           const(BigInt(0))])
+             in let @parent$id = mapField id (get @parent)
+                in join (get @parent)
+                   with (query «SELECT "public"."Post"."id",
+                                "public"."Post"."title",
+                                "public"."Post"."userId" FROM "public"."Post"
+                                WHERE "public"."Post"."userId" = $1 OFFSET $2»
+                         params [var(@parent$id as Int),
+                                 const(BigInt(0))]) on left.(id) = right.(userId) as posts
+      in get 4
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-connect.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-connect.json.snap
index aa277241add0..b7a5c03d6085 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-connect.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-connect.json.snap
@@ -1,33 +1,36 @@
 ---
 source: query-compiler/query-compiler/tests/queries.rs
+assertion_line: 46
 expression: pretty
 input_file: query-compiler/query-compiler/tests/data/create-nested-connect.json
+snapshot_kind: text
 ---
-let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
-                       RETURNING "public"."User"."id"»
-                params [const(String("user.1737556028164@prisma.io"))])
-in let 0$id = mapField id (get 0)
-   in let 1 = sum (execute «UPDATE "public"."Post" SET "userId" = $1 WHERE
-                            (("public"."Post"."id" = $2 AND 1=1) OR
-                            ("public"."Post"."id" = $3 AND 1=1) OR
-                            ("public"."Post"."id" = $4 AND 1=1))»
-                   params [var(0$id as Int), const(BigInt(8)), const(BigInt(9)),
-                           const(BigInt(10))])
-      in ;
-   let 3 = let 0$id = mapField id (get 0)
-       in let @parent = unique (query «SELECT "public"."User"."id",
-                                       "public"."User"."email" FROM
-                                       "public"."User" WHERE
-                                       "public"."User"."id" = $1 LIMIT $2 OFFSET
-                                       $3»
-                                params [var(0$id as Int), const(BigInt(1)),
-                                        const(BigInt(0))])
-          in let @parent$id = mapField id (get @parent)
-             in join (get @parent)
-                with (query «SELECT "public"."Post"."id",
-                             "public"."Post"."title", "public"."Post"."userId"
-                             FROM "public"."Post" WHERE "public"."Post"."userId"
-                             = $1 OFFSET $2»
-                      params [var(@parent$id as Int),
-                              const(BigInt(0))]) on left.(id) = right.(userId) as posts
-   in get 3
+transaction
+   let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
+                          RETURNING "public"."User"."id"»
+                   params [const(String("user.1737556028164@prisma.io"))])
+   in let 0$id = mapField id (get 0)
+      in let 1 = sum (execute «UPDATE "public"."Post" SET "userId" = $1 WHERE
+                               (("public"."Post"."id" = $2 AND 1=1) OR
+                               ("public"."Post"."id" = $3 AND 1=1) OR
+                               ("public"."Post"."id" = $4 AND 1=1))»
+                      params [var(0$id as Int), const(BigInt(8)),
+                              const(BigInt(9)), const(BigInt(10))])
+         in ;
+      let 3 = let 0$id = mapField id (get 0)
+          in let @parent = unique (query «SELECT "public"."User"."id",
+                                          "public"."User"."email" FROM
+                                          "public"."User" WHERE
+                                          "public"."User"."id" = $1 LIMIT $2
+                                          OFFSET $3»
+                                   params [var(0$id as Int), const(BigInt(1)),
+                                           const(BigInt(0))])
+             in let @parent$id = mapField id (get @parent)
+                in join (get @parent)
+                   with (query «SELECT "public"."Post"."id",
+                                "public"."Post"."title",
+                                "public"."Post"."userId" FROM "public"."Post"
+                                WHERE "public"."Post"."userId" = $1 OFFSET $2»
+                         params [var(@parent$id as Int),
+                                 const(BigInt(0))]) on left.(id) = right.(userId) as posts
+      in get 3
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create-with-composite-id.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create-with-composite-id.json.snap
index 50a3f0825c85..5dfaa3ae7ca0 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create-with-composite-id.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create-with-composite-id.json.snap
@@ -1,46 +1,51 @@
 ---
 source: query-compiler/query-compiler/tests/queries.rs
+assertion_line: 46
 expression: pretty
 input_file: query-compiler/query-compiler/tests/data/create-nested-create-with-composite-id.json
+snapshot_kind: text
 ---
-let 0 = unique (query «INSERT INTO "public"."ParentModelWithCompositeId"
-                       ("a","b") VALUES ($1,$2) RETURNING
-                       "public"."ParentModelWithCompositeId"."a",
-                       "public"."ParentModelWithCompositeId"."b"»
-                params [const(BigInt(1)), const(BigInt(1))])
-in let 0$a = mapField a (get 0);
-       0$b = mapField b (get 0)
-   in sum (execute «INSERT INTO "public"."ChildOfModelWithCompositeId"
-                    ("id","parentA","parentB") VALUES ($1,$2,$3), ($4,$5,$6)»
-           params [const(BigInt(1)), var(0$a as Int), var(0$b as Int),
-                   const(BigInt(2)), var(0$a as Int), var(0$b as Int)]);
-   let 2 = let 0$a = mapField a (get 0);
-               0$b = mapField b (get 0)
-       in let @parent = unique (query «SELECT
-                                       "public"."ParentModelWithCompositeId"."a",
-                                       "public"."ParentModelWithCompositeId"."b"
-                                       FROM
-                                       "public"."ParentModelWithCompositeId"
-                                       WHERE
-                                       ("public"."ParentModelWithCompositeId"."a"
-                                       = $1 AND
-                                       "public"."ParentModelWithCompositeId"."b"
-                                       = $2) LIMIT $3 OFFSET $4»
-                                params [var(0$a as Int), var(0$b as Int),
-                                        const(BigInt(1)), const(BigInt(0))])
-          in let @parent$a = mapField a (get @parent);
-                 @parent$b = mapField b (get @parent)
-             in join (get @parent)
-                with (query «SELECT "public"."ChildOfModelWithCompositeId"."id",
-                             "public"."ChildOfModelWithCompositeId"."parentA",
-                             "public"."ChildOfModelWithCompositeId"."parentB"
-                             FROM "public"."ChildOfModelWithCompositeId" WHERE
-                             ("public"."ChildOfModelWithCompositeId"."parentA" =
-                             $1 OR
-                             "public"."ChildOfModelWithCompositeId"."parentB" =
-                             $2) OFFSET $3»
-                      params [var(@parent$a as Int), var(@parent$b as Int),
-                              const(BigInt(0))]) on left.(a,
-                                                          b) = right.(parentA,
-                                                                      parentB) as children
-   in get 2
+transaction
+   let 0 = unique (query «INSERT INTO "public"."ParentModelWithCompositeId"
+                          ("a","b") VALUES ($1,$2) RETURNING
+                          "public"."ParentModelWithCompositeId"."a",
+                          "public"."ParentModelWithCompositeId"."b"»
+                   params [const(BigInt(1)), const(BigInt(1))])
+   in let 0$a = mapField a (get 0);
+          0$b = mapField b (get 0)
+      in sum (execute «INSERT INTO "public"."ChildOfModelWithCompositeId"
+                       ("id","parentA","parentB") VALUES ($1,$2,$3), ($4,$5,$6)»
+              params [const(BigInt(1)), var(0$a as Int), var(0$b as Int),
+                      const(BigInt(2)), var(0$a as Int), var(0$b as Int)]);
+      let 2 = let 0$a = mapField a (get 0);
+                  0$b = mapField b (get 0)
+          in let @parent = unique (query «SELECT
+                                          "public"."ParentModelWithCompositeId"."a",
+                                          "public"."ParentModelWithCompositeId"."b"
+                                          FROM
+                                          "public"."ParentModelWithCompositeId"
+                                          WHERE
+                                          ("public"."ParentModelWithCompositeId"."a"
+                                          = $1 AND
+                                          "public"."ParentModelWithCompositeId"."b"
+                                          = $2) LIMIT $3 OFFSET $4»
+                                   params [var(0$a as Int), var(0$b as Int),
+                                           const(BigInt(1)), const(BigInt(0))])
+             in let @parent$a = mapField a (get @parent);
+                    @parent$b = mapField b (get @parent)
+                in join (get @parent)
+                   with (query «SELECT
+                                "public"."ChildOfModelWithCompositeId"."id",
+                                "public"."ChildOfModelWithCompositeId"."parentA",
+                                "public"."ChildOfModelWithCompositeId"."parentB"
+                                FROM "public"."ChildOfModelWithCompositeId"
+                                WHERE
+                                ("public"."ChildOfModelWithCompositeId"."parentA"
+                                = $1 OR
+                                "public"."ChildOfModelWithCompositeId"."parentB"
+                                = $2) OFFSET $3»
+                         params [var(@parent$a as Int), var(@parent$b as Int),
+                                 const(BigInt(0))]) on left.(a,
+                                                             b) = right.(parentA,
+                                                                         parentB) as children
+      in get 2
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create.json.snap
index 168a1ce96c63..e1a8b6f4c21a 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@create-nested-create.json.snap
@@ -1,30 +1,34 @@
 ---
 source: query-compiler/query-compiler/tests/queries.rs
+assertion_line: 46
 expression: pretty
 input_file: query-compiler/query-compiler/tests/data/create-nested-create.json
+snapshot_kind: text
 ---
-let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
-                       RETURNING "public"."User"."id"»
-                params [const(String("user.1737556027349@prisma.io"))])
-in let 0$id = mapField id (get 0)
-   in sum (execute «INSERT INTO "public"."Post" ("title","userId") VALUES
-                    ($1,$2), ($3,$4)»
-           params [const(String("How to make an omelette")), var(0$id as Int),
-                   const(String("How to eat an omelette")), var(0$id as Int)]);
-   let 2 = let 0$id = mapField id (get 0)
-       in let @parent = unique (query «SELECT "public"."User"."id",
-                                       "public"."User"."email" FROM
-                                       "public"."User" WHERE
-                                       "public"."User"."id" = $1 LIMIT $2 OFFSET
-                                       $3»
-                                params [var(0$id as Int), const(BigInt(1)),
-                                        const(BigInt(0))])
-          in let @parent$id = mapField id (get @parent)
-             in join (get @parent)
-                with (query «SELECT "public"."Post"."id",
-                             "public"."Post"."title", "public"."Post"."userId"
-                             FROM "public"."Post" WHERE "public"."Post"."userId"
-                             = $1 OFFSET $2»
-                      params [var(@parent$id as Int),
-                              const(BigInt(0))]) on left.(id) = right.(userId) as posts
-   in get 2
+transaction
+   let 0 = unique (query «INSERT INTO "public"."User" ("email") VALUES ($1)
+                          RETURNING "public"."User"."id"»
+                   params [const(String("user.1737556027349@prisma.io"))])
+   in let 0$id = mapField id (get 0)
+      in sum (execute «INSERT INTO "public"."Post" ("title","userId") VALUES
+                       ($1,$2), ($3,$4)»
+              params [const(String("How to make an omelette")),
+                      var(0$id as Int), const(String("How to eat an omelette")),
+                      var(0$id as Int)]);
+      let 2 = let 0$id = mapField id (get 0)
+          in let @parent = unique (query «SELECT "public"."User"."id",
+                                          "public"."User"."email" FROM
+                                          "public"."User" WHERE
+                                          "public"."User"."id" = $1 LIMIT $2
+                                          OFFSET $3»
+                                   params [var(0$id as Int), const(BigInt(1)),
+                                           const(BigInt(0))])
+             in let @parent$id = mapField id (get @parent)
+                in join (get @parent)
+                   with (query «SELECT "public"."Post"."id",
+                                "public"."Post"."title",
+                                "public"."Post"."userId" FROM "public"."Post"
+                                WHERE "public"."Post"."userId" = $1 OFFSET $2»
+                         params [var(@parent$id as Int),
+                                 const(BigInt(0))]) on left.(id) = right.(userId) as posts
+      in get 2
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@update-connect.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@update-connect.json.snap
index 36290eb0c29c..e12cff4ddf9a 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@update-connect.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@update-connect.json.snap
@@ -1,32 +1,35 @@
 ---
 source: query-compiler/query-compiler/tests/queries.rs
+assertion_line: 46
 expression: pretty
 input_file: query-compiler/query-compiler/tests/data/update-connect.json
+snapshot_kind: text
 ---
-let 0 = unique (query «SELECT "public"."User"."id", "public"."User"."email" FROM
-                       "public"."User" WHERE ("public"."User"."email" = $1 AND
-                       1=1) LIMIT $2 OFFSET $3»
-                params [const(String("user.1737556028164@prisma.io")),
-                        const(BigInt(1)), const(BigInt(0))])
-in let 0$id = mapField id (get 0)
-   in let 1 = sum (execute «UPDATE "public"."Post" SET "userId" = $1 WHERE
-                            ("public"."Post"."id" = $2 AND 1=1)»
-                   params [var(0$id as Int), const(BigInt(11))])
-      in ;
-   let 3 = let 0$id = mapField id (get 0)
-       in let @parent = unique (query «SELECT "public"."User"."id",
-                                       "public"."User"."email" FROM
-                                       "public"."User" WHERE
-                                       "public"."User"."id" = $1 LIMIT $2 OFFSET
-                                       $3»
-                                params [var(0$id as Int), const(BigInt(1)),
-                                        const(BigInt(0))])
-          in let @parent$id = mapField id (get @parent)
-             in join (get @parent)
-                with (query «SELECT "public"."Post"."id",
-                             "public"."Post"."title", "public"."Post"."userId"
-                             FROM "public"."Post" WHERE "public"."Post"."userId"
-                             = $1 OFFSET $2»
-                      params [var(@parent$id as Int),
-                              const(BigInt(0))]) on left.(id) = right.(userId) as posts
-   in get 3
+transaction
+   let 0 = unique (query «SELECT "public"."User"."id", "public"."User"."email"
+                          FROM "public"."User" WHERE ("public"."User"."email" =
+                          $1 AND 1=1) LIMIT $2 OFFSET $3»
+                   params [const(String("user.1737556028164@prisma.io")),
+                           const(BigInt(1)), const(BigInt(0))])
+   in let 0$id = mapField id (get 0)
+      in let 1 = sum (execute «UPDATE "public"."Post" SET "userId" = $1 WHERE
+                               ("public"."Post"."id" = $2 AND 1=1)»
+                      params [var(0$id as Int), const(BigInt(11))])
+         in ;
+      let 3 = let 0$id = mapField id (get 0)
+          in let @parent = unique (query «SELECT "public"."User"."id",
+                                          "public"."User"."email" FROM
+                                          "public"."User" WHERE
+                                          "public"."User"."id" = $1 LIMIT $2
+                                          OFFSET $3»
+                                   params [var(0$id as Int), const(BigInt(1)),
+                                           const(BigInt(0))])
+             in let @parent$id = mapField id (get @parent)
+                in join (get @parent)
+                   with (query «SELECT "public"."Post"."id",
+                                "public"."Post"."title",
+                                "public"."Post"."userId" FROM "public"."Post"
+                                WHERE "public"."Post"."userId" = $1 OFFSET $2»
+                         params [var(@parent$id as Int),
+                                 const(BigInt(0))]) on left.(id) = right.(userId) as posts
+      in get 3
diff --git a/query-compiler/query-compiler/tests/snapshots/queries__queries@update-m2m-disconnect.json.snap b/query-compiler/query-compiler/tests/snapshots/queries__queries@update-m2m-disconnect.json.snap
index 88036c28c17a..becfff216337 100644
--- a/query-compiler/query-compiler/tests/snapshots/queries__queries@update-m2m-disconnect.json.snap
+++ b/query-compiler/query-compiler/tests/snapshots/queries__queries@update-m2m-disconnect.json.snap
@@ -5,25 +5,30 @@ expression: pretty
 input_file: query-compiler/query-compiler/tests/data/update-m2m-disconnect.json
 snapshot_kind: text
 ---
-let 0 = unique (query «SELECT "public"."Post"."id", "public"."Post"."title",
-                       "public"."Post"."userId" FROM "public"."Post" WHERE
-                       ("public"."Post"."id" = $1 AND 1=1) LIMIT $2 OFFSET $3»
-                params [const(BigInt(1)), const(BigInt(1)), const(BigInt(0))])
-in let 0$id = mapField id (get 0)
-   in let 1 = query «SELECT "t1"."id", "t0"."B" AS "CategoryToPost@Post" FROM
-                     "public"."_CategoryToPost" AS "t0" INNER JOIN
-                     "public"."Category" AS "t1" ON "t0"."A" = "t1"."id" WHERE
-                     ("t1"."id" = $1 AND 1=1)»
-              params [const(BigInt(1))]
-      in let 0$id = mapField id (get 0);
-             1$id = mapField id (get 1)
-         in execute «DELETE FROM "public"."_CategoryToPost" WHERE
-                     ("public"."_CategoryToPost"."B" = ($1) AND
-                     "public"."_CategoryToPost"."A" IN [$2])»
-            params [var(0$id as Int), var(1$id as Int)];
-   let 3 = let 0$id = mapField id (get 0)
-       in unique (query «SELECT "public"."Post"."id", "public"."Post"."title",
-                         "public"."Post"."userId" FROM "public"."Post" WHERE
-                         "public"."Post"."id" = $1 LIMIT $2 OFFSET $3»
-                  params [var(0$id as Int), const(BigInt(1)), const(BigInt(0))])
-   in get 3
+transaction
+   let 0 = unique (query «SELECT "public"."Post"."id", "public"."Post"."title",
+                          "public"."Post"."userId" FROM "public"."Post" WHERE
+                          ("public"."Post"."id" = $1 AND 1=1) LIMIT $2 OFFSET
+                          $3»
+                   params [const(BigInt(1)), const(BigInt(1)),
+                           const(BigInt(0))])
+   in let 0$id = mapField id (get 0)
+      in let 1 = query «SELECT "t1"."id", "t0"."B" AS "CategoryToPost@Post" FROM
+                        "public"."_CategoryToPost" AS "t0" INNER JOIN
+                        "public"."Category" AS "t1" ON "t0"."A" = "t1"."id"
+                        WHERE ("t1"."id" = $1 AND 1=1)»
+                 params [const(BigInt(1))]
+         in let 0$id = mapField id (get 0);
+                1$id = mapField id (get 1)
+            in execute «DELETE FROM "public"."_CategoryToPost" WHERE
+                        ("public"."_CategoryToPost"."B" = ($1) AND
+                        "public"."_CategoryToPost"."A" IN [$2])»
+               params [var(0$id as Int), var(1$id as Int)];
+      let 3 = let 0$id = mapField id (get 0)
+          in unique (query «SELECT "public"."Post"."id",
+                            "public"."Post"."title", "public"."Post"."userId"
+                            FROM "public"."Post" WHERE "public"."Post"."id" = $1
+                            LIMIT $2 OFFSET $3»
+                     params [var(0$id as Int), const(BigInt(1)),
+                             const(BigInt(0))])
+      in get 3
diff --git a/query-engine/core/src/query_graph/mod.rs b/query-engine/core/src/query_graph/mod.rs
index c05ae830e962..66a8d88a3f69 100644
--- a/query-engine/core/src/query_graph/mod.rs
+++ b/query-engine/core/src/query_graph/mod.rs
@@ -194,7 +194,7 @@ pub struct QueryGraph {
 
     finalized: bool,
 
-    /// For now a stupid marker if the query graph needs to be run inside a
+    /// For now a simple marker if the query graph needs to be run inside a
     /// transaction. Should happen if any of the queries is writing data.
     needs_transaction: bool,
 
@@ -334,7 +334,7 @@ impl QueryGraph {
     }
 
     /// If true, the graph should be executed inside of a transaction.
-    pub(crate) fn needs_transaction(&self) -> bool {
+    pub fn needs_transaction(&self) -> bool {
         self.needs_transaction
     }
 


Reviews:
user: login: jacek-prisma
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T11:23:09Z
user: login: aqrln
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T11:14:21Z
user: login: viktor-ferenczi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T11:45:10Z
user: login: viktor-ferenczi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T11:49:24Z
user: login: viktor-ferenczi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T11:54:55Z

Review comments:
diff_hunk: @@ -1,228 +1,223 @@
-import {Schema as S} from '@effect/schema'
-import type {ConnectionInfo, SqlDriverAdapter} from '@prisma/driver-adapter-utils'
-import {DriverAdaptersManager} from '../driver-adapters-manager'
+import { Schema as S } from '@effect/schema'
+import type { ConnectionInfo, SqlDriverAdapter } from '@prisma/driver-adapter-utils'
+import { DriverAdaptersManager } from '../driver-adapters-manager'
 import * as qc from '../query-compiler'
-import {TransactionManager} from '@prisma/client-engine-runtime'
-import {parentPort} from 'worker_threads'
-import {
-    CommitTxParams,
-    InitializeSchemaParams,
-    QueryParams,
-    RollbackTxParams,
-    StartTxParams,
-} from '../types/jsonRpc'
-import {assertNever, debug} from '../utils'
-import {setupDriverAdaptersManager} from '../setup'
-import {Env} from '../types'
-import {query} from './worker-query'
-import {
-    commitTransaction,
-    rollbackTransaction,
-    startTransaction,
-} from './worker-transaction'
-import {setupDefaultPanicHandler} from '../panic'
+import { TransactionManager, TransactionOptions } from '@prisma/client-engine-runtime'
+import { parentPort } from 'worker_threads'
+import { CommitTxParams, InitializeSchemaParams, QueryParams, RollbackTxParams, StartTxParams, } from '../types/jsonRpc'
+import { assertNever, debug } from '../utils'
+import { setupDriverAdaptersManager } from '../setup'
+import { Env } from '../types'
+import { query } from './worker-query'
+import { commitTransaction, rollbackTransaction, startTransaction, } from './worker-transaction'
+import { setupDefaultPanicHandler } from '../panic'
 
 const InitializeSchemaMessage = S.struct({
-    type: S.literal('initializeSchema'),
-    responsePort: S.instanceOf(MessagePort),
-    params: InitializeSchemaParams,
-    env: Env,
+  type: S.literal('initializeSchema'),
+  responsePort: S.instanceOf(MessagePort),
+  params: InitializeSchemaParams,
+  env: Env,
 })
 
 const QueryMessage = S.struct({
-    type: S.literal('query'),
-    responsePort: S.instanceOf(MessagePort),
-    params: QueryParams,
+  type: S.literal('query'),
+  responsePort: S.instanceOf(MessagePort),
+  params: QueryParams,
 })
 
 const StartTransactionMessage = S.struct({
-    type: S.literal('startTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: StartTxParams,
+  type: S.literal('startTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: StartTxParams,
 })
 
 const CommitTransactionMessage = S.struct({
-    type: S.literal('commitTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: CommitTxParams,
+  type: S.literal('commitTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: CommitTxParams,
 })
 
 const RollbackTransactionMessage = S.struct({
-    type: S.literal('rollbackTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: RollbackTxParams,
+  type: S.literal('rollbackTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: RollbackTxParams,
 })
 
 const TeardownMessage = S.struct({
-    type: S.literal('teardown'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('teardown'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const GetLogsMessage = S.struct({
-    type: S.literal('getLogs'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('getLogs'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const Message = S.union(
-    InitializeSchemaMessage,
-    QueryMessage,
-    StartTransactionMessage,
-    CommitTransactionMessage,
-    RollbackTransactionMessage,
-    TeardownMessage,
-    GetLogsMessage,
+  InitializeSchemaMessage,
+  QueryMessage,
+  StartTransactionMessage,
+  CommitTransactionMessage,
+  RollbackTransactionMessage,
+  TeardownMessage,
+  GetLogsMessage,
 )
 
 export type Message = S.Schema.Type<typeof Message>
 
 export type State = {
-    compiler: qc.QueryCompiler
-    driverAdapterManager: DriverAdaptersManager
-    driverAdapter: SqlDriverAdapter
-    transactionManager: TransactionManager
+  compiler: qc.QueryCompiler
+  driverAdapterManager: DriverAdaptersManager
+  driverAdapter: SqlDriverAdapter
+  transactionManager: TransactionManager
 }
 
 let state: State | undefined
 const logs: string[] = []
 
 if (!parentPort) {
-    throw new Error('This module must be run in a worker thread')
+  throw new Error('This module must be run in a worker thread')
 }
 
 setupDefaultPanicHandler()
 
 parentPort.on('message', async (rawMsg: unknown) => {
-    const msg = S.decodeUnknownSync(Message)(rawMsg)
-    let response: unknown
+  const msg = S.decodeUnknownSync(Message)(rawMsg)
+  let response: unknown
 
-    debug('worker received message:', msg.type)
+  debug('worker received message:', msg.type)
 
-    try {
-        response = await dispatchMessage(msg)
-    } catch (error) {
-        if (!(error instanceof Error)) {
-            // TODO: we should have a nicer mapping for driver adapter errors
-            error = new Error(JSON.stringify(error))
-        }
-        msg.responsePort.postMessage(error)
-        return
+  try {
+    response = await dispatchMessage(msg)
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      // TODO: we should have a nicer mapping for driver adapter errors
+      error = new Error(JSON.stringify(error))
     }
+    msg.responsePort.postMessage(error)
+    return
+  }
 
-    msg.responsePort.postMessage(response)
+  msg.responsePort.postMessage(response)
 })
 
 async function dispatchMessage(msg: Message): Promise<unknown> {
-    switch (msg.type) {
-        case 'initializeSchema':
-            return initializeSchema(msg.params, msg.env)
-        case 'query':
-            return query(msg.params, unwrapState(), logs)
-        case 'startTx':
-            return startTransaction(msg.params.options, unwrapState())
-        case 'commitTx':
-            return commitTransaction(msg.params.txId, unwrapState())
-        case 'rollbackTx':
-            return rollbackTransaction(msg.params.txId, unwrapState())
-        case 'teardown':
-            return teardown(unwrapState())
-        case 'getLogs':
-            return logs
-        default:
-            assertNever(
-                msg,
-                `Unknown message type: \`${(msg as { type: unknown }).type}\``,
-            )
-    }
+  switch (msg.type) {
+    case 'initializeSchema':
+      return initializeSchema(msg.params, msg.env)
+    case 'query':
+      return query(msg.params, unwrapState(), logs)
+    case 'startTx':
+      return startTransaction(msg.params.options, unwrapState())
+    case 'commitTx':
+      return commitTransaction(msg.params.txId, unwrapState())
+    case 'rollbackTx':
+      return rollbackTransaction(msg.params.txId, unwrapState())
+    case 'teardown':
+      return teardown(unwrapState())
+    case 'getLogs':
+      return logs
+    default:
+      assertNever(
+        msg,
+        `Unknown message type: \`${(msg as { type: unknown }).type}\``,
+      )
+  }
 }
 
 function unwrapState(): State {
-    if (state === undefined) {
-        throw new Error('State is not initialized, call `initializeSchema` first')
-    }
-    return state
+  if (state === undefined) {
+    throw new Error('State is not initialized, call `initializeSchema` first')
+  }
+  return state
 }
 
 async function initializeSchema(
-    params: InitializeSchemaParams,
-    env: Env,
+  params: InitializeSchemaParams,
+  env: Env,
 ): Promise<ConnectionInfo> {
-    const {url, schema, migrationScript} = params
-
-    const driverAdapterManager = await setupDriverAdaptersManager(
-        env,
-        migrationScript,
-    )
-
-    const {compiler, adapter} = await initQueryCompiler({
-        url,
-        driverAdapterManager,
-        schema,
-    })
-
-    const transactionManager = new TransactionManager({
-        driverAdapter: adapter,
-    })
-
-    state = {
-        compiler,
-        driverAdapterManager,
-        driverAdapter: adapter,
-        transactionManager,
-    }
-
-    if (adapter.getConnectionInfo) {
-        return adapter.getConnectionInfo()
-    }
-
-    return {maxBindValues: undefined}
+  const { url, schema, migrationScript } = params
+
+  const driverAdapterManager = await setupDriverAdaptersManager(
+    env,
+    migrationScript,
+  )
+
+  const { compiler, adapter } = await initQueryCompiler({
+    url,
+    driverAdapterManager,
+    schema,
+  })
+
+  const transactionManager = new TransactionManager({
+    driverAdapter: adapter,
+    transactionOptions: {
+      maxWait: 30,
+      timeout: 30,
path: libs/driver-adapters/executor/src/qc-test-worker/worker.ts
user: login: aqrln
body: Is 30 ms enough for all queries in tests, especially on slow CI runners? this looks like a potential source of flakiness 
created_at: 2025-03-26T11:14:21Z
author_association: MEMBER
diff_hunk: @@ -1,228 +1,223 @@
-import {Schema as S} from '@effect/schema'
-import type {ConnectionInfo, SqlDriverAdapter} from '@prisma/driver-adapter-utils'
-import {DriverAdaptersManager} from '../driver-adapters-manager'
+import { Schema as S } from '@effect/schema'
+import type { ConnectionInfo, SqlDriverAdapter } from '@prisma/driver-adapter-utils'
+import { DriverAdaptersManager } from '../driver-adapters-manager'
 import * as qc from '../query-compiler'
-import {TransactionManager} from '@prisma/client-engine-runtime'
-import {parentPort} from 'worker_threads'
-import {
-    CommitTxParams,
-    InitializeSchemaParams,
-    QueryParams,
-    RollbackTxParams,
-    StartTxParams,
-} from '../types/jsonRpc'
-import {assertNever, debug} from '../utils'
-import {setupDriverAdaptersManager} from '../setup'
-import {Env} from '../types'
-import {query} from './worker-query'
-import {
-    commitTransaction,
-    rollbackTransaction,
-    startTransaction,
-} from './worker-transaction'
-import {setupDefaultPanicHandler} from '../panic'
+import { TransactionManager, TransactionOptions } from '@prisma/client-engine-runtime'
+import { parentPort } from 'worker_threads'
+import { CommitTxParams, InitializeSchemaParams, QueryParams, RollbackTxParams, StartTxParams, } from '../types/jsonRpc'
+import { assertNever, debug } from '../utils'
+import { setupDriverAdaptersManager } from '../setup'
+import { Env } from '../types'
+import { query } from './worker-query'
+import { commitTransaction, rollbackTransaction, startTransaction, } from './worker-transaction'
+import { setupDefaultPanicHandler } from '../panic'
 
 const InitializeSchemaMessage = S.struct({
-    type: S.literal('initializeSchema'),
-    responsePort: S.instanceOf(MessagePort),
-    params: InitializeSchemaParams,
-    env: Env,
+  type: S.literal('initializeSchema'),
+  responsePort: S.instanceOf(MessagePort),
+  params: InitializeSchemaParams,
+  env: Env,
 })
 
 const QueryMessage = S.struct({
-    type: S.literal('query'),
-    responsePort: S.instanceOf(MessagePort),
-    params: QueryParams,
+  type: S.literal('query'),
+  responsePort: S.instanceOf(MessagePort),
+  params: QueryParams,
 })
 
 const StartTransactionMessage = S.struct({
-    type: S.literal('startTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: StartTxParams,
+  type: S.literal('startTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: StartTxParams,
 })
 
 const CommitTransactionMessage = S.struct({
-    type: S.literal('commitTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: CommitTxParams,
+  type: S.literal('commitTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: CommitTxParams,
 })
 
 const RollbackTransactionMessage = S.struct({
-    type: S.literal('rollbackTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: RollbackTxParams,
+  type: S.literal('rollbackTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: RollbackTxParams,
 })
 
 const TeardownMessage = S.struct({
-    type: S.literal('teardown'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('teardown'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const GetLogsMessage = S.struct({
-    type: S.literal('getLogs'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('getLogs'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const Message = S.union(
-    InitializeSchemaMessage,
-    QueryMessage,
-    StartTransactionMessage,
-    CommitTransactionMessage,
-    RollbackTransactionMessage,
-    TeardownMessage,
-    GetLogsMessage,
+  InitializeSchemaMessage,
+  QueryMessage,
+  StartTransactionMessage,
+  CommitTransactionMessage,
+  RollbackTransactionMessage,
+  TeardownMessage,
+  GetLogsMessage,
 )
 
 export type Message = S.Schema.Type<typeof Message>
 
 export type State = {
-    compiler: qc.QueryCompiler
-    driverAdapterManager: DriverAdaptersManager
-    driverAdapter: SqlDriverAdapter
-    transactionManager: TransactionManager
+  compiler: qc.QueryCompiler
+  driverAdapterManager: DriverAdaptersManager
+  driverAdapter: SqlDriverAdapter
+  transactionManager: TransactionManager
 }
 
 let state: State | undefined
 const logs: string[] = []
 
 if (!parentPort) {
-    throw new Error('This module must be run in a worker thread')
+  throw new Error('This module must be run in a worker thread')
 }
 
 setupDefaultPanicHandler()
 
 parentPort.on('message', async (rawMsg: unknown) => {
-    const msg = S.decodeUnknownSync(Message)(rawMsg)
-    let response: unknown
+  const msg = S.decodeUnknownSync(Message)(rawMsg)
+  let response: unknown
 
-    debug('worker received message:', msg.type)
+  debug('worker received message:', msg.type)
 
-    try {
-        response = await dispatchMessage(msg)
-    } catch (error) {
-        if (!(error instanceof Error)) {
-            // TODO: we should have a nicer mapping for driver adapter errors
-            error = new Error(JSON.stringify(error))
-        }
-        msg.responsePort.postMessage(error)
-        return
+  try {
+    response = await dispatchMessage(msg)
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      // TODO: we should have a nicer mapping for driver adapter errors
+      error = new Error(JSON.stringify(error))
     }
+    msg.responsePort.postMessage(error)
+    return
+  }
 
-    msg.responsePort.postMessage(response)
+  msg.responsePort.postMessage(response)
 })
 
 async function dispatchMessage(msg: Message): Promise<unknown> {
-    switch (msg.type) {
-        case 'initializeSchema':
-            return initializeSchema(msg.params, msg.env)
-        case 'query':
-            return query(msg.params, unwrapState(), logs)
-        case 'startTx':
-            return startTransaction(msg.params.options, unwrapState())
-        case 'commitTx':
-            return commitTransaction(msg.params.txId, unwrapState())
-        case 'rollbackTx':
-            return rollbackTransaction(msg.params.txId, unwrapState())
-        case 'teardown':
-            return teardown(unwrapState())
-        case 'getLogs':
-            return logs
-        default:
-            assertNever(
-                msg,
-                `Unknown message type: \`${(msg as { type: unknown }).type}\``,
-            )
-    }
+  switch (msg.type) {
+    case 'initializeSchema':
+      return initializeSchema(msg.params, msg.env)
+    case 'query':
+      return query(msg.params, unwrapState(), logs)
+    case 'startTx':
+      return startTransaction(msg.params.options, unwrapState())
+    case 'commitTx':
+      return commitTransaction(msg.params.txId, unwrapState())
+    case 'rollbackTx':
+      return rollbackTransaction(msg.params.txId, unwrapState())
+    case 'teardown':
+      return teardown(unwrapState())
+    case 'getLogs':
+      return logs
+    default:
+      assertNever(
+        msg,
+        `Unknown message type: \`${(msg as { type: unknown }).type}\``,
+      )
+  }
 }
 
 function unwrapState(): State {
-    if (state === undefined) {
-        throw new Error('State is not initialized, call `initializeSchema` first')
-    }
-    return state
+  if (state === undefined) {
+    throw new Error('State is not initialized, call `initializeSchema` first')
+  }
+  return state
 }
 
 async function initializeSchema(
-    params: InitializeSchemaParams,
-    env: Env,
+  params: InitializeSchemaParams,
+  env: Env,
 ): Promise<ConnectionInfo> {
-    const {url, schema, migrationScript} = params
-
-    const driverAdapterManager = await setupDriverAdaptersManager(
-        env,
-        migrationScript,
-    )
-
-    const {compiler, adapter} = await initQueryCompiler({
-        url,
-        driverAdapterManager,
-        schema,
-    })
-
-    const transactionManager = new TransactionManager({
-        driverAdapter: adapter,
-    })
-
-    state = {
-        compiler,
-        driverAdapterManager,
-        driverAdapter: adapter,
-        transactionManager,
-    }
-
-    if (adapter.getConnectionInfo) {
-        return adapter.getConnectionInfo()
-    }
-
-    return {maxBindValues: undefined}
+  const { url, schema, migrationScript } = params
+
+  const driverAdapterManager = await setupDriverAdaptersManager(
+    env,
+    migrationScript,
+  )
+
+  const { compiler, adapter } = await initQueryCompiler({
+    url,
+    driverAdapterManager,
+    schema,
+  })
+
+  const transactionManager = new TransactionManager({
+    driverAdapter: adapter,
+    transactionOptions: {
+      maxWait: 30,
+      timeout: 30,
path: libs/driver-adapters/executor/src/qc-test-worker/worker.ts
user: login: viktor-ferenczi
body: That's why the tests fail then. Thanks for the heads-up.

Where the unit is documented? Maybe we should name them `maxWaitMs` and `timeoutMs` to clarify.
created_at: 2025-03-26T11:45:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,228 +1,223 @@
-import {Schema as S} from '@effect/schema'
-import type {ConnectionInfo, SqlDriverAdapter} from '@prisma/driver-adapter-utils'
-import {DriverAdaptersManager} from '../driver-adapters-manager'
+import { Schema as S } from '@effect/schema'
+import type { ConnectionInfo, SqlDriverAdapter } from '@prisma/driver-adapter-utils'
+import { DriverAdaptersManager } from '../driver-adapters-manager'
 import * as qc from '../query-compiler'
-import {TransactionManager} from '@prisma/client-engine-runtime'
-import {parentPort} from 'worker_threads'
-import {
-    CommitTxParams,
-    InitializeSchemaParams,
-    QueryParams,
-    RollbackTxParams,
-    StartTxParams,
-} from '../types/jsonRpc'
-import {assertNever, debug} from '../utils'
-import {setupDriverAdaptersManager} from '../setup'
-import {Env} from '../types'
-import {query} from './worker-query'
-import {
-    commitTransaction,
-    rollbackTransaction,
-    startTransaction,
-} from './worker-transaction'
-import {setupDefaultPanicHandler} from '../panic'
+import { TransactionManager, TransactionOptions } from '@prisma/client-engine-runtime'
+import { parentPort } from 'worker_threads'
+import { CommitTxParams, InitializeSchemaParams, QueryParams, RollbackTxParams, StartTxParams, } from '../types/jsonRpc'
+import { assertNever, debug } from '../utils'
+import { setupDriverAdaptersManager } from '../setup'
+import { Env } from '../types'
+import { query } from './worker-query'
+import { commitTransaction, rollbackTransaction, startTransaction, } from './worker-transaction'
+import { setupDefaultPanicHandler } from '../panic'
 
 const InitializeSchemaMessage = S.struct({
-    type: S.literal('initializeSchema'),
-    responsePort: S.instanceOf(MessagePort),
-    params: InitializeSchemaParams,
-    env: Env,
+  type: S.literal('initializeSchema'),
+  responsePort: S.instanceOf(MessagePort),
+  params: InitializeSchemaParams,
+  env: Env,
 })
 
 const QueryMessage = S.struct({
-    type: S.literal('query'),
-    responsePort: S.instanceOf(MessagePort),
-    params: QueryParams,
+  type: S.literal('query'),
+  responsePort: S.instanceOf(MessagePort),
+  params: QueryParams,
 })
 
 const StartTransactionMessage = S.struct({
-    type: S.literal('startTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: StartTxParams,
+  type: S.literal('startTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: StartTxParams,
 })
 
 const CommitTransactionMessage = S.struct({
-    type: S.literal('commitTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: CommitTxParams,
+  type: S.literal('commitTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: CommitTxParams,
 })
 
 const RollbackTransactionMessage = S.struct({
-    type: S.literal('rollbackTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: RollbackTxParams,
+  type: S.literal('rollbackTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: RollbackTxParams,
 })
 
 const TeardownMessage = S.struct({
-    type: S.literal('teardown'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('teardown'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const GetLogsMessage = S.struct({
-    type: S.literal('getLogs'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('getLogs'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const Message = S.union(
-    InitializeSchemaMessage,
-    QueryMessage,
-    StartTransactionMessage,
-    CommitTransactionMessage,
-    RollbackTransactionMessage,
-    TeardownMessage,
-    GetLogsMessage,
+  InitializeSchemaMessage,
+  QueryMessage,
+  StartTransactionMessage,
+  CommitTransactionMessage,
+  RollbackTransactionMessage,
+  TeardownMessage,
+  GetLogsMessage,
 )
 
 export type Message = S.Schema.Type<typeof Message>
 
 export type State = {
-    compiler: qc.QueryCompiler
-    driverAdapterManager: DriverAdaptersManager
-    driverAdapter: SqlDriverAdapter
-    transactionManager: TransactionManager
+  compiler: qc.QueryCompiler
+  driverAdapterManager: DriverAdaptersManager
+  driverAdapter: SqlDriverAdapter
+  transactionManager: TransactionManager
 }
 
 let state: State | undefined
 const logs: string[] = []
 
 if (!parentPort) {
-    throw new Error('This module must be run in a worker thread')
+  throw new Error('This module must be run in a worker thread')
 }
 
 setupDefaultPanicHandler()
 
 parentPort.on('message', async (rawMsg: unknown) => {
-    const msg = S.decodeUnknownSync(Message)(rawMsg)
-    let response: unknown
+  const msg = S.decodeUnknownSync(Message)(rawMsg)
+  let response: unknown
 
-    debug('worker received message:', msg.type)
+  debug('worker received message:', msg.type)
 
-    try {
-        response = await dispatchMessage(msg)
-    } catch (error) {
-        if (!(error instanceof Error)) {
-            // TODO: we should have a nicer mapping for driver adapter errors
-            error = new Error(JSON.stringify(error))
-        }
-        msg.responsePort.postMessage(error)
-        return
+  try {
+    response = await dispatchMessage(msg)
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      // TODO: we should have a nicer mapping for driver adapter errors
+      error = new Error(JSON.stringify(error))
     }
+    msg.responsePort.postMessage(error)
+    return
+  }
 
-    msg.responsePort.postMessage(response)
+  msg.responsePort.postMessage(response)
 })
 
 async function dispatchMessage(msg: Message): Promise<unknown> {
-    switch (msg.type) {
-        case 'initializeSchema':
-            return initializeSchema(msg.params, msg.env)
-        case 'query':
-            return query(msg.params, unwrapState(), logs)
-        case 'startTx':
-            return startTransaction(msg.params.options, unwrapState())
-        case 'commitTx':
-            return commitTransaction(msg.params.txId, unwrapState())
-        case 'rollbackTx':
-            return rollbackTransaction(msg.params.txId, unwrapState())
-        case 'teardown':
-            return teardown(unwrapState())
-        case 'getLogs':
-            return logs
-        default:
-            assertNever(
-                msg,
-                `Unknown message type: \`${(msg as { type: unknown }).type}\``,
-            )
-    }
+  switch (msg.type) {
+    case 'initializeSchema':
+      return initializeSchema(msg.params, msg.env)
+    case 'query':
+      return query(msg.params, unwrapState(), logs)
+    case 'startTx':
+      return startTransaction(msg.params.options, unwrapState())
+    case 'commitTx':
+      return commitTransaction(msg.params.txId, unwrapState())
+    case 'rollbackTx':
+      return rollbackTransaction(msg.params.txId, unwrapState())
+    case 'teardown':
+      return teardown(unwrapState())
+    case 'getLogs':
+      return logs
+    default:
+      assertNever(
+        msg,
+        `Unknown message type: \`${(msg as { type: unknown }).type}\``,
+      )
+  }
 }
 
 function unwrapState(): State {
-    if (state === undefined) {
-        throw new Error('State is not initialized, call `initializeSchema` first')
-    }
-    return state
+  if (state === undefined) {
+    throw new Error('State is not initialized, call `initializeSchema` first')
+  }
+  return state
 }
 
 async function initializeSchema(
-    params: InitializeSchemaParams,
-    env: Env,
+  params: InitializeSchemaParams,
+  env: Env,
 ): Promise<ConnectionInfo> {
-    const {url, schema, migrationScript} = params
-
-    const driverAdapterManager = await setupDriverAdaptersManager(
-        env,
-        migrationScript,
-    )
-
-    const {compiler, adapter} = await initQueryCompiler({
-        url,
-        driverAdapterManager,
-        schema,
-    })
-
-    const transactionManager = new TransactionManager({
-        driverAdapter: adapter,
-    })
-
-    state = {
-        compiler,
-        driverAdapterManager,
-        driverAdapter: adapter,
-        transactionManager,
-    }
-
-    if (adapter.getConnectionInfo) {
-        return adapter.getConnectionInfo()
-    }
-
-    return {maxBindValues: undefined}
+  const { url, schema, migrationScript } = params
+
+  const driverAdapterManager = await setupDriverAdaptersManager(
+    env,
+    migrationScript,
+  )
+
+  const { compiler, adapter } = await initQueryCompiler({
+    url,
+    driverAdapterManager,
+    schema,
+  })
+
+  const transactionManager = new TransactionManager({
+    driverAdapter: adapter,
+    transactionOptions: {
+      maxWait: 30,
+      timeout: 30,
path: libs/driver-adapters/executor/src/qc-test-worker/worker.ts
user: login: viktor-ferenczi
body: Modified the timeouts here to match the values in `TransactionManager.test.ts` in the `prisma` repo.
created_at: 2025-03-26T11:49:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,228 +1,223 @@
-import {Schema as S} from '@effect/schema'
-import type {ConnectionInfo, SqlDriverAdapter} from '@prisma/driver-adapter-utils'
-import {DriverAdaptersManager} from '../driver-adapters-manager'
+import { Schema as S } from '@effect/schema'
+import type { ConnectionInfo, SqlDriverAdapter } from '@prisma/driver-adapter-utils'
+import { DriverAdaptersManager } from '../driver-adapters-manager'
 import * as qc from '../query-compiler'
-import {TransactionManager} from '@prisma/client-engine-runtime'
-import {parentPort} from 'worker_threads'
-import {
-    CommitTxParams,
-    InitializeSchemaParams,
-    QueryParams,
-    RollbackTxParams,
-    StartTxParams,
-} from '../types/jsonRpc'
-import {assertNever, debug} from '../utils'
-import {setupDriverAdaptersManager} from '../setup'
-import {Env} from '../types'
-import {query} from './worker-query'
-import {
-    commitTransaction,
-    rollbackTransaction,
-    startTransaction,
-} from './worker-transaction'
-import {setupDefaultPanicHandler} from '../panic'
+import { TransactionManager, TransactionOptions } from '@prisma/client-engine-runtime'
+import { parentPort } from 'worker_threads'
+import { CommitTxParams, InitializeSchemaParams, QueryParams, RollbackTxParams, StartTxParams, } from '../types/jsonRpc'
+import { assertNever, debug } from '../utils'
+import { setupDriverAdaptersManager } from '../setup'
+import { Env } from '../types'
+import { query } from './worker-query'
+import { commitTransaction, rollbackTransaction, startTransaction, } from './worker-transaction'
+import { setupDefaultPanicHandler } from '../panic'
 
 const InitializeSchemaMessage = S.struct({
-    type: S.literal('initializeSchema'),
-    responsePort: S.instanceOf(MessagePort),
-    params: InitializeSchemaParams,
-    env: Env,
+  type: S.literal('initializeSchema'),
+  responsePort: S.instanceOf(MessagePort),
+  params: InitializeSchemaParams,
+  env: Env,
 })
 
 const QueryMessage = S.struct({
-    type: S.literal('query'),
-    responsePort: S.instanceOf(MessagePort),
-    params: QueryParams,
+  type: S.literal('query'),
+  responsePort: S.instanceOf(MessagePort),
+  params: QueryParams,
 })
 
 const StartTransactionMessage = S.struct({
-    type: S.literal('startTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: StartTxParams,
+  type: S.literal('startTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: StartTxParams,
 })
 
 const CommitTransactionMessage = S.struct({
-    type: S.literal('commitTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: CommitTxParams,
+  type: S.literal('commitTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: CommitTxParams,
 })
 
 const RollbackTransactionMessage = S.struct({
-    type: S.literal('rollbackTx'),
-    responsePort: S.instanceOf(MessagePort),
-    params: RollbackTxParams,
+  type: S.literal('rollbackTx'),
+  responsePort: S.instanceOf(MessagePort),
+  params: RollbackTxParams,
 })
 
 const TeardownMessage = S.struct({
-    type: S.literal('teardown'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('teardown'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const GetLogsMessage = S.struct({
-    type: S.literal('getLogs'),
-    responsePort: S.instanceOf(MessagePort),
+  type: S.literal('getLogs'),
+  responsePort: S.instanceOf(MessagePort),
 })
 
 const Message = S.union(
-    InitializeSchemaMessage,
-    QueryMessage,
-    StartTransactionMessage,
-    CommitTransactionMessage,
-    RollbackTransactionMessage,
-    TeardownMessage,
-    GetLogsMessage,
+  InitializeSchemaMessage,
+  QueryMessage,
+  StartTransactionMessage,
+  CommitTransactionMessage,
+  RollbackTransactionMessage,
+  TeardownMessage,
+  GetLogsMessage,
 )
 
 export type Message = S.Schema.Type<typeof Message>
 
 export type State = {
-    compiler: qc.QueryCompiler
-    driverAdapterManager: DriverAdaptersManager
-    driverAdapter: SqlDriverAdapter
-    transactionManager: TransactionManager
+  compiler: qc.QueryCompiler
+  driverAdapterManager: DriverAdaptersManager
+  driverAdapter: SqlDriverAdapter
+  transactionManager: TransactionManager
 }
 
 let state: State | undefined
 const logs: string[] = []
 
 if (!parentPort) {
-    throw new Error('This module must be run in a worker thread')
+  throw new Error('This module must be run in a worker thread')
 }
 
 setupDefaultPanicHandler()
 
 parentPort.on('message', async (rawMsg: unknown) => {
-    const msg = S.decodeUnknownSync(Message)(rawMsg)
-    let response: unknown
+  const msg = S.decodeUnknownSync(Message)(rawMsg)
+  let response: unknown
 
-    debug('worker received message:', msg.type)
+  debug('worker received message:', msg.type)
 
-    try {
-        response = await dispatchMessage(msg)
-    } catch (error) {
-        if (!(error instanceof Error)) {
-            // TODO: we should have a nicer mapping for driver adapter errors
-            error = new Error(JSON.stringify(error))
-        }
-        msg.responsePort.postMessage(error)
-        return
+  try {
+    response = await dispatchMessage(msg)
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      // TODO: we should have a nicer mapping for driver adapter errors
+      error = new Error(JSON.stringify(error))
     }
+    msg.responsePort.postMessage(error)
+    return
+  }
 
-    msg.responsePort.postMessage(response)
+  msg.responsePort.postMessage(response)
 })
 
 async function dispatchMessage(msg: Message): Promise<unknown> {
-    switch (msg.type) {
-        case 'initializeSchema':
-            return initializeSchema(msg.params, msg.env)
-        case 'query':
-            return query(msg.params, unwrapState(), logs)
-        case 'startTx':
-            return startTransaction(msg.params.options, unwrapState())
-        case 'commitTx':
-            return commitTransaction(msg.params.txId, unwrapState())
-        case 'rollbackTx':
-            return rollbackTransaction(msg.params.txId, unwrapState())
-        case 'teardown':
-            return teardown(unwrapState())
-        case 'getLogs':
-            return logs
-        default:
-            assertNever(
-                msg,
-                `Unknown message type: \`${(msg as { type: unknown }).type}\``,
-            )
-    }
+  switch (msg.type) {
+    case 'initializeSchema':
+      return initializeSchema(msg.params, msg.env)
+    case 'query':
+      return query(msg.params, unwrapState(), logs)
+    case 'startTx':
+      return startTransaction(msg.params.options, unwrapState())
+    case 'commitTx':
+      return commitTransaction(msg.params.txId, unwrapState())
+    case 'rollbackTx':
+      return rollbackTransaction(msg.params.txId, unwrapState())
+    case 'teardown':
+      return teardown(unwrapState())
+    case 'getLogs':
+      return logs
+    default:
+      assertNever(
+        msg,
+        `Unknown message type: \`${(msg as { type: unknown }).type}\``,
+      )
+  }
 }
 
 function unwrapState(): State {
-    if (state === undefined) {
-        throw new Error('State is not initialized, call `initializeSchema` first')
-    }
-    return state
+  if (state === undefined) {
+    throw new Error('State is not initialized, call `initializeSchema` first')
+  }
+  return state
 }
 
 async function initializeSchema(
-    params: InitializeSchemaParams,
-    env: Env,
+  params: InitializeSchemaParams,
+  env: Env,
 ): Promise<ConnectionInfo> {
-    const {url, schema, migrationScript} = params
-
-    const driverAdapterManager = await setupDriverAdaptersManager(
-        env,
-        migrationScript,
-    )
-
-    const {compiler, adapter} = await initQueryCompiler({
-        url,
-        driverAdapterManager,
-        schema,
-    })
-
-    const transactionManager = new TransactionManager({
-        driverAdapter: adapter,
-    })
-
-    state = {
-        compiler,
-        driverAdapterManager,
-        driverAdapter: adapter,
-        transactionManager,
-    }
-
-    if (adapter.getConnectionInfo) {
-        return adapter.getConnectionInfo()
-    }
-
-    return {maxBindValues: undefined}
+  const { url, schema, migrationScript } = params
+
+  const driverAdapterManager = await setupDriverAdaptersManager(
+    env,
+    migrationScript,
+  )
+
+  const { compiler, adapter } = await initQueryCompiler({
+    url,
+    driverAdapterManager,
+    schema,
+  })
+
+  const transactionManager = new TransactionManager({
+    driverAdapter: adapter,
+    transactionOptions: {
+      maxWait: 30,
+      timeout: 30,
path: libs/driver-adapters/executor/src/qc-test-worker/worker.ts
user: login: viktor-ferenczi
body: Documented the units in `TransactionManager.test.ts` in the `prisma` repo.
created_at: 2025-03-26T11:54:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: codspeed-hq[bot]
created_at: 2025-03-24T15:03:35Z
body: <!-- __CODSPEED_PERFORMANCE_REPORT_COMMENT__ -->
<!-- __CODSPEED_INSTRUMENTATION_PERFORMANCE_REPORT_COMMENT__ -->

## [CodSpeed Performance Report](https://codspeed.io/prisma/prisma-engines/branches/feat%2Ftransactional-graphs)

### Merging #5291 will **not alter performance**

<sub>Comparing <code>feat/transactional-graphs</code> (c5bc59c) with <code>main</code> (9061db3)</sub>



### Summary

`✅ 11` untouched benchmarks  




author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-24T15:05:38Z
body: <!-- wasm-size -->
### WASM Query Engine file Size

|Engine            | This PR                                      | Base branch                                      | Diff
|------------------|----------------------------------------------|--------------------------------------------------|-----------------------------------------------
| Postgres         | 2.147MiB    |  2.147MiB    | 0.000B
| Postgres (gzip)  | 866.997KiB |  866.997KiB | 0.000B
| Mysql            | 2.111MiB         |  2.111MiB          | 0.000B
| Mysql (gzip)     | 851.584KiB      |  851.584KiB       | 0.000B
| Sqlite           | 2.021MiB        |  2.021MiB         | 0.000B
| Sqlite (gzip)    | 815.426KiB     |  815.425KiB     | 1.000B
author_association: CONTRIBUTOR