133 lines
4.6 KiB
Python
133 lines
4.6 KiB
Python
"""Add app_settings table and expand perceptual_hash to 64 chars for 256-bit hashes
|
|
|
|
Revision ID: b26011901
|
|
Revises: a46e641430b8
|
|
Create Date: 2026-01-19
|
|
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision = 'b26011901'
|
|
down_revision = 'a46e641430b8'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade():
|
|
# Create app_settings table
|
|
op.create_table(
|
|
'app_settings',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('key', sa.String(length=64), nullable=False),
|
|
sa.Column('value', sa.Text(), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('key')
|
|
)
|
|
|
|
# Expand perceptual_hash column from 16 to 64 chars
|
|
bind = op.get_bind()
|
|
dialect = bind.dialect.name
|
|
|
|
if dialect == "sqlite":
|
|
# SQLite requires table recreation for column type changes.
|
|
# IMPORTANT: When recreating image_record, we must also recreate
|
|
# image_tags to preserve foreign key relationships.
|
|
|
|
# Step 1: Backup image_tags data
|
|
op.execute("CREATE TABLE image_tags_backup AS SELECT * FROM image_tags")
|
|
|
|
# Step 2: Drop image_tags (it references image_record)
|
|
op.drop_table("image_tags")
|
|
|
|
# Step 3: Recreate image_record with new column size
|
|
with op.batch_alter_table("image_record", recreate="always") as batch_op:
|
|
batch_op.alter_column(
|
|
'perceptual_hash',
|
|
existing_type=sa.String(length=16),
|
|
type_=sa.String(length=64),
|
|
existing_nullable=True
|
|
)
|
|
|
|
# Step 4: Recreate image_tags with proper FK constraints
|
|
op.create_table(
|
|
"image_tags",
|
|
sa.Column("image_id", sa.Integer(), nullable=False),
|
|
sa.Column("tag_id", sa.Integer(), nullable=False),
|
|
sa.PrimaryKeyConstraint("image_id", "tag_id"),
|
|
sa.ForeignKeyConstraint(["image_id"], ["image_record.id"], ondelete="CASCADE"),
|
|
sa.ForeignKeyConstraint(["tag_id"], ["tag.id"], ondelete="CASCADE"),
|
|
sa.UniqueConstraint("image_id", "tag_id", name="uq_image_tag"),
|
|
)
|
|
|
|
# Step 5: Restore image_tags data
|
|
op.execute("INSERT INTO image_tags (image_id, tag_id) SELECT image_id, tag_id FROM image_tags_backup")
|
|
|
|
# Step 6: Drop backup table
|
|
op.drop_table("image_tags_backup")
|
|
else:
|
|
# PostgreSQL and others can alter directly
|
|
op.alter_column(
|
|
'image_record',
|
|
'perceptual_hash',
|
|
existing_type=sa.String(length=16),
|
|
type_=sa.String(length=64),
|
|
existing_nullable=True
|
|
)
|
|
|
|
|
|
def downgrade():
|
|
bind = op.get_bind()
|
|
dialect = bind.dialect.name
|
|
|
|
# Shrink perceptual_hash back to 16 chars (will truncate data!)
|
|
if dialect == "sqlite":
|
|
# Same approach as upgrade: backup image_tags, recreate image_record,
|
|
# then restore image_tags to preserve FK relationships
|
|
|
|
# Step 1: Backup image_tags data
|
|
op.execute("CREATE TABLE image_tags_backup AS SELECT * FROM image_tags")
|
|
|
|
# Step 2: Drop image_tags (it references image_record)
|
|
op.drop_table("image_tags")
|
|
|
|
# Step 3: Recreate image_record with old column size
|
|
with op.batch_alter_table("image_record", recreate="always") as batch_op:
|
|
batch_op.alter_column(
|
|
'perceptual_hash',
|
|
existing_type=sa.String(length=64),
|
|
type_=sa.String(length=16),
|
|
existing_nullable=True
|
|
)
|
|
|
|
# Step 4: Recreate image_tags with proper FK constraints
|
|
op.create_table(
|
|
"image_tags",
|
|
sa.Column("image_id", sa.Integer(), nullable=False),
|
|
sa.Column("tag_id", sa.Integer(), nullable=False),
|
|
sa.PrimaryKeyConstraint("image_id", "tag_id"),
|
|
sa.ForeignKeyConstraint(["image_id"], ["image_record.id"], ondelete="CASCADE"),
|
|
sa.ForeignKeyConstraint(["tag_id"], ["tag.id"], ondelete="CASCADE"),
|
|
sa.UniqueConstraint("image_id", "tag_id", name="uq_image_tag"),
|
|
)
|
|
|
|
# Step 5: Restore image_tags data
|
|
op.execute("INSERT INTO image_tags (image_id, tag_id) SELECT image_id, tag_id FROM image_tags_backup")
|
|
|
|
# Step 6: Drop backup table
|
|
op.drop_table("image_tags_backup")
|
|
else:
|
|
op.alter_column(
|
|
'image_record',
|
|
'perceptual_hash',
|
|
existing_type=sa.String(length=64),
|
|
type_=sa.String(length=16),
|
|
existing_nullable=True
|
|
)
|
|
|
|
# Drop app_settings table
|
|
op.drop_table('app_settings')
|