Skip to content

PG 12+ allows reusing a CHECK constraint when setting NOT NULL on existing column #300

@james-johnston-thumbtack

Description

The release notes for PG 12.0 note the following: https://www.postgresql.org/docs/release/12.0/

Allow ALTER TABLE ... SET NOT NULL to avoid unnecessary table scans (Sergei Kornilov)

This can be optimized when the table's column constraints can be recognized as disallowing nulls.

And the ALTER TABLE documentation now states:

SET NOT NULL may only be applied to a column provided none of the records in the table contain a NULL value for the column. Ordinarily this is checked during the ALTER TABLE by scanning the entire table; however, if a valid CHECK constraint is found which proves no NULL can exist, then the table scan is skipped.

I tested this on one of our large tables on a PG 13 installation, and it does seem to work as advertised. The VALIDATE CONSTRAINT step took a few minutes as expected. Then running ALTER TABLE ALTER COLUMN SET NOT NULL completed instantaneously (hardly any perceptible delay).

The documentation at https://squawkhq.com/docs/adding-not-nullable-field/#setting-an-existing-column-as-non-nullable could be updated for the existing column case as follows:

  1. Create CHECK constraint that is NOT VALID, as already listed in the example.
  2. Validate the CHECK constraint, as already listed in the example.
  3. New step: set column NOT NULL: ALTER TABLE "recipe" ALTER COLUMN "view_count" SET NOT NULL;
  4. New step: drop CHECK constraint: ALTER TABLE "recipe" DROP CONSTRAINT view_count_not_null;

The end state is as if you had never used a CHECK constraint at all, and had always used SET NOT NULL.

Of course, the lint rule will need to be updated since it does check for this. And the SQL is hard for linting because there is no way to tell PG to use an existing constraint. For example, ALTER TABLE ADD UNIQUE USING INDEX abc; provides no question that abc index will be used, and thus the linter can allow this. But there is no similar ALTER TABLE recipe ALTER COLUMN SET NOT NULL USING CONSTRAINT view_count_not_null; command. So without prior knowledge of whether that CHECK constraint exists, you can't be sure whether the operation is safe or not.

Some ideas:

  • SELECT from a function (that would need to be created by the user) which raises an exception if a compatible constraint can't be found. The linter can assert that this function is called immediately prior to SET NOT NULL.
  • If the user has tested and timed the migration on a copy of the production data set, and the migration that runs SET NOT NULL was tested to be short (potentially declared via a code comment), then the linter could allow it.

At the very least though, I think the documentation should note the new PG12+ capabilities.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions