56 lines
1.6 KiB
Python
56 lines
1.6 KiB
Python
"""Add superseded column to downloads
|
|
|
|
Revision ID: 003
|
|
Revises: 002
|
|
Create Date: 2026-03-18
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision: str = '003'
|
|
down_revision: Union[str, None] = '002'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Add superseded column with default False
|
|
op.add_column(
|
|
'downloads',
|
|
sa.Column('superseded', sa.Boolean(), nullable=False, server_default='false')
|
|
)
|
|
|
|
# Backfill: mark FAILED downloads as superseded where a later COMPLETED
|
|
# download exists for the same source. REQUIRED — without this, all
|
|
# previously-superseded failures reappear in the dashboard immediately.
|
|
op.execute("""
|
|
UPDATE downloads d
|
|
SET superseded = TRUE
|
|
WHERE d.status = 'failed'
|
|
AND EXISTS (
|
|
SELECT 1 FROM downloads d2
|
|
WHERE d2.source_id = d.source_id
|
|
AND d2.status = 'completed'
|
|
AND d2.created_at > d.created_at
|
|
)
|
|
""")
|
|
|
|
# Partial index covering the dominant query pattern: status + superseded = FALSE.
|
|
# A single boolean column index would be ignored in favour of status indexes;
|
|
# this form matches the WHERE clauses at all three affected query sites.
|
|
op.create_index(
|
|
'idx_downloads_not_superseded',
|
|
'downloads',
|
|
['status'],
|
|
postgresql_where=sa.text('superseded = FALSE')
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index('idx_downloads_not_superseded', table_name='downloads')
|
|
op.drop_column('downloads', 'superseded')
|