Public Access
fix(migration): simplify aisle migration + add persistent backup script
- Drop the empty batch_alter_table block and the meaningless set_config call from migration 0015. Temp tables still persist for the migration's session (Alembic's transactional_ddl). - New backend/scripts/persist_aisle_backup.sql creates public.ingredient_aisle_backup_0015 and public.grocery_item_aisle_backup_0015 permanent tables for operators who want a recoverable record beyond the migration. - Update Review/sprint2-verification.md, Review/ui-nielsen-audit.md and fix-ui-audit.md with the correct container-based deploy steps: docker compose exec db psql -U mealplanner -d mealplanner -f /dev/stdin < ...sql. Host psql is not available on the deployment host; the db runs inside the container.
This commit is contained in:
@@ -3,10 +3,28 @@
|
||||
Revision ID: 0015
|
||||
Revises: 0014
|
||||
Create Date: 2026-06-02
|
||||
|
||||
Normalizes free-text aisle values on `ingredient.aisle` and `grocery_item.aisle`
|
||||
to a fixed canonical set. Runs in a single transaction (Alembic default); both
|
||||
op.execute calls share the same session, so the temp backup tables persist
|
||||
for the duration of the upgrade.
|
||||
|
||||
Backup tables: the temp tables `ingredient_aisle_backup` and
|
||||
`grocery_item_aisle_backup` are created for the migration's session. They
|
||||
auto-drop when the session ends. If you need a persistent backup, run
|
||||
`backend/scripts/persist_aisle_backup.sql` BEFORE this migration.
|
||||
|
||||
Deploy via Docker (the db runs inside a container; no host psql required):
|
||||
|
||||
# 1. dry-run preview:
|
||||
docker compose exec db psql -U mealplanner -d mealplanner \\
|
||||
-f /dev/stdin < backend/scripts/dry_run_aisle_migration.sql
|
||||
|
||||
# 2. apply:
|
||||
docker compose exec backend alembic upgrade head
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
@@ -16,20 +34,6 @@ branch_labels: Union[Sequence[str], None] = None
|
||||
depends_on: Union[Sequence[str], None] = None
|
||||
|
||||
|
||||
CANONICAL_AISLES = (
|
||||
"Produce",
|
||||
"Meat & Seafood",
|
||||
"Dairy & Eggs",
|
||||
"Pantry",
|
||||
"Frozen",
|
||||
"Bakery",
|
||||
"Beverages",
|
||||
"Spices",
|
||||
"Other",
|
||||
)
|
||||
|
||||
# Map from lowercased source value to canonical label. Keep the rule
|
||||
# order narrow -> broad; longest matches win via SQL CASE.
|
||||
NORMALIZATION_RULES = [
|
||||
("canned goods", "Pantry"),
|
||||
("canned", "Pantry"),
|
||||
@@ -78,23 +82,15 @@ def _normalize(table: str) -> None:
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
with op.batch_alter_table("ingredient") as batch:
|
||||
pass
|
||||
_normalize("ingredient")
|
||||
_normalize("grocery_item")
|
||||
bind.execute(
|
||||
sa.text(
|
||||
"SELECT set_config('app.aisle_backup_retention', 'aisle_migration_0015', false)"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Best-effort downgrade: the backup temp tables only exist within the
|
||||
# upgrade transaction. Restoring the pre-normalization state is not
|
||||
# possible from this migration alone. Operators must restore from a
|
||||
# database snapshot taken before upgrade.
|
||||
# The temp backup tables only exist for the migration's session.
|
||||
# Restoring the pre-normalization state is not possible from this
|
||||
# migration alone. Operators must restore from a database snapshot
|
||||
# taken before upgrade.
|
||||
raise NotImplementedError(
|
||||
"Cannot reverse aisle normalization without an external backup. "
|
||||
"Restore the database from a snapshot taken before 0015 was applied."
|
||||
|
||||
Reference in New Issue
Block a user