Generalize uq -> uix dedupe logic
Adjusted the logic originally added for 🎫`276` that detects MySQL
unique constraints which are actually unique indexes to be generalized
for any dialect that has this behavior, for SQLAlchemy version 1.0 and
greater. This is to allow for upcoming SQLAlchemy support for unique
constraint reflection for Oracle, which also has no dedicated concept of
"unique constraint" and instead establishes a unique index.
Change-Id: Ie5770aba36005ec8618bdc18bc4633413d37fc16
This commit is contained in:
parent
c5ac1cddd6
commit
8e2af237ab
|
@ -195,11 +195,14 @@ def _make_index(params, conn_table):
|
|||
|
||||
|
||||
def _make_unique_constraint(params, conn_table):
|
||||
# TODO: add .info such as 'duplicates_index'
|
||||
return sa_schema.UniqueConstraint(
|
||||
uq = sa_schema.UniqueConstraint(
|
||||
*[conn_table.c[cname] for cname in params['column_names']],
|
||||
name=params['name']
|
||||
)
|
||||
if 'duplicates_index' in params:
|
||||
uq.info['duplicates_index'] = params['duplicates_index']
|
||||
|
||||
return uq
|
||||
|
||||
|
||||
def _make_foreign_key(params, conn_table):
|
||||
|
@ -364,6 +367,8 @@ def _compare_indexes_and_uniques(
|
|||
|
||||
supports_unique_constraints = False
|
||||
|
||||
unique_constraints_duplicate_unique_indexes = False
|
||||
|
||||
if conn_table is not None:
|
||||
# 1b. ... and from connection, if the table exists
|
||||
if hasattr(inspector, "get_unique_constraints"):
|
||||
|
@ -378,6 +383,10 @@ def _compare_indexes_and_uniques(
|
|||
# method in SQLAlchemy due to the cache decorator
|
||||
# not being present
|
||||
pass
|
||||
else:
|
||||
for uq in conn_uniques:
|
||||
if uq.get('duplicates_index'):
|
||||
unique_constraints_duplicate_unique_indexes = True
|
||||
try:
|
||||
conn_indexes = inspector.get_indexes(tname, schema=schema)
|
||||
except NotImplementedError:
|
||||
|
@ -389,6 +398,16 @@ def _compare_indexes_and_uniques(
|
|||
for uq_def in conn_uniques)
|
||||
conn_indexes = set(_make_index(ix, conn_table) for ix in conn_indexes)
|
||||
|
||||
# 2a. if the dialect dupes unique indexes as unique constraints
|
||||
# (mysql and oracle), correct for that
|
||||
|
||||
if unique_constraints_duplicate_unique_indexes:
|
||||
_correct_for_uq_duplicates_uix(
|
||||
conn_uniques, conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes
|
||||
)
|
||||
|
||||
# 3. give the dialect a chance to omit indexes and constraints that
|
||||
# we know are either added implicitly by the DB or that the DB
|
||||
# can't accurately report on
|
||||
|
@ -586,6 +605,48 @@ def _compare_indexes_and_uniques(
|
|||
obj_added(unnamed_metadata_uniques[uq_sig])
|
||||
|
||||
|
||||
def _correct_for_uq_duplicates_uix(
|
||||
conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes):
|
||||
|
||||
# dedupe unique indexes vs. constraints, since MySQL / Oracle
|
||||
# doesn't really have unique constraints as a separate construct.
|
||||
# but look in the metadata and try to maintain constructs
|
||||
# that already seem to be defined one way or the other
|
||||
# on that side. This logic was formerly local to MySQL dialect,
|
||||
# generalized to Oracle and others. See #276
|
||||
metadata_uq_names = set([
|
||||
cons.name for cons in metadata_unique_constraints
|
||||
if cons.name is not None])
|
||||
|
||||
unnamed_metadata_uqs = set([
|
||||
_uq_constraint_sig(cons).sig
|
||||
for cons in metadata_unique_constraints
|
||||
if cons.name is None
|
||||
])
|
||||
|
||||
metadata_ix_names = set([
|
||||
cons.name for cons in metadata_indexes if cons.unique])
|
||||
conn_ix_names = dict(
|
||||
(cons.name, cons) for cons in conn_indexes if cons.unique
|
||||
)
|
||||
|
||||
uqs_dupe_indexes = dict(
|
||||
(cons.name, cons) for cons in conn_unique_constraints
|
||||
if cons.info['duplicates_index']
|
||||
)
|
||||
for overlap in uqs_dupe_indexes:
|
||||
if overlap not in metadata_uq_names:
|
||||
if _uq_constraint_sig(uqs_dupe_indexes[overlap]).sig \
|
||||
not in unnamed_metadata_uqs:
|
||||
|
||||
conn_unique_constraints.discard(uqs_dupe_indexes[overlap])
|
||||
elif overlap not in metadata_ix_names:
|
||||
conn_indexes.discard(conn_ix_names[overlap])
|
||||
|
||||
|
||||
@comparators.dispatch_for("column")
|
||||
def _compare_nullable(
|
||||
autogen_context, alter_column_op, schema, tname, cname, conn_col,
|
||||
|
|
|
@ -10,7 +10,7 @@ from .base import ColumnNullable, ColumnName, ColumnDefault, \
|
|||
format_server_default
|
||||
from .base import alter_table
|
||||
from ..autogenerate import compare
|
||||
from ..util.sqla_compat import _is_type_bound
|
||||
from ..util.sqla_compat import _is_type_bound, sqla_100
|
||||
|
||||
|
||||
class MySQLImpl(DefaultImpl):
|
||||
|
@ -132,6 +132,19 @@ class MySQLImpl(DefaultImpl):
|
|||
if idx.name in removed:
|
||||
metadata_indexes.remove(idx)
|
||||
|
||||
if not sqla_100:
|
||||
self._legacy_correct_for_dupe_uq_uix(
|
||||
conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes
|
||||
)
|
||||
|
||||
def _legacy_correct_for_dupe_uq_uix(self, conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes):
|
||||
|
||||
# then dedupe unique indexes vs. constraints, since MySQL
|
||||
# doesn't really have unique constraints as a separate construct.
|
||||
# but look in the metadata and try to maintain constructs
|
||||
|
|
|
@ -14,6 +14,16 @@ Changelog
|
|||
primary key constraint would fail to remove the "primary_key" flag
|
||||
from the column, resulting in the constraint being recreated.
|
||||
|
||||
.. change:: update_uq_dedupe
|
||||
:tags: bug, autogenerate, oracle
|
||||
|
||||
Adjusted the logic originally added for :ticket:`276` that detects MySQL
|
||||
unique constraints which are actually unique indexes to be generalized
|
||||
for any dialect that has this behavior, for SQLAlchemy version 1.0 and
|
||||
greater. This is to allow for upcoming SQLAlchemy support for unique
|
||||
constraint reflection for Oracle, which also has no dedicated concept of
|
||||
"unique constraint" and instead establishes a unique index.
|
||||
|
||||
.. change:: 356
|
||||
:tags: bug, versioning
|
||||
:tickets: 356
|
||||
|
|
|
@ -712,6 +712,12 @@ class MySQLUniqueIndexTest(AutogenerateUniqueIndexTest):
|
|||
assert False, "unexpected success"
|
||||
|
||||
|
||||
class OracleUniqueIndexTest(AutogenerateUniqueIndexTest):
|
||||
reports_unnamed_constraints = True
|
||||
reports_unique_constraints_as_indexes = True
|
||||
__only_on__ = "oracle"
|
||||
|
||||
|
||||
class NoUqReflectionIndexTest(NoUqReflection, AutogenerateUniqueIndexTest):
|
||||
reports_unique_constraints = False
|
||||
__only_on__ = 'sqlite'
|
||||
|
|
Loading…
Reference in New Issue