Skip to content

fix(client-engine-runtime): avoid spreading row/param-sized arrays onto the stack - #29751

Merged
aqrln merged 3 commits into
prisma:mainfrom
Hprogram:fix/issue-29746-spread-stack-overflow
Jul 22, 2026
Merged

fix(client-engine-runtime): avoid spreading row/param-sized arrays onto the stack#29751
aqrln merged 3 commits into
prisma:mainfrom
Hprogram:fix/issue-29746-spread-stack-overflow

Conversation

@Hprogram

Copy link
Copy Markdown
Contributor

Problem

Closes #29746.

findMany throws RangeError: Maximum call stack size exceeded once a query processes roughly 59-60K items — both for a select with a to-many relation over enough parent rows, and for a flat in: [...] filter with ~60K values.

Both shapes funnel into the same routine: renderTemplateSql() appends every parameter behind an IN (...) clause by spreading the whole array as call arguments (flattenedParams.push(...flattenedFragmentParams(fragment))), which exceeds V8's argument-count/stack limit. Relation-loading subqueries are compiled with chunkable: false, so chunkParams — the intended mitigation — is bypassed entirely and the full parent-key array reaches the spread. When chunking does run, the per-chunk row merge uses the same pattern (results.rows.push(...result.rows) in query-interpreter.ts), which can still overflow on high-fanout relations (chunk size × fanout rows).

The exact threshold depends on stack already consumed when the query runs (a bare push(...arr) fails above ~125K elements; with a few thousand frames of app/framework machinery it fails around 60K), which matches the reported binary-searched boundary and its variation at deeper nesting.

Changes

  • render-query.ts: append flattened fragment params with a loop instead of spreading them as call arguments; keeps the added count used by the tuple-arity check
  • query-interpreter.ts: merge chunked query result rows with a loop instead of a spread
  • Regression tests for both sites (200K-element parameterTuple render with chunkable: false; chunked-result merge preserving 200K rows) — both fail with RangeError on the previous implementation and pass now

A repo-wide sweep found no other spread sites that scale with row/parameter count.

Test plan

  • pnpm --filter @prisma/client-engine-runtime test — 209 passed (includes the 2 new regression tests)
  • New tests verified to fail (RangeError at the exact fixed lines) against the previous implementation
  • Build, lint, and prettier clean

@CLAassistant

CLAassistant commented Jul 22, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5d9fbafb-0bef-4e0a-b92a-f633882c8c8b

📥 Commits

Reviewing files that changed from the base of the PR and between c01c1b3 and 4f06256.

📒 Files selected for processing (3)
  • packages/client-engine-runtime/src/interpreter/query-interpreter.ts
  • packages/client-engine-runtime/src/interpreter/render-query.ts
  • packages/client-engine-runtime/src/utils.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of very large query parameter lists without stack overflow errors.
    • Fixed merging of large, chunked query results for more reliable execution.
    • Preserved correct query argument counts and result contents when processing large datasets.
  • Tests

    • Added regression coverage for large non-chunked parameters and chunked query results.

Walkthrough

Updated query parameter rendering and query-result aggregation to avoid spread-based array pushes for large collections. Added regression tests covering a non-chunkable IN query with 200,000 parameters and chunked result merging where the second chunk contains 200,000 rows.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: replacing stack-unsafe array spreads in client-engine-runtime.
Description check ✅ Passed The description clearly matches the changeset and explains the stack-overflow fix and regression tests.
Linked Issues check ✅ Passed The PR addresses issue #29746 by removing stack-unsafe spreads in query rendering and row merging, with regressions covering the failure cases.
Out of Scope Changes check ✅ Passed The added helper and tests are directly related to the stack-overflow fix and no unrelated changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

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.

@aqrln aqrln left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thank you!

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jul 22, 2026
…to the stack

Rendering a query whose IN list or relation parent-key list holds hundreds of
thousands of parameters, and merging the results of a chunked query with a large
number of rows, both used the array spread syntax to append elements onto an
existing array. Because each spread element becomes a separate function argument,
this overflowed the call stack (`RangeError: Maximum call stack size exceeded`)
once the source array grew past ~125k elements. Non-chunkable relation-loading
subqueries bypass parameter chunking, so the render path could hit this even with
chunking enabled.

Replace both spreads with explicit loops that push one element at a time.

Fixes prisma#29746
@Hprogram
Hprogram force-pushed the fix/issue-29746-spread-stack-overflow branch from a5bef7b to c01c1b3 Compare July 22, 2026 11:35
@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 36.51%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 16 untouched benchmarks
⏩ 30 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
interpreter: sequence 353.7 µs 557.2 µs -36.51%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing Hprogram:fix/issue-29746-spread-stack-overflow (31f6381) with main (7ef2104)

Open in CodSpeed

Footnotes

  1. 30 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Hprogram

Copy link
Copy Markdown
Contributor Author

Addressed the CodSpeed regression on interpreter: findUnique. The plain per-element loop replaced the previous single-spread call even for small inputs, and the added iterator overhead in the hot render path is what the instrument-based benchmark picked up.

Switched to a shared appendToArray helper: a single push(...source) for arrays up to 8,192 elements (restoring the original fast path — this covers virtually all real queries), and batched spreads above that (preserving the stack-safety fix; the regression tests with 200K elements still pass). The 8,192 batch size stays far below V8's argument limit even with a deep call stack.

@Hprogram
Hprogram force-pushed the fix/issue-29746-spread-stack-overflow branch from a50892b to 4f06256 Compare July 22, 2026 11:51
@Hprogram

Copy link
Copy Markdown
Contributor Author

Follow-up on the updated report: the original interpreter: findUnique regression is resolved by the fast-path restore (it's back in the untouched list).

The newly flagged interpreter: sequence entry looks like a cross-environment artifact rather than a real change:

  • CodSpeed marks the comparison itself as "compared across different runtime environments" — BASE ran on an EPYC 7763 (64-core), HEAD on an EPYC 9V74 (80-core).
  • The benchmark is flagged for containing system calls that cannot be consistently instrumented (~47% of the profile is node:internal/crypto/random).
  • The same run reports an ×86 "improvement" on compile findUnique (uncached baseline) (79,875 µs → 929 µs), which this diff certainly didn't cause — pointing to the same environment variance.

Code-wise, SEQUENCE_PLAN carries only 1-2 scalar parameters per statement, so it takes the same single-spread fast path as before this PR. Happy to dig further if useful, but I believe both entries are measurement variance from the runner change.

@aqrln

aqrln commented Jul 22, 2026

Copy link
Copy Markdown
Member

Yes, I think this is just noise. The PR looks good to me as is, and is ready to be merged as soon as the CI passes.

@aqrln
aqrln merged commit 8cb6ee3 into prisma:main Jul 22, 2026
253 of 254 checks passed
lh0x00 pushed a commit to lh0x00/prisma that referenced this pull request Aug 9, 2026
…to the stack (prisma#29751)

## Problem

Closes prisma#29746.

`findMany` throws `RangeError: Maximum call stack size exceeded` once a
query processes roughly 59-60K items — both for a select with a to-many
relation over enough parent rows, and for a flat `in: [...]` filter with
~60K values.

Both shapes funnel into the same routine: `renderTemplateSql()` appends
every parameter behind an `IN (...)` clause by spreading the whole array
as call arguments
(`flattenedParams.push(...flattenedFragmentParams(fragment))`), which
exceeds V8's argument-count/stack limit. Relation-loading subqueries are
compiled with `chunkable: false`, so `chunkParams` — the intended
mitigation — is bypassed entirely and the full parent-key array reaches
the spread. When chunking does run, the per-chunk row merge uses the
same pattern (`results.rows.push(...result.rows)` in
`query-interpreter.ts`), which can still overflow on high-fanout
relations (chunk size × fanout rows).

The exact threshold depends on stack already consumed when the query
runs (a bare `push(...arr)` fails above ~125K elements; with a few
thousand frames of app/framework machinery it fails around 60K), which
matches the reported binary-searched boundary and its variation at
deeper nesting.

## Changes

- `render-query.ts`: append flattened fragment params with a loop
instead of spreading them as call arguments; keeps the `added` count
used by the tuple-arity check
- `query-interpreter.ts`: merge chunked query result rows with a loop
instead of a spread
- Regression tests for both sites (200K-element `parameterTuple` render
with `chunkable: false`; chunked-result merge preserving 200K rows) —
both fail with `RangeError` on the previous implementation and pass now

A repo-wide sweep found no other spread sites that scale with
row/parameter count.

## Test plan

- [x] `pnpm --filter @prisma/client-engine-runtime test` — 209 passed
(includes the 2 new regression tests)
- [x] New tests verified to fail (`RangeError` at the exact fixed lines)
against the previous implementation
- [x] Build, lint, and prettier clean
OIRNOIR pushed a commit to OIRNOIR/YouTube-Helper-Server that referenced this pull request Sep 1, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@prisma/adapter-pg](https://github.com/prisma/prisma) ([source](https://github.com/prisma/prisma/tree/HEAD/packages/adapter-pg)) | imports | minor | [`7.9.1` -> `7.10.0`](https://renovatebot.com/diffs/npm/@prisma%2fadapter-pg/7.9.1/7.10.0) |
| [@prisma/client](https://www.prisma.io) ([source](https://github.com/prisma/prisma/tree/HEAD/packages/client)) | imports | minor | [`7.9.1` -> `7.10.0`](https://renovatebot.com/diffs/npm/@prisma%2fclient/7.9.1/7.10.0) |

---

### Release Notes

<details>
<summary>prisma/prisma (@&#8203;prisma/adapter-pg)</summary>

### [`v7.10.0`](https://github.com/prisma/orm/releases/tag/7.10.0)

[Compare Source](prisma/orm@7.9.1...7.10.0)

##### Prisma ORM 7.10.0

Prisma ORM 7.10.0 introduces a compatibility package for running Prisma 7 alongside newer Prisma versions, secures Prisma Studio's local server, and includes fixes across Prisma Client and the PostgreSQL, MariaDB, Neon, SQLite, and Prisma Postgres Serverless adapters.

##### Highlights

##### Run Prisma 7 alongside Prisma 8

This release introduces `@prisma/prisma7`, a compatibility package that lets you retain a matching Prisma 7 CLI and configuration while installing Prisma 8 in the same project.

Once 7.10.0 is released, a side-by-side installation can use:

```sh
npm install --save-dev prisma@8 @prisma/prisma7@7.10.0
npm install @prisma/client@7.10.0
```

Use `prisma` for the directly installed Prisma 8 CLI and `prisma7` for Prisma 7:

```sh
npx prisma --version
npx prisma7 --version

npx prisma7 generate
npx prisma7 migrate dev
npx prisma7 db push
```

Prisma 7 now prefers version-specific configuration files, allowing its configuration to coexist with Prisma 8's `prisma.config.*` files:

```ts
// prisma7.config.ts
import { defineConfig } from '@prisma/prisma7/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: {
    path: 'prisma/migrations',
  },
})
```

Without an explicit `--config` option, Prisma 7 searches for:

1. Root-level `prisma7.config.*` files.
2. `.config/prisma7.*` files.
3. Existing `prisma.config.*` files as a backwards-compatible fallback.

The supported extensions are `.js`, `.ts`, `.mjs`, `.cjs`, `.mts`, and `.cts`. An explicit config path always takes precedence:

```sh
npx prisma7 generate --config ./custom/prisma7.config.ts
```

New projects initialized by the Prisma 7 CLI use `prisma7.config.ts`. Existing projects containing only `prisma.config.*` continue to work without migration or additional warnings. If a `prisma7.config.*` file exists but cannot be loaded, Prisma reports the error rather than silently falling back to another configuration.

The `prisma7` identity is carried through CLI help, version output, shell completion, initialization, migration, database, and generation guidance. Stable Prisma concepts such as `schema.prisma`, Prisma Migrate, `@prisma/client`, and `PRISMA_*` environment variables remain unchanged.

Together, the separate executable and configuration namespace make it possible to operate Prisma 7 and Prisma 8 side by side without command or config-file collisions.

[#&#8203;29949](prisma/orm#29949), [#&#8203;29969](prisma/orm#29969), [#&#8203;29994](prisma/orm#29994), [#&#8203;30000](prisma/orm#30000), [#&#8203;30002](prisma/orm#30002), [#&#8203;30020](prisma/orm#30020)

##### Prisma Studio security hardening

Prisma Studio's local HTTP server now:

- Binds explicitly to `127.0.0.1` instead of all network interfaces.
- Rejects browser requests from origins other than the active `localhost` or `127.0.0.1` Studio URL.
- No longer returns wildcard CORS headers.
- Applies the same protections across Node.js, Bun, and Deno.

This prevents network clients or malicious websites from accessing Studio's database endpoints while Studio is running.

[#&#8203;29890](prisma/orm#29890)

##### Prisma Client

- Fixed `P2002` errors from nested writes so `meta.modelName` identifies the model where the unique constraint violation occurred, including models using `@@map` and `@@schema`. [#&#8203;29628](prisma/orm#29628)
- Fixed automatically batched `findUniqueOrThrow()` calls so every missing record rejects with `P2025`; later misses no longer resolve to `undefined`. [#&#8203;29654](prisma/orm#29654)
- Parameter-chunked statements are now executed atomically in a transaction and rolled back if a later chunk fails. [#&#8203;29771](prisma/orm#29771)
- Improved interactive transaction cleanup during `$disconnect()`, including transactions whose driver-level startup is still in progress. [#&#8203;28768](prisma/orm#28768)
- Prevented transaction cleanup failures after a timeout or backend termination from becoming unhandled promise rejections. [#&#8203;29611](prisma/orm#29611)
- Fixed fluent relation queries when relation fields are literally named `select` or `include`. [#&#8203;29683](prisma/orm#29683)
- Fixed handling of `Date` and `Uint8Array` values created in other JavaScript realms, such as iframes, jsdom, and Node.js `vm` contexts. [#&#8203;29177](prisma/orm#29177)
- Invalid `Date` values passed to `$queryRaw` or `$executeRaw` now throw `PrismaClientValidationError` instead of a generic error. [#&#8203;29718](prisma/orm#29718)
- Fixed `moduleFormat` inference for the `prisma-client` generator in TypeScript projects using `module: "node16"` or `"nodenext"`. Generated output now follows the nearest `package.json` `type`, defaulting to CommonJS when absent. [#&#8203;29712](prisma/orm#29712)
- Deserialized `Bytes` values now own standalone `ArrayBuffer`s rather than exposing unrelated contents from Node.js's shared `Buffer` pool. This applies to both regular and raw query results. [#&#8203;29701](prisma/orm#29701)
- Fixed an incorrect logging context in the remote executor, including Accelerate-backed query execution. [#&#8203;28892](prisma/orm#28892)

##### Client extensions and observability

- Result-extension `compute` callbacks now receive the current model name as a typed second argument:

  ```ts
  compute(data, modelName) {
    // ...
  }
  ```

  The model name is also preserved when multiple extensions compose the same computed field. [#&#8203;29782](prisma/orm#29782)

- Improved OpenTelemetry context for remotely executed queries:

  - `$on('query')` callbacks run within the matching `db_query` span.
  - Events from one operation share the same trace.
  - Error events are recorded as span exceptions.
  - Log events continue to be emitted when tracing is disabled or their reported span is unavailable.

  [#&#8203;28892](prisma/orm#28892)

##### Driver adapters

##### MariaDB

- `@prisma/adapter-mariadb` now accepts an existing `mariadb` pool. External pools remain caller-owned unless `disposeExternalPool: true` is supplied. [#&#8203;27992](prisma/orm#27992)
- Fixed pooled connection leaks during commit, rollback, and failed transaction startup. Connections are now returned with `release()` and transaction-specific listeners are removed before reuse. [#&#8203;29612](prisma/orm#29612)
- Added support for bracketed IPv6 addresses in both `mysql://` and `mariadb://` connection strings. [#&#8203;29026](prisma/orm#29026)
- Prevented malformed connection strings from exposing embedded passwords in retained debug output and diagnostic reports. [#&#8203;27992](prisma/orm#27992)

##### PostgreSQL, Neon, and Prisma Postgres Serverless

- PostgreSQL deadlocks using SQLSTATE `40P01` are now reported as `P2034` transaction write conflicts. [#&#8203;29717](prisma/orm#29717)
- PostgreSQL `RESTRICT` violations using SQLSTATE `23001` are now reported as `P2003`, preserving an available field or constraint name. [#&#8203;29554](prisma/orm#29554)
- `@prisma/adapter-pg` now preserves database constraint names when reporting unique constraint violations through `P2002`. [#&#8203;29587](prisma/orm#29587)
- Prisma Postgres Serverless now prefers the named constraint for `P2002`, falling back to parsed field names when no constraint name is available. [#&#8203;29801](prisma/orm#29801)
- Fixed Neon HTTP adapter serialization for typed parameters such as `Bytes` and `DateTime`. [#&#8203;29747](prisma/orm#29747)

##### SQLite

- `@prisma/adapter-better-sqlite3` now converts previously unhandled SQLite result codes into typed database errors instead of exposing raw driver errors.
- The complete `SQLITE_BUSY` family is now mapped to socket timeout errors, with numeric extended result codes preserved where available.

[#&#8203;29794](prisma/orm#29794)

##### CLI and Migrate

- `prisma generate` can now offer to install Prisma's agent skills. The opt-in prompt:

  - Is shown at most once per machine.
  - Is skipped in CI, containers, Git hooks, npm lifecycle scripts, and watch mode.
  - Is skipped when `--no-hints` is used or Prisma skills are already installed.
  - Times out after 30 seconds.
  - Never causes generation to fail if installation is unsuccessful.

  [#&#8203;29690](prisma/orm#29690)

- A globally installed CLI now warns during `prisma generate` when its version differs from the project's local `prisma` or `@prisma/client`, and recommends running the local CLI. The check is best-effort and does not fail generation. [#&#8203;29593](prisma/orm#29593)

- `prisma version` and `prisma version --json` now include the resolved Prisma CLI package path, making global-versus-local installation issues easier to diagnose. [#&#8203;29573](prisma/orm#29573)

- Empty or generator-only schema files now report `Schema must contain a datasource block` from `db pull`, `db push`, and `migrate dev`, rather than reaching the schema engine and potentially producing inconsistent errors. [#&#8203;29657](prisma/orm#29657)

- CLI commands now tolerate corrupt, unreadable, or unwritable command-state files. Invalid state is reinitialized, writes are atomic, and persistence failures fall back to in-memory state. [#&#8203;29609](prisma/orm#29609)

- Studio now recognizes semicolon-delimited `sqlserver://` connection strings before reporting the existing explicit message that SQL Server is not supported by Studio. [#&#8203;29623](prisma/orm#29623)

- The AI-agent safety checkpoint now also covers interactive `prisma db push` confirmations involving data-loss warnings, rather than only invocations using `--accept-data-loss`. [#&#8203;29793](prisma/orm#29793)

##### Performance and reliability

- Optimized query-plan execution by eagerly evaluating plans with one unconditional database operation and synchronously interpreting the remaining pure plan. Cached plans remain immutable. [#&#8203;29004](prisma/orm#29004)
- Prevented call-stack overflows when rendering very large parameter lists or combining chunked results containing hundreds of thousands of rows. [#&#8203;29751](prisma/orm#29751)
- Reduced ordinary query setup overhead by constructing fluent-relation field maps lazily and in linear time. Non-fluent queries no longer build this map. [#&#8203;29752](prisma/orm#29752)

##### Dependencies

- Updated the transitive `fast-uri` dependency to a patched release addressing production audit advisories affecting versions through `3.1.3`. [#&#8203;29758](prisma/orm#29758)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zMC4zIiwidXBkYXRlZEluVmVyIjoiNDQuMzAuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Reviewed-on: https://git.oirnoir.dev/OIRNOIR/YouTube-Helper-Server/pulls/41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RangeError: Maximum call stack size exceeded in query-interpreter (client-engine-runtime) for findMany above ~59-60K rows/relations/in-filter values

3 participants