]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Refine domain nullable rules for PostgreSQL reflection
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 30 Mar 2021 18:48:41 +0000 (14:48 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 30 Mar 2021 18:49:46 +0000 (14:49 -0400)
Fixed issue in PostgreSQL reflection where a column expressing "NOT NULL"
will supersede the nullability of a corresponding domain.

Fixes #6161

Change-Id: I1a3de49afcdb952f71bd7a7cc7b264513c93eff5

doc/build/changelog/unreleased_13/6161.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/postgresql/base.py
test/dialect/postgresql/test_reflection.py

diff --git a/doc/build/changelog/unreleased_13/6161.rst b/doc/build/changelog/unreleased_13/6161.rst
new file mode 100644 (file)
index 0000000..df41b85
--- /dev/null
@@ -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.
index 6e6f5513d01a8fd932bbeb8b3bac5ced4de860b5..ba777459073afaf0a87c2ec09bd12b88d93cd242 100644 (file)
@@ -3767,8 +3767,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.
index 85546491649a8d9f4df70808500e59aefb75d276..a7876a766a40ec44bd3d4d8f661e693c625a8946 100644 (file)
@@ -36,6 +36,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_
 from sqlalchemy.testing.assertions import is_true
 
 
@@ -297,6 +298,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.exec_driver_sql(ddl)
@@ -329,6 +333,11 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
                 "CREATE TABLE quote_test "
                 '(id integer, data "SomeSchema"."Quoted.Domain")'
             )
+            con.exec_driver_sql(
+                "CREATE TABLE nullable_domain_test "
+                "(not_nullable_domain_col nullable_domain not null,"
+                "nullable_local not_nullable_domain)"
+            )
 
     @classmethod
     def teardown_test_class(cls):
@@ -347,6 +356,10 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
             con.exec_driver_sql('DROP DOMAIN "SomeSchema"."Quoted.Domain"')
             con.exec_driver_sql('DROP SCHEMA "SomeSchema"')
 
+            con.exec_driver_sql("DROP TABLE nullable_domain_test")
+            con.exec_driver_sql("DROP DOMAIN nullable_domain")
+            con.exec_driver_sql("DROP DOMAIN not_nullable_domain")
+
     def test_table_is_reflected(self, connection):
         metadata = MetaData()
         table = Table("testtable", metadata, autoload_with=connection)
@@ -357,6 +370,14 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
         )
         assert isinstance(table.c.answer.type, Integer)
 
+    def test_nullable_from_domain(self, connection):
+        metadata = MetaData()
+        table = Table(
+            "nullable_domain_test", metadata, autoload_with=connection
+        )
+        is_(table.c.not_nullable_domain_col.nullable, False)
+        is_(table.c.nullable_local.nullable, False)
+
     def test_domain_is_reflected(self, connection):
         metadata = MetaData()
         table = Table("testtable", metadata, autoload_with=connection)