Skip to content

Commit ffccbc7

Browse files
maver1ckzzzeek
authored andcommitted
Add ability to configure alembic_version table in DialectImpl
Added a new hook to the :class:`.DefaultImpl` :meth:`.DefaultImpl.version_table_impl`. This allows third party dialects to define the exact structure of the alembic_version table, to include use cases where the table requires special directives and/or additional columns so that it may function correctly on a particular backend. This is not intended as a user-expansion hook, only a dialect implementation hook to produce a working alembic_version table. Pull request courtesy Maciek Bryński. This will be 1.14 so this also version bumps Fixes: #1560 Closes: #1563 Pull-request: #1563 Pull-request-sha: e70fdc8 Change-Id: I5e565dff60a979526608d2a1c0c620fbca269a3f
1 parent 431fd07 commit ffccbc7

File tree

5 files changed

+110
-20
lines changed

5 files changed

+110
-20
lines changed

alembic/ddl/impl.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
from typing import Union
2222

2323
from sqlalchemy import cast
24+
from sqlalchemy import Column
25+
from sqlalchemy import MetaData
26+
from sqlalchemy import PrimaryKeyConstraint
2427
from sqlalchemy import schema
28+
from sqlalchemy import String
29+
from sqlalchemy import Table
2530
from sqlalchemy import text
2631

2732
from . import _autogen
@@ -43,11 +48,9 @@
4348
from sqlalchemy.sql import Executable
4449
from sqlalchemy.sql.elements import ColumnElement
4550
from sqlalchemy.sql.elements import quoted_name
46-
from sqlalchemy.sql.schema import Column
4751
from sqlalchemy.sql.schema import Constraint
4852
from sqlalchemy.sql.schema import ForeignKeyConstraint
4953
from sqlalchemy.sql.schema import Index
50-
from sqlalchemy.sql.schema import Table
5154
from sqlalchemy.sql.schema import UniqueConstraint
5255
from sqlalchemy.sql.selectable import TableClause
5356
from sqlalchemy.sql.type_api import TypeEngine
@@ -136,6 +139,40 @@ def static_output(self, text: str) -> None:
136139
self.output_buffer.write(text + "\n\n")
137140
self.output_buffer.flush()
138141

142+
def version_table_impl(
143+
self,
144+
*,
145+
version_table: str,
146+
version_table_schema: Optional[str],
147+
version_table_pk: bool,
148+
**kw: Any,
149+
) -> Table:
150+
"""Generate a :class:`.Table` object which will be used as the
151+
structure for the Alembic version table.
152+
153+
Third party dialects may override this hook to provide an alternate
154+
structure for this :class:`.Table`; requirements are only that it
155+
be named based on the ``version_table`` parameter and contains
156+
at least a single string-holding column named ``version_num``.
157+
158+
.. versionadded:: 1.14
159+
160+
"""
161+
vt = Table(
162+
version_table,
163+
MetaData(),
164+
Column("version_num", String(32), nullable=False),
165+
schema=version_table_schema,
166+
)
167+
if version_table_pk:
168+
vt.append_constraint(
169+
PrimaryKeyConstraint(
170+
"version_num", name=f"{version_table}_pkc"
171+
)
172+
)
173+
174+
return vt
175+
139176
def requires_recreate_in_batch(
140177
self, batch_op: BatchOperationsImpl
141178
) -> bool:

alembic/runtime/migration.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@
2424

2525
from sqlalchemy import Column
2626
from sqlalchemy import literal_column
27-
from sqlalchemy import MetaData
28-
from sqlalchemy import PrimaryKeyConstraint
29-
from sqlalchemy import String
30-
from sqlalchemy import Table
3127
from sqlalchemy.engine import Engine
3228
from sqlalchemy.engine import url as sqla_url
3329
from sqlalchemy.engine.strategies import MockEngineStrategy
@@ -36,6 +32,7 @@
3632
from .. import util
3733
from ..util import sqla_compat
3834
from ..util.compat import EncodedIO
35+
from ..util.sqla_compat import _select
3936

4037
if TYPE_CHECKING:
4138
from sqlalchemy.engine import Dialect
@@ -190,18 +187,6 @@ def __init__(
190187
self.version_table_schema = version_table_schema = opts.get(
191188
"version_table_schema", None
192189
)
193-
self._version = Table(
194-
version_table,
195-
MetaData(),
196-
Column("version_num", String(32), nullable=False),
197-
schema=version_table_schema,
198-
)
199-
if opts.get("version_table_pk", True):
200-
self._version.append_constraint(
201-
PrimaryKeyConstraint(
202-
"version_num", name="%s_pkc" % version_table
203-
)
204-
)
205190

206191
self._start_from_rev: Optional[str] = opts.get("starting_rev")
207192
self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
@@ -212,6 +197,13 @@ def __init__(
212197
self.output_buffer,
213198
opts,
214199
)
200+
201+
self._version = self.impl.version_table_impl(
202+
version_table=version_table,
203+
version_table_schema=version_table_schema,
204+
version_table_pk=opts.get("version_table_pk", True),
205+
)
206+
215207
log.info("Context impl %s.", self.impl.__class__.__name__)
216208
if self.as_sql:
217209
log.info("Generating static SQL")
@@ -540,7 +532,10 @@ def get_current_heads(self) -> Tuple[str, ...]:
540532
return ()
541533
assert self.connection is not None
542534
return tuple(
543-
row[0] for row in self.connection.execute(self._version.select())
535+
row[0]
536+
for row in self.connection.execute(
537+
_select(self._version.c.version_num)
538+
)
544539
)
545540

546541
def _ensure_version_table(self, purge: bool = False) -> None:

docs/build/changelog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Changelog
44
==========
55

66
.. changelog::
7-
:version: 1.13.4
7+
:version: 1.14.0
88
:include_notes_from: unreleased
99

1010
.. changelog::

docs/build/unreleased/1560.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.. change::
2+
:tags: usecase, runtime
3+
:tickets: 1560
4+
5+
Added a new hook to the :class:`.DefaultImpl`
6+
:meth:`.DefaultImpl.version_table_impl`. This allows third party dialects
7+
to define the exact structure of the alembic_version table, to include use
8+
cases where the table requires special directives and/or additional columns
9+
so that it may function correctly on a particular backend. This is not
10+
intended as a user-expansion hook, only a dialect implementation hook to
11+
produce a working alembic_version table. Pull request courtesy Maciek
12+
Bryński.

tests/test_version_table.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
from sqlalchemy import Column
22
from sqlalchemy import inspect
3+
from sqlalchemy import Integer
34
from sqlalchemy import MetaData
5+
from sqlalchemy import PrimaryKeyConstraint
46
from sqlalchemy import String
57
from sqlalchemy import Table
8+
from sqlalchemy.dialects import registry
9+
from sqlalchemy.engine import default
610

711
from alembic import migration
12+
from alembic.ddl import impl
813
from alembic.testing import assert_raises
914
from alembic.testing import assert_raises_message
1015
from alembic.testing import config
@@ -373,3 +378,44 @@ def test_delete_multi_match_no_sane_rowcount(self):
373378
self.connection.dialect, "supports_sane_rowcount", False
374379
):
375380
self.updater.update_to_step(_down("a", None, True))
381+
382+
383+
registry.register("custom_version", __name__, "CustomVersionDialect")
384+
385+
386+
class CustomVersionDialect(default.DefaultDialect):
387+
name = "custom_version"
388+
389+
390+
class CustomVersionTableImpl(impl.DefaultImpl):
391+
__dialect__ = "custom_version"
392+
393+
def version_table_impl(
394+
self,
395+
*,
396+
version_table,
397+
version_table_schema,
398+
version_table_pk,
399+
**kw,
400+
):
401+
vt = Table(
402+
version_table,
403+
MetaData(),
404+
Column("id", Integer, autoincrement=True),
405+
Column("version_num", String(32), nullable=False),
406+
schema=version_table_schema,
407+
)
408+
if version_table_pk:
409+
vt.append_constraint(
410+
PrimaryKeyConstraint("id", name=f"{version_table}_pkc")
411+
)
412+
return vt
413+
414+
415+
class CustomVersionTableTest(TestMigrationContext):
416+
417+
def test_custom_version_table(self):
418+
context = migration.MigrationContext.configure(
419+
dialect_name="custom_version",
420+
)
421+
eq_(len(context._version.columns), 2)

0 commit comments

Comments
 (0)