Skip to content

tml-2911: native scalar-array storage machinery (SQL, behind the PSL gate)#846

Open
SevInf wants to merge 3 commits into
mainfrom
scalar-lists
Open

tml-2911: native scalar-array storage machinery (SQL, behind the PSL gate)#846
SevInf wants to merge 3 commits into
mainfrom
scalar-lists

Conversation

@SevInf

@SevInf SevInf commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What ships

The internal machinery for native SQL scalar-array (many) columns, threaded through every layer — contract IR → query type-layer → emitter → migration DDL → runtime codecs → introspection → adapter capability — but deliberately not user-reachable yet. No PSL or builder path emits a many storage column until slice 2 flips the interpreter, so this ships invisibly. It is the foundation for first-class String[]/Int[]/DateTime[]/… fields, bringing the SQL family to parity with Mongo.

Part 1 of 3 of the Native Scalar Arrays project (TML-2911).

Why it's shaped this way

  • many is a structural flag on the storage column that keeps the element codec — not a per-type list codec (pg/int4-array@1) and not a higher-order array codec. Arraying is orthogonal to element type, so it's a modifier; every Postgres type has a companion array type, so the column type derives mechanically (pg/text@1 + manytext[]).
  • The implicit gate is the PSL interpreter. Every layer learns to handle many, but nothing emits it yet — so existing schemas and generated types are byte-identical. The only fixture change in the whole slice is the additive scalarList capability declaration (+ a routine version bump). Slice 2 flips the gate.
  • The discriminant lives on the operand slot (many?: never), not on scalar value types — so X and X[] stay distinct at the type level without spelling many: false on every scalar.

What's proven (slice ACs)

  • Round-trip fidelity (the headline fix): DateTime[], Bytes[], Decimal[], BigInt[] insert→select deep-equal element-wise — the element types the old JSONB fallback silently breaks — plus NULL-element passthrough and malformed-element → RUNTIME.DECODE_FAILED (with column/codec context).
  • Native array DDL (text[], int4[]) with container nullability (NOT NULL/nullable) and array defaults (DEFAULT '{}').
  • Introspection maps a native array column back to element codec id + many: true.
  • Generated domain types are ReadonlyArray<Element> (| null for a nullable container).
  • Postgres reports the scalarList capability (instance + descriptor surfaces); SQLite does not.
  • No scalar regression — the fixture diff is the additive capability + version bump only.

Deferred to slice 2 (intentional)

  • The element-non-null CHECK (array_position(col, NULL) IS NULL) — needs element-nullability represented in the IR, which the PSL interpreter (the layer that distinguishes T[] from T?[]) introduces.
  • parseArrayLiteralBody quoted/escaped array-element handling — unreached by slice-1 emitted defaults.
  • Real-pg-driver round-trip — exercised here via pg/pg-cursor over PGlite; full real-Postgres confirmation runs in CI.

Reviewer notes

  • History carries a .many()-builder trim→revert pair and an origin/main merge with re-integration fixups — all collapse on squash-merge.
  • Mongo test failures in the dev worktree are environmental (no mongodb/mongodb-memory-server); the SQL gate set is green. CI (with deps installed) is the arbiter for the mongo suite.

Closes TML-2911 (part 1 of the Native Scalar Arrays project).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Scalar list column support: You can now define PostgreSQL columns that store arrays of scalar values (text, integers, decimals, timestamps, and more). The system handles encoding and decoding of array values, generates proper migrations for array columns, and introspects existing array columns from your database schema.

@SevInf SevInf requested a review from a team as a code owner June 17, 2026 11:23
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SevInf, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 14 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 15cbbac0-efa1-4b54-b248-3881c51f2743

📥 Commits

Reviewing files that changed from the base of the PR and between f28e071 and bfaa41c.

📒 Files selected for processing (2)
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts
  • packages/3-targets/3-targets/postgres/test/migrations/planner-ddl-array-defaults.test.ts
📝 Walkthrough

Walkthrough

Adds end-to-end scalar list (native array column) support. An optional many flag is introduced on CodecRef, StorageColumn, and SqlColumnIR, then propagated through the authoring DSL's ScalarFieldBuilder.many() method, contract type inference, relational expression discrimination, runtime encode/decode loops, Postgres DDL generation, introspection, and SQL parameter casting. All contract fixtures and the scalarList adapter capability are regenerated.

Changes

Scalar List Support

Layer / File(s) Summary
Core contract and IR many flag foundations
packages/1-framework/1-core/framework-components/src/shared/codec-types.ts, packages/2-sql/1-core/contract/src/factories.ts, packages/2-sql/1-core/contract/src/ir/storage-column.ts, packages/2-sql/1-core/contract/src/ir/storage-entry-schemas.ts, packages/2-sql/1-core/contract/src/types.ts, packages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.ts, packages/2-sql/1-core/contract/test/storage-column-many.test.ts
CodecRef, StorageColumnInput/StorageColumn, and SqlColumnIR gain an optional many?: boolean field. The col() factory threads opts.many through. StorageColumnSchema accepts many?, and QueryOperationSelfSpec adds a many: true union arm with elementTraits. Unit tests cover round-trip serialization, factory behavior, constructor semantics, and validateStorage acceptance/rejection.
Authoring DSL and output typing for scalar lists
packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts, packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts, packages/2-sql/2-authoring/contract-ts/src/contract-types.ts, packages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.ts, packages/2-sql/2-authoring/contract-ts/test/scalar-list-enum-types.test-d.ts
ScalarFieldState gains a Many boolean generic. ScalarFieldBuilder.many() sets it to true, and all other fluent methods preserve the existing Many value. ExtractCodecTypesFromPack, StorageColumn, and CodecChannelType are updated to wrap output/input in ReadonlyArray<T> for many fields. Contract lowering emits many: true on FieldNode. Type-level tests verify ReadonlyArray<string>, nullable array, and scalar output types, including enum-many combos.
Relational expression and scope discriminants
packages/2-sql/4-lanes/relational-core/src/expression.ts, packages/2-sql/4-lanes/relational-core/src/ast/types.ts, packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts, packages/2-sql/4-lanes/sql-builder/src/scope.ts, packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts, packages/2-sql/4-lanes/relational-core/test/..., packages/2-sql/4-lanes/sql-builder/test/...
ScopeField gains many?: boolean. CodecExpression adds many?: never to exclude list expressions. A new ScalarListExpression<CodecId, Nullable> type is exported. frozenCodecRef and codecRefForStorageColumn propagate many: true onto codec refs. StorageTableToScopeTable and tableToScope forward the column flag. Compile-time tests verify scalar-vs-list discriminant and list operation element codec constraints.
Runtime many-aware codec encoding and decoding
packages/2-sql/5-runtime/src/codecs/decoding.ts, packages/2-sql/5-runtime/src/codecs/encoding.ts, packages/2-sql/5-runtime/test/scalar-list-codec.test.ts, packages/2-sql/5-runtime/test/utils.ts
DecodeContext gains manyAliases; buildDecodeContext populates it from item.codec?.many. decodeField decodes element-wise for many aliases and wraps failures in RUNTIME.DECODE_FAILED. encodeParamValue gains a many branch that asserts array input, encodes each element, and preserves null. Tests cover encode/decode element iteration, null element bypass, error wrapping, and ParamRef codec round-trips.
Postgres capabilities, defaults, SQL rendering, and normalization
packages/3-targets/6-adapters/postgres/src/core/adapter.ts, packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts, packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts, packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts, packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts, packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts, packages/3-targets/3-targets/postgres/test/..., packages/3-targets/6-adapters/postgres/test/..., packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
Postgres adapter declares scalarList: true. Introspection detects nativeType ending with [], strips the suffix, and emits many: true on SqlColumnIR. normalizeFormattedType re-appends [] for array types. buildColumnTypeSql appends [] for many columns. renderDefaultLiteral and new renderArrayLiteralDefault emit ARRAY[...] or '{}'. parsePostgresDefault handles array literal defaults. renderTypedParam accepts many and emits ::nativeType[] casts. pgInlineLiteral handles array wire values. SQLite asserts no scalarList capability. Unit and capability tests added.
Schema IR conversion, PSL inference, and verification
packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts, packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts, packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts
convertColumn appends [] to nativeType when column.many is true. PSL inference sets list from column.many === true for both supported and unsupported type branches. verifyColumn and renderExpectedNativeType normalize base type separately before appending the [] suffix.
Postgres integration coverage
packages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.ts, packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts, packages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.ts
New integration suites verify: introspected text[]/integer[]/nullable array columns report many: true with stripped element type; migrations produce native Postgres ARRAY column DDL, correct nullability, and empty-list defaults; verifySchema passes post-migration; and end-to-end codec roundtrips for DateTime/Bytes/Decimal/BigInt list columns preserve values, null elements, and surface decode failures through RUNTIME.DECODE_FAILED.
Regenerated contracts, fixtures, and upgrade docs
apps/..., examples/..., packages/3-extensions/..., test/integration/..., skills/...
All generated contract.json and contract.d.ts files across apps, examples, extensions, and integration test fixtures add capabilities.sql.scalarList: true. Upgrade instructions for 0.14→0.15 document the regenerated contract shape. One example test assertion is updated to full object equality.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A bunny hops through fields of arrays,
Each element encoded in fluffy ways,
many: true means lists abound,
ReadonlyArray whispers without a sound,
Round-trips complete, no scalar left behind—
The warren of types, perfectly aligned! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.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 accurately summarizes the main change: implementation of native scalar-array storage machinery (the 'many' flag) for SQL columns, clearly identifying TML-2911 and the internal nature (PSL gate) of the work.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch scalar-lists

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 and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-author-tools@846

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-runtime@846

@prisma-next/family-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-mongo@846

@prisma-next/sql-runtime

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-runtime@846

@prisma-next/family-sql

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/family-sql@846

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-arktype-json@846

@prisma-next/middleware-cache

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/middleware-cache@846

@prisma-next/mongo

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

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-paradedb@846

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-pgvector@846

@prisma-next/extension-postgis

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-postgis@846

@prisma-next/postgres

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

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-orm-client@846

@prisma-next/sqlite

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

@prisma-next/extension-supabase

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/extension-supabase@846

@prisma-next/target-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-mongo@846

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-mongo@846

@prisma-next/driver-mongo

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-mongo@846

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/framework-components@846

@prisma-next/operations

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

@prisma-next/ts-render

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/ts-render@846

@prisma-next/contract-authoring

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/contract-authoring@846

@prisma-next/ids

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

@prisma-next/psl-parser

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-parser@846

@prisma-next/psl-printer

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/psl-printer@846

@prisma-next/cli

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

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/cli-telemetry@846

@prisma-next/config-loader

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/config-loader@846

@prisma-next/emitter

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

@prisma-next/language-server

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/language-server@846

@prisma-next/migration-tools

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/migration-tools@846

prisma-next

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

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/vite-plugin-contract-emit@846

@prisma-next/mongo-codec

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-codec@846

@prisma-next/mongo-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract@846

@prisma-next/mongo-value

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-value@846

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-psl@846

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-contract-ts@846

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-emitter@846

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-schema-ir@846

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-ast@846

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-query-builder@846

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-lowering@846

@prisma-next/mongo-wire

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/mongo-wire@846

@prisma-next/sql-contract

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract@846

@prisma-next/sql-errors

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-errors@846

@prisma-next/sql-operations

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-operations@846

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-schema-ir@846

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-psl@846

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-ts@846

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-contract-emitter@846

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-lane-query-builder@846

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-relational-core@846

@prisma-next/sql-builder

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/sql-builder@846

@prisma-next/target-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-postgres@846

@prisma-next/target-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/target-sqlite@846

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-postgres@846

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/adapter-sqlite@846

@prisma-next/driver-postgres

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-postgres@846

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/prisma/prisma-next/@prisma-next/driver-sqlite@846

commit: bfaa41c

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 155.14 KB (+0.39% 🔺)
postgres / emit 142.77 KB (+0.46% 🔺)
mongo / no-emit 78 KB (0%)
mongo / emit 72.09 KB (0%)
cf-worker / no-emit 182.51 KB (+0.39% 🔺)
cf-worker / emit 168.5 KB (+0.43% 🔺)

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/prisma/prisma-next/issues/comments/4729373358","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/prisma/prisma-next/pull/846?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Path: .coderabbit.yml\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `898e2181-8918-4845-936b-1623e4d6d284`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 69b6e74194e3df840ce58d2912dbd1094f24d1df and ecf0b283df93a1c72f2c7e88596eedc3896e8c31.\n> \n> </details>\n> \n> <details>\n> <summary>⛔ Files ignored due to path filters (14)</summary>\n> \n> * `examples/bundle-size/src/postgres/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `examples/bundle-size/src/postgres/generated/contract.json` is excluded by `!**/generated/**`\n> * `packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json` is excluded by `!**/generated/**`\n> * `packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json` is excluded by `!**/generated/**`\n> * `test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `test/integration/test/namespaced-accessors/fixtures/generated/contract.json` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-builder/fixtures/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-builder/fixtures/generated/contract.json` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-orm-client/fixtures/generated/contract.json` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts` is excluded by `!**/generated/**`\n> * `test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json` is excluded by `!**/generated/**`\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (74)</summary>\n> \n> * `apps/telemetry-backend/src/prisma/contract.d.ts`\n> * `apps/telemetry-backend/src/prisma/contract.json`\n> * `examples/multi-extension-monorepo/app/src/contract.d.ts`\n> * `examples/multi-extension-monorepo/app/src/contract.json`\n> * `examples/multi-extension-monorepo/packages/audit/src/contract.d.ts`\n> * `examples/multi-extension-monorepo/packages/audit/src/contract.json`\n> * `examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts`\n> * `examples/multi-extension-monorepo/packages/feature-flags/src/contract.json`\n> * `examples/paradedb-demo/src/prisma/contract.d.ts`\n> * `examples/paradedb-demo/src/prisma/contract.json`\n> * `examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts`\n> * `examples/prisma-next-cloudflare-worker/src/prisma/contract.json`\n> * `examples/prisma-next-demo/src/prisma/contract.d.ts`\n> * `examples/prisma-next-demo/src/prisma/contract.json`\n> * `examples/prisma-next-postgis-demo/src/prisma/contract.d.ts`\n> * `examples/prisma-next-postgis-demo/src/prisma/contract.json`\n> * `examples/react-router-demo/src/prisma/contract.d.ts`\n> * `examples/react-router-demo/src/prisma/contract.json`\n> * `examples/supabase/src/contract.d.ts`\n> * `examples/supabase/src/contract.json`\n> * `packages/1-framework/1-core/framework-components/src/shared/codec-types.ts`\n> * `packages/2-sql/1-core/contract/src/factories.ts`\n> * `packages/2-sql/1-core/contract/src/ir/storage-column.ts`\n> * `packages/2-sql/1-core/contract/src/ir/storage-entry-schemas.ts`\n> * `packages/2-sql/1-core/contract/src/types.ts`\n> * `packages/2-sql/1-core/contract/test/storage-column-many.test.ts`\n> * `packages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.ts`\n> * `packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts`\n> * `packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts`\n> * `packages/2-sql/2-authoring/contract-ts/src/contract-types.ts`\n> * `packages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.ts`\n> * `packages/2-sql/4-lanes/relational-core/src/ast/types.ts`\n> * `packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts`\n> * `packages/2-sql/4-lanes/relational-core/src/expression.ts`\n> * `packages/2-sql/4-lanes/relational-core/test/scalar-list-discriminant.test-d.ts`\n> * `packages/2-sql/4-lanes/relational-core/test/scalar-list-ops.test-d.ts`\n> * `packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts`\n> * `packages/2-sql/4-lanes/sql-builder/src/scope.ts`\n> * `packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts`\n> * `packages/2-sql/5-runtime/src/codecs/decoding.ts`\n> * `packages/2-sql/5-runtime/src/codecs/encoding.ts`\n> * `packages/2-sql/5-runtime/test/scalar-list-codec.test.ts`\n> * `packages/2-sql/5-runtime/test/utils.ts`\n> * `packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts`\n> * `packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts`\n> * `packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts`\n> * `packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts`\n> * `packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts`\n> * `packages/3-targets/3-targets/postgres/src/core/native-type-normalizer.ts`\n> * `packages/3-targets/6-adapters/postgres/src/core/adapter.ts`\n> * `packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts`\n> * `packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts`\n> * `packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts`\n> * `packages/3-targets/6-adapters/postgres/test/adapter.test.ts`\n> * `packages/3-targets/6-adapters/postgres/test/descriptor-meta.test.ts`\n> * `packages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.ts`\n> * `packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts`\n> * `packages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.ts`\n> * `packages/3-targets/6-adapters/sqlite/test/adapter.test.ts`\n> * `test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json`\n> * `test/integration/test/authoring/parity/core-surface/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-cuid-2/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-nanoid/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-ulid/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json`\n> * `test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json`\n> * `test/integration/test/authoring/parity/map-attributes/expected.contract.json`\n> * `test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json`\n> * `test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json`\n> * `test/integration/test/fixtures/contract.d.ts`\n> * `test/integration/test/fixtures/contract.json`\n> \n> </details>\n> \n> ```ascii\n>  _________________________________________\n> < Time zones: the final boss of software. >\n>  -----------------------------------------\n>   \\\n>    \\   (\\__/)\n>        (•ㅅ•)\n>        /   づ\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>📝 Generate docstrings</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> Create stacked PR\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> Commit on current branch\n\n</details>\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `scalar-lists`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=prisma/prisma-next&utm_content=846)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/2-sql/4-lanes/sql-builder/src/scope.ts (1)

44-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Carry many through nullable-scope transforms.

This change preserves many in base scope tables, but nullable scope mappings still erase it (NullableScopeTable and runtime nullableScope builder path), so list fields degrade to scalar-typed fields after nullable widening.

💡 Suggested fix (type + runtime)
// packages/2-sql/4-lanes/sql-builder/src/scope.ts
 export type NullableScopeTable<S extends ScopeTable> = {
-  [K in keyof S]: { codecId: S[K]['codecId']; nullable: true };
+  [K in keyof S]: { codecId: S[K]['codecId']; nullable: true } &
+    (S[K] extends { many: true } ? { many: true } : Record<never, never>);
 };
// packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts
       result[k] = {
         codecId: v.codecId,
         nullable: true,
+        ...(v.many ? { many: true as const } : {}),
         ...(v.codec !== undefined ? { codec: v.codec } : {}),
       };
🤖 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/2-sql/4-lanes/sql-builder/src/scope.ts` around lines 44 - 49, The
StorageTableToScopeTable type currently preserves the many property, but the
NullableScopeTable type and the runtime nullableScope builder function still
erase it when transforming tables for nullable widening. Update the
NullableScopeTable type definition to include the same conditional many property
preservation logic that StorageTableToScopeTable uses (checking if the column
has many: true and preserving it in the resulting scope). Additionally, update
the corresponding runtime nullableScope builder implementation to also carry
through the many property when building nullable scope mappings, ensuring list
fields maintain their structure after nullable transformations.
packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts (1)

33-43: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate many in the typeRef path as well.

columnDef.many is currently ignored when columnDef.typeRef is used, so list-typed typeRef columns produce scalar CodecRefs and miss many-aware encode/decode behavior.

💡 Suggested fix
   if (columnDef.typeRef !== undefined) {
     const instance = storage.types?.[columnDef.typeRef];
     if (!instance) return undefined;
     if (isStorageTypeInstance(instance)) {
       const instanceParams = instance.typeParams;
       const hasParamKeys = instanceParams !== undefined && Object.keys(instanceParams).length > 0;
-      return hasParamKeys
-        ? { codecId: instance.codecId, typeParams: instanceParams as JsonValue }
-        : { codecId: instance.codecId };
+      const base = hasParamKeys
+        ? {
+            codecId: instance.codecId,
+            typeParams: blindCast<
+              JsonValue,
+              'storage type instance params are validated contract values and JSON-serialisable'
+            >(instanceParams),
+          }
+        : { codecId: instance.codecId };
+      return columnDef.many ? { ...base, many: true } : base;
     }
     return undefined;
   }

Also applies to: 46-56

🤖 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/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts` around
lines 33 - 43, The `columnDef.many` property is not being propagated when
handling the `columnDef.typeRef` path, causing list-typed columns to return
scalar CodecRef objects without many-aware encode/decode behavior. In both the
code section handling the isStorageTypeInstance check (where CodecRef is
constructed with codecId and typeParams) and the other affected section
mentioned (lines 46-56), add the `many` property from columnDef to the returned
CodecRef object whenever columnDef.many is true, ensuring that the CodecRef
includes the many flag alongside the codecId and typeParams.
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts (1)

1390-1459: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Encode many defaults element-wise before rendering the array literal.

Line 1458 still passes the whole default array to the element codec, so defaults like DateTime[], Bytes[], or BigInt[] can fail or produce the wrong wire values before the new array literal path runs. Also cast the final array literal to nativeType and cover scalar wire cases already handled by the non-array path.

🐛 Proposed fix
 function pgRenderArrayElement(el: unknown): string {
   if (el === null) return 'NULL';
-  if (typeof el === 'number' || typeof el === 'boolean') return String(el);
+  if (typeof el === 'number') {
+    if (!Number.isFinite(el)) {
+      throw new Error(
+        `pgRenderDdlExecuteRequest: non-finite number wire value ${String(el)} cannot be emitted as a DEFAULT array element`,
+      );
+    }
+    return String(el);
+  }
+  if (typeof el === 'bigint' || typeof el === 'boolean') return String(el);
   if (typeof el === 'string') return `'${escapeLiteral(el)}'`;
-  return `'${escapeLiteral(JSON.stringify(el))}'`;
+  if (el instanceof Uint8Array) {
+    const hex = Array.from(el)
+      .map((b) => b.toString(16).padStart(2, '0'))
+      .join('');
+    return `'\\x${hex}'`;
+  }
+  const json = JSON.stringify(el);
+  if (json === undefined) {
+    throw new Error(
+      `pgRenderDdlExecuteRequest: unexpected array element wire type "${typeof el}"`,
+    );
+  }
+  return `'${escapeLiteral(json)}'`;
 }
 
-function pgRenderArrayLiteral(elements: unknown[]): string {
+function pgRenderArrayLiteral(elements: readonly unknown[]): string {
   if (elements.length === 0) return "'{}'";
   return `ARRAY[${elements.map(pgRenderArrayElement).join(', ')}]`;
 }
@@
   if (Array.isArray(wire) && nativeType.endsWith('[]')) {
-    return pgRenderArrayLiteral(wire);
+    return `${pgRenderArrayLiteral(wire)}::${nativeType}`;
   }
@@
   if (codecRef !== undefined) {
     const codec = codecLookup.get(codecRef.codecId);
     if (codec !== undefined) {
+      if (codecRef.many) {
+        if (def.value === null) {
+          return `DEFAULT ${pgInlineLiteral(null, nativeType)}`;
+        }
+        if (!Array.isArray(def.value)) {
+          throw new Error(
+            `pgRenderDdlExecuteRequest: expected an array DEFAULT literal for native type "${nativeType}"`,
+          );
+        }
+        const elements = blindCast<
+          readonly unknown[],
+          'Array.isArray guard for many literal default'
+        >(def.value);
+        const wire = await Promise.all(
+          elements.map((element) =>
+            element === null || element === undefined ? null : codec.encode(element, {}),
+          ),
+        );
+        return `DEFAULT ${pgInlineLiteral(wire, nativeType)}`;
+      }
       const wire = await codec.encode(def.value, {});
       return `DEFAULT ${pgInlineLiteral(wire, nativeType)}`;
     }
   }
🤖 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/6-adapters/postgres/src/core/control-adapter.ts` around
lines 1390 - 1459, In the pgRenderDdlColumnDefault function, when handling array
column defaults (when nativeType ends with '[]'), the current code passes the
entire default array to the codec.encode() call, which fails for element-wise
encoded types like DateTime[], Bytes[], or BigInt[]. Instead, map over def.value
array elements and encode each element individually using the codec, then pass
the array of encoded wire values to pgRenderArrayLiteral to generate the array
literal syntax, and finally cast the result to nativeType using the format
'array_literal::nativeType'. This ensures array defaults are properly encoded
element-wise and the final literal includes the correct type cast.
🧹 Nitpick comments (2)
packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts (1)

159-173: ⚡ Quick win

Check runner.execute outcome in each test case.

These calls ignore the returned status; if execution returns ok: false, later assertions fail with less actionable diagnostics than the explicit failure path you already use in the first test.

Suggested fix pattern
-    await runner.execute({
+    const runResult = await runner.execute({
       driver: driver!,
       perSpaceOptions: [
         {
           space: APP_SPACE_ID,
           plan: planResult.plan,
           migrationEdges: synthEdges(planResult.plan),
           driver: driver!,
           destinationContract: contract,
           policy: INIT_ADDITIVE_POLICY,
           frameworkComponents,
         },
       ],
     });
+    if (!runResult.ok) {
+      throw new Error(`runner failed:\n${formatRunnerFailure(runResult.failure)}`);
+    }

Also applies to: 205-219, 254-268, 296-310

🤖 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/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts`
around lines 159 - 173, The runner.execute method calls are not checking the
returned status, which means if execution fails with ok: false, later assertions
will fail with unclear diagnostics. For each runner.execute call (appearing
around lines 159-173, 205-219, 254-268, and 296-310), capture the return value,
check if it contains ok: false, and handle the failure explicitly using the same
error handling pattern already implemented in the first test case rather than
allowing silent failures that cause subsequent assertions to report misleading
errors.
packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts (1)

187-200: ⚡ Quick win

Use blindCast for the modified state-transition casts.

These changed return casts still use as unknown as; please match the new many()/enum branch pattern so production code does not add bare casts. As per coding guidelines, “No bare as in production code. Use blindCast<T, "Reason"> or castAs<T> from @prisma-next/utils/casts.”

Also applies to: 218-231, 305-326, 352-373

🤖 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/2-sql/2-authoring/contract-ts/src/contract-dsl.ts` around lines 187
- 200, Replace the bare `as unknown as` type cast in the ScalarFieldBuilder
return statement with the appropriate utility function from
`@prisma-next/utils/casts`. Instead of casting the modified state object using `as
unknown as`, use either `blindCast<T, "Reason">` with a descriptive reason
string or `castAs<T>` to comply with the production code guidelines that forbid
bare casts. Apply this same fix to all other similar state-transition casts
throughout the file (also found around lines 218-231, 305-326, and 352-373)
where `as unknown as` is currently used for type assertions.

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/3-targets/3-targets/postgres/src/core/default-normalizer.ts`:
- Around line 69-99: The parseArrayLiteralBody function should return undefined
when it encounters elements that don't match any known types (NULL, true, false,
numeric, or quoted strings), instead of accepting them as unparsed values.
Currently the function falls through to pushing unrecognized elements as-is at
the end, which violates the fail-closed contract. Replace the final
result.push(el) statement with a return undefined to properly signal when an
array literal contains unsupported element formats, allowing the caller to fall
back to alternative normalization logic.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts`:
- Around line 209-214: The renderArrayElement function is missing Date object
handling, causing Date elements in array defaults to serialize inconsistently
compared to scalar defaults. Add an instanceof Date check before the
JSON.stringify fallback case in renderArrayElement. When a Date is detected,
return the ISO string representation wrapped in quotes (e.g.,
`'${el.toISOString()}'`) to match the format used by scalar default rendering,
ensuring Date objects in array defaults serialize consistently.

---

Outside diff comments:
In `@packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts`:
- Around line 33-43: The `columnDef.many` property is not being propagated when
handling the `columnDef.typeRef` path, causing list-typed columns to return
scalar CodecRef objects without many-aware encode/decode behavior. In both the
code section handling the isStorageTypeInstance check (where CodecRef is
constructed with codecId and typeParams) and the other affected section
mentioned (lines 46-56), add the `many` property from columnDef to the returned
CodecRef object whenever columnDef.many is true, ensuring that the CodecRef
includes the many flag alongside the codecId and typeParams.

In `@packages/2-sql/4-lanes/sql-builder/src/scope.ts`:
- Around line 44-49: The StorageTableToScopeTable type currently preserves the
many property, but the NullableScopeTable type and the runtime nullableScope
builder function still erase it when transforming tables for nullable widening.
Update the NullableScopeTable type definition to include the same conditional
many property preservation logic that StorageTableToScopeTable uses (checking if
the column has many: true and preserving it in the resulting scope).
Additionally, update the corresponding runtime nullableScope builder
implementation to also carry through the many property when building nullable
scope mappings, ensuring list fields maintain their structure after nullable
transformations.

In `@packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts`:
- Around line 1390-1459: In the pgRenderDdlColumnDefault function, when handling
array column defaults (when nativeType ends with '[]'), the current code passes
the entire default array to the codec.encode() call, which fails for
element-wise encoded types like DateTime[], Bytes[], or BigInt[]. Instead, map
over def.value array elements and encode each element individually using the
codec, then pass the array of encoded wire values to pgRenderArrayLiteral to
generate the array literal syntax, and finally cast the result to nativeType
using the format 'array_literal::nativeType'. This ensures array defaults are
properly encoded element-wise and the final literal includes the correct type
cast.

---

Nitpick comments:
In `@packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts`:
- Around line 187-200: Replace the bare `as unknown as` type cast in the
ScalarFieldBuilder return statement with the appropriate utility function from
`@prisma-next/utils/casts`. Instead of casting the modified state object using `as
unknown as`, use either `blindCast<T, "Reason">` with a descriptive reason
string or `castAs<T>` to comply with the production code guidelines that forbid
bare casts. Apply this same fix to all other similar state-transition casts
throughout the file (also found around lines 218-231, 305-326, and 352-373)
where `as unknown as` is currently used for type assertions.

In
`@packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts`:
- Around line 159-173: The runner.execute method calls are not checking the
returned status, which means if execution fails with ok: false, later assertions
will fail with unclear diagnostics. For each runner.execute call (appearing
around lines 159-173, 205-219, 254-268, and 296-310), capture the return value,
check if it contains ok: false, and handle the failure explicitly using the same
error handling pattern already implemented in the first test case rather than
allowing silent failures that cause subsequent assertions to report misleading
errors.
🪄 Autofix (Beta)

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

Run ID: 9b38559f-a51b-4a7e-bf77-077ba50d3244

📥 Commits

Reviewing files that changed from the base of the PR and between 69b6e74 and b8eb872.

⛔ Files ignored due to path filters (16)
  • examples/bundle-size/src/postgres/generated/contract.d.ts is excluded by !**/generated/**
  • examples/bundle-size/src/postgres/generated/contract.json is excluded by !**/generated/**
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-builder/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-builder/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json is excluded by !**/generated/**
📒 Files selected for processing (110)
  • apps/telemetry-backend/src/prisma/contract.d.ts
  • apps/telemetry-backend/src/prisma/contract.json
  • examples/multi-extension-monorepo/app/src/contract.d.ts
  • examples/multi-extension-monorepo/app/src/contract.json
  • examples/multi-extension-monorepo/packages/audit/src/contract.d.ts
  • examples/multi-extension-monorepo/packages/audit/src/contract.json
  • examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts
  • examples/multi-extension-monorepo/packages/feature-flags/src/contract.json
  • examples/paradedb-demo/src/prisma/contract.d.ts
  • examples/paradedb-demo/src/prisma/contract.json
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.json
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • examples/prisma-next-demo/src/prisma/contract.json
  • examples/prisma-next-demo/test/contract-authoring.test.ts
  • examples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.d.ts
  • examples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.json
  • examples/prisma-next-postgis-demo/src/prisma/contract.d.ts
  • examples/prisma-next-postgis-demo/src/prisma/contract.json
  • examples/react-router-demo/src/prisma/contract.d.ts
  • examples/react-router-demo/src/prisma/contract.json
  • examples/supabase/src/contract.d.ts
  • examples/supabase/src/contract.json
  • packages/1-framework/1-core/framework-components/src/shared/codec-types.ts
  • packages/2-sql/1-core/contract/src/factories.ts
  • packages/2-sql/1-core/contract/src/ir/storage-column.ts
  • packages/2-sql/1-core/contract/src/ir/storage-entry-schemas.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/1-core/contract/test/storage-column-many.test.ts
  • packages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-types.ts
  • packages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts
  • packages/2-sql/4-lanes/relational-core/src/expression.ts
  • packages/2-sql/4-lanes/relational-core/test/scalar-list-discriminant.test-d.ts
  • packages/2-sql/4-lanes/relational-core/test/scalar-list-ops.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/test/scalar-list-codec.test.ts
  • packages/2-sql/5-runtime/test/utils.ts
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
  • packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts
  • packages/3-extensions/paradedb/src/contract.d.ts
  • packages/3-extensions/paradedb/src/contract.json
  • packages/3-extensions/pgvector/src/contract.d.ts
  • packages/3-extensions/pgvector/src/contract.json
  • packages/3-extensions/postgis/src/contract.d.ts
  • packages/3-extensions/postgis/src/contract.json
  • packages/3-extensions/supabase/src/contract/contract.d.ts
  • packages/3-extensions/supabase/src/contract/contract.json
  • packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts
  • packages/3-targets/3-targets/postgres/src/core/native-type-normalizer.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
  • packages/3-targets/6-adapters/postgres/test/adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/descriptor-meta.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
  • test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json
  • test/integration/test/authoring/parity/core-surface/expected.contract.json
  • test/integration/test/authoring/parity/default-cuid-2/expected.contract.json
  • test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json
  • test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json
  • test/integration/test/authoring/parity/default-nanoid/expected.contract.json
  • test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json
  • test/integration/test/authoring/parity/default-ulid/expected.contract.json
  • test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json
  • test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json
  • test/integration/test/authoring/parity/map-attributes/expected.contract.json
  • test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json
  • test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json
  • test/integration/test/authoring/side-by-side/postgres/contract.json
  • test/integration/test/fixtures/contract.d.ts
  • test/integration/test/fixtures/contract.json

Comment thread packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts (1)

33-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate many in the typeRef branch too.

On Line 39, typeRef columns return { codecId, typeParams } / { codecId } without many: true, so list-typed columns backed by storage.types are treated as scalar downstream.

Suggested fix
   if (columnDef.typeRef !== undefined) {
     const instance = storage.types?.[columnDef.typeRef];
     if (!instance) return undefined;
     if (isStorageTypeInstance(instance)) {
       const instanceParams = instance.typeParams;
       const hasParamKeys = instanceParams !== undefined && Object.keys(instanceParams).length > 0;
-      return hasParamKeys
-        ? { codecId: instance.codecId, typeParams: instanceParams as JsonValue }
-        : { codecId: instance.codecId };
+      const base = hasParamKeys
+        ? { codecId: instance.codecId, typeParams: instanceParams as JsonValue }
+        : { codecId: instance.codecId };
+      return columnDef.many ? { ...base, many: true } : base;
     }
     return undefined;
   }
🤖 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/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts` around
lines 33 - 41, The return statements in the typeRef branch of the
codecRefForColumn function (lines 39-40) are missing the many property that
should be propagated for list-typed columns. When returning the codec reference
object, check if columnDef has a many property set to true, and if so, include
many: true in both return statements alongside codecId and typeParams. This
ensures that list-typed columns backed by storage.types are not treated as
scalars downstream.
packages/2-sql/4-lanes/sql-builder/src/scope.ts (1)

44-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep many through nullable scope mapping.

Line 48 preserves listness in base scope mapping, but NullableScopeTable drops it, so nullable projections of list columns become scalar at type level (breaking list-op typing on outer-joined scopes).

Suggested fix
 export type NullableScopeTable<S extends ScopeTable> = {
-  [K in keyof S]: { codecId: S[K]['codecId']; nullable: true };
+  [K in keyof S]: {
+    codecId: S[K]['codecId'];
+    nullable: true;
+  } & (S[K] extends { many: true } ? { many: true } : Record<never, never>);
 };
🤖 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/2-sql/4-lanes/sql-builder/src/scope.ts` around lines 44 - 49, The
StorageTableToScopeTable type correctly preserves the many property through
conditional logic, but the NullableScopeTable type definition is dropping the
many property when mapping nullable columns, causing list columns to lose their
list-operation typing in outer-joined scopes. Locate the NullableScopeTable type
definition and apply the same conditional preservation logic used in
StorageTableToScopeTable (the pattern T['columns'][K] extends { many: true } ? {
many: true } : Record<never, never>) to ensure the many property is maintained
through nullable scope mappings.
packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts (1)

86-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject many autoincrement columns before returning SERIAL.

With column.many === true and default: autoincrement(), this branch returns scalar SERIAL/BIGSERIAL/SMALLSERIAL, so the emitted DDL no longer matches the array column contract. Fail fast for this invalid combination before pseudo-type lowering.

🐛 Proposed fix
   if (allowPseudoTypes) {
     const columnDefault = column.default;
     if (columnDefault?.kind === 'function' && columnDefault.expression === 'autoincrement()') {
+      if (column.many) {
+        throw new Error('Array columns cannot use autoincrement() defaults.');
+      }
       if (resolved.nativeType === 'int4' || resolved.nativeType === 'integer') {
         return 'SERIAL';
       }
🤖 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/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts`
around lines 86 - 104, The autoincrement pseudo-type handling (SERIAL,
BIGSERIAL, SMALLSERIAL returns) does not validate that column.many is false
before returning, which creates a mismatch between the array column contract and
the scalar pseudo-type returned. Add a validation check for column.many === true
within the autoincrement branch (after checking columnDefault?.kind ===
'function' && columnDefault.expression === 'autoincrement()' but before
returning any of the SERIAL variants), and throw an error or reject this invalid
combination to fail fast before pseudo-type lowering occurs.
🧹 Nitpick comments (1)
packages/2-sql/1-core/contract/src/factories.ts (1)

23-28: ⚡ Quick win

Prefer ifDefined for conditional many spread.

Use the repo-standard ifDefined('many', opts?.many) pattern here instead of boolean-expression spreading to keep object construction consistent.

Suggested refactor
 import {
   applyFkDefaults,
   ForeignKey,
@@
 } from './types';
+import { ifDefined } from '`@prisma-next/utils/defined`';
@@
   return new StorageColumn({
     nativeType,
     codecId,
     nullable,
-    ...(opts?.many !== undefined && { many: opts.many }),
+    ...ifDefined('many', opts?.many),
   });

Based on learnings: prefer ifDefined from prisma-next/utils/defined for conditional object spreads and use the (key, value) API.

🤖 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/2-sql/1-core/contract/src/factories.ts` around lines 23 - 28, The
StorageColumn constructor object uses a boolean-expression spread pattern for
the conditional many property instead of the repo-standard ifDefined utility.
Import ifDefined from prisma-next/utils/defined and replace the spread
expression ...(opts?.many !== undefined && { many: opts.many }) with
ifDefined('many', opts?.many) in the StorageColumn constructor call to keep the
object construction consistent with repository patterns.

Source: Learnings

🤖 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/2-authoring/contract-ts/src/contract-types.ts`:
- Around line 708-710: The conditional check for StorageColumnManyOf at line 708
applies array wrapping only to the codec-channel type branches, but enum fields
bypass this by taking the EnumValueUnion path at line 722, resulting in scalar
values instead of ReadonlyArray types for many enum fields. Ensure that the many
wrapping logic is also applied to enum-backed channels by either relocating the
StorageColumnManyOf check to apply before the enum value union resolution, or by
applying the same ReadonlyArray conditional wrapping to the enum field inference
path so that many enum fields correctly infer as
ReadonlyArray<EnumValueUnion<...>> instead of just EnumValueUnion<...>.

In `@packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts`:
- Around line 80-86: The boolean array parsing in the default-normalizer.ts file
currently only handles the string literals 'true' and 'false', but Postgres
boolean array literals also use the tokens 't' and 'f' (e.g.,
'{t,f}'::boolean[]). Add two additional conditional blocks after the existing
'true' and 'false' checks to handle the cases where el === 't' (push true to
result) and el === 'f' (push false to result), following the same pattern as the
existing boolean string handling.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts`:
- Around line 175-180: The renderDefaultLiteral function currently allows
non-array scalar values to pass through for array columns (when column.many is
true), which results in invalid SQL defaults. Add a validation check after the
existing condition that checks if column.many is true: if the value is neither
an array nor null, throw an error to fail fast and reject the invalid scalar
default. This ensures that only arrays or null values are accepted as defaults
for array columns, preventing invalid SQL generation.

---

Outside diff comments:
In `@packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts`:
- Around line 33-41: The return statements in the typeRef branch of the
codecRefForColumn function (lines 39-40) are missing the many property that
should be propagated for list-typed columns. When returning the codec reference
object, check if columnDef has a many property set to true, and if so, include
many: true in both return statements alongside codecId and typeParams. This
ensures that list-typed columns backed by storage.types are not treated as
scalars downstream.

In `@packages/2-sql/4-lanes/sql-builder/src/scope.ts`:
- Around line 44-49: The StorageTableToScopeTable type correctly preserves the
many property through conditional logic, but the NullableScopeTable type
definition is dropping the many property when mapping nullable columns, causing
list columns to lose their list-operation typing in outer-joined scopes. Locate
the NullableScopeTable type definition and apply the same conditional
preservation logic used in StorageTableToScopeTable (the pattern T['columns'][K]
extends { many: true } ? { many: true } : Record<never, never>) to ensure the
many property is maintained through nullable scope mappings.

In
`@packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts`:
- Around line 86-104: The autoincrement pseudo-type handling (SERIAL, BIGSERIAL,
SMALLSERIAL returns) does not validate that column.many is false before
returning, which creates a mismatch between the array column contract and the
scalar pseudo-type returned. Add a validation check for column.many === true
within the autoincrement branch (after checking columnDefault?.kind ===
'function' && columnDefault.expression === 'autoincrement()' but before
returning any of the SERIAL variants), and throw an error or reject this invalid
combination to fail fast before pseudo-type lowering occurs.

---

Nitpick comments:
In `@packages/2-sql/1-core/contract/src/factories.ts`:
- Around line 23-28: The StorageColumn constructor object uses a
boolean-expression spread pattern for the conditional many property instead of
the repo-standard ifDefined utility. Import ifDefined from
prisma-next/utils/defined and replace the spread expression ...(opts?.many !==
undefined && { many: opts.many }) with ifDefined('many', opts?.many) in the
StorageColumn constructor call to keep the object construction consistent with
repository patterns.
🪄 Autofix (Beta)

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

Run ID: 147f4334-b0c5-425e-aaf4-0052f4665060

📥 Commits

Reviewing files that changed from the base of the PR and between c793885 and 5b6ef74.

⛔ Files ignored due to path filters (22)
  • examples/bundle-size/src/postgres/generated/contract.d.ts is excluded by !**/generated/**
  • examples/bundle-size/src/postgres/generated/contract.json is excluded by !**/generated/**
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.ts is excluded by !**/generated/**
  • packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.json is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/e2e/framework/test/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/namespaced-accessors/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-builder/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-builder/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.json is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.ts is excluded by !**/generated/**
  • test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.json is excluded by !**/generated/**
📒 Files selected for processing (110)
  • apps/telemetry-backend/src/prisma/contract.d.ts
  • apps/telemetry-backend/src/prisma/contract.json
  • examples/multi-extension-monorepo/app/src/contract.d.ts
  • examples/multi-extension-monorepo/app/src/contract.json
  • examples/multi-extension-monorepo/packages/audit/src/contract.d.ts
  • examples/multi-extension-monorepo/packages/audit/src/contract.json
  • examples/multi-extension-monorepo/packages/feature-flags/src/contract.d.ts
  • examples/multi-extension-monorepo/packages/feature-flags/src/contract.json
  • examples/paradedb-demo/src/prisma/contract.d.ts
  • examples/paradedb-demo/src/prisma/contract.json
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.d.ts
  • examples/prisma-next-cloudflare-worker/src/prisma/contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.json
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.d.ts
  • examples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.json
  • examples/prisma-next-demo/src/prisma/contract.d.ts
  • examples/prisma-next-demo/src/prisma/contract.json
  • examples/prisma-next-demo/test/contract-authoring.test.ts
  • examples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.d.ts
  • examples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.json
  • examples/prisma-next-postgis-demo/src/prisma/contract.d.ts
  • examples/prisma-next-postgis-demo/src/prisma/contract.json
  • examples/react-router-demo/src/prisma/contract.d.ts
  • examples/react-router-demo/src/prisma/contract.json
  • examples/supabase/src/contract.d.ts
  • examples/supabase/src/contract.json
  • packages/1-framework/1-core/framework-components/src/shared/codec-types.ts
  • packages/2-sql/1-core/contract/src/factories.ts
  • packages/2-sql/1-core/contract/src/ir/storage-column.ts
  • packages/2-sql/1-core/contract/src/ir/storage-entry-schemas.ts
  • packages/2-sql/1-core/contract/src/types.ts
  • packages/2-sql/1-core/contract/test/storage-column-many.test.ts
  • packages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-dsl.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-types.ts
  • packages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.ts
  • packages/2-sql/4-lanes/relational-core/src/ast/types.ts
  • packages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.ts
  • packages/2-sql/4-lanes/relational-core/src/expression.ts
  • packages/2-sql/4-lanes/relational-core/test/scalar-list-discriminant.test-d.ts
  • packages/2-sql/4-lanes/relational-core/test/scalar-list-ops.test-d.ts
  • packages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts
  • packages/2-sql/4-lanes/sql-builder/src/scope.ts
  • packages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.ts
  • packages/2-sql/5-runtime/src/codecs/decoding.ts
  • packages/2-sql/5-runtime/src/codecs/encoding.ts
  • packages/2-sql/5-runtime/test/scalar-list-codec.test.ts
  • packages/2-sql/5-runtime/test/utils.ts
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
  • packages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.ts
  • packages/3-extensions/paradedb/src/contract.d.ts
  • packages/3-extensions/paradedb/src/contract.json
  • packages/3-extensions/pgvector/src/contract.d.ts
  • packages/3-extensions/pgvector/src/contract.json
  • packages/3-extensions/postgis/src/contract.d.ts
  • packages/3-extensions/postgis/src/contract.json
  • packages/3-extensions/supabase/src/contract/contract.d.ts
  • packages/3-extensions/supabase/src/contract/contract.json
  • packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts
  • packages/3-targets/3-targets/postgres/src/core/native-type-normalizer.ts
  • packages/3-targets/6-adapters/postgres/src/core/adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/6-adapters/postgres/src/core/sql-renderer.ts
  • packages/3-targets/6-adapters/postgres/test/adapter.test.ts
  • packages/3-targets/6-adapters/postgres/test/descriptor-meta.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.ts
  • packages/3-targets/6-adapters/sqlite/test/adapter.test.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
  • test/integration/test/authoring/parity/callback-mode-scalars/expected.contract.json
  • test/integration/test/authoring/parity/core-surface/expected.contract.json
  • test/integration/test/authoring/parity/default-cuid-2/expected.contract.json
  • test/integration/test/authoring/parity/default-dbgenerated/expected.contract.json
  • test/integration/test/authoring/parity/default-nanoid-16/expected.contract.json
  • test/integration/test/authoring/parity/default-nanoid/expected.contract.json
  • test/integration/test/authoring/parity/default-pack-slugid/expected.contract.json
  • test/integration/test/authoring/parity/default-ulid/expected.contract.json
  • test/integration/test/authoring/parity/default-uuid-v4/expected.contract.json
  • test/integration/test/authoring/parity/default-uuid-v7/expected.contract.json
  • test/integration/test/authoring/parity/map-attributes/expected.contract.json
  • test/integration/test/authoring/parity/pgvector-named-type/expected.contract.json
  • test/integration/test/authoring/parity/relation-backrelation-list/expected.contract.json
  • test/integration/test/authoring/side-by-side/postgres/contract.json
  • test/integration/test/fixtures/contract.d.ts
  • test/integration/test/fixtures/contract.json

Comment thread packages/2-sql/2-authoring/contract-ts/src/contract-types.ts
…gate)

Internal machinery for native SQL scalar-array (`many`) columns, threaded through
contract IR, the query type-layer, the emitter, migration DDL, runtime codecs,
introspection, and adapter capability reporting — behind the PSL gate (no
authoring path emits a `many` storage column yet, so existing schemas are
byte-identical). Foundation for first-class String[]/Int[]/DateTime[]/… fields;
Mongo parity for the SQL family. Part 1 of 3 of the Native Scalar Arrays project.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
expect(userIdColumn.codecId).toBe(userIdTargetColumn.codecId);
expect(userIdColumn.nativeType).toBe(userIdTargetColumn.nativeType);
expect(userIdColumn).toHaveProperty('typeParams', userIdTargetColumn['typeParams']);
expect(Object.hasOwn(userIdColumn, 'typeParams')).toBe(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why did this change? Why aren't they equal anymore? hasOwn test is not engoug

export type ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer C }
? C extends Record<string, { output: unknown }>
? C
? Exclude<C, undefined> extends Record<string, { output: unknown }>

@SevInf SevInf Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Where does undefined come from now? Why wasn't it necessary before?

return { codecId: columnDef.codecId, typeParams: columnDef.typeParams as JsonValue };
const typeParams = blindCast<
JsonValue,
'column typeParams is a validated contract record; its values are JSON-serialisable'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Then why the cast is needed?

export function renderDefaultLiteral(value: unknown, column?: StorageColumn): string {
const isJsonColumn = column?.nativeType === 'json' || column?.nativeType === 'jsonb';

if (column?.many) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can we validate this earlier, before it gets to migration rendering? Ideally, at authoring time

return `ARRAY[${rendered}]`;
}

function renderArrayElement(el: unknown): string {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why do we need separate array element rendering function? Why aren't we re-using scalar rendering function here?

* Lookup map for verbatim scalar type aliases that Postgres format_type() may return.
* Keys are the verbose form; values are the canonical internal name.
*/
const SCALAR_ALIAS_MAP: ReadonlyMap<string, string> = new Map([

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

wtf is this? why we need for arrays when we never needed it for scalars?

SevInf and others added 2 commits June 22, 2026 14:57
…ering, drop duplicate alias map

- T1: restore meaningful contract-authoring assertion (full-shape toEqual via
  spread) instead of the weakened Object.hasOwn check; runtime values are
  identical (sql/char@1 + typeParams {length:36}), the static column type just
  does not carry typeParams, so spread compares the real own props.
- T2: drop Exclude<C, undefined> by constraining the inferred type at the
  inference site (infer C extends Record<string, { output: unknown }>); the
  optional __codecTypes? made C include undefined.
- T5: renderArrayLiteralDefault reuses renderDefaultLiteral per element instead
  of a duplicate renderArrayElement.
- T6: array-element normalization moves into normalizeFormattedType (which
  already carries the integer->int4 scalar aliases for scalars), removing the
  duplicate SCALAR_ALIAS_MAP; native-type-normalizer.ts is now byte-identical to
  base.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>
… migration-render throw)

Remove the FIX5 guard in `renderDefaultLiteral` that threw for scalar
defaults on `many` columns. Only the array short-circuit stays; a
`.many().default(<scalar>)` now falls through to generic scalar rendering,
matching the operator decision that authoring-time diagnostics (FR3/FR14,
slice 2) are the right gate — not the migration planner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Serhii Tatarintsev <tatarintsev@prisma.io>

@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/2-sql/2-authoring/contract-ts/src/contract-types.ts`:
- Around line 24-27: The ExtractCodecTypesFromPack type definition currently has
an optional __codecTypes property which allows TypeScript to infer the type as
including undefined, causing downstream codec lookups to incorrectly resolve to
unknown instead of properly rejecting packs without codec types. Make
__codecTypes required by removing the optional modifier (?) and adding the
readonly modifier, changing from __codecTypes?: to readonly __codecTypes:. This
ensures that packs without explicit codec types are rejected by the type system
at compile time.
🪄 Autofix (Beta)

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

Run ID: 2bb6fdc3-d75a-4f6a-9a4d-cfdb005e87bd

📥 Commits

Reviewing files that changed from the base of the PR and between aef93e3 and f28e071.

📒 Files selected for processing (4)
  • examples/prisma-next-demo/test/contract-authoring.test.ts
  • packages/2-sql/2-authoring/contract-ts/src/contract-types.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts

Comment on lines +24 to +27
export type ExtractCodecTypesFromPack<P> = P extends {
__codecTypes?: infer C extends Record<string, { output: unknown }>;
}
? C

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
cd packages/2-sql/2-authoring/contract-ts/src || cd . 2>/dev/null
wc -l contract-types.ts 2>/dev/null || find . -name "contract-types.ts" -type f

Repository: prisma/prisma-next

Length of output: 84


🏁 Script executed:

# Read the file to see the full context around lines 24-27
cat -n contract-types.ts 2>/dev/null || find . -name "contract-types.ts" -exec cat -n {} \;

Repository: prisma/prisma-next

Length of output: 46009


🏁 Script executed:

# Search for usages of ExtractCodecTypesFromPack to understand how it's used
rg "ExtractCodecTypesFromPack" --context 3

Repository: prisma/prisma-next

Length of output: 5937


🏁 Script executed:

# Look for test files or type tests related to this type
fd -e "test.ts" -e "spec.ts" -e ".test.ts" | xargs rg "ExtractCodecTypesFromPack" 2>/dev/null || echo "No test matches found"

Repository: prisma/prisma-next

Length of output: 84


🏁 Script executed:

# Check if there are type definitions or interfaces that show the expected shape of packs
rg "__codecTypes" --context 5 -t ts

Repository: prisma/prisma-next

Length of output: 18065


Make __codecTypes required on packs to ensure complete codec type inference.

The optional __codecTypes property allows TypeScript's conditional type to infer C as Record<string, { output: unknown }> | undefined rather than falling back to Record<string, never> when a pack omits the phantom property. This causes downstream codec lookups to resolve to unknown instead of failing at the type level. Change the conditional constraint from __codecTypes?: to readonly __codecTypes: so that packs without explicit codec types are rejected by the type system.

A test in your codebase already documents this as a known gap: "Tracked as a known DSL type-inference gap to fix when __codecTypes becomes required on packs."

🐛 Proposed fix
 export type ExtractCodecTypesFromPack<P> = P extends {
-  __codecTypes?: infer C extends Record<string, { output: unknown }>;
+  readonly __codecTypes: infer C extends Record<string, { output: unknown }>;
 }
   ? C
   : Record<string, never>;
🤖 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/2-sql/2-authoring/contract-ts/src/contract-types.ts` around lines 24
- 27, The ExtractCodecTypesFromPack type definition currently has an optional
__codecTypes property which allows TypeScript to infer the type as including
undefined, causing downstream codec lookups to incorrectly resolve to unknown
instead of properly rejecting packs without codec types. Make __codecTypes
required by removing the optional modifier (?) and adding the readonly modifier,
changing from __codecTypes?: to readonly __codecTypes:. This ensures that packs
without explicit codec types are rejected by the type system at compile time.

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.

1 participant