From: Gilbert Gilb's Date: Sun, 23 Jan 2022 18:00:35 +0000 (-0500) Subject: Add compiler support for PostgreSQL "NOT VALID" constraints. X-Git-Tag: rel_2_0_0b1~507^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=77dd6808f250e0431f9bce824f46f6e1ef63eef3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add compiler support for PostgreSQL "NOT VALID" constraints. Added compiler support for the PostgreSQL ``NOT VALID`` phrase when rendering DDL for the :class:`.CheckConstraint`, :class:`.ForeignKeyConstraint` and :class:`.ForeignKey` schema constructs. Pull request courtesy Gilbert Gilb's. Fixes: #7600 Closes: #7601 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/7601 Pull-request-sha: 78eecd55fd9fad07030d963f5fd6713c4af60e80 Change-Id: I84bfe84596856eeea2bcca45c04ad23d980a75ec --- diff --git a/doc/build/changelog/unreleased_14/7600.rst b/doc/build/changelog/unreleased_14/7600.rst new file mode 100644 index 0000000000..2f843ea198 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7600.rst @@ -0,0 +1,12 @@ +.. change:: + :tags: usecase, postgresql + :tickets: 7600 + + Added compiler support for the PostgreSQL ``NOT VALID`` phrase when rendering + DDL for the :class:`.CheckConstraint`, :class:`.ForeignKeyConstraint` + and :class:`.ForeignKey` schema constructs. Pull request courtesy + Gilbert Gilb's. + + .. seealso:: + + :ref:`postgresql_constraint_options` diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index e4ebbcb807..af81f39b0e 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -1058,7 +1058,54 @@ dialect in conjunction with the :class:`_schema.Table` construct: .. seealso:: `PostgreSQL CREATE TABLE options - `_ + `_ - + in the PostgreSQL documentation. + +.. _postgresql_constraint_options: + +PostgreSQL Constraint Options +----------------------------- + +The following option(s) are supported by the PostgreSQL dialect in conjunction +with selected constraint constructs: + +* ``NOT VALID``: This option applies towards CHECK and FOREIGN KEY constraints + when the constraint is being added to an existing table via ALTER TABLE, + and has the effect that existing rows are not scanned during the ALTER + operation against the constraint being added. + + When using a SQL migration tool such as `Alembic `_ + that renders ALTER TABLE constructs, the ``postgresql_not_valid`` argument + may be specified as an additional keyword argument within the operation + that creates the constraint, as in the following Alembic example:: + + def update(): + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + postgresql_not_valid=True + ) + + The keyword is ultimately accepted directly by the + :class:`_schema.CheckConstraint`, :class:`_schema.ForeignKeyConstraint` + and :class:`_schema.ForeignKey` constructs; when using a tool like + Alembic, dialect-specific keyword arguments are passed through to + these constructs from the migration operation directives:: + + CheckConstraint("some_field IS NOT NULL", postgresql_not_valid=True) + + ForeignKeyConstraint(["some_id"], ["some_table.some_id"], postgresql_not_valid=True) + + .. versionadded:: 1.4.32 + + .. seealso:: + + `PostgreSQL ALTER TABLE options + `_ - + in the PostgreSQL documentation. .. _postgresql_table_valued_overview: @@ -2580,6 +2627,10 @@ class PGDDLCompiler(compiler.DDLCompiler): colspec += " NULL" return colspec + def _define_constraint_validity(self, constraint): + not_valid = constraint.dialect_options["postgresql"]["not_valid"] + return " NOT VALID" if not_valid else "" + def visit_check_constraint(self, constraint): if constraint._type_bound: typ = list(constraint.columns)[0].type @@ -2594,7 +2645,16 @@ class PGDDLCompiler(compiler.DDLCompiler): "create_constraint=False on this Enum datatype." ) - return super(PGDDLCompiler, self).visit_check_constraint(constraint) + text = super(PGDDLCompiler, self).visit_check_constraint(constraint) + text += self._define_constraint_validity(constraint) + return text + + def visit_foreign_key_constraint(self, constraint): + text = super(PGDDLCompiler, self).visit_foreign_key_constraint( + constraint + ) + text += self._define_constraint_validity(constraint) + return text def visit_drop_table_comment(self, drop): return "COMMENT ON TABLE %s IS NULL" % self.preparer.format_table( @@ -3210,6 +3270,18 @@ class PGDialect(default.DefaultDialect): "inherits": None, }, ), + ( + schema.CheckConstraint, + { + "not_valid": False, + }, + ), + ( + schema.ForeignKeyConstraint, + { + "not_valid": False, + }, + ), ] reflection_options = ("postgresql_ignore_search_path",) diff --git a/test/dialect/postgresql/test_compiler.py b/test/dialect/postgresql/test_compiler.py index 0e04ccb955..b98d0fac6a 100644 --- a/test/dialect/postgresql/test_compiler.py +++ b/test/dialect/postgresql/test_compiler.py @@ -3,6 +3,7 @@ from sqlalchemy import and_ from sqlalchemy import BigInteger from sqlalchemy import bindparam from sqlalchemy import cast +from sqlalchemy import CheckConstraint from sqlalchemy import Column from sqlalchemy import Computed from sqlalchemy import Date @@ -10,6 +11,8 @@ from sqlalchemy import delete from sqlalchemy import Enum from sqlalchemy import exc from sqlalchemy import Float +from sqlalchemy import ForeignKey +from sqlalchemy import ForeignKeyConstraint from sqlalchemy import func from sqlalchemy import Identity from sqlalchemy import Index @@ -828,6 +831,62 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL): schema.DropIndex(idx1), "DROP INDEX test_idx1", dialect=dialect_9_1 ) + def test_create_check_constraint_not_valid(self): + m = MetaData() + + tbl = Table( + "testtbl", + m, + Column("data", Integer), + CheckConstraint("data = 0", postgresql_not_valid=True), + ) + + self.assert_compile( + schema.CreateTable(tbl), + "CREATE TABLE testtbl (data INTEGER, CHECK (data = 0) NOT VALID)", + ) + + def test_create_foreign_key_constraint_not_valid(self): + m = MetaData() + + tbl = Table( + "testtbl", + m, + Column("a", Integer), + Column("b", Integer), + ForeignKeyConstraint( + "b", ["testtbl.a"], postgresql_not_valid=True + ), + ) + + self.assert_compile( + schema.CreateTable(tbl), + "CREATE TABLE testtbl (" + "a INTEGER, " + "b INTEGER, " + "FOREIGN KEY(b) REFERENCES testtbl (a) NOT VALID" + ")", + ) + + def test_create_foreign_key_column_not_valid(self): + m = MetaData() + + tbl = Table( + "testtbl", + m, + Column("a", Integer), + Column("b", ForeignKey("testtbl.a", postgresql_not_valid=True)), + ) + + self.assert_compile( + schema.CreateTable(tbl), + "CREATE TABLE testtbl (" + "a INTEGER, " + "b INTEGER, " + "FOREIGN KEY(b) REFERENCES testtbl (a) NOT VALID" + ")", + ) + def test_exclude_constraint_min(self): m = MetaData() tbl = Table("testtbl", m, Column("room", Integer, primary_key=True))