fix(target-postgres): resolve inferrable array parameters against the target column instead of always casting - #30193
fix(target-postgres): resolve inferrable array parameters against the target column instead of always casting#30193StevenMcClankerton wants to merge 2 commits into
Conversation
… 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>
@prisma/orm-extension-arktype-json
@prisma/orm-extension-middleware-cache
@prisma/orm-extension-paradedb
@prisma/orm-extension-pgvector
@prisma/orm-extension-postgis
@prisma/orm-extension-supabase
@prisma/orm-family-mongo
@prisma/orm-family-sql
@prisma/orm-framework
@prisma/orm-mongo
@prisma/orm-postgres
@prisma/orm-sqlite
@prisma/orm-target-mongo
@prisma/orm-target-postgres
@prisma/orm-target-sqlite
@prisma/orm-toolchain
commit: |
size-limit report 📦
|
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughPostgres 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. ChangesPostgres array cast handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Merge Risk: ⚪ Minimal · up to 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: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The changes address issue
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Summary
renderTypedParamshort-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 with42804: column "moods" is of type "Mood"[] but expression is of type text[], leaving the column unwritable through the ORM.The fix changes
|| manyto|| (many && forceArrayCast)atsql-renderer.ts:125, so an array whose element type is inferrable reaches the bare-$Npath and Postgres resolves it against the target column — exactly as a scalartextparameter already did.Fixes #30165
The cast policy, stated in full
Array parameters cast only when:
FunctionSourceargs,FunctionCallExpr,WindowFuncExpr)Otherwise they bind bare
$N.The exception in (b) is grounded in polymorphic functions:
unnest(anyarray)cannot resolve anunknown-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)inadapter.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_TYPESdocblock insql-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 whetherself/argssit in a function-call position:'{{self}} <=> {{arg0}}'(pgvector,descriptor-meta.ts:33) and'{{self}} @@@ {{arg0}}'(paradedb,descriptor-meta.ts:33) are both taggedstrategy: '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
selfor argument — so nothing regresses today. Closing it properly would first require correcting the misdeclared strategies in thepgvector/paradedbextension packages, which is a separate PR against those packages, not this one.What still casts, each pinned by a test
isPgEnumParamsbranch.$1::foo[]).unnest($1::integer[])— the one productionFunctionSourcecase in the suite.$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 realtext[]column — it previously compared a scalarint4column against an array param, which is not a legal Postgres comparison to begin with.The
|| manybehaviour originated in TML-2911 (01b1488758) and was simply carried forward, unexamined, bye0e739ca6a. It was a conservative default, never a designed policy — ADR 205 (which did design this codebase's cast policy) predates arrays entirely.Testing
render-typescript.roundtrip.test.tsflake —timeouts.typeScriptCompilation= 8000ms under concurrent CI/local load; passes 5/5 in isolation).enum-array-inferrable-write.integration.test.tswrites both a populated array and an empty array into a native"Mood"[]column against real Postgres.timestamptz,numeric,int8now bare;byteastill cast; null arrays and null elements) confirm nothing regressed where casts were dropped.pnpm lintandpnpm typecheckboth 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 with42804before 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
Tests