mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-07-30 15:26:14 +00:00
fix revision
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""add mint url to lightning invoices
|
||||
|
||||
Revision ID: 11eaab843b49
|
||||
Revises: d7e8f9a0b1c2
|
||||
Create Date: 2026-07-22 22:25:45.278261
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# This revision was deployed from the pre-merge branch. Keep it in the graph so
|
||||
# those databases can migrate forward instead of being stamped past migrations.
|
||||
revision = "11eaab843b49"
|
||||
down_revision = "d7e8f9a0b1c2"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" not in columns:
|
||||
op.add_column(
|
||||
"lightning_invoices",
|
||||
sa.Column("mint_url", sa.String(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" in columns:
|
||||
op.drop_column("lightning_invoices", "mint_url")
|
||||
@@ -0,0 +1,37 @@
|
||||
"""add mint url to lightning invoices
|
||||
|
||||
Revision ID: 21c84cd5ad83
|
||||
Revises: c6d7e8f9a0b1
|
||||
Create Date: 2026-07-12 15:04:01.675455
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# This revision was deployed before the migration was recreated. Retain it so
|
||||
# databases stamped with it remain connected to the migration graph.
|
||||
revision = "21c84cd5ad83"
|
||||
down_revision = "c6d7e8f9a0b1"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" not in columns:
|
||||
op.add_column(
|
||||
"lightning_invoices",
|
||||
sa.Column("mint_url", sa.String(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" in columns:
|
||||
op.drop_column("lightning_invoices", "mint_url")
|
||||
@@ -16,10 +16,21 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"lightning_invoices", sa.Column("mint_url", sa.String(), nullable=True)
|
||||
)
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" not in columns:
|
||||
op.add_column(
|
||||
"lightning_invoices",
|
||||
sa.Column("mint_url", sa.String(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("lightning_invoices", "mint_url")
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("lightning_invoices")
|
||||
}
|
||||
if "mint_url" in columns:
|
||||
op.drop_column("lightning_invoices", "mint_url")
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"""merge replaced mint revision and repair fee checkpoint
|
||||
|
||||
Revision ID: d8e6f974960a
|
||||
Revises: c7d5f8638599, 11eaab843b49, 21c84cd5ad83
|
||||
Create Date: 2026-07-26 00:00:00.000000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "d8e6f974960a"
|
||||
down_revision = ("c7d5f8638599", "11eaab843b49", "21c84cd5ad83")
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Repair nodes previously stamped past the fee checkpoint migration."""
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in sa.inspect(op.get_bind()).get_columns("routstr_fees")
|
||||
}
|
||||
|
||||
if "payout_in_progress_msats" not in columns:
|
||||
op.add_column(
|
||||
"routstr_fees",
|
||||
sa.Column(
|
||||
"payout_in_progress_msats",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
),
|
||||
)
|
||||
|
||||
if "payout_started_at" not in columns:
|
||||
op.add_column(
|
||||
"routstr_fees",
|
||||
sa.Column("payout_started_at", sa.Integer(), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Both parent branches expect these columns when their histories are intact.
|
||||
pass
|
||||
+6
-37
@@ -11,9 +11,8 @@ from typing import AsyncGenerator
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from alembic.util.exc import CommandError
|
||||
from sqlalchemy import Index, UniqueConstraint, case, delete, or_
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio.engine import create_async_engine
|
||||
from sqlalchemy.orm import aliased
|
||||
from sqlmodel import Field, Relationship, SQLModel, col, func, select, update
|
||||
@@ -790,17 +789,6 @@ def fix_cashu_migrations() -> None:
|
||||
logger.warning(f"Could not check/fix Cashu database {db_file}: {e}")
|
||||
|
||||
|
||||
def _clear_alembic_version() -> None:
|
||||
"""Clear the alembic_version table so stamp/upgrade can proceed."""
|
||||
sync_url = DATABASE_URL.replace("+aiosqlite", "")
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
eng = create_engine(sync_url)
|
||||
with eng.begin() as conn:
|
||||
conn.execute(text("DELETE FROM alembic_version"))
|
||||
eng.dispose()
|
||||
|
||||
|
||||
def run_migrations() -> None:
|
||||
"""Run Alembic migrations programmatically."""
|
||||
try:
|
||||
@@ -822,30 +810,11 @@ def run_migrations() -> None:
|
||||
# Set the database URL in the config
|
||||
alembic_cfg.set_main_option("sqlalchemy.url", DATABASE_URL)
|
||||
|
||||
try:
|
||||
command.upgrade(alembic_cfg, "head")
|
||||
except CommandError as e:
|
||||
if "Can't locate revision" in str(e):
|
||||
logger.warning(
|
||||
"Database stamped with unknown revision (likely from another branch). "
|
||||
"Re-stamping to current head.",
|
||||
extra={"error": str(e)},
|
||||
)
|
||||
_clear_alembic_version()
|
||||
command.stamp(alembic_cfg, "head")
|
||||
else:
|
||||
raise
|
||||
except OperationalError as e:
|
||||
if "duplicate column name" in str(e).lower():
|
||||
logger.warning(
|
||||
"Migration hit a column that already exists (likely added via "
|
||||
"create_all on another branch). Stamping to current head.",
|
||||
extra={"error": str(e)},
|
||||
)
|
||||
_clear_alembic_version()
|
||||
command.stamp(alembic_cfg, "head")
|
||||
else:
|
||||
raise
|
||||
# Never stamp past a migration failure. Doing so makes Alembic's version
|
||||
# metadata claim schema changes were applied when the physical schema may
|
||||
# still be missing them. Unknown revisions must be restored or merged into
|
||||
# the revision graph; other migration errors must fail startup visibly.
|
||||
command.upgrade(alembic_cfg, "head")
|
||||
|
||||
logger.info("Database migrations completed successfully")
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _run_alembic(root: Path, database_url: str, revision: str) -> None:
|
||||
env = os.environ.copy()
|
||||
@@ -18,6 +20,23 @@ def _run_alembic(root: Path, database_url: str, revision: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _run_routstr_startup_migrations(root: Path, database_url: str) -> None:
|
||||
env = os.environ.copy()
|
||||
env["DATABASE_URL"] = database_url
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
"from routstr.core.db import run_migrations; run_migrations()",
|
||||
],
|
||||
cwd=root,
|
||||
env=env,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def test_fresh_node_migrates_fee_payout_schema_to_head(tmp_path: Path) -> None:
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
database_path = tmp_path / "fresh-node.db"
|
||||
@@ -37,7 +56,7 @@ def test_fresh_node_migrates_fee_payout_schema_to_head(tmp_path: Path) -> None:
|
||||
"payout_in_progress_msats, payout_started_at FROM routstr_fees"
|
||||
).fetchone()
|
||||
|
||||
assert version == ("c7d5f8638599",)
|
||||
assert version == ("d8e6f974960a",)
|
||||
assert {
|
||||
"id",
|
||||
"accumulated_msats",
|
||||
@@ -77,14 +96,46 @@ def test_fee_payout_checkpoint_migration_preserves_existing_row(
|
||||
assert row == (5000, 1000, 123, 0, None)
|
||||
|
||||
|
||||
def test_fee_payout_checkpoint_repair_restores_columns_missing_at_old_head(
|
||||
@pytest.mark.parametrize("replaced_revision", ["21c84cd5ad83", "11eaab843b49"])
|
||||
def test_branch_update_repairs_database_stamped_with_replaced_revision(
|
||||
tmp_path: Path,
|
||||
replaced_revision: str,
|
||||
) -> None:
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
database_path = tmp_path / "replaced-revision.db"
|
||||
database_url = f"sqlite+aiosqlite:///{database_path}"
|
||||
_run_alembic(root, database_url, "c6d7e8f9a0b1")
|
||||
|
||||
with sqlite3.connect(database_path) as connection:
|
||||
connection.execute("ALTER TABLE lightning_invoices ADD COLUMN mint_url VARCHAR")
|
||||
connection.execute(
|
||||
"UPDATE alembic_version SET version_num = ?",
|
||||
(replaced_revision,),
|
||||
)
|
||||
connection.commit()
|
||||
|
||||
_run_routstr_startup_migrations(root, database_url)
|
||||
|
||||
with sqlite3.connect(database_path) as connection:
|
||||
version = connection.execute(
|
||||
"SELECT version_num FROM alembic_version"
|
||||
).fetchone()
|
||||
columns = {
|
||||
row[1] for row in connection.execute("PRAGMA table_info(routstr_fees)")
|
||||
}
|
||||
|
||||
assert version == ("d8e6f974960a",)
|
||||
assert {"payout_in_progress_msats", "payout_started_at"} <= columns
|
||||
|
||||
|
||||
def test_fee_payout_checkpoint_repair_restores_columns_missing_at_previous_head(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
database_path = tmp_path / "migration.db"
|
||||
database_url = f"sqlite+aiosqlite:///{database_path}"
|
||||
old_head = "7f2843d3f4e4"
|
||||
_run_alembic(root, database_url, old_head)
|
||||
previous_head = "c7d5f8638599"
|
||||
_run_alembic(root, database_url, previous_head)
|
||||
|
||||
# Reproduce a database that was stamped to head after a duplicate-column or
|
||||
# unknown-revision recovery skipped part of the migration chain.
|
||||
|
||||
Reference in New Issue
Block a user