From: Jakub Synowiec Date: Sat, 1 Dec 2018 18:26:43 +0000 (-0500) Subject: Fix PostgreSQL reflection of domains expressed as arrays X-Git-Tag: rel_1_3_0b2~76^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46f9c3c7d4d2c31f3f1627dcf777bd3215e13e3d;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Fix PostgreSQL reflection of domains expressed as arrays 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 --- diff --git a/doc/build/changelog/unreleased_12/4377.rst b/doc/build/changelog/unreleased_12/4377.rst new file mode 100644 index 0000000000..9d3477efa0 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4377.rst @@ -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. + diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 8468d6099d..ce809db9f7 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -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: diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index b30cb506bc..2a9887e0e1 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -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,