Skip to content

Commit

Permalink
first prototype of migration script (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
dionhaefner committed Aug 27, 2022
1 parent 9c551bb commit 3ec39e9
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 0 deletions.
17 changes: 17 additions & 0 deletions terracotta/migrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""migrations/__init__.py
Define available migrations.
"""

import os
import glob
import importlib


MIGRATIONS = {}

for modname in glob.glob("v*_*.py", root_dir=os.path.dirname(__file__)):
mod = importlib.import_module(f"{__name__}.{modname[:-3]}")
assert all(hasattr(mod, attr) for attr in ("up_version", "down_version", "upgrade_sql", "downgrade_sql"))
assert mod.down_version not in MIGRATIONS
MIGRATIONS[mod.down_version] = mod
13 changes: 13 additions & 0 deletions terracotta/migrations/v0_8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@


up_version = (0, 8)
down_version = (0, 7)

upgrade_sql = [
# "CREATE TABLE",
# "ALTER TABLE keys ADD COLUMN index INTEGER UNIQUE",
# "ALTER TABLE keys RENAME TO key_names",
"UPDATE terracotta SET version='v0.8.0'",
]

downgrade_sql = []
2 changes: 2 additions & 0 deletions terracotta/scripts/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def entrypoint() -> None:
from terracotta.scripts.serve import serve
cli.add_command(serve)

from terracotta.scripts.migrate import migrate
cli.add_command(migrate)

if __name__ == '__main__':
entrypoint()
72 changes: 72 additions & 0 deletions terracotta/scripts/migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""scripts/migrate.py
Migrate databases between Terracotta versions.
"""

from typing import Tuple

import click
import sqlalchemy as sqla

from terracotta import get_driver, __version__
from terracotta.migrations import MIGRATIONS


def parse_version(verstr: str) -> Tuple[int]:
"""Convert 'v<major>.<minor>.<patch>' to (major, minor, patch)"""
components = verstr.split(".")
components[0] = components[0].lstrip("v")
return tuple(int(c) for c in components[:3])


def join_version(vertuple: Tuple[int]) -> str:
return "v" + ".".join(map(str, vertuple))


@click.argument("DATABASE", required=True)
@click.option("--from", "from_version", required=False, default=None)
@click.option("--to", "to_version", required=False, default=__version__)
@click.command("migrate")
def migrate(database: str, to_version: str, from_version: str):
from_version, to_version, tc_version = (parse_version(v)[:2] if v else None for v in (from_version, to_version, __version__))

driver = get_driver(database)

# if to_version > tc_version:
# raise ValueError(f"Unknown target version {join_version(to_version)} (this is {join_version(tc_version)}). Try upgrading terracotta.")

if from_version is None:
try:
with driver.connect(verify=False):
from_version = parse_version(driver.db_version)[:2]
except:
raise RuntimeError("Cannot determine database version.")

migration_chain = []
current_version = from_version

while current_version != to_version:
if current_version not in MIGRATIONS:
raise RuntimeError("Unexpected error")

migration = MIGRATIONS[current_version]
migration_chain.append(migration)
current_version = migration.up_version

click.echo("Upgrade path found\n")

for migration in migration_chain:
click.echo(f"{join_version(migration.down_version)} -> {join_version(migration.up_version)}")

for cmd in migration.upgrade_sql:
click.echo(f" {cmd}")

click.echo("")

click.echo(f"This will upgrade the database from {join_version(from_version)} -> {join_version(to_version)} and execute the above SQL commands.")
click.confirm("Continue?", abort=True)

with driver.connect(verify=False):
for migration in migration_chain:
for cmd in migration.upgrade_sql:
driver.meta_store.connection.execute(sqla.text(cmd))

0 comments on commit 3ec39e9

Please sign in to comment.