Skip to content

Commit b1a287f

Browse files
committed
new pre-caching architecture for autogenerate
Autogenerate reflection sweeps now use the "bulk" inspector methods introduced in SQLAlchemy 2.0, which for selected dialects including PostgreSQL and Oracle use batched queries to reflect whole collections of tables using O(1) queries rather than O(N). This is the original proposed version that uses the Inspector entirely with its public API, with the exception of reflect_table() which makes a _ReflectionInfo on a per-table basis. Other than that, no private API assumptions are made. If SQLAlchemy needed to add new fields to _ReflectionInfo, it just needs to make sure they have default functions (which all the current fields should have anyway, since there is even a ReflectionDefaults constant that already provides these!) This version is the one that does not imply any particular changes in SQLAlchemy and does not have any sqla_compat logic, so that we may have alembic using the new performance enhancements allowing for SQLAlchemy to potentially improve its API for a later release. Other than that, typing of reflection functions is improved. Fixes: #1771 Change-Id: I7b9a75fa81cefc377fdb1a22fc1cfc3da1765769
1 parent f532224 commit b1a287f

8 files changed

Lines changed: 498 additions & 44 deletions

File tree

alembic/autogenerate/compare/constraints.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
from typing import Any
77
from typing import cast
8+
from typing import Collection
89
from typing import Dict
910
from typing import Mapping
1011
from typing import Optional
@@ -28,6 +29,9 @@
2829
from ...util import sqla_compat
2930

3031
if TYPE_CHECKING:
32+
from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint
33+
from sqlalchemy.engine.interfaces import ReflectedIndex
34+
from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint
3135
from sqlalchemy.sql.elements import quoted_name
3236
from sqlalchemy.sql.elements import TextClause
3337
from sqlalchemy.sql.schema import Column
@@ -40,7 +44,6 @@
4044
from ...operations.ops import ModifyTableOps
4145
from ...runtime.plugins import Plugin
4246

43-
4447
_C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index])
4548

4649

@@ -72,18 +75,24 @@ def _compare_indexes_and_uniques(
7275
metadata_unique_constraints = set()
7376
metadata_indexes = set()
7477

75-
conn_uniques = conn_indexes = frozenset() # type:ignore[var-annotated]
78+
conn_uniques: Collection[UniqueConstraint] = frozenset()
79+
conn_indexes: Collection[Index] = frozenset()
7680

7781
supports_unique_constraints = False
7882

7983
unique_constraints_duplicate_unique_indexes = False
8084

8185
if conn_table is not None:
86+
conn_uniques_reflected: Collection[ReflectedUniqueConstraint] = (
87+
frozenset()
88+
)
89+
conn_indexes_reflected: Collection[ReflectedIndex] = frozenset()
90+
8291
# 1b. ... and from connection, if the table exists
8392
try:
84-
conn_uniques = _InspectorConv(inspector).get_unique_constraints(
85-
tname, schema=schema
86-
)
93+
conn_uniques_reflected = _InspectorConv(
94+
inspector
95+
).get_unique_constraints(tname, schema=schema)
8796

8897
supports_unique_constraints = True
8998
except NotImplementedError:
@@ -94,28 +103,28 @@ def _compare_indexes_and_uniques(
94103
# not being present
95104
pass
96105
else:
97-
conn_uniques = [ # type:ignore[assignment]
106+
conn_uniques_reflected = [
98107
uq
99-
for uq in conn_uniques
108+
for uq in conn_uniques_reflected
100109
if autogen_context.run_name_filters(
101110
uq["name"],
102111
"unique_constraint",
103112
{"table_name": tname, "schema_name": schema},
104113
)
105114
]
106-
for uq in conn_uniques:
115+
for uq in conn_uniques_reflected:
107116
if uq.get("duplicates_index"):
108117
unique_constraints_duplicate_unique_indexes = True
109118
try:
110-
conn_indexes = _InspectorConv(inspector).get_indexes(
119+
conn_indexes_reflected = _InspectorConv(inspector).get_indexes(
111120
tname, schema=schema
112121
)
113122
except NotImplementedError:
114123
pass
115124
else:
116-
conn_indexes = [ # type:ignore[assignment]
125+
conn_indexes_reflected = [
117126
ix
118-
for ix in conn_indexes
127+
for ix in conn_indexes_reflected
119128
if autogen_context.run_name_filters(
120129
ix["name"],
121130
"index",
@@ -127,17 +136,18 @@ def _compare_indexes_and_uniques(
127136
# into schema objects
128137
if is_drop_table:
129138
# for DROP TABLE uniques are inline, don't need them
130-
conn_uniques = set() # type:ignore[assignment]
139+
conn_uniques = set()
131140
else:
132-
conn_uniques = { # type:ignore[assignment]
141+
conn_uniques = {
133142
_make_unique_constraint(impl, uq_def, conn_table)
134-
for uq_def in conn_uniques
143+
for uq_def in conn_uniques_reflected
135144
}
136145

137-
conn_indexes = { # type:ignore[assignment]
146+
conn_indexes = {
138147
index
139148
for index in (
140-
_make_index(impl, ix, conn_table) for ix in conn_indexes
149+
_make_index(impl, ix, conn_table)
150+
for ix in conn_indexes_reflected
141151
)
142152
if index is not None
143153
}
@@ -507,7 +517,7 @@ def _correct_for_uq_duplicates_uix(
507517

508518

509519
def _make_index(
510-
impl: DefaultImpl, params: Dict[str, Any], conn_table: Table
520+
impl: DefaultImpl, params: ReflectedIndex, conn_table: Table
511521
) -> Optional[Index]:
512522
exprs: list[Union[Column[Any], TextClause]] = []
513523
sorting = params.get("column_sorting")
@@ -539,7 +549,7 @@ def _make_index(
539549

540550

541551
def _make_unique_constraint(
542-
impl: DefaultImpl, params: Dict[str, Any], conn_table: Table
552+
impl: DefaultImpl, params: ReflectedUniqueConstraint, conn_table: Table
543553
) -> UniqueConstraint:
544554
uq = sa_schema.UniqueConstraint(
545555
*[conn_table.c[cname] for cname in params["column_names"]],
@@ -553,7 +563,7 @@ def _make_unique_constraint(
553563

554564

555565
def _make_foreign_key(
556-
params: Dict[str, Any], conn_table: Table
566+
params: ReflectedForeignKeyConstraint, conn_table: Table
557567
) -> ForeignKeyConstraint:
558568
tname = params["referred_table"]
559569
if params["referred_schema"]:

alembic/autogenerate/compare/tables.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,25 @@ def _autogen_for_tables(
4848
version_table = autogen_context.migration_context.version_table
4949

5050
for schema_name in schemas:
51-
tables = set(inspector.get_table_names(schema=schema_name))
51+
tables = available = set(inspector.get_table_names(schema=schema_name))
5252
if schema_name == version_table_schema:
5353
tables = tables.difference(
5454
[autogen_context.migration_context.version_table]
5555
)
5656

57-
conn_table_names.update(
58-
(schema_name, tname)
57+
tablenames = [
58+
tname
5959
for tname in tables
6060
if autogen_context.run_name_filters(
6161
tname, "table", {"schema_name": schema_name}
6262
)
63-
)
63+
]
64+
65+
conn_table_names.update((schema_name, tname) for tname in tablenames)
66+
67+
inspector = autogen_context.inspector
68+
insp = _InspectorConv(inspector)
69+
insp.pre_cache_tables(schema_name, tablenames, available)
6470

6571
metadata_table_names = OrderedSet(
6672
[(table.schema, table.name) for table in autogen_context.sorted_tables]
@@ -139,6 +145,9 @@ def _compare_tables(
139145
removal_metadata = sa_schema.MetaData()
140146
for s, tname in conn_table_names.difference(metadata_table_names):
141147
name = sa_schema._get_table_key(tname, s)
148+
149+
# a name might be present already if a previous reflection pulled
150+
# this table in via foreign key constraint
142151
exists = name in removal_metadata.tables
143152
t = sa_schema.Table(tname, removal_metadata, schema=s)
144153

@@ -152,7 +161,7 @@ def _compare_tables(
152161
(inspector),
153162
# fmt: on
154163
)
155-
_InspectorConv(inspector).reflect_table(t, include_columns=None)
164+
_InspectorConv(inspector).reflect_table(t)
156165
if autogen_context.run_object_filters(t, tname, "table", True, None):
157166
modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
158167

@@ -172,6 +181,9 @@ def _compare_tables(
172181
for s, tname in existing_tables:
173182
name = sa_schema._get_table_key(tname, s)
174183
exists = name in existing_metadata.tables
184+
185+
# a name might be present already if a previous reflection pulled
186+
# this table in via foreign key constraint
175187
t = sa_schema.Table(tname, existing_metadata, schema=s)
176188
if not exists:
177189
event.listen(
@@ -182,7 +194,7 @@ def _compare_tables(
182194
_compat_autogen_column_reflect(inspector),
183195
# fmt: on
184196
)
185-
_InspectorConv(inspector).reflect_table(t, include_columns=None)
197+
_InspectorConv(inspector).reflect_table(t)
186198

187199
conn_column_info[(s, tname)] = t
188200

@@ -296,6 +308,7 @@ def _compare_columns(
296308

297309

298310
def setup(plugin: Plugin) -> None:
311+
299312
plugin.add_autogenerate_comparator(
300313
_autogen_for_tables,
301314
"schema",

0 commit comments

Comments
 (0)