From: Mike Bayer Date: Thu, 28 Sep 2017 20:47:28 +0000 (-0400) Subject: Take schema name into account when querying sqlite_master X-Git-Tag: rel_1_2_0~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=68b52c48b775f9a99d0bc3666ebe02c54e401303;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Take schema name into account when querying sqlite_master Fixed bug where SQLite CHECK constraint reflection would fail if the referenced table were in a remote schema, e.g. on SQLite a remote database referred to by ATTACH. Also add suite support for general CHECK constraint reflection. Change-Id: I073a72cb47dc4f8c5683000d708768523759332f Fixes: #4099 --- diff --git a/doc/build/changelog/unreleased_11/4099.rst b/doc/build/changelog/unreleased_11/4099.rst new file mode 100644 index 0000000000..e41e900032 --- /dev/null +++ b/doc/build/changelog/unreleased_11/4099.rst @@ -0,0 +1,8 @@ +.. change: + :tags: bug, sqlite + :tickets: 4099 + :versions: 1.2.0b3 + + Fixed bug where SQLite CHECK constraint reflection would fail + if the referenced table were in a remote schema, e.g. on SQLite a + remote database referred to by ATTACH. \ No newline at end of file diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 4bc60be62e..d8ce7f394c 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -1545,14 +1545,19 @@ class SQLiteDialect(default.DefaultDialect): def _get_table_sql(self, connection, table_name, schema=None, **kw): try: s = ("SELECT sql FROM " - " (SELECT * FROM sqlite_master UNION ALL " - " SELECT * FROM sqlite_temp_master) " - "WHERE name = '%s' " - "AND type = 'table'") % table_name + " (SELECT * FROM %(schema)ssqlite_master UNION ALL " + " SELECT * FROM %(schema)ssqlite_temp_master) " + "WHERE name = '%(table)s' " + "AND type = 'table'" % { + "schema": ("%s." % schema) if schema else "", + "table": table_name}) rs = connection.execute(s) except exc.DBAPIError: - s = ("SELECT sql FROM sqlite_master WHERE name = '%s' " - "AND type = 'table'") % table_name + s = ("SELECT sql FROM %(schema)ssqlite_master " + "WHERE name = '%(table)s' " + "AND type = 'table'" % { + "schema": ("%s." % schema) if schema else "", + "table": table_name}) rs = connection.execute(s) return rs.scalar() diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index b3ad29a3b1..a66b091aa7 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -429,6 +429,11 @@ class SuiteRequirements(Requirements): """target dialect supports reflection of unique constraints""" return exclusions.open() + @property + def check_constraint_reflection(self): + """target dialect supports reflection of check constraints""" + return exclusions.closed() + @property def duplicate_key_raises_integrity_error(self): """target dialect raises IntegrityError when reporting an INSERT diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index 7674338b4e..f541165e33 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -16,6 +16,7 @@ from sqlalchemy.schema import DDL, Index from sqlalchemy import event from sqlalchemy.sql.elements import quoted_name from sqlalchemy import ForeignKey +import re metadata, users = None, None @@ -760,6 +761,50 @@ class ComponentReflectionTest(fixtures.TablesTest): eq_(names_that_duplicate_index, idx_names) eq_(uq_names, set()) + @testing.requires.check_constraint_reflection + def test_get_check_constraints(self): + self._test_get_check_constraints() + + @testing.requires.check_constraint_reflection + @testing.requires.schemas + def test_get_check_constraints_schema(self): + self._test_get_check_constraints(schema=testing.config.test_schema) + + @testing.provide_metadata + def _test_get_check_constraints(self, schema=None): + orig_meta = self.metadata + Table( + 'sa_cc', orig_meta, + Column('a', Integer()), + sa.CheckConstraint('a > 1 AND a < 5', name='cc1'), + sa.CheckConstraint('a = 1 OR (a > 2 AND a < 5)', name='cc2'), + schema=schema + ) + + orig_meta.create_all() + + inspector = inspect(orig_meta.bind) + reflected = sorted( + inspector.get_check_constraints('sa_cc', schema=schema), + key=operator.itemgetter('name') + ) + + reflected = [ + {"name": item["name"], + # trying to minimize effect of quoting, parenthesis, etc. + # may need to add more to this as new dialects get CHECK + # constraint reflection support + "sqltext": re.sub(r"[`'\(\)]", '', item["sqltext"].lower())} + for item in reflected + ] + eq_( + reflected, + [ + {'name': 'cc1', 'sqltext': 'a > 1 and a < 5'}, + {'name': 'cc2', 'sqltext': 'a = 1 or a > 2 and a < 5'} + ] + ) + @testing.provide_metadata def _test_get_view_definition(self, schema=None): meta = self.metadata diff --git a/test/requirements.py b/test/requirements.py index 0829607cd9..2cd5d0b907 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -389,6 +389,7 @@ class DefaultRequirements(SuiteRequirements): return self.unique_constraint_reflection + \ skip_if("mysql") + skip_if("oracle") + @property def check_constraint_reflection(self): return fails_on_everything_except(