tml-2911: native scalar-array storage machinery (SQL, behind the PSL gate)#846
tml-2911: native scalar-array storage machinery (SQL, behind the PSL gate)#846SevInf wants to merge 3 commits into
Conversation
|
Warning Review limit reached
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 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 configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds end-to-end scalar list (native array column) support. An optional ChangesScalar List Support
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
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 winCarry
manythrough nullable-scope transforms.This change preserves
manyin base scope tables, but nullable scope mappings still erase it (NullableScopeTableand runtimenullableScopebuilder 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 winPropagate
manyin thetypeRefpath as well.
columnDef.manyis currently ignored whencolumnDef.typeRefis used, so list-typedtypeRefcolumns produce scalarCodecRefs 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 winEncode 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[], orBigInt[]can fail or produce the wrong wire values before the new array literal path runs. Also cast the final array literal tonativeTypeand 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 winCheck
runner.executeoutcome 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 winUse
blindCastfor the modified state-transition casts.These changed return casts still use
as unknown as; please match the newmany()/enum branch pattern so production code does not add bare casts. As per coding guidelines, “No bareasin production code. UseblindCast<T, "Reason">orcastAs<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
⛔ Files ignored due to path filters (16)
examples/bundle-size/src/postgres/generated/contract.d.tsis excluded by!**/generated/**examples/bundle-size/src/postgres/generated/contract.jsonis excluded by!**/generated/**packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.jsonis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.jsonis excluded by!**/generated/**test/e2e/framework/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/e2e/framework/test/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/namespaced-accessors/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/namespaced-accessors/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-builder/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-builder/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.jsonis excluded by!**/generated/**
📒 Files selected for processing (110)
apps/telemetry-backend/src/prisma/contract.d.tsapps/telemetry-backend/src/prisma/contract.jsonexamples/multi-extension-monorepo/app/src/contract.d.tsexamples/multi-extension-monorepo/app/src/contract.jsonexamples/multi-extension-monorepo/packages/audit/src/contract.d.tsexamples/multi-extension-monorepo/packages/audit/src/contract.jsonexamples/multi-extension-monorepo/packages/feature-flags/src/contract.d.tsexamples/multi-extension-monorepo/packages/feature-flags/src/contract.jsonexamples/paradedb-demo/src/prisma/contract.d.tsexamples/paradedb-demo/src/prisma/contract.jsonexamples/prisma-next-cloudflare-worker/src/prisma/contract.d.tsexamples/prisma-next-cloudflare-worker/src/prisma/contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.jsonexamples/prisma-next-demo/src/prisma/contract.d.tsexamples/prisma-next-demo/src/prisma/contract.jsonexamples/prisma-next-demo/test/contract-authoring.test.tsexamples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.d.tsexamples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.jsonexamples/prisma-next-postgis-demo/src/prisma/contract.d.tsexamples/prisma-next-postgis-demo/src/prisma/contract.jsonexamples/react-router-demo/src/prisma/contract.d.tsexamples/react-router-demo/src/prisma/contract.jsonexamples/supabase/src/contract.d.tsexamples/supabase/src/contract.jsonpackages/1-framework/1-core/framework-components/src/shared/codec-types.tspackages/2-sql/1-core/contract/src/factories.tspackages/2-sql/1-core/contract/src/ir/storage-column.tspackages/2-sql/1-core/contract/src/ir/storage-entry-schemas.tspackages/2-sql/1-core/contract/src/types.tspackages/2-sql/1-core/contract/test/storage-column-many.test.tspackages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.tspackages/2-sql/2-authoring/contract-ts/src/contract-dsl.tspackages/2-sql/2-authoring/contract-ts/src/contract-lowering.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.tspackages/2-sql/4-lanes/relational-core/src/ast/types.tspackages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.tspackages/2-sql/4-lanes/relational-core/src/expression.tspackages/2-sql/4-lanes/relational-core/test/scalar-list-discriminant.test-d.tspackages/2-sql/4-lanes/relational-core/test/scalar-list-ops.test-d.tspackages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.tspackages/2-sql/4-lanes/sql-builder/src/scope.tspackages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.tspackages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/2-sql/5-runtime/test/scalar-list-codec.test.tspackages/2-sql/5-runtime/test/utils.tspackages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.tspackages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.tspackages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.tspackages/3-extensions/paradedb/src/contract.d.tspackages/3-extensions/paradedb/src/contract.jsonpackages/3-extensions/pgvector/src/contract.d.tspackages/3-extensions/pgvector/src/contract.jsonpackages/3-extensions/postgis/src/contract.d.tspackages/3-extensions/postgis/src/contract.jsonpackages/3-extensions/supabase/src/contract/contract.d.tspackages/3-extensions/supabase/src/contract/contract.jsonpackages/3-targets/3-targets/postgres/src/core/default-normalizer.tspackages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.tspackages/3-targets/3-targets/postgres/src/core/native-type-normalizer.tspackages/3-targets/6-adapters/postgres/src/core/adapter.tspackages/3-targets/6-adapters/postgres/src/core/control-adapter.tspackages/3-targets/6-adapters/postgres/src/core/descriptor-meta.tspackages/3-targets/6-adapters/postgres/src/core/sql-renderer.tspackages/3-targets/6-adapters/postgres/test/adapter.test.tspackages/3-targets/6-adapters/postgres/test/descriptor-meta.test.tspackages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.tspackages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.tspackages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.tspackages/3-targets/6-adapters/sqlite/test/adapter.test.tsskills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.mdskills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.mdtest/integration/test/authoring/parity/callback-mode-scalars/expected.contract.jsontest/integration/test/authoring/parity/core-surface/expected.contract.jsontest/integration/test/authoring/parity/default-cuid-2/expected.contract.jsontest/integration/test/authoring/parity/default-dbgenerated/expected.contract.jsontest/integration/test/authoring/parity/default-nanoid-16/expected.contract.jsontest/integration/test/authoring/parity/default-nanoid/expected.contract.jsontest/integration/test/authoring/parity/default-pack-slugid/expected.contract.jsontest/integration/test/authoring/parity/default-ulid/expected.contract.jsontest/integration/test/authoring/parity/default-uuid-v4/expected.contract.jsontest/integration/test/authoring/parity/default-uuid-v7/expected.contract.jsontest/integration/test/authoring/parity/map-attributes/expected.contract.jsontest/integration/test/authoring/parity/pgvector-named-type/expected.contract.jsontest/integration/test/authoring/parity/relation-backrelation-list/expected.contract.jsontest/integration/test/authoring/side-by-side/postgres/contract.jsontest/integration/test/fixtures/contract.d.tstest/integration/test/fixtures/contract.json
There was a problem hiding this comment.
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 winPropagate
manyin thetypeRefbranch too.On Line 39,
typeRefcolumns return{ codecId, typeParams }/{ codecId }withoutmany: true, so list-typed columns backed bystorage.typesare 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 winKeep
manythrough nullable scope mapping.Line 48 preserves listness in base scope mapping, but
NullableScopeTabledrops 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 winReject
manyautoincrement columns before returningSERIAL.With
column.many === trueanddefault: autoincrement(), this branch returns scalarSERIAL/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 winPrefer
ifDefinedfor conditionalmanyspread.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
ifDefinedfromprisma-next/utils/definedfor 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
⛔ Files ignored due to path filters (22)
examples/bundle-size/src/postgres/generated/contract.d.tsis excluded by!**/generated/**examples/bundle-size/src/postgres/generated/contract.jsonis excluded by!**/generated/**packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**packages/2-sql/4-lanes/sql-builder/test/fixtures/generated/contract.jsonis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/generated/contract.jsonis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.d.tsis excluded by!**/generated/**packages/3-extensions/sql-orm-client/test/fixtures/junction-namespaces/generated/contract.jsonis excluded by!**/generated/**test/e2e/framework/test/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/e2e/framework/test/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/namespaced-accessors/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/namespaced-accessors/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-builder/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-builder/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/execution-defaulted-tags/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/junction-namespaces/generated/contract.jsonis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.d.tsis excluded by!**/generated/**test/integration/test/sql-orm-client/fixtures/polymorphism/generated/contract.jsonis excluded by!**/generated/**
📒 Files selected for processing (110)
apps/telemetry-backend/src/prisma/contract.d.tsapps/telemetry-backend/src/prisma/contract.jsonexamples/multi-extension-monorepo/app/src/contract.d.tsexamples/multi-extension-monorepo/app/src/contract.jsonexamples/multi-extension-monorepo/packages/audit/src/contract.d.tsexamples/multi-extension-monorepo/packages/audit/src/contract.jsonexamples/multi-extension-monorepo/packages/feature-flags/src/contract.d.tsexamples/multi-extension-monorepo/packages/feature-flags/src/contract.jsonexamples/paradedb-demo/src/prisma/contract.d.tsexamples/paradedb-demo/src/prisma/contract.jsonexamples/prisma-next-cloudflare-worker/src/prisma/contract.d.tsexamples/prisma-next-cloudflare-worker/src/prisma/contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0720_initial/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0742_migration/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0742_migration/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0748_migration/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260422T0748_migration/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/start-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/end-contract.jsonexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.d.tsexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/start-contract.jsonexamples/prisma-next-demo/src/prisma/contract.d.tsexamples/prisma-next-demo/src/prisma/contract.jsonexamples/prisma-next-demo/test/contract-authoring.test.tsexamples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.d.tsexamples/prisma-next-postgis-demo/migrations/app/20260512T1309_migration/end-contract.jsonexamples/prisma-next-postgis-demo/src/prisma/contract.d.tsexamples/prisma-next-postgis-demo/src/prisma/contract.jsonexamples/react-router-demo/src/prisma/contract.d.tsexamples/react-router-demo/src/prisma/contract.jsonexamples/supabase/src/contract.d.tsexamples/supabase/src/contract.jsonpackages/1-framework/1-core/framework-components/src/shared/codec-types.tspackages/2-sql/1-core/contract/src/factories.tspackages/2-sql/1-core/contract/src/ir/storage-column.tspackages/2-sql/1-core/contract/src/ir/storage-entry-schemas.tspackages/2-sql/1-core/contract/src/types.tspackages/2-sql/1-core/contract/test/storage-column-many.test.tspackages/2-sql/1-core/schema-ir/src/ir/sql-column-ir.tspackages/2-sql/2-authoring/contract-ts/src/contract-dsl.tspackages/2-sql/2-authoring/contract-ts/src/contract-lowering.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/2-sql/2-authoring/contract-ts/test/scalar-list-output-types.test-d.tspackages/2-sql/4-lanes/relational-core/src/ast/types.tspackages/2-sql/4-lanes/relational-core/src/codec-ref-for-column.tspackages/2-sql/4-lanes/relational-core/src/expression.tspackages/2-sql/4-lanes/relational-core/test/scalar-list-discriminant.test-d.tspackages/2-sql/4-lanes/relational-core/test/scalar-list-ops.test-d.tspackages/2-sql/4-lanes/sql-builder/src/runtime/builder-base.tspackages/2-sql/4-lanes/sql-builder/src/scope.tspackages/2-sql/4-lanes/sql-builder/test/types/scalar-list-ops.types.test-d.tspackages/2-sql/5-runtime/src/codecs/decoding.tspackages/2-sql/5-runtime/src/codecs/encoding.tspackages/2-sql/5-runtime/test/scalar-list-codec.test.tspackages/2-sql/5-runtime/test/utils.tspackages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.tspackages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.tspackages/2-sql/9-family/src/core/schema-verify/verify-sql-schema.tspackages/3-extensions/paradedb/src/contract.d.tspackages/3-extensions/paradedb/src/contract.jsonpackages/3-extensions/pgvector/src/contract.d.tspackages/3-extensions/pgvector/src/contract.jsonpackages/3-extensions/postgis/src/contract.d.tspackages/3-extensions/postgis/src/contract.jsonpackages/3-extensions/supabase/src/contract/contract.d.tspackages/3-extensions/supabase/src/contract/contract.jsonpackages/3-targets/3-targets/postgres/src/core/default-normalizer.tspackages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.tspackages/3-targets/3-targets/postgres/src/core/native-type-normalizer.tspackages/3-targets/6-adapters/postgres/src/core/adapter.tspackages/3-targets/6-adapters/postgres/src/core/control-adapter.tspackages/3-targets/6-adapters/postgres/src/core/descriptor-meta.tspackages/3-targets/6-adapters/postgres/src/core/sql-renderer.tspackages/3-targets/6-adapters/postgres/test/adapter.test.tspackages/3-targets/6-adapters/postgres/test/descriptor-meta.test.tspackages/3-targets/6-adapters/postgres/test/migrations/array-column-introspection.integration.test.tspackages/3-targets/6-adapters/postgres/test/migrations/native-array-columns.integration.test.tspackages/3-targets/6-adapters/postgres/test/scalar-list-codec-roundtrip.integration.test.tspackages/3-targets/6-adapters/sqlite/test/adapter.test.tsskills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.mdskills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.mdtest/integration/test/authoring/parity/callback-mode-scalars/expected.contract.jsontest/integration/test/authoring/parity/core-surface/expected.contract.jsontest/integration/test/authoring/parity/default-cuid-2/expected.contract.jsontest/integration/test/authoring/parity/default-dbgenerated/expected.contract.jsontest/integration/test/authoring/parity/default-nanoid-16/expected.contract.jsontest/integration/test/authoring/parity/default-nanoid/expected.contract.jsontest/integration/test/authoring/parity/default-pack-slugid/expected.contract.jsontest/integration/test/authoring/parity/default-ulid/expected.contract.jsontest/integration/test/authoring/parity/default-uuid-v4/expected.contract.jsontest/integration/test/authoring/parity/default-uuid-v7/expected.contract.jsontest/integration/test/authoring/parity/map-attributes/expected.contract.jsontest/integration/test/authoring/parity/pgvector-named-type/expected.contract.jsontest/integration/test/authoring/parity/relation-backrelation-list/expected.contract.jsontest/integration/test/authoring/side-by-side/postgres/contract.jsontest/integration/test/fixtures/contract.d.tstest/integration/test/fixtures/contract.json
…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( |
There was a problem hiding this comment.
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 }> |
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Can we validate this earlier, before it gets to migration rendering? Ideally, at authoring time
| return `ARRAY[${rendered}]`; | ||
| } | ||
|
|
||
| function renderArrayElement(el: unknown): string { |
There was a problem hiding this comment.
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([ |
There was a problem hiding this comment.
wtf is this? why we need for arrays when we never needed it for scalars?
…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>
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
examples/prisma-next-demo/test/contract-authoring.test.tspackages/2-sql/2-authoring/contract-ts/src/contract-types.tspackages/3-targets/3-targets/postgres/src/core/migrations/planner-ddl-builders.tspackages/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
| export type ExtractCodecTypesFromPack<P> = P extends { | ||
| __codecTypes?: infer C extends Record<string, { output: unknown }>; | ||
| } | ||
| ? C |
There was a problem hiding this comment.
🧩 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 fRepository: 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 3Repository: 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 tsRepository: 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.
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 amanystorage column until slice 2 flips the interpreter, so this ships invisibly. It is the foundation for first-classString[]/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
manyis 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+many→text[]).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 additivescalarListcapability declaration (+ a routine version bump). Slice 2 flips the gate.many?: never), not on scalar value types — soXandX[]stay distinct at the type level without spellingmany: falseon every scalar.What's proven (slice ACs)
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).text[],int4[]) with container nullability (NOT NULL/nullable) and array defaults (DEFAULT '{}').many: true.ReadonlyArray<Element>(| nullfor a nullable container).scalarListcapability (instance + descriptor surfaces); SQLite does not.Deferred to slice 2 (intentional)
CHECK (array_position(col, NULL) IS NULL)— needs element-nullability represented in the IR, which the PSL interpreter (the layer that distinguishesT[]fromT?[]) introduces.parseArrayLiteralBodyquoted/escaped array-element handling — unreached by slice-1 emitted defaults.pg-driver round-trip — exercised here viapg/pg-cursorover PGlite; full real-Postgres confirmation runs in CI.Reviewer notes
.many()-builder trim→revert pair and anorigin/mainmerge with re-integration fixups — all collapse on squash-merge.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