Skip to content

Commit

Permalink
Modify migration 005 to tolerate the absence of river_migration
Browse files Browse the repository at this point in the history
Version 0.10.0's migration (005) brought in a number of changes
including one that adds a new `line` field to the migration table.

It works, but could present a problem for River installations that are
purposely not using River's migration system, which is a path that even
if not explicitly recommended, we've been trying to support.

Here, add some conditionals to 005 so that the migration-related changes
only run if `river_migration` exists. A test verifies that even if 001
(which brings in `river_migration`) had never run, the up and own
migrations still work.
  • Loading branch information
brandur committed Jul 24, 2024
1 parent 2ae1b6c commit 2bd397b
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 80 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Migration version 005 has been altered so that it can run even if the `river_migration` table isn't present, making it more friendly for projects that aren't using River's internal migration system. [PR #465](https://github.com/riverqueue/river/pull/465).

## [0.10.0] - 2024-07-19

⚠️ Version 0.10.0 contains a new database migration, version 5. See [documentation on running River migrations](https://riverqueue.com/docs/migrations). If migrating with the CLI, make sure to update it to its latest version:
Expand All @@ -16,6 +20,14 @@ go install github.com/riverqueue/river/cmd/river@latest
river migrate-up --database-url "$DATABASE_URL"
```

If not using River's internal migration system, the raw SQL can alternatively be dumped with:

```shell
go install github.com/riverqueue/river/cmd/river@latest
river migrate-get --version 5 --up > river5.up.sql
river migrate-get --version 5 --down > river5.down.sql
```

The migration **includes a new index**. Users with a very large job table may want to consider raising the index separately using `CONCURRENTLY` (which must be run outside of a transaction), then run `river migrate-up` to finalize the process (it will tolerate an index that already exists):

```sql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,41 @@
DO
$body$
BEGIN
IF EXISTS (
SELECT *
FROM river_migration
WHERE line <> 'main'
) THEN
RAISE EXCEPTION 'Found non-main migration lines in the database; version 005 migration is irreversible because it would result in loss of migration information.';
-- Tolerate users who may be using their own migration system rather than
-- River's. If they are, they will have skipped version 001 containing
-- `CREATE TABLE river_migration`, so this table won't exist.
IF (SELECT to_regclass('river_migration') IS NOT NULL) THEN
IF EXISTS (
SELECT *
FROM river_migration
WHERE line <> 'main'
) THEN
RAISE EXCEPTION 'Found non-main migration lines in the database; version 005 migration is irreversible because it would result in loss of migration information.';
END IF;

ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
id bigserial PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT NOW(),
version bigint NOT NULL,
CONSTRAINT version CHECK (version >= 1)
);

CREATE UNIQUE INDEX ON river_migration USING btree(version);

INSERT INTO river_migration
(created_at, version)
SELECT created_at, version
FROM river_migration_old;

DROP TABLE river_migration_old;
END IF;
END;
$body$
LANGUAGE 'plpgsql';

ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
id bigserial PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT NOW(),
version bigint NOT NULL,
CONSTRAINT version CHECK (version >= 1)
);

CREATE UNIQUE INDEX ON river_migration USING btree(version);

INSERT INTO river_migration
(created_at, version)
SELECT created_at, version
FROM river_migration_old;

DROP TABLE river_migration_old;

--
-- Drop `river_job.unique_key`.
--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@
-- Rebuild the migration table so it's based on `(line, version)`.
--

ALTER TABLE river_migration
RENAME TO river_migration_old;
DO
$body$
BEGIN
-- Tolerate users who may be using their own migration system rather than
-- River's. If they are, they will have skipped version 001 containing
-- `CREATE TABLE river_migration`, so this table won't exist.
IF (SELECT to_regclass('river_migration') IS NOT NULL) THEN
ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
line TEXT NOT NULL,
version bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
CONSTRAINT line_length CHECK (char_length(line) > 0 AND char_length(line) < 128),
CONSTRAINT version_gte_1 CHECK (version >= 1),
PRIMARY KEY (line, version)
);
CREATE TABLE river_migration(
line TEXT NOT NULL,
version bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
CONSTRAINT line_length CHECK (char_length(line) > 0 AND char_length(line) < 128),
CONSTRAINT version_gte_1 CHECK (version >= 1),
PRIMARY KEY (line, version)
);

INSERT INTO river_migration
(created_at, line, version)
SELECT created_at, 'main', version
FROM river_migration_old;
INSERT INTO river_migration
(created_at, line, version)
SELECT created_at, 'main', version
FROM river_migration_old;

DROP TABLE river_migration_old;
DROP TABLE river_migration_old;
END IF;
END;
$body$
LANGUAGE 'plpgsql';

--
-- Add `river_job.unique_key` and bring up an index on it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,41 @@
DO
$body$
BEGIN
IF EXISTS (
SELECT *
FROM river_migration
WHERE line <> 'main'
) THEN
RAISE EXCEPTION 'Found non-main migration lines in the database; version 005 migration is irreversible because it would result in loss of migration information.';
-- Tolerate users who may be using their own migration system rather than
-- River's. If they are, they will have skipped version 001 containing
-- `CREATE TABLE river_migration`, so this table won't exist.
IF (SELECT to_regclass('river_migration') IS NOT NULL) THEN
IF EXISTS (
SELECT *
FROM river_migration
WHERE line <> 'main'
) THEN
RAISE EXCEPTION 'Found non-main migration lines in the database; version 005 migration is irreversible because it would result in loss of migration information.';
END IF;

ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
id bigserial PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT NOW(),
version bigint NOT NULL,
CONSTRAINT version CHECK (version >= 1)
);

CREATE UNIQUE INDEX ON river_migration USING btree(version);

INSERT INTO river_migration
(created_at, version)
SELECT created_at, version
FROM river_migration_old;

DROP TABLE river_migration_old;
END IF;
END;
$body$
LANGUAGE 'plpgsql';

ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
id bigserial PRIMARY KEY,
created_at timestamptz NOT NULL DEFAULT NOW(),
version bigint NOT NULL,
CONSTRAINT version CHECK (version >= 1)
);

CREATE UNIQUE INDEX ON river_migration USING btree(version);

INSERT INTO river_migration
(created_at, version)
SELECT created_at, version
FROM river_migration_old;

DROP TABLE river_migration_old;

--
-- Drop `river_job.unique_key`.
--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@
-- Rebuild the migration table so it's based on `(line, version)`.
--

ALTER TABLE river_migration
RENAME TO river_migration_old;
DO
$body$
BEGIN
-- Tolerate users who may be using their own migration system rather than
-- River's. If they are, they will have skipped version 001 containing
-- `CREATE TABLE river_migration`, so this table won't exist.
IF (SELECT to_regclass('river_migration') IS NOT NULL) THEN
ALTER TABLE river_migration
RENAME TO river_migration_old;

CREATE TABLE river_migration(
line TEXT NOT NULL,
version bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
CONSTRAINT line_length CHECK (char_length(line) > 0 AND char_length(line) < 128),
CONSTRAINT version_gte_1 CHECK (version >= 1),
PRIMARY KEY (line, version)
);
CREATE TABLE river_migration(
line TEXT NOT NULL,
version bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
CONSTRAINT line_length CHECK (char_length(line) > 0 AND char_length(line) < 128),
CONSTRAINT version_gte_1 CHECK (version >= 1),
PRIMARY KEY (line, version)
);

INSERT INTO river_migration
(created_at, line, version)
SELECT created_at, 'main', version
FROM river_migration_old;
INSERT INTO river_migration
(created_at, line, version)
SELECT created_at, 'main', version
FROM river_migration_old;

DROP TABLE river_migration_old;
DROP TABLE river_migration_old;
END IF;
END;
$body$
LANGUAGE 'plpgsql';

--
-- Add `river_job.unique_key` and bring up an index on it.
Expand Down
36 changes: 36 additions & 0 deletions rivermigrate/river_migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,42 @@ func TestMigrator(t *testing.T) {
_, err = alternateMigrator.MigrateTx(ctx, bundle.tx, DirectionUp, &MigrateOpts{})
require.EqualError(t, err, "can't add a non-main migration line until `river_migration` is raised; fully migrate the main migration line and try again")
})

// Demonstrates that even when not using River's internal migration system,
// version 005 is still able to run.
t.Run("Version005ToleratesRiverMigrateNotPresent", func(t *testing.T) {
t.Parallel()

migrator, bundle := setup(t)

// The migration version in which `line` is added to `river_migration`.
// This is special because it's the first time the table's changed since
// version 001.
const migrateVersionTarget = 5

// Migrate down to version 004.
for i := migrationsBundle.MaxVersion; i > migrateVersionTarget-1; i-- {
res, err := migrator.MigrateTx(ctx, bundle.tx, DirectionDown, &MigrateOpts{})
require.NoError(t, err)
require.Equal(t, DirectionDown, res.Direction)
require.Equal(t, []int{i}, sliceutil.Map(res.Versions, migrateVersionToInt))
}

// Drop `river_migration` table as if version 001 had never originally run.
_, err := bundle.tx.Exec(ctx, "DROP TABLE river_migration")
require.NoError(t, err)

// Run version 005 to make sure it can tolerate the absence of
// `river_migration`. Note that we have to run the version's SQL
// directly because using the migrator will try to interact with
// `river_migration`, which is no longer present.
_, err = bundle.tx.Exec(ctx, migrationsBundle.WithTestVersionsMap[5].SQLUp)
require.NoError(t, err)

// And the version 005 down migration to verify the same.
_, err = bundle.tx.Exec(ctx, migrationsBundle.WithTestVersionsMap[5].SQLDown)
require.NoError(t, err)
})
}

// This test uses a custom set of test-only migration files on the file system
Expand Down

0 comments on commit 2bd397b

Please sign in to comment.