summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-01-09 11:42:02 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2019-01-09 21:06:57 -0500
commit08994cb97c501a3cf984fd827eba9aa9614b9dd3 (patch)
tree49d9a618b0f0468061bff277ca7c22062962add9
parent86f243a8747c28f87dce5d34fb42501ddfb5d387 (diff)
downloadsqlalchemy-08994cb97c501a3cf984fd827eba9aa9614b9dd3.tar.gz
Skip expression-based index reflection for SQLite
Reflection of an index based on SQL expressions are now skipped with a warning, in the same way as that of the Postgresql dialect, where we currently do not support reflecting indexes that have SQL expressions within them. Previously, an index with columns of None were produced which would break tools like Alembic. Fixes: #4431 Change-Id: I1363ade912d206b42669331e2be2bb6f444b65a2
-rw-r--r--doc/build/changelog/unreleased_12/4431.rst9
-rw-r--r--lib/sqlalchemy/dialects/sqlite/base.py13
-rw-r--r--lib/sqlalchemy/testing/requirements.py5
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py30
-rw-r--r--test/requirements.py4
5 files changed, 58 insertions, 3 deletions
diff --git a/doc/build/changelog/unreleased_12/4431.rst b/doc/build/changelog/unreleased_12/4431.rst
new file mode 100644
index 000000000..9aea49644
--- /dev/null
+++ b/doc/build/changelog/unreleased_12/4431.rst
@@ -0,0 +1,9 @@
+.. change::
+ :tags: bug, sqlite
+ :tickets: 4431
+
+ Reflection of an index based on SQL expressions are now skipped with a
+ warning, in the same way as that of the Postgresql dialect, where we currently
+ do not support reflecting indexes that have SQL expressions within them.
+ Previously, an index with columns of None were produced which would break
+ tools like Alembic.
diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py
index 1eea2b6c6..424f91fa6 100644
--- a/lib/sqlalchemy/dialects/sqlite/base.py
+++ b/lib/sqlalchemy/dialects/sqlite/base.py
@@ -1941,17 +1941,24 @@ class SQLiteDialect(default.DefaultDialect):
"sqlite_autoindex"
):
continue
-
indexes.append(dict(name=row[1], column_names=[], unique=row[2]))
# loop thru unique indexes to get the column names.
- for idx in indexes:
+ for idx in list(indexes):
pragma_index = self._get_table_pragma(
connection, "index_info", idx["name"]
)
for row in pragma_index:
- idx["column_names"].append(row[2])
+ if row[2] is None:
+ util.warn(
+ "Skipped unsupported reflection of "
+ "expression-based index %s" % idx["name"]
+ )
+ indexes.remove(idx)
+ break
+ else:
+ idx["column_names"].append(row[2])
return indexes
@reflection.cache
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index 941a9458b..ce9954cfd 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -454,6 +454,11 @@ class SuiteRequirements(Requirements):
return exclusions.open()
@property
+ def indexes_with_expressions(self):
+ """target database supports CREATE INDEX against SQL expressions."""
+ return exclusions.closed()
+
+ @property
def unique_constraint_reflection(self):
"""target dialect supports reflection of unique constraints"""
return exclusions.open()
diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py
index 96bd188ee..d589c046f 100644
--- a/lib/sqlalchemy/testing/suite/test_reflection.py
+++ b/lib/sqlalchemy/testing/suite/test_reflection.py
@@ -6,6 +6,7 @@ from .. import assert_raises_message
from .. import config
from .. import engines
from .. import eq_
+from .. import expect_warnings
from .. import fixtures
from .. import is_
from ..schema import Column
@@ -792,6 +793,35 @@ class ComponentReflectionTest(fixtures.TablesTest):
def test_get_noncol_index_pk(self):
self._test_get_noncol_index("noncol_idx_test_pk", "noncol_idx_pk")
+ @testing.requires.indexes_with_expressions
+ @testing.provide_metadata
+ def test_reflect_expression_based_indexes(self):
+ Table(
+ "t",
+ self.metadata,
+ Column("x", String(30)),
+ Column("y", String(30)),
+ )
+ event.listen(
+ self.metadata,
+ "after_create",
+ DDL("CREATE INDEX t_idx ON t(lower(x), lower(y))"),
+ )
+ event.listen(
+ self.metadata, "after_create", DDL("CREATE INDEX t_idx_2 ON t(x)")
+ )
+ self.metadata.create_all()
+
+ insp = inspect(self.metadata.bind)
+
+ with expect_warnings(
+ "Skipped unsupported reflection of expression-based index t_idx"
+ ):
+ eq_(
+ insp.get_indexes("t"),
+ [{"name": "t_idx_2", "column_names": ["x"], "unique": 0}],
+ )
+
@testing.requires.unique_constraint_reflection
def test_get_unique_constraints(self):
self._test_get_unique_constraints()
diff --git a/test/requirements.py b/test/requirements.py
index d95a3c58b..c70169acf 100644
--- a/test/requirements.py
+++ b/test/requirements.py
@@ -457,6 +457,10 @@ class DefaultRequirements(SuiteRequirements):
)
@property
+ def indexes_with_expressions(self):
+ return only_on(["postgresql", "sqlite>=3.9.0"])
+
+ @property
def temp_table_names(self):
"""target dialect supports listing of temporary table names"""