From: Bill Finn Date: Tue, 27 Aug 2019 16:21:57 +0000 (-0400) Subject: PGDialect.get_check_constraints: Handle "NOT VALID" X-Git-Tag: rel_1_4_0b1~745 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3980a9a455d08c5073cabc3c2b77de46fa36d7b4;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git PGDialect.get_check_constraints: Handle "NOT VALID" Added support for reflection of CHECK constraints that include the special PostgreSQL qualifier "NOT VALID", which can be present for CHECK constraints that were added to an exsiting table with the directive that they not be applied to existing data in the table. The PostgreSQL dictionary for CHECK constraints as returned by :meth:`.Inspector.get_check_constraints` may include an additional entry ``dialect_options`` which within will contain an entry ``"not_valid": True`` if this symbol is detected. Pull request courtesy Bill Finn. Fixes: #4824 Closes: #4825 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4825 Pull-request-sha: a0e1ab133c2d46521a74e55423ac2ba866682dae Change-Id: I78365f50055c95474c92124b85df66c5c80c00c8 --- diff --git a/doc/build/changelog/unreleased_13/4824.rst b/doc/build/changelog/unreleased_13/4824.rst new file mode 100644 index 0000000000..0a624b3f1c --- /dev/null +++ b/doc/build/changelog/unreleased_13/4824.rst @@ -0,0 +1,12 @@ +.. change:: + :tags: usecase, postgresql + :tickets: 4824 + + Added support for reflection of CHECK constraints that include the special + PostgreSQL qualifier "NOT VALID", which can be present for CHECK + constraints that were added to an exsiting table with the directive that + they not be applied to existing data in the table. The PostgreSQL + dictionary for CHECK constraints as returned by + :meth:`.Inspector.get_check_constraints` may include an additional entry + ``dialect_options`` which within will contain an entry ``"not_valid": + True`` if this symbol is detected. Pull request courtesy Bill Finn. diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 8fbd97ebe1..0ea6d42960 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -3447,20 +3447,24 @@ class PGDialect(default.DefaultDialect): c = connection.execute(sql.text(CHECK_SQL), table_oid=table_oid) - # samples: - # "CHECK (((a > 1) AND (a < 5)))" - # "CHECK (((a = 1) OR ((a > 2) AND (a < 5))))" - def match_cons(src): - m = re.match(r"^CHECK *\(\((.+)\)\)$", src) + ret = [] + for name, src in c: + # samples: + # "CHECK (((a > 1) AND (a < 5)))" + # "CHECK (((a = 1) OR ((a > 2) AND (a < 5))))" + # "CHECK (((a > 1) AND (a < 5))) NOT VALID" + m = re.match(r"^CHECK *\(\((.+)\)\)( NOT VALID)?$", src) if not m: util.warn("Could not parse CHECK constraint text: %r" % src) - return "" - return m.group(1) + sqltext = "" + else: + sqltext = m.group(1) + entry = {"name": name, "sqltext": sqltext} + if m and m.group(2): + entry["dialect_options"] = {"not_valid": True} - return [ - {"name": name, "sqltext": match_cons(src)} - for name, src in c.fetchall() - ] + ret.append(entry) + return ret def _load_enums(self, connection, schema=None): schema = schema or self.default_schema_name diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py index 872a21bb76..e36d99d5d1 100644 --- a/lib/sqlalchemy/engine/reflection.py +++ b/lib/sqlalchemy/engine/reflection.py @@ -541,6 +541,12 @@ class Inspector(object): sqltext the check constraint's SQL expression + dialect_options + may or may not be present; a dictionary with additional + dialect-specific options for this CHECK constraint + + .. versionadded:: 1.3.8 + :param table_name: string name of the table. For special quoting, use :class:`.quoted_name`. diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index 4b5c4b949d..7054a9fddf 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -1540,9 +1540,10 @@ class ReflectionTest(fixtures.TestBase): ) def test_reflect_check_warning(self): + rows = [("some name", "NOTCHECK foobar")] conn = mock.Mock( - execute=lambda *arg, **kw: mock.Mock( - fetchall=lambda: [("some name", "NOTCHECK foobar")] + execute=lambda *arg, **kw: mock.MagicMock( + fetchall=lambda: rows, __iter__=lambda self: iter(rows) ) ) with mock.patch.object( @@ -1553,6 +1554,30 @@ class ReflectionTest(fixtures.TestBase): ): testing.db.dialect.get_check_constraints(conn, "foo") + def test_reflect_with_not_valid_check_constraint(self): + rows = [("some name", "CHECK ((a IS NOT NULL)) NOT VALID")] + conn = mock.Mock( + execute=lambda *arg, **kw: mock.MagicMock( + fetchall=lambda: rows, __iter__=lambda self: iter(rows) + ) + ) + with mock.patch.object( + testing.db.dialect, "get_table_oid", lambda *arg, **kw: 1 + ): + check_constraints = testing.db.dialect.get_check_constraints( + conn, "foo" + ) + eq_( + check_constraints, + [ + { + "name": "some name", + "sqltext": "a IS NOT NULL", + "dialect_options": {"not_valid": True}, + } + ], + ) + class CustomTypeReflectionTest(fixtures.TestBase): class CustomType(object):