Skip to content

TML-3167: Split SQL query and execute driver SPI - #29907

Merged
SevInf merged 19 commits into
mainfrom
tml-3167-query-execute-split
Aug 6, 2026
Merged

TML-3167: Split SQL query and execute driver SPI#29907
SevInf merged 19 commits into
mainfrom
tml-3167-query-execute-split

Conversation

@StevenMcClankerton

@StevenMcClankerton StevenMcClankerton commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-3167

At a glance

interface SqlQueryable {
  query<Row>(request: SqlExecuteRequest): AsyncIterable<Row>
  execute(request: SqlExecuteRequest): Promise<SqlStatementStats>
}

Drivers now distinguish streaming rows from DML execution statistics instead of exposing separate prepared variants or a buffered query API.

Decision

This PR ships the Slice 1 driver-SPI split for affected-row counts: query() streams rows, execute() returns { affectedRows }, and prepared execution is represented by an optional handle on the request. PostgreSQL and SQLite implement the final surface; the SQL runtime, Supabase runtime, adapters, and test doubles use it end to end.

It also makes a failed PostgreSQL stale prepared-statement retry emit the structural DRIVER.PREPARE_FAILED envelope required by ADR 239, preserving the normalized driver error as its cause.

Reviewer notes

  • This is a deliberate hard-cut migration. The broad test/fake changes are mechanical consumers of the two-method driver contract, not independent behavior changes.
  • PostgreSQL counts rowCount; SQLite counts stmt.run().changes. Their distinct engine semantics are intentionally preserved.
  • SQLite rejects a RETURNING statement routed to execute() before execution, preventing silent row loss.
  • pnpm test:packages still has unrelated telemetry/CLI harness failures: telemetry-backend cannot locate prisma-next, and seven CLI process tests exceed their timeout. The changed packages and workspace typecheck are green.

How it fits together

  1. The relational driver contract exposes one streaming path and one statistics path in packages/2-sql/4-lanes/relational-core/src/ast/driver-types.ts.
  2. PostgreSQL maps buffered command results to { affectedRows }, while SQLite maps StatementSync.run().changes and retains its defensive RETURNING guard.
  3. Preparedness moves onto the request, letting packages/2-sql/5-runtime/src/sql-runtime.ts use one streaming execution pipeline for ad-hoc and prepared plans.
  4. Supabase session setup, target adapters, runtime helpers, and all fakes consume the same request-shaped contract.

Behavior changes & evidence

  • Drivers report write statistics directly through execute(request).
    • Implementation: packages/3-targets/7-drivers/postgres/src/postgres-driver.ts, packages/3-targets/7-drivers/sqlite/src/sqlite-driver.ts
    • Evidence: packages/3-targets/7-drivers/postgres/test/driver.basic.test.ts, packages/3-targets/7-drivers/sqlite/test/sqlite-driver.test.ts
  • Prepared retry failures carry a stable DRIVER error code and original cause.
    • Implementation: packages/3-targets/7-drivers/postgres/src/postgres-driver.ts, packages/3-targets/7-drivers/postgres/src/driver-error.ts
    • Evidence: packages/3-targets/7-drivers/postgres/test/driver.prepared.test.ts
  • The runtime has one streamed-row execution flow for normal and prepared plans.
    • Implementation: packages/2-sql/5-runtime/src/sql-runtime.ts
    • Evidence: packages/2-sql/5-runtime/test/prepared.test.ts, packages/2-sql/5-runtime/test/plan-execution-id.test.ts

Compatibility / migration / risk

This is a breaking internal driver SPI change. All in-repository implementations and fakes are migrated in this PR. ORM-visible count-terminal behavior remains unchanged; the count-terminal behavior change belongs to TML-3168.

Testing performed

  • pnpm typecheck — 165/165 tasks passed
  • pnpm lint:deps passed
  • PostgreSQL driver typecheck, test (127 tests), and lint passed
  • SQLite driver typecheck, test (29 tests), and lint passed
  • Supabase build, typecheck, lint, and test (89 tests) passed
  • executePrepared and SqlQueryResult searches under packages/ and test/ returned zero results
  • pnpm test:packages ran but has the unrelated telemetry/CLI harness failures noted above

Skill update

n/a — internal SPI refactor; no end-user skill surface changed.

Alternatives considered

  • Keeping a separate executePrepared() method was rejected because preparedness is a request property, not a distinct execution operation.
  • Returning statistics in the row stream was rejected because count consumers should not need to demultiplex row and metadata frames.
  • Normalizing affected-row semantics across engines was rejected; each driver reports its native engine result.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • The Skill update section is filled in.

Summary by CodeRabbit

  • New Features

    • SQL queries now stream rows asynchronously, improving support for large result sets.
    • Statement execution reports affected-row statistics.
    • Prepared statements reuse handles and recover from stale handles.
    • PostgreSQL and SQLite support the unified SQL execution interface.
    • Added structured PostgreSQL driver errors with standardized codes and severity metadata.
  • Breaking Changes

    • Removed separate prepared-execution methods and the legacy query-result format.
    • Query and execution methods now use request objects.

@StevenMcClankerton
StevenMcClankerton requested a review from a team as a code owner August 6, 2026 10:25
@SevInf
SevInf force-pushed the tml-3167-query-execute-split branch from 2993440 to 78c43b1 Compare August 6, 2026 10:26
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The SQL API now separates row streaming from statement execution. query accepts request objects and returns async row iterables. execute returns statement statistics. Runtime, PostgreSQL, SQLite, Supabase, adapters, and tests use unified prepared-statement handling.

Changes

Unified SQL API migration

Layer / File(s) Summary
Relational contracts and runtime executor
packages/2-sql/4-lanes/relational-core/src/**, packages/2-sql/5-runtime/src/**, packages/2-sql/5-runtime/test/**
The driver contracts now use request-based async queries, statistics-returning execution, and shared prepared-statement handles. Runtime plan and prepared execution now use one executor.
PostgreSQL driver and coverage
packages/3-targets/7-drivers/postgres/src/**, packages/3-targets/7-drivers/postgres/test/**
PostgreSQL separates streaming queries from statement execution, retries stale prepared statements, updates handles, and reports structured preparation errors. Tests use shared SQL helpers and the new APIs.
SQLite driver and adapters
packages/3-targets/7-drivers/sqlite/**, packages/3-targets/6-adapters/{postgres,sqlite}/**
SQLite shares query behavior across connections and transactions, returns affected-row statistics, rejects row-producing execute statements, and streams rows through query. Adapters collect async query results.
Supabase integration
packages/3-extensions/supabase/**
Supabase configuration and reset operations drain query streams. Connection and transaction execution use scoped executors.
Integration runtime helpers
test/integration/test/sql-orm-client/runtime-helpers.ts
Integration recording supports both plan and prepared-statement execution overloads while preserving plan recording.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • prisma/prisma#29839: Changes PostgreSQL driver query and execute paths that overlap with this PR’s PostgreSQL execution and serialization changes.

Suggested labels: lgtm

Suggested reviewers: aqrln

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: splitting the SQL driver SPI into separate query and execute operations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-3167-query-execute-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

prisma-next

npm i https://pkg.pr.new/prisma-next@29907

@prisma/orm-extension-arktype-json

npm i https://pkg.pr.new/@prisma/orm-extension-arktype-json@29907

@prisma/orm-extension-middleware-cache

npm i https://pkg.pr.new/@prisma/orm-extension-middleware-cache@29907

@prisma/orm-extension-paradedb

npm i https://pkg.pr.new/@prisma/orm-extension-paradedb@29907

@prisma/orm-extension-pgvector

npm i https://pkg.pr.new/@prisma/orm-extension-pgvector@29907

@prisma/orm-extension-postgis

npm i https://pkg.pr.new/@prisma/orm-extension-postgis@29907

@prisma/orm-extension-supabase

npm i https://pkg.pr.new/@prisma/orm-extension-supabase@29907

@prisma/orm-family-mongo

npm i https://pkg.pr.new/@prisma/orm-family-mongo@29907

@prisma/orm-family-sql

npm i https://pkg.pr.new/@prisma/orm-family-sql@29907

@prisma/orm-framework

npm i https://pkg.pr.new/@prisma/orm-framework@29907

@prisma/orm-mongo

npm i https://pkg.pr.new/@prisma/orm-mongo@29907

@prisma/orm-postgres

npm i https://pkg.pr.new/@prisma/orm-postgres@29907

@prisma/orm-sqlite

npm i https://pkg.pr.new/@prisma/orm-sqlite@29907

@prisma/orm-target-mongo

npm i https://pkg.pr.new/@prisma/orm-target-mongo@29907

@prisma/orm-target-postgres

npm i https://pkg.pr.new/@prisma/orm-target-postgres@29907

@prisma/orm-target-sqlite

npm i https://pkg.pr.new/@prisma/orm-target-sqlite@29907

@prisma/orm-toolchain

npm i https://pkg.pr.new/@prisma/orm-toolchain@29907

commit: 152ce8a

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
postgres / no-emit 167.01 KB (+0.16% 🔺)
postgres / emit 145.83 KB (+0.22% 🔺)
mongo / no-emit 100.4 KB (0%)
mongo / emit 90.24 KB (0%)
cf-worker / no-emit 191.61 KB (+0.17% 🔺)
cf-worker / emit 168.08 KB (+0.18% 🔺)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/3-targets/7-drivers/sqlite/src/sqlite-driver.ts (1)

110-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Keep SqliteConnectionImpl package-private.

Expose SqlConnection through a createSqliteConnection() factory. Change public consumers, including acquireConnection(), to depend on SqlConnection instead of SqliteConnectionImpl. Agree the scope with maintainers before changing this package boundary. Based on coding guidelines, “Expose stateful services through an interface plus a createX() factory; keep the implementing class package-private and make consumers depend on the interface.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-targets/7-drivers/sqlite/src/sqlite-driver.ts` around lines 110 -
131, Keep SqliteConnectionImpl package-private and expose connections only
through a createSqliteConnection() factory returning SqlConnection. Update
acquireConnection() and other public consumers to type against SqlConnection
rather than the implementation class, preserving existing behavior and
coordinating the package-boundary change with maintainers.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/2-sql/5-runtime/src/sql-runtime.ts`:
- Around line 622-636: Scope `#preparedStatementHandles` entries by both the
physical client and PreparedStatementImpl, so different PostgresQueryable
instances cannot reuse each other’s prepared-statement handles. Update the
get/set closures in the prepared-statement request construction to use the
current physical client as part of the key while preserving existing handle
reuse and retry behavior.

---

Nitpick comments:
In `@packages/3-targets/7-drivers/sqlite/src/sqlite-driver.ts`:
- Around line 110-131: Keep SqliteConnectionImpl package-private and expose
connections only through a createSqliteConnection() factory returning
SqlConnection. Update acquireConnection() and other public consumers to type
against SqlConnection rather than the implementation class, preserving existing
behavior and coordinating the package-boundary change with maintainers.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 293c52bc-c239-4a9d-9129-0801376e718b

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc73aa and 78c43b1.

⛔ Files ignored due to path filters (4)
  • projects/affected-row-counts/plan.md is excluded by !projects/**
  • projects/affected-row-counts/slices/query-execute-split/plan.md is excluded by !projects/**
  • projects/affected-row-counts/slices/query-execute-split/spec.md is excluded by !projects/**
  • projects/affected-row-counts/spec.md is excluded by !projects/**
📒 Files selected for processing (39)
  • packages/2-sql/4-lanes/relational-core/src/ast/driver-types.ts
  • packages/2-sql/4-lanes/relational-core/test/ast/driver-types.test.ts
  • packages/2-sql/5-runtime/src/prepared/prepared-statement.ts
  • packages/2-sql/5-runtime/src/prepared/types.ts
  • packages/2-sql/5-runtime/src/sql-runtime.ts
  • packages/2-sql/5-runtime/test/async-iterable-result.test.ts
  • packages/2-sql/5-runtime/test/intercept-decoding.test.ts
  • packages/2-sql/5-runtime/test/marker-verification.test.ts
  • packages/2-sql/5-runtime/test/marker-vs-intercept-ordering.test.ts
  • packages/2-sql/5-runtime/test/plan-execution-id.test.ts
  • packages/2-sql/5-runtime/test/prepared.test.ts
  • packages/2-sql/5-runtime/test/raw-connection-seam.test.ts
  • packages/2-sql/5-runtime/test/runtime-ctx-passthrough.test.ts
  • packages/2-sql/5-runtime/test/scope-plumbing.test.ts
  • packages/2-sql/5-runtime/test/sql-family-adapter.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime-abort.test.ts
  • packages/2-sql/5-runtime/test/sql-runtime.test.ts
  • packages/3-extensions/supabase/src/runtime/supabase-runtime.ts
  • packages/3-extensions/supabase/test/supabase-runtime.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/test/adapter.test.ts
  • packages/3-targets/6-adapters/sqlite/src/core/adapter.ts
  • packages/3-targets/7-drivers/postgres/src/driver-error.ts
  • packages/3-targets/7-drivers/postgres/src/exports/runtime.ts
  • packages/3-targets/7-drivers/postgres/src/postgres-driver.ts
  • packages/3-targets/7-drivers/postgres/test/driver.basic.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.errors.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.pinned-client-serialization.integration.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.pinned-client-serialization.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.prepared.integration.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.prepared.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.stream-portal-protection.integration.test.ts
  • packages/3-targets/7-drivers/postgres/test/driver.unbound.test.ts
  • packages/3-targets/7-drivers/postgres/test/sql-queryable-test-utils.ts
  • packages/3-targets/7-drivers/sqlite/src/sqlite-driver.ts
  • packages/3-targets/7-drivers/sqlite/test/runtime-driver.test.ts
  • packages/3-targets/7-drivers/sqlite/test/sql-queryable-test-utils.ts
  • packages/3-targets/7-drivers/sqlite/test/sqlite-driver.test.ts
  • test/integration/test/sql-orm-client/runtime-helpers.ts

Comment thread packages/2-sql/5-runtime/src/sql-runtime.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/3-targets/7-drivers/postgres/src/postgres-driver.ts`:
- Around line 206-225: Restrict the stale-handle retry logic in
withStaleHandleRetry: retry SQLSTATE 26000 directly, but retry 0A000 only when
the PostgreSQL error message indicates cached-plan invalidation (for example,
“cached plan must not change result type”); otherwise rethrow the original
error. Add a regression test covering a non-stale 0A000 failure and verify it is
not converted to DRIVER.PREPARE_FAILED.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 266b5d51-b556-4e3a-a5d9-da899266ffe4

📥 Commits

Reviewing files that changed from the base of the PR and between 78c43b1 and ac84dbe.

📒 Files selected for processing (2)
  • packages/3-targets/7-drivers/postgres/src/postgres-driver.ts
  • packages/3-targets/7-drivers/postgres/test/driver.prepared.test.ts

Comment thread packages/3-targets/7-drivers/postgres/src/postgres-driver.ts
@SevInf
SevInf force-pushed the tml-3167-query-execute-split branch from ac84dbe to 297e799 Compare August 6, 2026 14:20
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Comment thread packages/2-sql/5-runtime/src/sql-runtime.ts Outdated
Comment thread packages/3-targets/7-drivers/postgres/src/postgres-driver.ts Outdated
Comment thread packages/3-targets/7-drivers/postgres/src/postgres-driver.ts Outdated
Comment thread packages/3-targets/7-drivers/postgres/src/postgres-driver.ts Outdated
Comment thread packages/2-sql/5-runtime/src/sql-runtime.ts Outdated
Comment thread packages/2-sql/5-runtime/src/sql-runtime.ts Outdated
Comment thread packages/3-extensions/supabase/src/runtime/supabase-runtime.ts Outdated
Comment thread packages/2-sql/5-runtime/test/async-iterable-result.test.ts Outdated
@SevInf
SevInf enabled auto-merge August 6, 2026 15:45
SevInf and others added 11 commits August 6, 2026 17:50
Shaping artifacts for the **affected-row-counts** project — spec and
three-slice plan, per the project lifecycle in `projects/README.md`.

## What this project does

`updateAndCount` and `deleteAndCount` run two statements: a `SELECT` of
every matching primary key, then the write — returning the *read's* row
count and discarding whatever the write reported. That is not atomic
(outside a transaction a concurrent insert is updated but not counted),
it evaluates the filter twice and materialises every matching key in JS
purely to call `.length` on it, and it builds the two `WHERE` clauses
through different code paths that already drifted once for MTI variants
(#940).

The count already exists and is thrown away — Postgres reports it in the
`CommandComplete` tag, SQLite in `sqlite3_changes64()` via
`StatementSync.run()`. The gap is structural: `RuntimeScope.execute()`
returns a row stream with nowhere for statement metadata to live.

After this project the driver SPI splits along the question being asked,
named the way every prior art names it (JDBC, ADO.NET, Go):

    query<Row>(req): AsyncIterable<Row>          // rows
    execute(req):    Promise<SqlStatementStats>  // { affectedRows: number }

`affectedRows` is not optional — absence is not a state either engine has
for the statements `execute()` exists to serve. Statistics never travel
through a row stream, so the seven `for await` re-wrap sites between
driver and caller stop being a hazard. Prepared-ness rides on the request
rather than doubling the method surface, so four driver methods become
two.

## Scope boundaries

Streaming write terminals, `createAndCount`, and new targets are out.
Count semantics are deliberately *not* unified across targets —
Postgres's command tag, SQLite's `sqlite3_changes64()`, and Mongo's
`modifiedCount` each mean something different, and the project documents
the difference rather than reconciling it.

## Decision provenance

Three questions were settled with the operator at spec time and moved
into the spec body: the execution shape (an earlier single-`execute()`
frame-yielding design was considered and reversed), the naming falling
out of that shape, and per-driver count semantics. Spec § Open Questions
records the reversal.

The project amends ADR 210 — Prepared Statements rather than adding a new
ADR: every principle it states survives, only the shape they were
expressed through changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Six-dispatch decomposition for TML-3167, in the hard-cut-migration shape:
conformance fix, then interface + postgres reference implementation,
sqlite, the runtime merge, supabase, and the test-fake fan-out that
closes the grep gate.

Two findings from grounding that the project spec did not anticipate:
the cursor-side count extraction disappears entirely (statistics no
longer ride the row stream, so postgres execute() is just the buffered
path), and supabase openRoleSession issues three buffered query() calls
on a raw runtime connection — a judgment site the "control plane is a
separate interface" boundary does not cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
…andle retry fails

ADR 210 § Stale-handle retry requires a failed re-prepare to surface the
ADAPTER.PREPARE_FAILED envelope with the originating driver error as
`cause`; the retry path rethrew a bare normalised pg error instead, so
consumers had no stable code to match on.

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
SevInf added 6 commits August 6, 2026 17:50
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
@SevInf
SevInf force-pushed the tml-3167-query-execute-split branch from 637a084 to 51328f4 Compare August 6, 2026 15:50
SevInf added 2 commits August 6, 2026 15:53
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
@SevInf
SevInf added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 4cb9b12 Aug 6, 2026
3 of 7 checks passed
@SevInf
SevInf deleted the tml-3167-query-execute-split branch August 6, 2026 16:03
Shgit29 pushed a commit to Shgit29/prisma that referenced this pull request Aug 12, 2026
## Linked issue

Refs [TML-3168](https://linear.app/prisma-company/issue/TML-3168)

Prerequisite: prisma#29907 provides the driver-level `query()` / `execute()`
split consumed here.

## At a glance

```ts
const count = await users.where({ name: 'Stale' }).updateAndCount({ name: 'Updated' });
expect(count).toBe(2);
expect(observedStatements).toHaveLength(1);
```

This real integration case now gets the count from the single `UPDATE`;
previously the terminal selected matching rows before writing.

## Summary

Count-returning writes now report the database's affected-row result
from the write itself, closing the race and extra round trip created by
a pre-`SELECT`. The runtime hard cut makes row queries and statistics
execution explicit all the way through middleware and bound scopes.

## Decision

This PR ships two connected changes:

1. Runtime callers explicitly choose `query()` / `queryPrepared()` for
rows or `execute()` for statement statistics, with
operation-discriminated middleware results.
2. SQL `updateAndCount` and `deleteAndCount` execute one non-returning
write and return `stats.affectedRows`, while Mongo preserves its native
modified/deleted count semantics through the same vocabulary.

## Reviewer notes

- The large mechanical fan-out is deliberate: this is a
compatibility-free runtime vocabulary hard cut, so examples, fakes,
integrations, Mongo, SQL, and Supabase callers migrate together.
- Middleware rejects a query result offered to `execute()` or statistics
offered to `query()`; it never converts row length into `affectedRows`.
- `updateAndCount({})` remains a zero-statement no-op.
- Counts remain target-native rather than normalized: Mongo updates use
`modifiedCount`; SQL uses each driver's affected-row source.
- Contract entities, capabilities, `contract.json`, and `contract.d.ts`
are unchanged.
- The package and integration aggregate commands remain locally red only
in unrelated CLI timeout tests; affected package suites, typecheck, E2E,
fixtures, and focused integration coverage are green.

## How it fits together

1. The shared runtime contract separates streaming rows from eager
statement statistics in
[runtime-middleware.ts](packages/1-framework/1-core/framework-components/src/execution/runtime-middleware.ts)
and
[runtime-scope.ts](packages/2-sql/4-lanes/relational-core/src/runtime-scope.ts).
2. Runtime middleware carries an `operation` discriminant through
interception and completion, preserving cancellation, codecs, telemetry,
fresh execution IDs, and lifecycle hooks for both paths.
3. SQL connection and transaction scopes, Mongo runtime, and Supabase
role-bound scopes route each operation to the already-split driver
surface from prisma#29907 without losing connection binding or cleanup
behavior.
4. The ORM count terminals in
[collection.ts](packages/3-extensions/sql-orm-client/src/collection.ts)
compile one non-returning DML plan, call `execute()`, and return the
driver's `affectedRows` unchanged.

## Behavior changes & evidence

- **`updateAndCount` returns a write-derived count from one `UPDATE`.**
The empty-data case still returns zero without execution.
Implementation:
[collection.ts](packages/3-extensions/sql-orm-client/src/collection.ts).
Evidence:
[update.test.ts](test/integration/test/sql-orm-client/update.test.ts)
and
[count-terminal-interleaving.test.ts](test/integration/test/sql-orm-client/count-terminal-interleaving.test.ts).
- **`deleteAndCount` returns a write-derived count from one `DELETE`.**
Implementation:
[collection.ts](packages/3-extensions/sql-orm-client/src/collection.ts).
Evidence:
[delete.test.ts](test/integration/test/sql-orm-client/delete.test.ts).
- **Row and statistics interception cannot be confused.**
Wrong-operation middleware results fail loudly, while bound SQL and
Supabase scopes retain their connection and transaction semantics.
Implementation:
[runtime-middleware.ts](packages/1-framework/1-core/framework-components/src/execution/runtime-middleware.ts)
and [sql-runtime.ts](packages/2-sql/5-runtime/src/sql-runtime.ts).
Evidence:
[run-execute-with-middleware.test.ts](packages/1-framework/1-core/framework-components/test/run-execute-with-middleware.test.ts)
and
[sql-runtime.test.ts](packages/2-sql/5-runtime/test/sql-runtime.test.ts).
- **Mongo keeps engine-native count meaning while adopting the explicit
operations.** Update statistics map from `modifiedCount`; delete
statistics map from `deletedCount`. Implementation:
[mongo-runtime.ts](packages/2-mongo-family/7-runtime/src/mongo-runtime.ts)
and
[collection.ts](packages/2-mongo-family/5-query-builders/orm/src/collection.ts).
Evidence:
[mongo-middleware.test.ts](packages/2-mongo-family/7-runtime/test/mongo-middleware.test.ts)
and
[collection.test.ts](packages/2-mongo-family/5-query-builders/orm/test/collection.test.ts).

## Compatibility / migration / risk

This is a deliberate public runtime hard cut: row consumers must use
`query()` / `queryPrepared()`, and `execute()` now means statistics.
Upgrade instructions are recorded in
`skills/prisma-next-upgrade/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/` and
`skills/prisma-8-extension-upgrade/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/`.
There are no contract artifact or capability changes. The principal risk
is missed callers across the broad migration; public type tests, focused
package suites, integrations, and E2E coverage exercise the migrated
surfaces.

## Testing performed

- `pnpm typecheck` — 165/165 workspace tasks passed.
- `pnpm lint:deps` — 1,921 modules and 2,934 dependencies passed.
- `pnpm test:e2e` — 20 files and 112 tests passed.
- `pnpm fixtures:check` — passed with no tracked fixture delta.
- Affected framework, SQL runtime, SQL ORM, Mongo, Supabase, cache,
example, and focused integration suites passed; the final React Router
correction passed typecheck, lint, and 2 focused tests.
- `pnpm test:packages` — 14,542 tests passed; the command remained red
in nine unrelated CLI tests that exceeded their 500 ms timeout.
- `pnpm test:integration` — 1,819 tests passed; the command remained red
in four CLI journey timeouts and one CLI cleanup-hook timeout.
- `pnpm test:examples` passed all locally runnable example tasks; the
Cloudflare Worker suite requires an unavailable Hyperdrive database
environment.

## Follow-ups

- [TML-3169](https://linear.app/prisma-company/issue/TML-3169) owns the
broader count-semantics documentation and scorecard work.

## Skill update

This public hard cut adds `runtime-query-execute-hard-cut` entries at:

-
`skills/prisma-next-upgrade/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md`
-
`skills/prisma-8-extension-upgrade/upgrades/8.0.0-rc.1-to-8.0.0-rc.2/instructions.md`

## Alternatives considered

- **Keep the pre-`SELECT` count:** rejected because it adds a round trip
and can become stale before the write executes.
- **Infer query versus statistics from SQL or plan shape:** rejected
because lowering state does not define result semantics and inference
makes middleware ambiguous.
- **Add prepared statistics execution now:** rejected because there is
no prepared count caller; `queryPrepared()` covers the existing prepared
row use case without speculative API surface.
- **Normalize counts across targets:** rejected so Postgres/SQLite and
Mongo continue to expose their engines' established affected versus
modified semantics.

## Checklist

- [x] All commits are signed off (`git commit -s`) per the
[DCO](../CONTRIBUTING.md#developer-certificate-of-origin-dco). The DCO
status check will block merge if any commit is missing a
`Signed-off-by:` trailer.
- [x] I read [CONTRIBUTING.md](../CONTRIBUTING.md) and the change is
scoped to one logical concern.
- [x] Tests are updated (or `n/a` if the change is doc-only / refactor
with no behavioural delta).
- [x] The PR title is in `TML-NNNN: <sentence-case title>` form (Linear
ticket prefix + concise title naming the concrete deliverable).
- [x] The **Skill update** section above is filled in (or stated `n/a —
internal only`).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Separated row-returning operations (`query`/`queryPrepared`) from
write execution (`execute`), which now reports affected-row statistics.
- Added distinct middleware lifecycle hooks for queries and executions.
- Improved mutation count operations to use affected-row results
directly.
- Added query support across database, connection, transaction, MongoDB,
PostgreSQL, SQLite, and Supabase interfaces.

- **Documentation**
- Updated guides, examples, upgrade instructions, and error references
for the revised APIs and middleware terminology.

- **Bug Fixes**
- Improved execution error preservation, cancellation handling, cleanup,
and statistics validation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Steven McClankerton <tatarintsev@prisma.io>
Thegreatsura pushed a commit to Thegreatsura/prisma that referenced this pull request Aug 14, 2026
## Linked issue

Refs [TML-3166](https://linear.app/prisma-company/issue/TML-3166)

Delivered by [prisma#29907](prisma#29907),
[prisma#29920](prisma#29920), and
[prisma#29921](prisma#29921).

## At a glance

```ts
interface SqlQueryable {
  query<Row>(request: SqlExecuteRequest): AsyncIterable<Row>
  execute(request: SqlExecuteRequest): Promise<{ affectedRows: number }>
}
```

The project is complete: count-returning writes use one statement and
return the database-reported statistic rather than counting rows from a
preceding read.

## Decision

Close the affected-row-counts project by preserving its durable
architecture and semantics in the canonical ADRs, subsystem
documentation, and scorecards, then deleting the transient project
workspace.

## Project DoD verification

- The SQL driver SPI has separate row-query and statement-statistics
operations, with prepared-ness carried by the request handle.
- Postgres and SQLite return real `affectedRows` values, and Mongo maps
`modifiedCount` or `deletedCount` according to the command kind.
- `updateAndCount` and `deleteAndCount` issue one write statement;
integration coverage proves the returned count comes from that write.
- The pre-`SELECT` fallback and retired operation names are absent.
- ADR 210 documents the two-method prepared-statement SPI and
`DRIVER.PREPARE_FAILED` under ADR 239.
- ADR 215 and the runtime subsystem documentation describe the
operation-specific query and execute middleware lifecycles.
- SQL and Mongo scorecards record the shipped count-terminal behavior
and target-specific semantics.
- Manual documentation QA passed for both application users and
extension/driver/middleware authors.
- The mandatory final retro landed a dispatch-DoR guard requiring
explicit design-owner approval for public or cross-family API shapes.
- All tracked project artifacts are deleted and no tracked references to
the removed workspace remain.

## How it fits together

1. ADR 210 records the two-method SQL driver contract and the
runtime-owned prepared-handle lifecycle.
2. ADR 215 and the runtime subsystem page describe `beforeQuery` /
`interceptQuery` / `afterQuery` separately from `beforeExecute` /
`interceptExecute` / `afterExecute`.
3. The Mongo subsystem and both scorecards state each target's native
affected-row semantics and point to qualifying evidence.
4. The reusable process lesson lands in Drive's dispatch Definition of
Ready.
5. The transient specs, plans, dispatch briefs, decision log, QA
artifacts, and retro log are removed.

## Notes for the reviewer

The large runtime-documentation diff replaces stale generic middleware
terminology with the operation-specific lifecycle on `main`. ADR 215
preserves the original May 2026 decision and its rationale under a
historical section while adding the August 2026 amendment.

The manual-QA script, report, and retro were created and committed as
close-out evidence before being removed with the rest of the transient
workspace; their evidence remains in this branch's signed commit
history.

## Testing performed

- Manual QA: durable-documentation read-through for application and
extension-author audiences — pass, no findings
- Local Markdown link validation — no missing links
- `pnpm lint:docs` — pass, with pre-existing package README warnings
- `git diff --check origin/main...HEAD` — pass
- Source and integration behavior — green on merged PR prisma#29921 CI,
including Test, Integration Tests, E2E, Coverage, Type Check, Lint, and
Supabase Acceptance

## Skill update

The existing Prisma Next upgrade instructions shipped with prisma#29921. This
close-out adds no new user-facing API change.

## Checklist

- [x] All commits are signed off (`git commit -s`) per the DCO.
- [x] I read `CONTRIBUTING.md` and the change is scoped to one logical
concern.
- [x] Tests are n/a for this documentation and project-cleanup PR;
merged implementation CI and close-out manual QA provide the behavior
evidence.
- [x] The PR title is in `TML-NNNN: <sentence-case title>` form.
- [x] The Skill update section is filled in.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Query and execute operations now have distinct lifecycles and results:
queries stream rows, while executions report affected-row statistics.
* SQL and MongoDB provide clearer update and delete counts, including
no-op updates and deleted-document totals.
* Prepared statement and transaction behavior is documented with clearer
lazy execution and resource handling details.

* **Documentation**
* Updated architecture, middleware, MongoDB, SQL ORM, and scorecard
documentation to reflect current behavior and supported operations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
Co-authored-by: Steven McClankerton <tatarintsev@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants