Skip to content

fix(postgres-enums): emit enums by native type and address live enum storage by schema coordinate#791

Open
wmadden-electric wants to merge 3 commits into
mainfrom
fix/infer-enum-null-byte-map
Open

fix(postgres-enums): emit enums by native type and address live enum storage by schema coordinate#791
wmadden-electric wants to merge 3 commits into
mainfrom
fix/infer-enum-null-byte-map

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

contract infer on a Postgres database (e.g. Supabase) produced enum @@map strings containing a literal NUL byte (@@map("public\0application_kind")) and emitted Unsupported("...") for columns whose enum type had been inferred (#781, #782).

Both traced to one root cause: the Postgres control adapter stored introspected enums in annotations.pg.storageTypes under a hand-rolled (schema, nativeType) string key built with a NUL separator (enumStorageCompoundKey). The infer path used that compound key as the enum's type name (NUL leaked into @@map), and typeMap.resolve(column.nativeType) never matched it (so columns fell through to Unsupported).

The deeper issue: the introspected Schema IR isn't namespace-aware (flat tables, no schema on SqlTableIR), so live enums couldn't be addressed by the framework's EntityCoordinate (namespaceId + entityKind + entityName) like every other entity. The compound string key was a hack to recover the schema dimension the IR had discarded — and the NUL was a symptom of that hack.

Fix

Address live enum storage by the same (schema, nativeType) coordinate the contract side uses, structurally instead of as a packed string:

  • Enums now live in annotations.pg.enumTypes as Record<schema, Record<nativeType, entry>>. Non-enum codec types (vector, decimal) stay flat in storageTypes, so SQLite and those paths are untouched.
  • Deleted enumStorageCompoundKey and ENUM_STORAGE_KEY_SEP. The family↔target seam changed from "target returns an opaque string key" (EnumStorageKeyResolver) to "target resolves a namespace to its DDL schema" (EnumNamespaceSchemaResolver); the family projector nests by that schema.
  • Introspection writer, the multi-schema merge, and readExistingEnumValues all index enumTypes[schema][nativeType]. resolveDdlSchemaForNamespaceStorage (the legitimate namespace→schema bridge) stays.
  • extractEnumInfo reads the nested map and keys results by the unqualified native type, so inferred enum names (ApplicationKind) and @@map("application_kind") are clean — no schema prefix, no NUL.

No persisted artifact changes: these annotations are transient introspection IR, never written to contract.json (verified — no NUL byte in any committed .json/.prisma). Cross-schema same-native-type enums stay distinct (the enum-collision regression tests still pass).

Tests

  • postgres-type-map / sql-schema-ir-to-psl-ast: enum columns link to their type and emit a clean, NUL-free @@map; extractEnumInfo reads the nested enumTypes shape.
  • enum-collision (target) and postgres-issue-planner (integration): cross-schema enum disambiguation preserved under the nested shape.
  • control-adapter: introspection emits enumTypes nested by schema.
  • Print-psl snapshots unchanged (confirms emitted PSL is identical apart from the bug fix). Full family-sql (369), target-postgres (279), extension-pgvector (140) suites pass; lint, typecheck, and lint:deps clean.

Closes #781
Closes #782

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • PostgreSQL enums nested by schema are now correctly discovered and mapped to their native enum identities.
  • Refactor

    • Enum metadata restructured to group enums by schema, aligning introspection, printing, and migration flows.
  • Tests

    • Updated and added tests covering schema-qualified enum discovery, printing, and migration planning.

@wmadden-electric wmadden-electric requested a review from a team as a code owner June 9, 2026 15:42
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: f9bd1598-9abc-458a-b60a-e0078275fcff

📥 Commits

Reviewing files that changed from the base of the PR and between 1851752 and 45fe942.

📒 Files selected for processing (15)
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts
  • packages/2-sql/9-family/src/exports/control.ts
  • packages/2-sql/9-family/test/psl-contract-infer/postgres-type-map.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.defaults-and-types.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.enums.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.naming-and-constraints.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts
  • packages/3-targets/3-targets/postgres/src/exports/control.ts
  • packages/3-targets/3-targets/postgres/src/exports/enum-planning.ts
  • packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter.test.ts
  • test/integration/test/cross-package/postgres-issue-planner.test.ts
💤 Files with no reviewable changes (1)
  • packages/3-targets/3-targets/postgres/src/exports/enum-planning.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.enums.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/postgres-type-map.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/2-sql/9-family/src/exports/control.ts
  • packages/3-targets/3-targets/postgres/src/exports/control.ts
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts
  • packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts
  • test/integration/test/cross-package/postgres-issue-planner.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.naming-and-constraints.test.ts
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts

📝 Walkthrough

Walkthrough

Reworks Postgres enum IR: contract→schema options now resolve enum namespaces to DDL schemas, annotations carry enums under pg.enumTypes[schema][nativeType], extractors/planners/adapters read the nested shape, and tests/exports updated accordingly.

Changes

Postgres enum namespace & annotation refactor

Layer / File(s) Summary
Contract→Schema IR: enum namespace schema resolver and annotation emission
packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
Adds EnumNamespaceSchemaResolver, replaces resolveEnumStorageKey with resolveEnumNamespaceSchema, and emits annotations.pg.enumTypes[schema][nativeType] plus storageTypes for non-enum entries.
PSL enum extraction and unit/integration tests
packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts, packages/2-sql/9-family/src/exports/control.ts, packages/2-sql/9-family/test/psl-contract-infer/*
extractEnumInfo iterates pg.enumTypes nested maps, uses typeInstance.nativeType for enum identities, stores typeParams.values under that nativeType, and tests updated to pg.enumTypes.public fixtures including a column→enum linkage test.
Postgres enum planning: annotations parser and existing enum read
packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts
Adds PgEnumTypesMap, readPgEnumTypesMap, and makes readPostgresSchemaIrAnnotations / readExistingEnumValues consume enumTypes[schema][nativeType] instead of a flattened storageTypes.
Postgres target: wiring resolveEnumNamespaceSchema and exports
packages/3-targets/3-targets/postgres/src/exports/control.ts, packages/3-targets/3-targets/postgres/src/exports/enum-planning.ts
Target descriptor now supplies resolveEnumNamespaceSchema (via resolveDdlSchemaForNamespaceStorage) and removes enumStorageCompoundKey re-export.
Postgres control-adapter introspection & namespace merging
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts, tests
Introspection and namespace merging now accumulate pg.enumTypes keyed by schema and type name and emit pg.enumTypes in SqlSchemaIR.annotations instead of flattened storageTypes. Tests adjusted accordingly.
Tests and integration fixtures updated to new enumTypes shape
packages/3-targets/*/test/*, test/integration/test/cross-package/*
All affected unit and integration test fixtures and assertions updated to use annotations.pg.enumTypes[schema][nativeType]; removed unused enumStorageCompoundKey imports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through enums, schema and name,
Nested maps now tidy the game.
Columns link clean, no null-byte gloom,
Values snug in their schema room.
Hooray — compact enums, free to bloom!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% 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 describes the main changes: replacing compound-key enum storage with nested schema-based structure and fixing native type resolution for Postgres enums.
Linked Issues check ✅ Passed The PR addresses all coding requirements from both linked issues: eliminates null bytes in enum @@Map strings by removing enumStorageCompoundKey, and enables enum column resolution via nested enumTypes indexing by nativeType.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing Postgres enum storage: refactoring enum metadata structure, updating type resolvers, replacing compound-key logic, and updating corresponding tests across the codebase.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/infer-enum-null-byte-map

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 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/emitter

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 45fe942

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 150.55 KB (+0.04% 🔺)
postgres / emit 119.32 KB (-0.02% 🔽)
mongo / no-emit 76.67 KB (0%)
mongo / emit 70.96 KB (0%)
cf-worker / no-emit 179.99 KB (+0.02% 🔺)
cf-worker / emit 145.48 KB (+0.01% 🔺)

@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/9-family/src/core/psl-contract-infer/postgres-type-map.ts`:
- Around line 147-150: The code stores typeInstance.typeParams?.['values'] into
definitions with an unchecked cast (values as string[]); change this to a
runtime guard that ensures values is an array whose every element is a string
before adding to definitions (use Array.isArray(values) && values.every(v =>
typeof v === 'string')), and then insert it as a readonly string[] (e.g., copy
or cast only after the guard) for the Map keyed by nativeType so definitions
remains Map<string, readonly string[]>.
🪄 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: 20eb3cf8-41ee-41f7-9f5b-f23b7143af6b

📥 Commits

Reviewing files that changed from the base of the PR and between 01230c7 and 1fe4c19.

📒 Files selected for processing (3)
  • packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts
  • packages/2-sql/9-family/test/psl-contract-infer/postgres-type-map.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts

Comment thread packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts Outdated
@wmadden-electric wmadden-electric changed the title fix(contract-infer): key inferred enums by native type, not storage map key fix(postgres-enums): emit enums by native type and address live enum storage by schema coordinate Jun 9, 2026

@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.

Caution

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

⚠️ Outside diff range comments (1)
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts (1)

636-649: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the unbound schema when merging per-schema annotations.

pg.schema is carried over from perSchema[0], not from the namespace that resolved from UNBOUND_NAMESPACE_ID. If an explicit namespace comes first, resolveExistingEnumValues() later looks up unbound enums under the wrong schema and treats existing enums as missing.

Suggested fix
+    const unboundIndex = namespaceIds.indexOf(UNBOUND_NAMESPACE_ID);
+    const unboundSchema = unboundIndex === -1 ? undefined : resolvedSchemas[unboundIndex];
+
     return {
       tables: mergedTables,
       ...ifDefined('annotations', {
         ...firstAnnotations,
         pg: {
           ...firstPg,
+          ...ifDefined('schema', unboundSchema),
           ...ifDefined(
             'enumTypes',
             Object.keys(mergedEnumTypes).length > 0 ? mergedEnumTypes : 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/3-targets/6-adapters/postgres/src/core/control-adapter.ts` around
lines 636 - 649, The merged annotations currently take pg.schema from
perSchema[0] (firstAnnotations/firstPg) causing unbound enums to be looked up
under the wrong schema; modify the merge logic in control-adapter.ts so that
when building the returned annotations (the pg object merged from firstPg and
enumTypes) you choose pg.schema from the perSchema entry whose namespace equals
UNBOUND_NAMESPACE_ID if that entry exists, otherwise fall back to firstPg;
update the code paths around perSchema, firstAnnotations, firstPg,
mergedEnumTypes and resolveExistingEnumValues to use that selected schema so
unbound enums are resolved under the correct schema.
🤖 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.

Outside diff comments:
In `@packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts`:
- Around line 636-649: The merged annotations currently take pg.schema from
perSchema[0] (firstAnnotations/firstPg) causing unbound enums to be looked up
under the wrong schema; modify the merge logic in control-adapter.ts so that
when building the returned annotations (the pg object merged from firstPg and
enumTypes) you choose pg.schema from the perSchema entry whose namespace equals
UNBOUND_NAMESPACE_ID if that entry exists, otherwise fall back to firstPg;
update the code paths around perSchema, firstAnnotations, firstPg,
mergedEnumTypes and resolveExistingEnumValues to use that selected schema so
unbound enums are resolved under the correct schema.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: c90dc2b5-9744-4c90-9a7c-250e8de12781

📥 Commits

Reviewing files that changed from the base of the PR and between 1fe4c19 and 1851752.

📒 Files selected for processing (15)
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/postgres-type-map.ts
  • packages/2-sql/9-family/src/exports/control.ts
  • packages/2-sql/9-family/test/psl-contract-infer/postgres-type-map.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.defaults-and-types.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.enums.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.naming-and-constraints.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/enum-planning.ts
  • packages/3-targets/3-targets/postgres/src/exports/control.ts
  • packages/3-targets/3-targets/postgres/src/exports/enum-planning.ts
  • packages/3-targets/3-targets/postgres/test/migrations/enum-collision.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/test/control-adapter.test.ts
  • test/integration/test/cross-package/postgres-issue-planner.test.ts
💤 Files with no reviewable changes (1)
  • packages/3-targets/3-targets/postgres/src/exports/enum-planning.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/2-sql/9-family/src/exports/control.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts

wmadden and others added 3 commits June 9, 2026 19:04
…ap key

The Postgres control adapter stores introspected enum types in
`annotations.pg.storageTypes` keyed by a compound `schema\0nativeType`
string (NUL separator). `extractEnumInfo` used that map key directly as
the enum's type name, which broke two ways:

- The NUL byte leaked into the emitted enum `@@map`, producing PSL with
  embedded U+0000 that downstream tools treat as binary (#781).
- `typeMap.resolve(column.nativeType)` compares against the unqualified
  `udt_name`, so it never matched the compound key and every enum column
  fell through to `Unsupported("...")` (#782).

Each storage entry already carries the unqualified `nativeType` field, so
key `typeNames`/`definitions` off that instead. Names and `@@map` now
derive from the clean type name and columns resolve to their enum.

Closes #781
Closes #782

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ate, delete compound key

The introspected Schema IR isn't namespace-aware (flat `tables`, no schema on
`SqlTableIR`), so live enums couldn't be addressed by the framework's
`EntityCoordinate` like everything else. The enum lookup had hand-rolled a
`(schema, nativeType)` string key (`enumStorageCompoundKey`) with a NUL
separator and stored it in `annotations.pg.storageTypes` — the source of the
null-byte leak.

Replace that with a structural nesting keyed by the same `(schema, nativeType)`
coordinate the contract side uses: enums now live in `annotations.pg.enumTypes`
as `Record<schema, Record<nativeType, entry>>`. Non-enum codec types stay flat
in `storageTypes`, so SQLite and the vector/decimal paths are untouched.

- Delete `enumStorageCompoundKey` and `ENUM_STORAGE_KEY_SEP`.
- `EnumStorageKeyResolver(storage, ns, nativeType) -> string` becomes
  `EnumNamespaceSchemaResolver(storage, ns) -> schema`; the family projector
  nests by that schema instead of treating an opaque string key.
- Introspection writer, the multi-schema merge, and `readExistingEnumValues`
  all index `enumTypes[schema][nativeType]`. `resolveDdlSchemaForNamespaceStorage`
  (the legitimate namespace→schema bridge) stays.
- `extractEnumInfo` reads the nested map; inferred enum names/`@@map` are
  unchanged (still derived from the unqualified native type).

No persisted artifact changes (these annotations are transient introspection
IR, never in contract.json). Cross-schema same-native-type enums stay distinct
(enum-collision regression tests still pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
`extractEnumInfo` read `typeParams.values` (typed `unknown`) and stored it with
a bare `as string[]`, bypassing validation. Guard every element with a
`typeof === 'string'` check so `definitions` (Map<string, readonly string[]>)
only holds validated string arrays, and drop the cast.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden wmadden force-pushed the fix/infer-enum-null-byte-map branch from a7bcc91 to 45fe942 Compare June 9, 2026 17:04
@wmadden wmadden enabled auto-merge (squash) June 9, 2026 17:04
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.

contract infer emits Unsupported() for Postgres enums it already inferred contract infer emits U+0000 null bytes in enum @@map strings

2 participants