drizzle-team / drizzle-team/drizzle-orm

[BUG]: drizzle-kit generate crashes when a table with a partial index changes schema from "" to "public"

Open
#5,761 0 comments 0 reactions 0 assignees View on GitHub
bug/fixed-in-beta
Dominant language
TypeScript
Stars
35.8k
Forks
1.6k
Avg merge
2d 7h
Merged PRs (30d)
4

Description

### Report hasn't been filed before.

- [x] I have verified that the bug I'm about to report hasn't been filed before.

### What version of `drizzle-orm` are you using?

0.45.2

### What version of `drizzle-kit` are you using?

0.31.10

### Other packages

`pg@8.20.0`

### Describe the Bug

When a table with a **partial index** is migrated from the default schema (unqualified, `schema: ""` in the snapshot) to the `public` schema, `drizzle-kit generate` crashes. The user-visible message is also misleading: drizzle-kit catches the real error and `console.error(e)`s it, but `util.inspect` then crashes on the ZodError's internals, so the message that actually surfaces is:

```
Cannot read properties of undefined (reading 'value')
```

…with no stack and no indication of which table is at fault.

The real error (visible after patching `case "unknown_error"` in `bin.cjs` to log `e.stack` instead of relying on `util.inspect`):

```
ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "object",
"path": [ "alteredTablesWithColumns", 0, "schema" ],
"message": "Expected string, received object"
}
]
at applyPgSnapshotsDiff (drizzle-kit/bin.cjs:28417:44)
at prepareAndMigratePg (drizzle-kit/bin.cjs:32191:42)
```

Tables that don't have a partial index migrate from unqualified to `public` without issue. Only tables with `uniqueIndex(...).where(sql\`...\`)` (or `index(...).where(...)`) trigger it.

This is the Postgres counterpart of #5593 (MSSQL Filtered Index, where the bug surfaces as invalid T-SQL). In our case it's the snapshot diff that fails before any SQL is emitted.

### Why we hit this

Related to #5359 — we have a hard requirement to schema-qualify Postgres tables (`"public"."users"` instead of `"users"`) because we're behind a PgBouncer pooler that has been handing out backends with `search_path = ''`. Unqualified queries hit 42P01 on those backends, causing intermittent outages. `pgSchema("public")` is rejected by the factory, so we construct `PgSchema` directly:

```ts
import { PgSchema } from "drizzle-orm/pg-core"
const publicSchema = new PgSchema("public")
```

Runtime works fine — `db.select(...).toSQL()` correctly emits `"public"."users"`. The `drizzle-kit generate` path is where this falls over.

### Minimal repro

Before:

```ts
import { sql } from "drizzle-orm"
import { pgTable, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"

export const widgetsTable = pgTable(
"widgets",
{
id: uuid("id").primaryKey().defaultRandom(),
deletedAt: timestamp("deleted_at", { mode: "string" }),
},
(t) => [uniqueIndex("idx_widgets_active").on(t.id).where(sql`${t.deletedAt} IS NULL`)],
)
```

Generate a snapshot. Then switch to:

```ts
import { sql } from "drizzle-orm"
import { PgSchema, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core"

const publicSchema = new PgSchema("public")

export const widgetsTable = publicSchema.table(
"widgets",
{
id: uuid("id").primaryKey().defaultRandom(),
deletedAt: timestamp("deleted_at", { mode: "string" }),
},
(t) => [uniqueIndex("idx_widgets_active").on(t.id).where(sql`${t.deletedAt} IS NULL`)],
)
```

Run `drizzle-kit generate`. Crashes as above.

A table-level CHECK constraint with column references in its expression triggers the same crash on the same code path.

### Workaround

Hand-edit the latest `migrations/meta/NNNN_snapshot.json` so the affected tables already have `schema: "public"` and the column references in partial-index `where` clauses (and any CHECK `value` expressions) are pre-qualified to `"public"."widgets"."deleted_at"` etc. With both sides of the diff aligned, the buggy `alteredTablesWithColumns` path isn't entered, and `drizzle-kit generate` reports "No schema changes, nothing to migrate" — which is the correct outcome since `"public"."widgets"` and `"widgets"` are equivalent in Postgres.

### Two sub-fixes that would help

1. Handle the schema field change correctly in the snapshot diff for tables with partial indexes / CHECK constraints (i.e. don't drop into the `alteredTablesWithColumns[].schema = { ... }` shape that the Zod schema rejects).
2. Log the real `e.stack` (or at least `e.message` of the ZodError, including its `issues`) in the `case "unknown_error"` handler in `bin.cjs`, instead of relying on `util.inspect(e)`. The current behavior turns a real error into a misleading `Cannot read properties of undefined (reading 'value')` that took us a while to track down.

Contributor guide

Open the contributing guide

Research direction

Reproduce the migration with the provided partial-index example, then inspect applyPgSnapshotsDiff in bin.cjs and the case "unknown_error" handler. Compare the generated snapshot diff for schema changes involving partial indexes or CHECK constraints. Done means drizzle-kit generate no longer crashes, reports the real validation error when applicable, and correctly recognizes equivalent unqualified and public-qualified tables.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgresql, typescript
Domain
cli, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.