cockroachdb / cockroachdb/cockroach
sql/schema: track NOT NULL as proper table constraints (PG18 compatibility)
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
**Summary:**
PostgreSQL 18 reworked how NOT NULL constraints are stored and managed. Previously,
NOT NULL was tracked only as a boolean flag (`attnotnull`) in `pg_attribute`. In PG18,
each NOT NULL constraint gets its own row in `pg_constraint`, making it a proper named
table constraint — consistent with how CHECK, UNIQUE, and FK constraints are handled.
CockroachDB currently follows PG16 (or earlier) behavior for table constraints.
As part of the 26.3 domain type support work, we should track and plan for alignment
with PG18's constraint model.
**PG18 Changes:**
The key commit is [a379061a22a8](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=a379061a22a8fdf421e1a457cc6af8503def6252). The changes include:
1. **NOT NULL as catalog entries**: Each NOT NULL constraint now gets a row in
`pg_constraint` with its own name, OID, and metadata — just like CHECK or FK
constraints.
2. **Named NOT NULL constraints**: Users can assign custom names via
`ADD CONSTRAINT "name" NOT NULL column`. Constraints appear in a dedicated
"Not-null constraints" section in `psql` output.
3. **NOT VALID support**: NOT NULL constraints can be added as `NOT VALID`,
deferring the table scan. They can later be validated via
`ALTER TABLE ... VALIDATE CONSTRAINT` or `ALTER TABLE ... SET NOT NULL`
without holding an exclusive lock.
4. **INHERIT / NO INHERIT**: NOT NULL constraints can be marked `NO INHERIT`
to prevent propagation to child tables, and can be converted via
`ALTER TABLE ... ALTER CONSTRAINT ... INHERIT`.
5. **Internal nullability tracking (`attnullability`)**: PG18 adds an
`attnullability` field, but it is **not** a `pg_attribute` catalog column and
does **not** replace `attnotnull` — `pg_attribute.attnotnull` still exists.
`attnullability` is a runtime-only field on the in-memory `CompactAttribute`
tuple descriptor (`src/include/access/tupdesc.h`), derived from `attnotnull`
plus the NOT NULL `pg_constraint` row's validated flag. It has four states:
`ATTNULLABLE_UNRESTRICTED` ('f'), `ATTNULLABLE_UNKNOWN` ('u'),
`ATTNULLABLE_VALID` ('v'), `ATTNULLABLE_INVALID` ('i'). Its consumers are the
planner (`plancat.c`) and JIT tuple deforming (`llvmjit_deform.c`), letting
internal code cheaply distinguish a validated NOT NULL from a `NOT VALID` one.
Because it is invisible to clients/tools, it carries no catalog-compatibility
obligation for CRDB — it would only become relevant internally if CRDB
implements `NOT VALID` NOT NULL.
**References:**
- PG commit: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=a379061a22a8fdf421e1a457cc6af8503def6252
- EDB blog post: https://www.enterprisedb.com/blog/changes-not-null-postgres-18
**Relevance to CRDB:**
- CRDB currently has PG16-era behavior for NOT NULL (stored as a column property,
not a proper constraint).
- Domain type support (in progress for 26.3) interacts with constraint handling —
domain CHECK constraints and NOT NULL constraints on domain-typed columns need
a coherent model.
- The schema changer already models CHECK constraints as elements
(`CheckConstraint` in `scpb`); extending this pattern to NOT NULL constraints
would align with PG18 and simplify domain type integration.
- `pg_catalog.pg_constraint` compatibility: PG18 clients and tools will expect
NOT NULL rows in `pg_constraint`.
**Status (updated 2026-06-05):**
The introspection-compatibility surface and the domain-type slice have landed;
the broader "NOT NULL as a first-class, named, NOT-VALID-capable table
constraint" model has not.
**Work Items:**
- [x] Evaluate impact on `pg_catalog.pg_constraint` virtual table — done.
`pg_constraint` now synthesizes `contype='n'` rows for every non-nullable
column (#169937), matching PG's introspection surface (used by `pg_dump`).
- [ ] Evaluate impact on schema changer constraint elements — partially done.
Domain NOT NULL goes through the schema changer (`ALTER DOMAIN ... NOT NULL`,
#166936); table-level NOT NULL is still a column property, not an `scpb`
element.
- [x] Evaluate interaction with domain type constraint handling — done.
Domain CHECK and NOT NULL constraints are enforced, and unvalidated
`ALTER DOMAIN ... { SET | DROP } NOT NULL` is implemented.
- [ ] Determine version gating strategy for the behavioral change.
**Still missing (full PG18 table-level model):**
- Named NOT NULL constraints on tables (`ADD [CONSTRAINT name] NOT NULL col`);
the grammar only supports `ALTER TABLE ... SET/DROP NOT NULL`. The
`ADD CONSTRAINT` form exists only for `ALTER DOMAIN`.
- `NOT VALID` NOT NULL on tables; `INHERIT` / `NO INHERIT`.
- Modeling table NOT NULL as a first-class schema-changer (`scpb`) element.
Jira issue: CRDB-63204
Epic CRDB-60817
Contributor guide
Assessment
This issue has not been assessed yet.