Esempio n. 1
0
async def run_migrations(conn) -> None:
    result = await _apply_migrations(conn,
                                     retrieve_migrations(),
                                     dry_run=False)
    if result.error:
        migration, msg = result.error
        raise RuntimeError(
            f"Error while applying migration {migration.file_name}: {msg}")
Esempio n. 2
0
async def _trio_test_migration(postgresql_url, pg_dump, psql):
    async def reset_db_schema():
        # Now drop the database clean...
        async with triopg.connect(postgresql_url) as conn:
            rep = await conn.execute("DROP SCHEMA public CASCADE")
            assert rep == "DROP SCHEMA"
            rep = await conn.execute("CREATE SCHEMA public")
            assert rep == "CREATE SCHEMA"

    async def dump_schema() -> str:
        cmd = [pg_dump, "--schema=public", "--schema-only", postgresql_url]
        print(f"run: {' '.join(cmd)}")
        process = await trio.run_process(cmd, capture_stdout=True)
        return process.stdout.decode()

    async def dump_data() -> str:
        cmd = [pg_dump, "--schema=public", "--data-only", postgresql_url]
        print(f"run: {' '.join(cmd)}")
        process = await trio.run_process(cmd, capture_stdout=True)
        return process.stdout.decode()

    async def restore_data(data: str) -> None:
        cmd = [
            psql, "--no-psqlrc", "--set", "ON_ERROR_STOP=on", postgresql_url
        ]
        print(f"run: {' '.join(cmd)}")
        process = await trio.run_process(cmd,
                                         capture_stdout=True,
                                         stdin=data.encode())
        return process.stdout.decode()

    # The schema may start with an automatic comment, something like:
    # `COMMENT ON SCHEMA public IS 'standard public schema';`
    # So we clean everything first
    await reset_db_schema()

    # Now we apply migrations one after another and also insert the provided data
    migrations = retrieve_migrations()
    patches = collect_data_patches()
    async with triopg.connect(postgresql_url) as conn:
        for migration in migrations:
            result = await apply_migrations(postgresql_url, [migration],
                                            dry_run=False)
            assert not result.error
            patch_sql = patches.get(migration.idx, "")
            if patch_sql:
                await conn.execute(patch_sql)

    # Save the final state of the database schema
    schema_from_migrations = await dump_schema()

    # Also save the final data
    data_from_migrations = await dump_data()

    # Now drop the database clean...
    await reset_db_schema()

    # ...and reinitialize it with the current datamodel script
    sql = importlib_resources.files(migrations_module).joinpath(
        "datamodel.sql").read_text()
    async with triopg.connect(postgresql_url) as conn:
        await conn.execute(sql)

    # The resulting database schema should be equivalent to what we add after
    # all the migrations
    schema_from_init = await dump_schema()
    assert schema_from_init == schema_from_migrations

    # Final check is to re-import all the data, this requires some cooking first:

    # Remove the migration related data given their should already be in the database
    data_from_migrations = re.sub(r"COPY public.migration[^\\]*\\.",
                                  "",
                                  data_from_migrations,
                                  flags=re.DOTALL)
    # Modify the foreign key constraint between `user_` and `device` to be
    # checked at the end of the transaction. This is needed because `user_`
    # table is entirely populated before switching to `device`. So the
    # constraint should break as soon as an `user_` row references a device_id.
    data_from_migrations = f"""
ALTER TABLE public."user_" ALTER CONSTRAINT fk_user_device_user_certifier DEFERRABLE;
ALTER TABLE public."user_" ALTER CONSTRAINT fk_user_device_revoked_user_certifier DEFERRABLE;

BEGIN;

SET CONSTRAINTS fk_user_device_user_certifier DEFERRED;
SET CONSTRAINTS fk_user_device_revoked_user_certifier DEFERRED;

{data_from_migrations}

COMMIT;
"""

    await restore_data(data_from_migrations)