From: Mike Bayer Date: Tue, 30 Mar 2021 18:48:41 +0000 (-0400) Subject: Refine domain nullable rules for PostgreSQL reflection X-Git-Tag: rel_1_3_24~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e1ee44322c25c05064e7d10c870af8c4be2f718e;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Refine domain nullable rules for PostgreSQL reflection Fixed issue in PostgreSQL reflection where a column expressing "NOT NULL" will supersede the nullability of a corresponding domain. Fixes #6161 Change-Id: I1a3de49afcdb952f71bd7a7cc7b264513c93eff5 (cherry picked from commit ab61d66dee9b3c3639907557852908858daacb6f) --- diff --git a/doc/build/changelog/unreleased_13/6161.rst b/doc/build/changelog/unreleased_13/6161.rst new file mode 100644 index 0000000000..df41b857a4 --- /dev/null +++ b/doc/build/changelog/unreleased_13/6161.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, postgresql, reflection + :tickets: 6161 + :versions: 1.4.4 + + Fixed issue in PostgreSQL reflection where a column expressing "NOT NULL" + will supersede the nullability of a corresponding domain. diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index a33435216e..93ee1fa1ab 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -3213,8 +3213,9 @@ class PGDialect(default.DefaultDialect): attype, is_array = _handle_array_type(attype) # strip quotes from case sensitive enum or domain names enum_or_domain_key = tuple(util.quoted_token_parser(attype)) - # A table can't override whether the domain is nullable. - nullable = domain["nullable"] + # A table can't override a not null on the domain, + # but can override nullable + nullable = nullable and domain["nullable"] if domain["default"] and not default: # It can, however, override the default # value, but can't set it to null. diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index b92d46c7be..dc1d517905 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -32,6 +32,7 @@ from sqlalchemy.testing import mock from sqlalchemy.testing.assertions import assert_raises from sqlalchemy.testing.assertions import AssertsExecutionResults from sqlalchemy.testing.assertions import eq_ +from sqlalchemy.testing.assertions import is_ class ForeignTableReflectionTest(fixtures.TablesTest, AssertsExecutionResults): @@ -276,6 +277,9 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults): "CREATE DOMAIN enumdomain AS testtype", "CREATE DOMAIN arraydomain AS INTEGER[]", 'CREATE DOMAIN "SomeSchema"."Quoted.Domain" INTEGER DEFAULT 0', + "CREATE DOMAIN nullable_domain AS TEXT CHECK " + "(VALUE IN('FOO', 'BAR'))", + "CREATE DOMAIN not_nullable_domain AS TEXT NOT NULL", ]: try: con.execute(ddl) @@ -303,6 +307,11 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults): "CREATE TABLE quote_test " '(id integer, data "SomeSchema"."Quoted.Domain")' ) + con.execute( + "CREATE TABLE nullable_domain_test " + "(not_nullable_domain_col nullable_domain not null," + "nullable_local not_nullable_domain)" + ) @classmethod def teardown_class(cls): @@ -321,6 +330,10 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults): con.execute('DROP DOMAIN "SomeSchema"."Quoted.Domain"') con.execute('DROP SCHEMA "SomeSchema"') + con.execute("DROP TABLE nullable_domain_test") + con.execute("DROP DOMAIN nullable_domain") + con.execute("DROP DOMAIN not_nullable_domain") + def test_table_is_reflected(self): metadata = MetaData(testing.db) table = Table("testtable", metadata, autoload=True) @@ -343,6 +356,12 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults): not table.columns.answer.nullable ), "Expected reflected column to not be nullable." + def test_nullable_from_domain(self): + metadata = MetaData(testing.db) + table = Table("nullable_domain_test", metadata, autoload=True) + is_(table.c.not_nullable_domain_col.nullable, False) + is_(table.c.nullable_local.nullable, False) + def test_enum_domain_is_reflected(self): metadata = MetaData(testing.db) table = Table("enum_test", metadata, autoload=True)