Skip to content

fix(target-postgres): resolve inferrable array parameters against the target column instead of always casting - #30193

Closed
StevenMcClankerton wants to merge 2 commits into
mainfrom
issue-30165
Closed

fix(target-postgres): resolve inferrable array parameters against the target column instead of always casting#30193
StevenMcClankerton wants to merge 2 commits into
mainfrom
issue-30165

Conversation

@StevenMcClankerton

@StevenMcClankerton StevenMcClankerton commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

renderTypedParam short-circuited on || many, so every array parameter got an explicit cast regardless of whether its element type was inferrable. Against a native Postgres enum array column that produced $1::text[] and failed with 42804: column "moods" is of type "Mood"[] but expression is of type text[], leaving the column unwritable through the ORM.

The fix changes || many to || (many && forceArrayCast) at sql-renderer.ts:125, so an array whose element type is inferrable reaches the bare-$N path and Postgres resolves it against the target column — exactly as a scalar text parameter already did.

Fixes #30165

The cast policy, stated in full

Array parameters cast only when:

  • (a) their element native type is not in the inferrable set, or
  • (b) they appear in a bare function-call argument position (FunctionSource args, FunctionCallExpr, WindowFuncExpr)

Otherwise they bind bare $N.

The exception in (b) is grounded in polymorphic functions: unnest(anyarray) cannot resolve an unknown-typed argument at all ("could not determine polymorphic type because input has type unknown"). It is not a general "function arguments cannot infer" rule — concat($1, ...) is the counterexample: a non-polymorphic function resolves an untyped scalar argument fine from its single candidate signature and stays uncast (concat("user"."email", $2) in adapter.test.ts).

ADR 205

ADR 205 predates array parameters and explicitly lists array casts as out of scope (see its "Out of scope" section) — nothing it settled has been overturned here. The policy above is recorded in full in the POSTGRES_INFERRABLE_NATIVE_TYPES docblock in sql-renderer.ts, since that's the only executable/reviewable surface this PR can touch. An ADR amendment recording this policy is needed as a follow-up, and requires owner sign-off — deliberately not done in this PR, since architecture docs are Ask First per this repo's contributor guidelines.

Known gap, deliberately not closed

Array parameters inside operation lowering templates (OperationExpr / renderOperation) are not covered by this fix, and this is intentional.

lowering.strategy ('infix' | 'function') classifies the authoring surface — whether the operation reads as a method call or an operator on the builder — not the emitted SQL shape. It cannot be used to decide whether self/args sit in a function-call position:

  • '{{self}} <=> {{arg0}}' (pgvector, descriptor-meta.ts:33) and '{{self}} @@@ {{arg0}}' (paradedb, descriptor-meta.ts:33) are both tagged strategy: 'function' despite being binary operators.
  • '{{self}} ILIKE {{arg0}}' (postgres, descriptor-meta.ts:165) is tagged 'infix'.

The gap is currently inert — no operation in this codebase declares an array-typed self or argument — so nothing regresses today. Closing it properly would first require correcting the misdeclared strategies in the pgvector/paradedb extension packages, which is a separate PR against those packages, not this one.

What still casts, each pinned by a test

  • Native enums, via the untouched isPgEnumParams branch.
  • Array parameters whose element native type is outside the inferrable set, in every position ($1::foo[]).
  • unnest($1::integer[]) — the one production FunctionSource case in the suite.
  • The marker ledger's $3::text[].

Scalar behaviour is unchanged, pinned by concat("user"."email", $2) staying bare.

A pre-existing test asserted the bug as correct

sql-renderer.cast-policy.test.ts's 'casts scalar arrays even when their element native type is inferrable' has been inverted and retargeted at a real text[] column — it previously compared a scalar int4 column against an array param, which is not a legal Postgres comparison to begin with.

The || many behaviour originated in TML-2911 (01b1488758) and was simply carried forward, unexamined, by e0e739ca6a. It was a conservative default, never a designed policy — ADR 205 (which did design this codebase's cast policy) predates arrays entirely.

Testing

  • Postgres adapter suite: 870 passed / 874 (remaining 4 are the pre-existing render-typescript.roundtrip.test.ts flake — timeouts.typeScriptCompilation = 8000ms under concurrent CI/local load; passes 5/5 in isolation).
  • enum-array-inferrable-write.integration.test.ts writes both a populated array and an empty array into a native "Mood"[] column against real Postgres.
  • Four live-database scalar-list round trips (timestamptz, numeric, int8 now bare; bytea still cast; null arrays and null elements) confirm nothing regressed where casts were dropped.
  • pnpm lint and pnpm typecheck both exit 0 for the touched package.

One scope note

The integration test verifies the write via a raw client query using moods::text[] rather than an ORM round-trip read, because reading an enum-array column back through the ORM is blocked by #30164 (a separate decode-side defect, to be fixed in its own PR). The write is what this PR is about, and an INSERT rendering the wrong SQL would fail with 42804 before the read ever ran.

Where the policy is recorded

The position-dependent cast policy above is stated in this PR description only. It is deliberately not written into the source as a comment, and ADR 205 is untouched — amending an architecture doc needs owner sign-off. ADR 205 predates array parameters and lists array casts as out of scope, so nothing it settled was overturned; folding this policy into it is a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq

Summary by CodeRabbit

  • Bug Fixes

    • Improved PostgreSQL handling of array parameters, including empty arrays and arrays used with enum-based columns.
    • Ensured array parameters receive casts when required in function and window-function calls.
    • Preserved concise SQL for array comparisons when PostgreSQL can infer the element type.
  • Tests

    • Added coverage for array writes, comparison expressions, function arguments, and window-function arguments.

… target column instead of always casting

renderTypedParam short-circuited on `|| many`, so every array parameter got an explicit `$N::<nativeType>[]` cast regardless of whether its element type was inferrable. Against a native Postgres enum array column that produced `$1::text[]`, which fails with `42804: column "moods" is of type "Mood"[] but expression is of type text[]` -- the column was unwritable through the ORM.

Change `|| many` to `|| (many && forceArrayCast)`: an array whose element type is inferrable now reaches the bare-`$N` path and resolves against the target column, exactly as a scalar `text` parameter already did. `forceArrayCast` stays true at the three bare function-call-argument positions (FunctionSource, FunctionCallExpr, WindowFuncExpr) where a polymorphic function like `unnest(anyarray)` cannot resolve an untyped argument -- non-polymorphic scalar function args (`concat($1, ...)`) were never affected.

Fixes #30165

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
@StevenMcClankerton
StevenMcClankerton requested a review from a team as a code owner September 1, 2026 17:27
@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma/orm-extension-arktype-json

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

@prisma/orm-extension-middleware-cache

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

@prisma/orm-extension-paradedb

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

@prisma/orm-extension-pgvector

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

@prisma/orm-extension-postgis

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

@prisma/orm-extension-supabase

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

@prisma/orm-family-mongo

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

@prisma/orm-family-sql

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

@prisma/orm-framework

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

@prisma/orm-mongo

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

@prisma/orm-postgres

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

@prisma/orm-sqlite

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

@prisma/orm-target-mongo

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

@prisma/orm-target-postgres

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

@prisma/orm-target-sqlite

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

@prisma/orm-toolchain

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

commit: 9cbcb0a

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
postgres / no-emit 174.92 KB (+0.01% 🔺)
postgres / emit 152.11 KB (+0.02% 🔺)
mongo / no-emit 101.09 KB (0%)
mongo / emit 90.95 KB (0%)
cf-worker / no-emit 198.88 KB (+0.02% 🔺)
cf-worker / emit 173.41 KB (+0.03% 🔺)

Reverts the cast-policy docblock to its original text and removes the
test file header comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UcqoY3CKfnubdZt5YQk2Rq
Signed-off-by: Steven McClankerton <tatarintsev@prisma.io>
@coderabbitai

coderabbitai Bot commented Sep 2, 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Team

Run ID: 5a8c070e-0798-4645-8a58-1f235e4cb880

📥 Commits

Reviewing files that changed from the base of the PR and between 5e0f135 and 9cbcb0a.

📒 Files selected for processing (3)
  • packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
  • packages/3-targets/6-adapters/postgres/test/enum-array-inferrable-write.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Postgres array parameters no longer receive casts when their element types are inferrable. Function and window-function arguments force array casts. Renderer tests and an integration test cover comparison rendering, function arguments, and writes to native enum-array columns.

Changes

Postgres array cast handling

Layer / File(s) Summary
Array cast policy and function argument rendering
packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
renderTypedParam omits casts for inferrable array types unless requested. Function-source, window-function, and function-call parameter arguments request explicit array casts.
SQL renderer cast-policy coverage
packages/3-targets/6-adapters/postgres/test/sql-renderer.cast-policy.test.ts
Tests cover uncast inferrable arrays, casts for custom element types, and casts for direct function and window-function arguments.
Enum-array write integration coverage
packages/3-targets/6-adapters/postgres/test/enum-array-inferrable-write.integration.test.ts
The integration test writes populated and empty text arrays to a native PostgreSQL enum-array column and verifies the stored rows.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 9cbcb

This localized change allows inferrable PostgreSQL array parameters to bind against their target columns while preserving casts where required; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: wmadden-electric

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. 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 identifies the main change: inferrable PostgreSQL array parameters can resolve against the target column instead of always receiving explicit casts.
Linked Issues check ✅ Passed The changes address issue #30165 by allowing inferrable array parameters to render as bare parameters while preserving casts for non-inferrable arrays and function arguments that require explicit typi…
Out of Scope Changes check ✅ Passed The code and test changes remain within the linked issue scope. The renderer updates, function-argument handling, and regression tests all support PostgreSQL array parameter cast behavior.
Full details: Linked Issues check

Explanation

The changes address issue #30165 by allowing inferrable array parameters to render as bare parameters while preserving casts for non-inferrable arrays and function arguments that require explicit typing. The added tests cover enum-array writes and the required cast policies.

  • Fix all pre-merge checks with AI
✨ 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 issue-30165

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

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.

renderTypedParam always casts array params (|| many), making array-of-enum writes unassignable (42804)

2 participants