Skip to content

Postgres enum types and unique constraints - #27

Merged
Makisuo merged 4 commits into
mainfrom
feat/pg-enums-unique-constraints
Oct 12, 2026
Merged

Makisuo merged 4 commits into
mainfrom
feat/pg-enums-unique-constraints

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Adds first-class Postgres enums and UNIQUE constraints across the query side, DDL, snapshots, the diff, generate, migrations, verify and the drizzle-kit importer. Hazel hit both gaps moving off drizzle (MapleTechLabs/hazel#330). Also fixes Runnable under TypeScript 5.

API

const ChannelType = PG.enumType("channel_type", ["public", "private", "direct"]) // { schema: "auth" } optional

const Channels = PG.table("channels", {
  columns: {
    id: PG.text,
    slug: PG.column(PG.text, { unique: true }),           // channels_slug_unique; or unique: "name" | { name, nullsNotDistinct }
    type: PG.column(ChannelType, { default: "public" }),  // rows: "public" | "private" | "direct"
    allowed: PG.nullable(PG.array(ChannelType)),
  },
  primaryKey: ["id"],
  uniqueConstraints: [PG.unique("channels_org_slug_unique", ["org_id", "slug"], { nullsNotDistinct: true })],
})
  • PG.enumType is both a column type and a schema object (S.isSchemaObject). It reads, writes and compares like PG.brand(PG.text, Schema.Literals(...)): a value that is not a label is a type error. Exporting it is optional, because tables bring the enums they use. Drivers send enum arrays as text ({a,b}), so array(enum) decodes that form and writes array text.
  • PG.unique / column unique declare ADD CONSTRAINT ... UNIQUE, not unique indexes. unique does not make a column optional on insert. onConflict* takes the constraint's columns or { constraint } as before.

Generate / migrate

  • CREATE TYPE runs first and DROP TYPE last. Dropping a type needs a confirm_data_loss hint (kind: "enum"). CREATE TYPE is guarded by duplicate_object, so moving a hand-made PG.custom("public.x") enum to enumType applies cleanly.
  • A new label becomes ADD VALUE ... BEFORE <next>, so the order is kept.
  • A label replaced in place (same labels, same order, one swapped) becomes RENAME VALUE, behind a new confirm_rename hint.
  • Removing or reordering labels is reported as unsupported (it needs a type rebuild).
  • Using a new label in the migration that adds it (in a default or a partial-index predicate) is also reported as unsupported. Migrations run in one transaction, and Postgres raises unsafe use of new value; a live test confirms it. The docs cover this and the PG12 requirement for ADD VALUE in a transaction.
  • Unique constraints are dropped and re-added on change. They are added before foreign keys that reference them and dropped after. A uniqueIndex → unique with the same name drops the index first.

Verify

  • Reads enums from pg_enum and unique constraints from pg_constraint (contype = 'u', NULLS NOT DISTINCT read from the definition, so it works before PG15). A constraint's backing index is never treated as an index.
  • The scratch rebuild searches the current schema after the scratch one, and an enum that already exists is used as it is. So public types resolve, and both sides deparse them unqualified. This removes the false public.x vs x drift in types, defaults and predicates.
  • An undeclared enum is reported only if no column uses it, so PG.custom enums still verify clean.

Drizzle importer

Converts enums (public → unqualified), enum columns (typeSchema), and uniques.

Tests

  • Unit: definitions, DDL, every enum and unique diff case, the importer.
  • Type tests (src/pg/enum.test-d.ts): row, insert and comparison types.
  • CLI generate: label add, rename via --hints, removal refused.
  • Live PGlite (src/migrate/pg-enums-uniques.test.ts): migrate/verify round trips through label add, rename, index→constraint, type drop; drift detection; an enum in another schema; the PG.custom workaround and its move to enumType; and the Hazel case, a drizzle-kit-pushed database with enums and unique constraints adopted via baseline that verifies clean and generates nothing.

Other commits

  • Runnable / CHWrite took CHInsert<any, …>. TS 5 compares that method by method, and the generic onConflictDoUpdate fails the check, so run(insert) and compile(insert) were type errors for consumers on TS 5. They now take AnyInsert (tag, state, phantom). There is a test-d for it. check-package also typechecks a dedicated consumer file with TS 5.9, in its own program because TS 5 variance measurement is order-dependent. That check fails without the fix.
  • A pre-existing union:crossTenant coverage gap on main made bun run test fail; it is now covered.

bun run build, bun run typecheck, bun run test and bun run test:package pass. There are two changesets: minor (features) and patch (Runnable).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Summary by CodeRabbit

  • New Features
    • Added PostgreSQL enum types and unique constraints, including schema generation, migrations, and drift verification support.
    • Added enum value additions and confirmed renames; removing or reordering labels remains unsupported.
    • Expanded Drizzle baseline conversion to include enums and unique constraints.
    • Improved compatibility with TypeScript 5 when running and compiling ClickHouse inserts.
  • Documentation
    • Expanded PostgreSQL guides with enum and unique-constraint usage, migration behavior, and supported limits.

Makisuo and others added 4 commits October 13, 2026 01:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PG.enumType declares a Postgres enum that is both a column type (rows
decode to its labels, inserts and comparisons take only them) and a
schema object: generate creates it before the tables that use it, adds
labels in place with ADD VALUE ... BEFORE, renames one behind a
confirm_rename hint, and asks before dropping it. Removing or reordering
labels, or using a label in the migration that adds it, is reported as
unsupported.

PG.unique and the column-level `unique` option declare UNIQUE
constraints, diffed and verified as constraints rather than indexes, so
a database drizzle-kit pushed verifies clean and a changed constraint is
dropped with DROP CONSTRAINT.

verify reads enums from pg_enum and unique constraints from
pg_constraint, and searches the current schema after its scratch schema,
so types of the current schema deparse alike on both sides. The
drizzle-kit importer converts enums, enum columns and unique constraints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runnable and CHWrite took CHInsert<any, ...>. TypeScript 5 compares a
concrete insert with it method by method, and the generic
onConflictDoUpdate fails that comparison: ConflictTarget<any> is any
string, and `any` is not assignable to a `never` Computed. TypeScript 7
accepts it, so the repo never saw it. Both now take AnyInsert, an insert
by its tag, state and phantom, which is all compiling and running read.

check-package typechecks a dedicated consumer file with TypeScript 5.9
as well, in its own program, since TypeScript 5's variance measurement
depends on which comparison runs first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 890fa3f4-e3d2-4107-ae57-3147345ca85c
📥 Commits

Reviewing files that changed from the base of the PR and between a379869 and 3e8eb07.

⛔ Files ignored due to path filters (1)
  • tests/__snapshots__/core-sql.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (40)
  • .changeset/pg-enums-unique-constraints.md
  • .changeset/runnable-typescript-5.md
  • design/migrations.md
  • docs/migrations.md
  • docs/postgres.md
  • docs/reference.md
  • docs/tables-and-types.md
  • scripts/check-doc-examples.mjs
  • scripts/check-package.ts
  • src/ch/compile.ts
  • src/ch/insert.ts
  • src/database/database.test-d.ts
  • src/database/database.ts
  • src/kit/cli.ts
  • src/kit/generate.ts
  • src/kit/graph.ts
  • src/kit/kit.test.ts
  • src/migrate/pg-enums-uniques.test.ts
  • src/migrate/pg-verify.ts
  • src/migrate/verify.ts
  • src/pg/enum.test-d.ts
  • src/pg/types.ts
  • src/postgres.ts
  • src/schema.ts
  • src/schema/diff.ts
  • src/schema/drizzle.ts
  • src/schema/entities.ts
  • src/schema/pg-define.ts
  • src/schema/pg-diff.ts
  • src/schema/pg-entities.ts
  • src/schema/pg-enums-uniques.test.ts
  • src/schema/pg-ops.ts
  • src/schema/pg-schema.test.ts
  • src/schema/snapshot.ts
  • tests/core-cases.ts
  • tests/dialect-cases.postgres.ts
  • tests/dialect-cases.ts
  • tests/dialect-coverage.test.ts
  • tests/dialect.postgres.test.ts
  • tests/package-consumer-ts5.mts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Walkthrough

This change adds PostgreSQL enum types and unique constraints across schema declarations, migration generation, drift verification, and Drizzle snapshot conversion. It also adds enum-array decoding support, broadens ClickHouse insert typing for Runnable, and adds cross-tenant union test coverage.

Changes

PostgreSQL enums and unique constraints

Layer / File(s) Summary
Schema declarations and entities
src/schema/pg-entities.ts, src/schema/pg-define.ts, src/schema/snapshot.ts, src/schema/entities.ts, src/postgres.ts, src/schema.ts, src/pg/enum.test-d.ts, docs/reference.md
PostgreSQL schema entities now include enums and unique constraints. Table definitions support enum columns and column-level or table-level unique constraints, including NULLS NOT DISTINCT. Snapshot construction validates and collects these entities.
Migration operations and generation
src/schema/pg-ops.ts, src/schema/pg-diff.ts, src/schema/diff.ts, src/kit/*, src/kit/kit.test.ts, src/schema/pg-enums-uniques.test.ts, src/schema/pg-schema.test.ts, docs/migrations.md, design/migrations.md, .changeset/pg-enums-unique-constraints.md, tests/dialect-coverage.test.ts
Migration generation renders enum and unique-constraint operations and orders enum changes around dependent schema operations. Enum label additions and confirmed renames are supported; removal and reordering are reported as unsupported. The CLI reports confirmation requirements for enum drops and label renames.
Drift verification and Drizzle conversion
src/migrate/pg-verify.ts, src/migrate/verify.ts, src/schema/drizzle.ts, src/migrate/pg-enums-uniques.test.ts, src/schema/pg-enums-uniques.test.ts, docs/migrations.md
Verification compares enum labels and unique constraints with the live PostgreSQL catalog. Drizzle snapshot conversion imports enums, schema-qualified types, and unique constraints. Tests cover migration drift and baseline adoption.
Enum typing and array handling
src/pg/types.ts, src/pg/enum.test-d.ts, tests/dialect-cases.postgres.ts, tests/dialect.postgres.test.ts, docs/postgres.md, docs/tables-and-types.md, docs/reference.md, scripts/check-doc-examples.mjs
Enum columns constrain values in rows, inserts, and comparisons. Enum arrays accept driver arrays or validated PostgreSQL array text. Fixtures, documentation, and example checks cover these types and behaviors.

ClickHouse insert Runnable typing

Layer / File(s) Summary
Broaden insert types for Runnable and compile
src/ch/insert.ts, src/ch/compile.ts, src/database/database.ts, src/database/database.test-d.ts, tests/package-consumer-ts5.mts, scripts/check-package.ts, .changeset/runnable-typescript-5.md
The exported AnyInsert type replaces the parameterized CHInsert type in CHWrite and Runnable. Type tests and a TypeScript 5 consumer test cover insert and upsert assignability, required parameters, and inferred result types.

Cross-tenant union test coverage

Layer / File(s) Summary
Add cross-tenant union cases
tests/core-cases.ts, tests/dialect-cases.ts
The core and dialect cases now cover cross-tenant scope on union queries. The core-case metadata route is optional when metadata is present.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant SchemaSnapshot
  participant diffPgSchemas
  participant renderPgOp
  participant Postgres
  participant verifyPg
  SchemaSnapshot->>diffPgSchemas: Compare declared schema entities
  diffPgSchemas->>renderPgOp: Supply ordered migration operations
  renderPgOp->>Postgres: Provide rendered migration SQL
  Postgres->>verifyPg: Expose the live schema catalog
  verifyPg->>SchemaSnapshot: Compare catalog entities with declarations
Loading

Merge Risk: ⚪ Minimal · up to 3e8eb

No actionable issue has been established that would prevent merging after normal checks.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 33 files. (7 skipped: 7… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the pull request's primary changes: PostgreSQL enum types and unique constraints.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 33 files. (7 skipped: 7 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@Makisuo
Makisuo merged commit 98a002d into main Oct 12, 2026
3 checks passed
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