Skip to content

Schema engine panics introspecting PostgreSQL 18: NOT NULL constraints (contype 'n') fail constraint_type parsing #29635

Description

@drudolf

Bug description

PostgreSQL 18 represents column NOT NULL constraints as first-class pg_constraint rows with contype = 'n' (PG 18 docs). The schema engine's constraint introspection query (schema-engine/sql-schema-describer/src/postgres/constraints_query.sql) filters with a denylist:

AND contype NOT IN ('p', 'u', 'f')

so on PostgreSQL 18 the new 'n' rows pass through to get_constraints, where row.get_expect_char(\"constraint_type\") panics:

panicked at schema-engine/sql-schema-describer/src/postgres.rs:1193:39:
called `Result::unwrap()` on an `Err` value: "Getting constraint_type from ResultRow ResultRow { columns: [\"namespace\", \"table_name\", \"constraint_name\", \"constraint_type\", \"constraint_definition\", \"is_deferrable\", \"is_deferred\"], ... values: [... Text(Some(\"Widget_color_not_null\")) ..., Text(Some(\"n\")) ..., Text(Some(\"NOT NULL color\")) ...] } as char failed"

Observed through the WASM schema engine (@prisma/schema-engine-wasm) with a driver adapter, where contype arrives as a text value; with no panic handler registered the schemaPush promise never settles, so callers hang indefinitely. Any PostgreSQL 18 database whose schema has at least one NOT NULL column (i.e. effectively all of them) triggers this as soon as introspection runs against a non-empty schema — db push diffing, db pull, migrate dev/deploy. On PG ≤ 17 the query returns no rows for a typical Prisma-managed schema (no check/exclusion constraints), which is why this path went unnoticed.

Note: PostgreSQL 18 is listed as supported in the supported databases matrix.

Severity

⚠️ Major: Breaks core functionality (e.g., migrations fail)

Reproduction

Discovered via in-process PGlite 0.5.x (PostgreSQL 18.3) behind @prisma/adapter-pg, no Docker needed:

  1. schemaPush a schema containing a model with a NOT NULL column (any non-optional field), e.g.:

    model Widget {
      id    Int    @id @default(autoincrement())
      name  String @unique
      color Color  @default(RED)
    }
  2. Run a second schemaPush (or any introspection) against the now non-empty schema.

  3. The constraints query returns rows like (\"public\", \"Widget\", \"Widget_color_not_null\", \"n\", \"NOT NULL color\", f, f) and the engine panics as above.

The same applies to a plain PostgreSQL 18 server via driver adapters; step-by-step: create any table with a NOT NULL column on PG 18, then run introspection through the WASM engine + driver adapter. (Whether the native-engine value path converts the \"char\" column type without panicking was not verified — if it does, the rows still reach the match and are discarded by the _ => () arm, which is the correct outcome.)

Expected vs. Actual Behavior

Expected: introspection on PostgreSQL 18 ignores contype = 'n' rows — column nullability is already read from pg_attribute.attnotnull, and NOT-NULL-as-constraint is PG 18 catalog bookkeeping, not schema information Prisma models.

Actual: the engine panics (as char failed), and via the WASM engine the returned promise never settles (indefinite hang).

Suggested fix

Add 'n' to the denylist in constraints_query.sql:

AND contype NOT IN ('p', 'u', 'f', 'n')

or invert to an allowlist (contype IN ('c', 'x')), which is robust against future contype additions (e.g. 't' trigger constraints are also currently unfiltered).

Prisma information

  • prisma / @prisma/client: 7.8.0
  • @prisma/schema-engine-wasm: 7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a (latest at time of filing; engine hash 3c6e192761c0362d496ed980de936e2f3cebcd3a)
  • Driver adapter: @prisma/adapter-pg 7.8.0

Environment & setup

  • OS: macOS 15 (also reproduces on Linux)
  • Database: PostgreSQL 18.3 (via PGlite 0.5.x; applies to any PostgreSQL 18)
  • Node.js: 24.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions