summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/dialects/postgresql/base.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-12-21 11:04:14 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2018-12-21 13:53:16 -0500
commit0b0a4c8ba2465fce5fa1954a0d31b44840f1b4b8 (patch)
tree0ef7a440172532f626b613632626c2c78784f0db /lib/sqlalchemy/dialects/postgresql/base.py
parentb5592de30ecc986c1862261513ab99f43de885b4 (diff)
downloadsqlalchemy-0b0a4c8ba2465fce5fa1954a0d31b44840f1b4b8.tar.gz
Handle PostgreSQL enums in remote schemas
Fixed issue where a :class:`.postgresql.ENUM` or a custom domain present in a remote schema would not be recognized within column reflection if the name of the enum/domain or the name of the schema required quoting. A new parsing scheme now fully parses out quoted or non-quoted tokens including support for SQL-escaped quotes. Fixed issue where multiple :class:`.postgresql.ENUM` objects referred to by the same :class:`.MetaData` object would fail to be created if multiple objects had the same name under different schema names. The internal memoization the Postgresql dialect uses to track if it has created a particular :class:`.postgresql.ENUM` in the database during a DDL creation sequence now takes schema name into account. Fixes: #4416 Change-Id: I8cf03069e10b12f409e9b6796e24fc5850979955
Diffstat (limited to 'lib/sqlalchemy/dialects/postgresql/base.py')
-rw-r--r--lib/sqlalchemy/dialects/postgresql/base.py56
1 files changed, 33 insertions, 23 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py
index ce809db9f..d68ab8ef5 100644
--- a/lib/sqlalchemy/dialects/postgresql/base.py
+++ b/lib/sqlalchemy/dialects/postgresql/base.py
@@ -1344,8 +1344,8 @@ class ENUM(sqltypes.NativeForEmulated, sqltypes.Enum):
pg_enums = ddl_runner.memo['_pg_enums']
else:
pg_enums = ddl_runner.memo['_pg_enums'] = set()
- present = self.name in pg_enums
- pg_enums.add(self.name)
+ present = (self.schema, self.name) in pg_enums
+ pg_enums.add((self.schema, self.name))
return present
else:
return False
@@ -2580,20 +2580,26 @@ class PGDialect(default.DefaultDialect):
)
c = connection.execute(s, table_oid=table_oid)
rows = c.fetchall()
+
+ # dictionary with (name, ) if default search path or (schema, name)
+ # as keys
domains = self._load_domains(connection)
+
+ # dictionary with (name, ) if default search path or (schema, name)
+ # as keys
enums = dict(
- (
- "%s.%s" % (rec['schema'], rec['name'])
- if not rec['visible'] else rec['name'], rec) for rec in
- self._load_enums(connection, schema='*')
+ ((rec['name'], ), rec)
+ if rec['visible'] else ((rec['schema'], rec['name']), rec)
+ for rec in self._load_enums(connection, schema='*')
)
# format columns
columns = []
- for name, format_type, default, notnull, attnum, table_oid, \
+
+ for name, format_type, default_, notnull, attnum, table_oid, \
comment in rows:
column_info = self._get_column_info(
- name, format_type, default, notnull, domains, enums,
+ name, format_type, default_, notnull, domains, enums,
schema, comment)
columns.append(column_info)
return columns
@@ -2602,7 +2608,8 @@ class PGDialect(default.DefaultDialect):
notnull, domains, enums, schema, comment):
def _handle_array_type(attype):
return (
- attype.replace('[]', ''), # strip '[]' from integer[], etc.
+ # strip '[]' from integer[], etc.
+ re.sub(r'\[\]$', '', attype),
attype.endswith('[]'),
)
@@ -2610,12 +2617,12 @@ class PGDialect(default.DefaultDialect):
# with time zone, geometry(POLYGON), etc.
attype = re.sub(r'\(.*\)', '', format_type)
- # strip quotes from case sensitive enum names
- attype = re.sub(r'^"|"$', '', attype)
-
# strip '[]' from integer[], etc. and check if an array
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))
+
nullable = not notnull
charlen = re.search(r'\(([\d,]+)\)', format_type)
@@ -2668,21 +2675,24 @@ class PGDialect(default.DefaultDialect):
args = (int(charlen),)
while True:
+ # looping here to suit nested domains
if attype in self.ischema_names:
coltype = self.ischema_names[attype]
break
- elif attype in enums:
- enum = enums[attype]
+ elif enum_or_domain_key in enums:
+ enum = enums[enum_or_domain_key]
coltype = ENUM
kwargs['name'] = enum['name']
if not enum['visible']:
kwargs['schema'] = enum['schema']
args = tuple(enum['labels'])
break
- elif attype in domains:
- domain = domains[attype]
+ elif enum_or_domain_key in domains:
+ domain = domains[enum_or_domain_key]
attype = domain['attype']
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']
if domain['default'] and not default:
@@ -3166,16 +3176,16 @@ class PGDialect(default.DefaultDialect):
for domain in c.fetchall():
# strip (30) from character varying(30)
attype = re.search(r'([^\(]+)', domain['attype']).group(1)
+ # 'visible' just means whether or not the domain is in a
+ # schema that's on the search path -- or not overridden by
+ # a schema with higher precedence. If it's not visible,
+ # it will be prefixed with the schema-name when it's used.
if domain['visible']:
- # 'visible' just means whether or not the domain is in a
- # schema that's on the search path -- or not overridden by
- # a schema with higher precedence. If it's not visible,
- # it will be prefixed with the schema-name when it's used.
- name = domain['name']
+ key = (domain['name'], )
else:
- name = "%s.%s" % (domain['schema'], domain['name'])
+ key = (domain['schema'], domain['name'])
- domains[name] = {
+ domains[key] = {
'attype': attype,
'nullable': domain['nullable'],
'default': domain['default']