]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix PostgreSQL reflection of domains expressed as arrays
authorJakub Synowiec <github@jakubsynowiec.info>
Sat, 1 Dec 2018 18:26:43 +0000 (13:26 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 1 Dec 2018 18:30:42 +0000 (13:30 -0500)
Fixed issue where reflection of a PostgreSQL domain that is expressed as an
array would fail to be recognized.  Pull request courtesy Jakub Synowiec.

Fixes: #4377
Change-Id: I252c79ca435b87d4d9172b1c84e0e74e789ef676
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4380

doc/build/changelog/unreleased_12/4377.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_12/4377.rst b/doc/build/changelog/unreleased_12/4377.rst
new file mode 100644 (file)
index 0000000..9d3477e
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+   :tag: bug, postgresql
+   :tickets: 4377, 4380
+
+   Fixed issue where reflection of a PostgreSQL domain that is expressed as an
+   array would fail to be recognized.  Pull request courtesy Jakub Synowiec.
+
index 8468d6099d8baede1a6121ca5cc83e393a0ded8d..ce809db9f7f656330b530dc6124b70f363e9c579 100644 (file)
@@ -2600,6 +2600,12 @@ class PGDialect(default.DefaultDialect):
 
     def _get_column_info(self, name, format_type, default,
                          notnull, domains, enums, schema, comment):
+        def _handle_array_type(attype):
+            return (
+                attype.replace('[]', ''), # strip '[]' from integer[], etc.
+                attype.endswith('[]'),
+            )
+
         # strip (*) from character varying(5), timestamp(5)
         # with time zone, geometry(POLYGON), etc.
         attype = re.sub(r'\(.*\)', '', format_type)
@@ -2607,11 +2613,11 @@ class PGDialect(default.DefaultDialect):
         # strip quotes from case sensitive enum names
         attype = re.sub(r'^"|"$', '', attype)
 
-        # strip '[]' from integer[], etc.
-        attype = attype.replace('[]', '')
+        # strip '[]' from integer[], etc. and check if an array
+        attype, is_array = _handle_array_type(attype)
 
         nullable = not notnull
-        is_array = format_type.endswith('[]')
+
         charlen = re.search(r'\(([\d,]+)\)', format_type)
         if charlen:
             charlen = charlen.group(1)
@@ -2676,6 +2682,7 @@ class PGDialect(default.DefaultDialect):
             elif attype in domains:
                 domain = domains[attype]
                 attype = domain['attype']
+                attype, is_array = _handle_array_type(attype)
                 # A table can't override whether the domain is nullable.
                 nullable = domain['nullable']
                 if domain['default'] and not default:
index b30cb506bc6d6b036c11e1a1b6a34cfab373b7a0..2a9887e0e15475f04d7257a8775959908d434ac8 100644 (file)
@@ -13,7 +13,7 @@ from sqlalchemy import Table, Column, MetaData, Integer, String, \
 from sqlalchemy import exc
 import sqlalchemy as sa
 from sqlalchemy.dialects.postgresql import base as postgresql
-from sqlalchemy.dialects.postgresql import ARRAY, INTERVAL, TSRANGE
+from sqlalchemy.dialects.postgresql import ARRAY, INTERVAL, INTEGER, TSRANGE
 from sqlalchemy.dialects.postgresql import ExcludeConstraint
 import re
 
@@ -221,7 +221,8 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
                 'CREATE DOMAIN testdomain INTEGER NOT NULL DEFAULT 42', \
                 'CREATE DOMAIN test_schema.testdomain INTEGER DEFAULT 0', \
                 "CREATE TYPE testtype AS ENUM ('test')", \
-                'CREATE DOMAIN enumdomain AS testtype':
+                'CREATE DOMAIN enumdomain AS testtype', \
+                'CREATE DOMAIN arraydomain AS INTEGER[]':
             try:
                 con.execute(ddl)
             except exc.DBAPIError as e:
@@ -237,6 +238,8 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
 
         con.execute('CREATE TABLE enum_test (id integer, data enumdomain)')
 
+        con.execute('CREATE TABLE array_test (id integer, data arraydomain)')
+
     @classmethod
     def teardown_class(cls):
         con = testing.db.connect()
@@ -248,6 +251,8 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
         con.execute("DROP TABLE enum_test")
         con.execute("DROP DOMAIN enumdomain")
         con.execute("DROP TYPE testtype")
+        con.execute('DROP TABLE array_test')
+        con.execute('DROP DOMAIN arraydomain')
 
     def test_table_is_reflected(self):
         metadata = MetaData(testing.db)
@@ -272,6 +277,18 @@ class DomainReflectionTest(fixtures.TestBase, AssertsExecutionResults):
             ['test']
         )
 
+    def test_array_domain_is_reflected(self):
+        metadata = MetaData(testing.db)
+        table = Table('array_test', metadata, autoload=True)
+        eq_(
+            table.c.data.type.__class__,
+            ARRAY
+        )
+        eq_(
+            table.c.data.type.item_type.__class__,
+            INTEGER
+        )
+
     def test_table_is_reflected_test_schema(self):
         metadata = MetaData(testing.db)
         table = Table('testtable', metadata, autoload=True,