From: Murod Date: Fri, 24 Jul 2026 17:15:15 +0000 (-0400) Subject: Don't emit DROP of version table in offline mode at base (#1822) X-Git-Tag: rel_1_19_0~5 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=5faedb28742f830edde8be7dbdc1db633e64ae2b;p=thirdparty%2Fsqlalchemy%2Falembic.git Don't emit DROP of version table in offline mode at base (#1822) Fixes #1822. As diagnosed by @zzzeek in the issue, offline (`--sql`) mode emitted a `DROP TABLE alembic_version` when running `stamp` or `downgrade` to `base`, while online mode never drops the version table — an inconsistency dating to the version table's introduction. This removes the offline-only `DROP` so both modes behave the same. The version table is still created when it does not exist; only the spurious offline drop is gone. **Tests** - Updated `UpgradeDowngradeStampTest::test_version_to_none` to assert the version table is no longer dropped in offline downgrade-to-base. - Added `UpgradeDowngradeStampTest::test_sql_stamp_to_base_no_drop` regressing the exact scenario from the issue (`stamp --sql base`). - Added a changelog entry under `docs/build/unreleased/`. Full local run: 1763 passed (the only failures are pre-existing `test_post_write.py` hook tests unrelated to this change). `black` + `flake8` clean. Closes: #1823 Pull-request: https://github.com/sqlalchemy/alembic/pull/1823 Pull-request-sha: 8403c7083d9a1acbb9410ec0fd92331eace5ca8b Change-Id: Ic5310b989f5217c4c74f88987c8d6a5459226b6b --- diff --git a/alembic/runtime/migration.py b/alembic/runtime/migration.py index a108539d..0035540a 100644 --- a/alembic/runtime/migration.py +++ b/alembic/runtime/migration.py @@ -639,9 +639,11 @@ class MigrationContext: run_args=kw, ) - if self.as_sql and not head_maintainer.heads: - assert self.connection is not None - self._version.drop(self.connection) + # NOTE: offline ("--sql") mode intentionally does not emit a DROP + # of the version table when ending at base. Online mode never drops + # the version table (e.g. ``downgrade base`` only deletes its row), + # so dropping it in offline mode only was an inconsistency present + # since the version table was first introduced. See #1822. def _in_connection_transaction(self) -> bool: try: diff --git a/docs/build/unreleased/1822.rst b/docs/build/unreleased/1822.rst new file mode 100644 index 00000000..f1561612 --- /dev/null +++ b/docs/build/unreleased/1822.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: bug, commands + :tickets: 1822 + + Fixed inconsistency where running ``stamp`` or ``downgrade`` to ``base`` in + offline (``--sql``) mode would emit a ``DROP TABLE alembic_version`` + statement, while the same operations in online mode never drop the version + table. Offline mode no longer emits this ``DROP``, matching online + behavior. The version table continues to be created when it does not exist; + only the spurious offline-only drop has been removed. Pull request + courtesy imurodl. diff --git a/tests/test_command.py b/tests/test_command.py index 16e2b1cc..67aabd18 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -977,11 +977,20 @@ class UpgradeDowngradeStampTest(TestBase): command.downgrade(self.cfg, "%s:base" % self.c, sql=True) assert "CREATE TABLE alembic_version" not in buf.getvalue() assert "INSERT INTO alembic_version" not in buf.getvalue() - assert "DROP TABLE alembic_version" in buf.getvalue() + # offline mode no longer drops the version table, matching online + # mode which never drops it. See #1822. + assert "DROP TABLE alembic_version" not in buf.getvalue() assert "DROP STEP 3" in buf.getvalue() assert "DROP STEP 2" in buf.getvalue() assert "DROP STEP 1" in buf.getvalue() + def test_sql_stamp_to_base_no_drop(self): + # stamping to "base" in offline mode must not emit a DROP of the + # version table; online mode never drops it. See #1822. + with capture_context_buffer() as buf: + command.stamp(self.cfg, "base", sql=True) + assert "DROP TABLE alembic_version" not in buf.getvalue() + def test_version_to_middle(self): with capture_context_buffer() as buf: command.downgrade(self.cfg, "%s:%s" % (self.c, self.a), sql=True)