This refactors the schema package and subpackages to support editions.
An Edition is a collection of sql migrations. A given boundary binary
can have multiple migration editions. The `schema.Manager` is updated to
apply the necessary migrations in the correct order, leveraging an
Edition's priority to determine the order in which the separate Editions
are applied.
The sql migration files are now nested under a directory for the
edition.
This also switches from the generated code to using embed, which
eliminates the need for the `make migrations` target and the generated
code. As such this commit removes the target and generated code.
See:
https://pkg.go.dev/embed
204 lines
5.6 KiB
Go
204 lines
5.6 KiB
Go
package postgres_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/boundary/internal/db/schema/internal/log"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestEnsureMigrationLog(t *testing.T) {
|
|
ctx := context.Background()
|
|
p, _, _ := setup(ctx, t)
|
|
|
|
err := p.StartRun(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.EnsureVersionTable(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.EnsureMigrationLogTable(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.CommitRun(ctx)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestEnsureMigrationLog_NoTxn(t *testing.T) {
|
|
ctx := context.Background()
|
|
p, _, _ := setup(ctx, t)
|
|
err := p.EnsureMigrationLogTable(ctx)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// Test that a database that was created with a log_migration table
|
|
// prior to edition support, can be updated to support editions,
|
|
// and any existing log entries can be updated.
|
|
//
|
|
// The scenario involves a case where:
|
|
// - The database was initialized with the original boundary_schema_version table
|
|
// - The original migrations where run, creating the log_migration table and some entries in the table.
|
|
func TestEnsureMigrationLog_UpdateForEditionSupport(t *testing.T) {
|
|
ctx := context.Background()
|
|
p, db, _ := setup(ctx, t)
|
|
|
|
statements := []string{
|
|
// original boundary_schema_version table
|
|
`create table boundary_schema_version (
|
|
version bigint primary key,
|
|
dirty boolean not null);`,
|
|
// populate the table with a version
|
|
`insert into boundary_schema_version
|
|
(version, dirty)
|
|
values
|
|
(1001, false);`,
|
|
// need to create some things from other migrations that the original tables used
|
|
`create domain wt_timestamp as
|
|
timestamp with time zone
|
|
default current_timestamp;
|
|
|
|
create or replace function
|
|
default_create_time()
|
|
returns trigger
|
|
as $$
|
|
begin
|
|
if new.create_time is distinct from now() then
|
|
raise warning 'create_time cannot be set to %', new.create_time;
|
|
new.create_time = now();
|
|
end if;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
|
|
create or replace function
|
|
immutable_columns()
|
|
returns trigger
|
|
as $$
|
|
declare
|
|
col_name text;
|
|
new_value text;
|
|
old_value text;
|
|
begin
|
|
foreach col_name in array tg_argv loop
|
|
execute format('SELECT $1.%I', col_name) into new_value using new;
|
|
execute format('SELECT $1.%I', col_name) into old_value using old;
|
|
if new_value is distinct from old_value then
|
|
raise exception 'immutable column: %.%', tg_table_name, col_name using
|
|
errcode = '23601',
|
|
schema = tg_table_schema,
|
|
table = tg_table_name,
|
|
column = col_name;
|
|
end if;
|
|
end loop;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
`,
|
|
// create original log_migration table, triggers
|
|
`create table log_migration(
|
|
id bigint generated always as identity primary key,
|
|
migration_version bigint not null, -- cannot declare FK since the table is truncated during runtime
|
|
create_time wt_timestamp,
|
|
entry text not null
|
|
);
|
|
comment on table log_migration is
|
|
'log_migration entries are logging output from databaes migrations';
|
|
|
|
create trigger
|
|
default_create_time_column
|
|
before
|
|
insert on log_migration
|
|
for each row execute procedure default_create_time();
|
|
|
|
create trigger
|
|
immutable_columns
|
|
before
|
|
update on log_migration
|
|
for each row execute procedure immutable_columns('id', 'migration_version', 'create_time', 'entry');
|
|
|
|
create or replace function
|
|
log_migration_version()
|
|
returns trigger
|
|
as $$
|
|
declare current_version bigint;
|
|
begin
|
|
select max(version) from boundary_schema_version into current_version;
|
|
new.migration_version = current_version;
|
|
return new;
|
|
end;
|
|
$$ language plpgsql;
|
|
comment on function log_migration_version() is
|
|
'log_migration_version will set the log_migration entries to the current migration version';
|
|
|
|
create trigger
|
|
migration_version_column
|
|
before
|
|
insert on log_migration
|
|
for each row execute procedure log_migration_version();
|
|
`,
|
|
// insert some logs
|
|
`insert into log_migration (entry)
|
|
values
|
|
('log entry');`,
|
|
}
|
|
|
|
for _, s := range statements {
|
|
_, err := db.ExecContext(ctx, s)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
err := p.StartRun(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.EnsureVersionTable(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.EnsureMigrationLogTable(ctx)
|
|
require.NoError(t, err)
|
|
|
|
err = p.CommitRun(ctx)
|
|
require.NoError(t, err)
|
|
|
|
logs, err := p.GetMigrationLog(ctx, log.WithDeleteLog(true))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(logs))
|
|
assert.NotEqual(t, logs[0].Id, 0)
|
|
assert.False(t, logs[0].CreateTime.IsZero())
|
|
assert.Equal(t, "oss", logs[0].MigrationEdition)
|
|
assert.Equal(t, 1001, logs[0].MigrationVersion)
|
|
assert.Equal(t, "log entry", logs[0].Entry)
|
|
|
|
// now ensure the updated table and trigger still works
|
|
statements = []string{
|
|
// update the version, to make it easy to filter on log_migrations
|
|
`update boundary_schema_version set version = 1002;`,
|
|
// insert a new log entry
|
|
`insert into log_migration (entry, edition)
|
|
values
|
|
('new log entry', 'oss');`,
|
|
}
|
|
for _, s := range statements {
|
|
_, err := db.ExecContext(ctx, s)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
logs, err = p.GetMigrationLog(ctx, log.WithDeleteLog(true))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(logs))
|
|
assert.NotEqual(t, logs[0].Id, 0)
|
|
assert.False(t, logs[0].CreateTime.IsZero())
|
|
assert.Equal(t, "oss", logs[0].MigrationEdition)
|
|
assert.Equal(t, 1002, logs[0].MigrationVersion)
|
|
assert.Equal(t, "new log entry", logs[0].Entry)
|
|
|
|
// ensure it is idempotent
|
|
err = p.StartRun(ctx)
|
|
require.NoError(t, err)
|
|
err = p.EnsureMigrationLogTable(ctx)
|
|
require.NoError(t, err)
|
|
err = p.CommitRun(ctx)
|
|
require.NoError(t, err)
|
|
}
|