mirror of https://gitlab.com/litecord/litecord.git
manage.cmd.migration: add rudimentary implementation
Also add table changes for future message embeds.
This commit is contained in:
parent
c3210bf5b0
commit
db9fd783f5
|
|
@ -1,10 +1,134 @@
|
||||||
async def migrate_cmd(app, args):
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from collections import namedtuple
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import asyncpg
|
||||||
|
from logbook import Logger
|
||||||
|
|
||||||
|
log = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
Migration = namedtuple('Migration', 'id name path')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MigrationContext:
|
||||||
|
"""Hold information about migration."""
|
||||||
|
migration_folder: Path
|
||||||
|
scripts: Dict[int, Migration]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def latest(self):
|
||||||
|
"""Return the latest migration ID."""
|
||||||
|
return max(self.scripts.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def make_migration_ctx() -> MigrationContext:
|
||||||
|
"""Create the MigrationContext instance."""
|
||||||
|
# taken from https://stackoverflow.com/a/6628348
|
||||||
|
script_path = inspect.stack()[0][1]
|
||||||
|
script_folder = '/'.join(script_path.split('/')[:-1])
|
||||||
|
script_folder = Path(script_folder)
|
||||||
|
|
||||||
|
migration_folder = script_folder / 'scripts'
|
||||||
|
|
||||||
|
mctx = MigrationContext(migration_folder, {})
|
||||||
|
|
||||||
|
for mig_path in migration_folder.glob('*.sql'):
|
||||||
|
mig_path_str = str(mig_path)
|
||||||
|
|
||||||
|
# extract migration script id and name
|
||||||
|
mig_filename = mig_path_str.split('/')[-1].split('.')[0]
|
||||||
|
name_fragments = mig_filename.split('_')
|
||||||
|
|
||||||
|
mig_id = int(name_fragments[0])
|
||||||
|
mig_name = '_'.join(name_fragments[1:])
|
||||||
|
|
||||||
|
mctx.scripts[mig_id] = Migration(
|
||||||
|
mig_id, mig_name, mig_path)
|
||||||
|
|
||||||
|
return mctx
|
||||||
|
|
||||||
|
|
||||||
|
async def _ensure_changelog(app, ctx):
|
||||||
|
# make sure we have the migration table up
|
||||||
|
|
||||||
|
try:
|
||||||
|
await app.db.execute("""
|
||||||
|
CREATE TABLE migration_log (
|
||||||
|
change_num bigint NOT NULL,
|
||||||
|
|
||||||
|
apply_ts timestamp without time zone default
|
||||||
|
(now() at time zone 'utc'),
|
||||||
|
|
||||||
|
description text,
|
||||||
|
|
||||||
|
PRIMARY KEY (change_num)
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
|
||||||
|
# if we were able to create the
|
||||||
|
# migration_log table, insert that we are
|
||||||
|
# on the latest version.
|
||||||
|
await app.db.execute("""
|
||||||
|
INSERT INTO migration_log (change_num, description)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
""", ctx.latest, 'migration setup')
|
||||||
|
except asyncpg.DuplicateTableError:
|
||||||
|
log.debug('existing migration table')
|
||||||
|
|
||||||
|
|
||||||
|
async def apply_migration(app, migration: Migration):
|
||||||
|
"""Apply a single migration."""
|
||||||
|
migration_sql = migration.path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
await app.db.execute("""
|
||||||
|
INSERT INTO migration_log (change_num, description)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
""", migration.id, f'migration: {migration.name}')
|
||||||
|
except asyncpg.UniqueViolationError:
|
||||||
|
log.warning('already applied {}', migration.id)
|
||||||
|
return
|
||||||
|
|
||||||
|
await app.db.execute(migration_sql)
|
||||||
|
log.info('applied {}', migration.id)
|
||||||
|
|
||||||
|
|
||||||
|
async def migrate_cmd(app, _args):
|
||||||
"""Main migration command.
|
"""Main migration command.
|
||||||
|
|
||||||
This makes sure the database
|
This makes sure the database
|
||||||
is updated.
|
is updated.
|
||||||
"""
|
"""
|
||||||
print('not implemented yet')
|
|
||||||
|
ctx = make_migration_ctx()
|
||||||
|
|
||||||
|
await _ensure_changelog(app, ctx)
|
||||||
|
|
||||||
|
# local point in the changelog
|
||||||
|
local_change = await app.db.fetchval("""
|
||||||
|
SELECT max(change_num)
|
||||||
|
FROM migration_log
|
||||||
|
""")
|
||||||
|
|
||||||
|
local_change = local_change or 0
|
||||||
|
latest_change = ctx.latest
|
||||||
|
|
||||||
|
if local_change == latest_change:
|
||||||
|
print('no changes to do, exiting')
|
||||||
|
return
|
||||||
|
|
||||||
|
# we do local_change + 1 so we start from the
|
||||||
|
# next migration to do, end in latest_change + 1
|
||||||
|
# because of how range() works.
|
||||||
|
for idx in range(local_change + 1, latest_change + 1):
|
||||||
|
migration = ctx.scripts.get(idx)
|
||||||
|
|
||||||
|
print('applying', migration.id, migration.name)
|
||||||
|
await apply_migration(app, migration)
|
||||||
|
|
||||||
|
|
||||||
def setup(subparser):
|
def setup(subparser):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
-- unused tables
|
||||||
|
DROP TABLE message_embeds;
|
||||||
|
DROP TABLE embeds;
|
||||||
|
|
||||||
|
ALTER TABLE messages
|
||||||
|
ADD COLUMN embeds jsonb DEFAULT '[]'
|
||||||
13
schema.sql
13
schema.sql
|
|
@ -486,11 +486,6 @@ CREATE TABLE IF NOT EXISTS bans (
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS embeds (
|
|
||||||
-- TODO: this table
|
|
||||||
id bigint PRIMARY KEY
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS messages (
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
id bigint PRIMARY KEY,
|
id bigint PRIMARY KEY,
|
||||||
channel_id bigint REFERENCES channels (id) ON DELETE CASCADE,
|
channel_id bigint REFERENCES channels (id) ON DELETE CASCADE,
|
||||||
|
|
@ -511,6 +506,8 @@ CREATE TABLE IF NOT EXISTS messages (
|
||||||
tts bool default false,
|
tts bool default false,
|
||||||
mention_everyone bool default false,
|
mention_everyone bool default false,
|
||||||
|
|
||||||
|
embeds jsonb DEFAULT '[]',
|
||||||
|
|
||||||
nonce bigint default 0,
|
nonce bigint default 0,
|
||||||
|
|
||||||
message_type int NOT NULL
|
message_type int NOT NULL
|
||||||
|
|
@ -522,12 +519,6 @@ CREATE TABLE IF NOT EXISTS message_attachments (
|
||||||
PRIMARY KEY (message_id, attachment)
|
PRIMARY KEY (message_id, attachment)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS message_embeds (
|
|
||||||
message_id bigint REFERENCES messages (id) UNIQUE,
|
|
||||||
embed_id bigint REFERENCES embeds (id),
|
|
||||||
PRIMARY KEY (message_id, embed_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS message_reactions (
|
CREATE TABLE IF NOT EXISTS message_reactions (
|
||||||
message_id bigint REFERENCES messages (id),
|
message_id bigint REFERENCES messages (id),
|
||||||
user_id bigint REFERENCES users (id),
|
user_id bigint REFERENCES users (id),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue