Skip to content

Commit

Permalink
replace migration system with db erasure
Browse files Browse the repository at this point in the history
  • Loading branch information
azuline committed Oct 11, 2023
1 parent 39fcc49 commit ce2452f
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 73 deletions.
15 changes: 9 additions & 6 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import hashlib
import logging
import sqlite3
from collections.abc import Iterator
from pathlib import Path

import _pytest.pathlib
import pytest
import yoyo
from click.testing import CliRunner

from rose.foundation.conf import MIGRATIONS_PATH, Config
from rose.foundation.conf import SCHEMA_PATH, Config

logger = logging.getLogger(__name__)

Expand All @@ -25,10 +25,13 @@ def config(isolated_dir: Path) -> Config:
cache_dir.mkdir()
cache_database_path = cache_dir / "cache.sqlite3"

db_backend = yoyo.get_backend(f"sqlite:///{cache_database_path}")
db_migrations = yoyo.read_migrations(str(MIGRATIONS_PATH))
with db_backend.lock():
db_backend.apply_migrations(db_backend.to_apply(db_migrations))
with sqlite3.connect(cache_database_path) as conn:
with SCHEMA_PATH.open("r") as fp:
conn.executescript(fp.read())
conn.execute("CREATE TABLE _schema_hash (value TEXT PRIMARY KEY)")
with SCHEMA_PATH.open("rb") as fp:
latest_schema_hash = hashlib.sha256(fp.read()).hexdigest()
conn.execute("INSERT INTO _schema_hash (value) VALUES (?)", (latest_schema_hash,))

return Config(
music_source_dir=isolated_dir / "source",
Expand Down
1 change: 0 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
fuse
mutagen
uuid6-python
yoyo-migrations
];
dev-deps = with python.pkgs; [
black
Expand Down
15 changes: 0 additions & 15 deletions migrations/20231009_01_qlEHa-bootstrap.rollback.sql

This file was deleted.

3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ strict = true
strict_optional = true
explicit_package_bases = true

[[tool.mypy.overrides]]
module = "yoyo"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "fuse"
ignore_missing_imports = true
Expand Down
40 changes: 32 additions & 8 deletions rose/cache/database.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import binascii
import hashlib
import logging
import random
import sqlite3
import time
from collections.abc import Iterator
from contextlib import contextmanager

import yoyo

from rose.foundation.conf import MIGRATIONS_PATH, Config
from rose.foundation.conf import SCHEMA_PATH, Config

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -72,9 +71,34 @@ def connect_fn(c: Config) -> sqlite3.Connection:


def migrate_database(c: Config) -> None:
db_backend = yoyo.get_backend(f"sqlite:///{c.cache_database_path}")
db_migrations = yoyo.read_migrations(str(MIGRATIONS_PATH))
"""
"Migrate" the database. If the schema in the database does not match that on disk, then nuke the
database and recreate it from scratch. Otherwise, no op.
logger.debug("Applying database migrations")
with db_backend.lock():
db_backend.apply_migrations(db_backend.to_apply(db_migrations))
We can do this because the database is just a read cache. It is not source-of-truth for any of
its own data.
"""
with SCHEMA_PATH.open("rb") as fp:
latest_schema_hash = hashlib.sha256(fp.read()).hexdigest()

with connect(c) as conn:
cursor = conn.execute(
"""
SELECT EXISTS(
SELECT * FROM sqlite_master
WHERE type = 'table' AND name = '_schema_hash'
)
"""
)
if cursor.fetchone()[0]:
cursor = conn.execute("SELECT value FROM _schema_hash")
if (row := cursor.fetchone()) and row[0] == latest_schema_hash:
# Everything matches! Exit!
return

c.cache_database_path.unlink(missing_ok=True)
with connect(c) as conn:
with SCHEMA_PATH.open("r") as fp:
conn.executescript(fp.read())
conn.execute("CREATE TABLE _schema_hash (value TEXT PRIMARY KEY)")
conn.execute("INSERT INTO _schema_hash (value) VALUES (?)", (latest_schema_hash,))
51 changes: 24 additions & 27 deletions rose/cache/database_test.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
import sqlite3
from pathlib import Path
import hashlib

import yoyo
from rose.cache.database import connect, migrate_database
from rose.foundation.conf import SCHEMA_PATH, Config

from conftest import freeze_database_time
from rose.cache.database import migrate_database
from rose.foundation.conf import MIGRATIONS_PATH, Config


def test_run_database_migrations(config: Config) -> None:
def test_schema(config: Config) -> None:
# Test that the schema successfully bootstraps.
with SCHEMA_PATH.open("rb") as fp:
latest_schema_hash = hashlib.sha256(fp.read()).hexdigest()
migrate_database(config)
assert config.cache_database_path.exists()

with sqlite3.connect(str(config.cache_database_path)) as conn:
freeze_database_time(conn)
cursor = conn.execute("SELECT 1 FROM _yoyo_version")
assert len(cursor.fetchall()) > 0
with connect(config) as conn:
cursor = conn.execute("SELECT value FROM _schema_hash")
assert cursor.fetchone()[0] == latest_schema_hash


def test_migrations(isolated_dir: Path) -> None:
"""
Test that, for each migration, the up -> down -> up path doesn't
cause an error. Basically, ladder our way up through the migration
chain.
"""
backend = yoyo.get_backend(f"sqlite:///{isolated_dir / 'db.sqlite3'}")
migrations = yoyo.read_migrations(str(MIGRATIONS_PATH))
def test_migration(config: Config) -> None:
# Test that "migrating" the database correctly migrates it.
config.cache_database_path.unlink()
with connect(config) as conn:
conn.execute("CREATE TABLE _schema_hash (value TEXT PRIMARY KEY)")
conn.execute("INSERT INTO _schema_hash (value) VALUES ('haha')")

assert len(migrations) > 0
for mig in migrations:
backend.apply_one(mig)
backend.rollback_one(mig)
backend.apply_one(mig)
with SCHEMA_PATH.open("rb") as fp:
latest_schema_hash = hashlib.sha256(fp.read()).hexdigest()
migrate_database(config)
with connect(config) as conn:
cursor = conn.execute("SELECT value FROM _schema_hash")
assert cursor.fetchone()[0] == latest_schema_hash
cursor = conn.execute("SELECT COUNT(*) FROM _schema_hash")
assert cursor.fetchone()[0] == 1
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
-- bootstrap
-- depends:

CREATE TABLE release_type_enum (value TEXT PRIMARY KEY);
INSERT INTO release_type_enum (value) VALUES
('album'),
Expand Down
2 changes: 1 addition & 1 deletion rose/foundation/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
XDG_CACHE_HOME = Path(os.environ.get("XDG_CACHE_HOME", os.environ["HOME"] + "/.cache"))
CACHE_PATH = XDG_CACHE_HOME / "rose"

MIGRATIONS_PATH = Path(__file__).resolve().parent.parent.parent / "migrations"
SCHEMA_PATH = Path(__file__).resolve().parent.parent / "cache" / "schema.sql"


class ConfigNotFoundError(RoseError):
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@
"fuse-python",
"mutagen",
"uuid6-python",
"yoyo-migrations",
],
)
8 changes: 0 additions & 8 deletions yoyo.ini

This file was deleted.

0 comments on commit ce2452f

Please sign in to comment.