alembic: make migration chain SQLite-compatible (fresh upgrade)
Five existing migrations used op.alter_column / op.create_unique_constraint / op.drop_constraint / op.create_foreign_key directly on the users + quotes + quotes_daily tables. SQLite has no native support for those operations and requires Alembic's batch_alter_table copy-and-rename workaround. This wasn't noticed until now because the test suite uses Base.metadata.create_all to materialise schema, not the migration chain itself; and prod is MariaDB. But running `alembic upgrade head` against a fresh SQLite database (developer onboarding, CI smoke tests, the test container's own bootstrap) would fail at 0005. Fixes: - alembic/env.py: set render_as_batch=True when the dialect is SQLite. This auto-wraps any future autogenerated migration but doesn't retroactively rewrite existing op.* calls. - 0005 (widen quotes.symbol), 0013 (referrals), 0018 (polar webhook), 0019 (stripe), 0023 (users.lang index + qd_symbol widen) explicitly wrap their problematic ops in `with op.batch_alter_table(...) as bop`. Now `alembic upgrade head` + `alembic downgrade base` round-trip cleanly on a fresh SQLite database. MariaDB prod behaviour unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2b9cd875b4
commit
78ce8c8b0d
6 changed files with 79 additions and 74 deletions
|
|
@ -44,10 +44,17 @@ def run_migrations_offline() -> None:
|
||||||
|
|
||||||
|
|
||||||
def do_run_migrations(connection: Connection) -> None:
|
def do_run_migrations(connection: Connection) -> None:
|
||||||
|
# render_as_batch is required for SQLite, which doesn't support
|
||||||
|
# most ALTER COLUMN / ADD CONSTRAINT operations natively. With
|
||||||
|
# batch mode enabled, Alembic emits a copy-and-rename dance under
|
||||||
|
# SQLite while still producing plain ALTER on MariaDB / Postgres,
|
||||||
|
# so prod migrations are unchanged. Detect via the dialect name.
|
||||||
|
render_as_batch = connection.dialect.name == "sqlite"
|
||||||
context.configure(
|
context.configure(
|
||||||
connection=connection,
|
connection=connection,
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
compare_type=True,
|
compare_type=True,
|
||||||
|
render_as_batch=render_as_batch,
|
||||||
)
|
)
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,14 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.alter_column(
|
# batch_alter_table wraps the ALTER in a copy-and-rename dance for
|
||||||
"quotes", "symbol",
|
# SQLite (which doesn't support ALTER COLUMN TYPE) while remaining a
|
||||||
|
# plain ALTER on MariaDB / Postgres. Required for `alembic upgrade
|
||||||
|
# head` to work against a fresh SQLite database during local tooling
|
||||||
|
# or test bootstrap.
|
||||||
|
with op.batch_alter_table("quotes") as bop:
|
||||||
|
bop.alter_column(
|
||||||
|
"symbol",
|
||||||
existing_type=sa.String(64),
|
existing_type=sa.String(64),
|
||||||
type_=sa.String(128),
|
type_=sa.String(128),
|
||||||
existing_nullable=False,
|
existing_nullable=False,
|
||||||
|
|
@ -26,8 +32,9 @@ def upgrade() -> None:
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.alter_column(
|
with op.batch_alter_table("quotes") as bop:
|
||||||
"quotes", "symbol",
|
bop.alter_column(
|
||||||
|
"symbol",
|
||||||
existing_type=sa.String(128),
|
existing_type=sa.String(128),
|
||||||
type_=sa.String(64),
|
type_=sa.String(64),
|
||||||
existing_nullable=False,
|
existing_nullable=False,
|
||||||
|
|
|
||||||
|
|
@ -30,20 +30,18 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.add_column(
|
# batch_alter_table wraps ADD CONSTRAINT in a copy-and-rename for
|
||||||
"users",
|
# SQLite (no native ALTER constraints support); on MariaDB/Postgres
|
||||||
sa.Column("referral_code", sa.String(16), nullable=True),
|
# it falls through to plain ALTER statements.
|
||||||
|
with op.batch_alter_table("users") as bop:
|
||||||
|
bop.add_column(sa.Column("referral_code", sa.String(16), nullable=True))
|
||||||
|
bop.create_unique_constraint(
|
||||||
|
"uq_users_referral_code", ["referral_code"],
|
||||||
)
|
)
|
||||||
op.create_unique_constraint(
|
bop.add_column(sa.Column("referred_by_user_id", sa.Integer, nullable=True))
|
||||||
"uq_users_referral_code", "users", ["referral_code"],
|
bop.create_foreign_key(
|
||||||
)
|
|
||||||
op.add_column(
|
|
||||||
"users",
|
|
||||||
sa.Column("referred_by_user_id", sa.Integer, nullable=True),
|
|
||||||
)
|
|
||||||
op.create_foreign_key(
|
|
||||||
"fk_users_referred_by",
|
"fk_users_referred_by",
|
||||||
"users", "users",
|
"users",
|
||||||
["referred_by_user_id"], ["id"],
|
["referred_by_user_id"], ["id"],
|
||||||
ondelete="SET NULL",
|
ondelete="SET NULL",
|
||||||
)
|
)
|
||||||
|
|
@ -71,7 +69,8 @@ def upgrade() -> None:
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.drop_index("ix_referrals_referrer", table_name="referrals")
|
op.drop_index("ix_referrals_referrer", table_name="referrals")
|
||||||
op.drop_table("referrals")
|
op.drop_table("referrals")
|
||||||
op.drop_constraint("fk_users_referred_by", "users", type_="foreignkey")
|
with op.batch_alter_table("users") as bop:
|
||||||
op.drop_column("users", "referred_by_user_id")
|
bop.drop_constraint("fk_users_referred_by", type_="foreignkey")
|
||||||
op.drop_constraint("uq_users_referral_code", "users", type_="unique")
|
bop.drop_column("referred_by_user_id")
|
||||||
op.drop_column("users", "referral_code")
|
bop.drop_constraint("uq_users_referral_code", type_="unique")
|
||||||
|
bop.drop_column("referral_code")
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,11 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.add_column(
|
with op.batch_alter_table("users") as bop:
|
||||||
"users",
|
bop.add_column(sa.Column("polar_customer_id", sa.String(length=64), nullable=True))
|
||||||
sa.Column("polar_customer_id", sa.String(length=64), nullable=True),
|
bop.add_column(sa.Column("polar_subscription_id", sa.String(length=64), nullable=True))
|
||||||
)
|
bop.create_unique_constraint(
|
||||||
op.add_column(
|
"uq_users_polar_customer", ["polar_customer_id"],
|
||||||
"users",
|
|
||||||
sa.Column("polar_subscription_id", sa.String(length=64), nullable=True),
|
|
||||||
)
|
|
||||||
op.create_unique_constraint(
|
|
||||||
"uq_users_polar_customer", "users", ["polar_customer_id"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
|
|
@ -50,6 +45,7 @@ def upgrade() -> None:
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.drop_index("ix_polar_events_type_received", table_name="polar_events")
|
op.drop_index("ix_polar_events_type_received", table_name="polar_events")
|
||||||
op.drop_table("polar_events")
|
op.drop_table("polar_events")
|
||||||
op.drop_constraint("uq_users_polar_customer", "users", type_="unique")
|
with op.batch_alter_table("users") as bop:
|
||||||
op.drop_column("users", "polar_subscription_id")
|
bop.drop_constraint("uq_users_polar_customer", type_="unique")
|
||||||
|
bop.drop_column("polar_subscription_id")
|
||||||
op.drop_column("users", "polar_customer_id")
|
op.drop_column("users", "polar_customer_id")
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,11 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.add_column(
|
with op.batch_alter_table("users") as bop:
|
||||||
"users",
|
bop.add_column(sa.Column("stripe_customer_id", sa.String(length=64), nullable=True))
|
||||||
sa.Column("stripe_customer_id", sa.String(length=64), nullable=True),
|
bop.add_column(sa.Column("stripe_subscription_id", sa.String(length=64), nullable=True))
|
||||||
)
|
bop.create_unique_constraint(
|
||||||
op.add_column(
|
"uq_users_stripe_customer", ["stripe_customer_id"],
|
||||||
"users",
|
|
||||||
sa.Column("stripe_subscription_id", sa.String(length=64), nullable=True),
|
|
||||||
)
|
|
||||||
op.create_unique_constraint(
|
|
||||||
"uq_users_stripe_customer", "users", ["stripe_customer_id"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
|
|
@ -51,6 +46,7 @@ def upgrade() -> None:
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.drop_index("ix_stripe_events_type_received", table_name="stripe_events")
|
op.drop_index("ix_stripe_events_type_received", table_name="stripe_events")
|
||||||
op.drop_table("stripe_events")
|
op.drop_table("stripe_events")
|
||||||
op.drop_constraint("uq_users_stripe_customer", "users", type_="unique")
|
with op.batch_alter_table("users") as bop:
|
||||||
op.drop_column("users", "stripe_subscription_id")
|
bop.drop_constraint("uq_users_stripe_customer", type_="unique")
|
||||||
op.drop_column("users", "stripe_customer_id")
|
bop.drop_column("stripe_subscription_id")
|
||||||
|
bop.drop_column("stripe_customer_id")
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
op.create_index("ix_users_lang", "users", ["lang"])
|
op.create_index("ix_users_lang", "users", ["lang"])
|
||||||
op.alter_column(
|
with op.batch_alter_table("quotes_daily") as bop:
|
||||||
"quotes_daily",
|
bop.alter_column(
|
||||||
"symbol",
|
"symbol",
|
||||||
existing_type=sa.String(length=64),
|
existing_type=sa.String(length=64),
|
||||||
type_=sa.String(length=128),
|
type_=sa.String(length=128),
|
||||||
|
|
@ -28,8 +28,8 @@ def upgrade() -> None:
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
op.alter_column(
|
with op.batch_alter_table("quotes_daily") as bop:
|
||||||
"quotes_daily",
|
bop.alter_column(
|
||||||
"symbol",
|
"symbol",
|
||||||
existing_type=sa.String(length=128),
|
existing_type=sa.String(length=128),
|
||||||
type_=sa.String(length=64),
|
type_=sa.String(length=64),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue