From db2ea4dd1c77263cdb81852f241c704de46cfca3 Mon Sep 17 00:00:00 2001 From: Chris Hundt Date: Tue, 14 Jul 2026 10:35:51 -0400 Subject: [PATCH 1/3] [CMCSMACD-6648] Allow for a setup function in SchemaTest This allows callers to set up preerquisite data or tables that are needed for the migrations without running afoul of the empty- database check at the beginning of SchemaTest. --- cmd/schema-test/main.go | 32 ++++++++++++++++++++++++-- cmd/schema-test/migrations.go | 26 ++++++++++++++++++++++ migrations/verify.go | 42 ++++++++++++++++++++++------------- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/cmd/schema-test/main.go b/cmd/schema-test/main.go index 7f35858..67b9f6f 100644 --- a/cmd/schema-test/main.go +++ b/cmd/schema-test/main.go @@ -1,11 +1,13 @@ package main import ( + "context" "fmt" "log" "os" "github.com/corbaltcode/go-libraries/migrations" + "github.com/corbaltcode/go-libraries/pgutils" _ "github.com/lib/pq" ) @@ -22,11 +24,37 @@ func main() { os.Exit(1) } - err := migrations.SchemaTest(&cfg, allMigrations) + commonSchema := "common" + + postgresConnectionString := fmt.Sprintf( + "postgres://%s:%s@%s:%s/%s?sslmode=disable", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database) + connectionStringProvider, err := pgutils.NewConnectionStringProviderFromURLString(context.Background(), postgresConnectionString) + if err != nil { + log.Fatalf("NewConnectionStringProviderFromURLString: %v", err) + } + dbWithSearchPath, err := pgutils.ConnectDB(pgutils.ToConnector(pgutils.WithSchemaSearchPath(connectionStringProvider, commonSchema))) + if err != nil { + log.Fatalf("ConnectDB: %v", err) + } + + commonSetup := func() error { + err = migrations.EnsureSchema(dbWithSearchPath, commonSchema) + if err != nil { + return fmt.Errorf("EnsureSchema: %v", err) + } + return migrations.Migrate(dbWithSearchPath, commonMigrations) + } + + err = migrations.SchemaTestWithSetup(&cfg, allMigrations, commonSetup) if err != nil { log.Fatalf("First schema test failed: %s", err) } - err = migrations.SchemaTest(&cfg, allMigrations) + + // Database must be empty before calling SchemaTest a second time. + dbWithSearchPath.MustExec(fmt.Sprintf("DROP SCHEMA %s CASCADE", commonSchema)) + + err = migrations.SchemaTestWithSetup(&cfg, allMigrations, commonSetup) if err != nil { log.Fatalf("Second schema test failed: %s", err) } diff --git a/cmd/schema-test/migrations.go b/cmd/schema-test/migrations.go index 882b343..57440d8 100644 --- a/cmd/schema-test/migrations.go +++ b/cmd/schema-test/migrations.go @@ -2,6 +2,20 @@ package main import "github.com/corbaltcode/go-libraries/migrations" +var commonMigrations = []migrations.NamedMigration{ + { + Name: "Create a common table", + Migration: migrations.StaticMigration([]string{ + `CREATE TABLE common ( + id serial PRIMARY KEY + )`, + }), + Reverse: migrations.StaticMigration([]string{ + `DROP TABLE common`, + }), + }, +} + var allMigrations = []migrations.NamedMigration{ { Name: "Create a table", @@ -77,4 +91,16 @@ var allMigrations = []migrations.NamedMigration{ `DROP VIEW v`, }), }, + { + Name: "Create a table referencing a common table", + Migration: migrations.StaticMigration([]string{ + `CREATE TABLE dependent ( + id serial PRIMARY KEY, + common_id integer REFERENCES common.common(id) + )`, + }), + Reverse: migrations.StaticMigration([]string{ + `DROP TABLE dependent`, + }), + }, } diff --git a/migrations/verify.go b/migrations/verify.go index f7985e1..3aa2f19 100644 --- a/migrations/verify.go +++ b/migrations/verify.go @@ -107,22 +107,9 @@ func migrateAndRollback(emptyDBConfig *PostgresConfig, db *sqlx.DB, allMigration return err } -// Schema test expects a new *empty* postgres database. -// It will: -// 1. Apply all migrations -// 2. Reverse all migrations -// 3. For each migration: -// a. Apply the migration -// b. Reverse the migration -// c. Apply the migration again -// -// Before and after each step it will use pg_dump to dump the database schema. -// It will verify that: -// A. The schema is the same after reversing as before applying. -// B. (If re-applying) The schema is the same after applying as after re-applying. -// -// You must have `pg_dump` in your `PATH` to run this. -func SchemaTest(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration) error { +// Does a SchemaTest but calls the provided setup function after verifying that the +// database is empty. +func SchemaTestWithSetup(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration, setup func() error) error { for _, v := range []string{ emptyDBConfig.Host, emptyDBConfig.Port, @@ -145,6 +132,10 @@ func SchemaTest(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration) e if err != nil { return err } + err = setup() + if err != nil { + return fmt.Errorf("setup: %s", err) + } err = Migrate(db, []NamedMigration{}) if err != nil { return fmt.Errorf("Setting up migrations table failed: %s", err) @@ -170,3 +161,22 @@ func SchemaTest(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration) e } return nil } + +// Schema test expects a new *empty* postgres database. +// It will: +// 1. Apply all migrations +// 2. Reverse all migrations +// 3. For each migration: +// a. Apply the migration +// b. Reverse the migration +// c. Apply the migration again +// +// Before and after each step it will use pg_dump to dump the database schema. +// It will verify that: +// A. The schema is the same after reversing as before applying. +// B. (If re-applying) The schema is the same after applying as after re-applying. +// +// You must have `pg_dump` in your `PATH` to run this. +func SchemaTest(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration) error { + return SchemaTestWithSetup(emptyDBConfig, allMigrations, func() error { return nil }) +} From 8856b8c4b18738b7c1bac7c273ae8d571c4c5c6d Mon Sep 17 00:00:00 2001 From: Chris Hundt Date: Thu, 16 Jul 2026 12:37:28 -0400 Subject: [PATCH 2/3] Address review comments --- migrations/verify.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/migrations/verify.go b/migrations/verify.go index 3aa2f19..eb6522f 100644 --- a/migrations/verify.go +++ b/migrations/verify.go @@ -45,7 +45,7 @@ func dump(c *PostgresConfig) ([]byte, error) { return out, err } -func verifyNoTables(db *sqlx.DB) error { +func verifyNoRelations(db *sqlx.DB) error { // Based on the query run for the "\d" command in psql // (as revealed when started with -E flag). q := `SELECT 1 FROM pg_catalog.pg_class c @@ -60,9 +60,9 @@ func verifyNoTables(db *sqlx.DB) error { // Expected return nil } else if err != nil { - return fmt.Errorf("Error checking for existing tables: %w", err) + return fmt.Errorf("Error checking for existing relations: %w", err) } - return errors.New("Existing tables found. You must run SchemaTest on an empty database.") + return errors.New("Existing relations found. You must run this on an empty database.") } func migrateAndRollback(emptyDBConfig *PostgresConfig, db *sqlx.DB, allMigrations []NamedMigration, migrateToIndex, rollbackThroughIndex int, repeatForward bool) error { @@ -128,7 +128,7 @@ func SchemaTestWithSetup(emptyDBConfig *PostgresConfig, allMigrations []NamedMig if err != nil { return fmt.Errorf("Error connecting: %s", err) } - err = verifyNoTables(db) + err = verifyNoRelations(db) if err != nil { return err } From e605b31986fdfbfa594a4493ec08c33f048b614d Mon Sep 17 00:00:00 2001 From: Chris Hundt Date: Thu, 16 Jul 2026 12:52:50 -0400 Subject: [PATCH 3/3] Adjust comment --- migrations/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/verify.go b/migrations/verify.go index eb6522f..3249979 100644 --- a/migrations/verify.go +++ b/migrations/verify.go @@ -107,7 +107,7 @@ func migrateAndRollback(emptyDBConfig *PostgresConfig, db *sqlx.DB, allMigration return err } -// Does a SchemaTest but calls the provided setup function after verifying that the +// Does the same thing as SchemaTest but calls the provided setup function after verifying that the // database is empty. func SchemaTestWithSetup(emptyDBConfig *PostgresConfig, allMigrations []NamedMigration, setup func() error) error { for _, v := range []string{