Aug 4 - Update code to follow up - MT11
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""phase45 — widen inspection_schedules.frequency ENUM
|
||||
|
||||
Converges MT's cadence set onto the agreed union:
|
||||
|
||||
once | daily | weekly | monthly | quarterly | bi-annually | annually
|
||||
|
||||
'bi-annually' means every 6 months (not twice a year).
|
||||
|
||||
Why now
|
||||
-------
|
||||
phase46 (recurrence) and phase47 (end date) both branch on 'once':
|
||||
* a one-time schedule deactivates on completion instead of rolling forward;
|
||||
* a one-time schedule may not carry an end date — it ends by being completed.
|
||||
Adding the value later would mean a second ENUM rewrite of the same table.
|
||||
'bi-annually' and 'annually' are added in the same pass for the same reason.
|
||||
|
||||
The original four values are retained in their original order, so NO existing
|
||||
row changes and no data migration is needed. This is purely a widening.
|
||||
|
||||
MySQL ENUM changes
|
||||
------------------
|
||||
`MODIFY COLUMN` on an ENUM cannot be done in place — MySQL rebuilds the table.
|
||||
`ALGORITHM=COPY, LOCK=SHARED` is stated explicitly rather than left to the
|
||||
server's default so the behaviour is predictable across versions: the table is
|
||||
readable throughout and blocks writes for the duration of the copy. It also
|
||||
takes a metadata lock, which will WAIT behind any long-running transaction
|
||||
touching inspection_schedules — see the deploy notes. The table is small (one
|
||||
row per schedule), so the copy itself is fast.
|
||||
|
||||
No batch_alter_table (MySQL).
|
||||
|
||||
RE-RUNNABLE. An existence check is not enough here — the column already exists;
|
||||
what changes is its type. This inspects COLUMN_TYPE and skips when the target
|
||||
values are already present, so a re-run against a fully-migrated schema is a
|
||||
genuine no-op rather than a needless table rebuild.
|
||||
"""
|
||||
|
||||
revision = 'phase45_schedule_frequency_enum'
|
||||
down_revision = 'phase44_internal_handler'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
_TABLE = 'inspection_schedules'
|
||||
_COLUMN = 'frequency'
|
||||
|
||||
_NEW_ENUM = ("ENUM('once','daily','weekly','monthly','quarterly',"
|
||||
"'bi-annually','annually')")
|
||||
_OLD_ENUM = "ENUM('daily','weekly','monthly','quarterly')"
|
||||
|
||||
|
||||
def _table_exists(conn, table):
|
||||
return conn.execute(sa.text(
|
||||
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t"
|
||||
), {"t": table}).scalar() > 0
|
||||
|
||||
|
||||
def _column_type(conn, table, column):
|
||||
"""Lowercased COLUMN_TYPE, or None when the column does not exist."""
|
||||
row = conn.execute(sa.text(
|
||||
"SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS "
|
||||
"WHERE TABLE_SCHEMA = DATABASE() "
|
||||
"AND TABLE_NAME = :t AND COLUMN_NAME = :c"
|
||||
), {"t": table, "c": column}).first()
|
||||
if not row:
|
||||
return None
|
||||
value = row[0]
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode('utf-8', 'replace')
|
||||
return value.lower()
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
if not _table_exists(bind, _TABLE):
|
||||
return
|
||||
|
||||
col_type = _column_type(bind, _TABLE, _COLUMN)
|
||||
if col_type is None:
|
||||
return
|
||||
# Already widened? Every new value present means there is nothing to do.
|
||||
if all(v in col_type for v in ("'once'", "'bi-annually'", "'annually'")):
|
||||
return
|
||||
|
||||
op.execute(sa.text(
|
||||
f"ALTER TABLE {_TABLE} MODIFY COLUMN {_COLUMN} {_NEW_ENUM} "
|
||||
f"NOT NULL DEFAULT 'weekly', ALGORITHM=COPY, LOCK=SHARED"
|
||||
))
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
if not _table_exists(bind, _TABLE):
|
||||
return
|
||||
|
||||
col_type = _column_type(bind, _TABLE, _COLUMN)
|
||||
if col_type is None or "'once'" not in col_type:
|
||||
return
|
||||
|
||||
# Rows holding a value the narrow ENUM cannot represent would be silently
|
||||
# truncated to '' by the MODIFY. Map them to the nearest surviving cadence
|
||||
# first so the downgrade is lossy in a defined, inspectable way rather than
|
||||
# producing invalid empty-string rows.
|
||||
op.execute(sa.text(
|
||||
f"UPDATE {_TABLE} SET {_COLUMN} = 'monthly' "
|
||||
f"WHERE {_COLUMN} IN ('once','bi-annually','annually')"
|
||||
))
|
||||
op.execute(sa.text(
|
||||
f"ALTER TABLE {_TABLE} MODIFY COLUMN {_COLUMN} {_OLD_ENUM} "
|
||||
f"NOT NULL DEFAULT 'weekly', ALGORITHM=COPY, LOCK=SHARED"
|
||||
))
|
||||
Reference in New Issue
Block a user