diff --git a/doc/04-Upgrading.md b/doc/04-Upgrading.md index 74382e58f..8c97369a3 100644 --- a/doc/04-Upgrading.md +++ b/doc/04-Upgrading.md @@ -1,7 +1,10 @@ # Upgrading Icinga DB Specific version upgrades are described below. Please note that version upgrades are incremental. -If you are upgrading across multiple versions, make sure to follow the steps for each of them. + +If you are upgrading across multiple versions, make sure to follow the steps for each version in order of release. +For example, when upgrading from 1.1.0 to 1.2.0, +follow all instructions for upgrading to 1.1.1, then all for 1.2.0, including schema upgrades. ## Upgrading to Icinga DB v1.2.0 diff --git a/pkg/icingadb/schema.go b/pkg/icingadb/schema.go index 9aa7374e9..8eefc1df4 100644 --- a/pkg/icingadb/schema.go +++ b/pkg/icingadb/schema.go @@ -16,6 +16,11 @@ const ( ) // CheckSchema asserts the database schema of the expected version being present. +// +// Icinga DB uses incremental schema updates. Each schema version is identified by a continuous rising positive integer. +// With the initial schema import, the schema version of that time will be inserted in a row within the icingadb_schema +// table. Each subsequent schema update inserts another row with its version number. To have a consistent database +// schema, each schema update must be applied. NOTE: This might change in the future. func CheckSchema(ctx context.Context, db *database.DB) error { var expectedDbSchemaVersion uint16 switch db.DriverName() { @@ -25,13 +30,13 @@ func CheckSchema(ctx context.Context, db *database.DB) error { expectedDbSchemaVersion = expectedPostgresSchemaVersion } - var version uint16 + var versions []uint16 err := retry.WithBackoff( ctx, func(ctx context.Context) (err error) { - query := "SELECT version FROM icingadb_schema ORDER BY id DESC LIMIT 1" - err = db.QueryRowxContext(ctx, query).Scan(&version) + query := "SELECT version FROM icingadb_schema ORDER BY version ASC" + err = db.SelectContext(ctx, &versions, query) if err != nil { err = database.CantPerformQuery(err, query) } @@ -44,13 +49,29 @@ func CheckSchema(ctx context.Context, db *database.DB) error { return errors.Wrap(err, "can't check database schema version") } - if version != expectedDbSchemaVersion { + if len(versions) == 0 { + return fmt.Errorf("no database schema version is stored in the database") + } + + // Check if each schema update between the initial import and the latest version was applied or, in other words, + // that no schema update was left out. The loop goes over the ascending sorted array of schema versions, verifying + // that each element's successor is the increment of this version, ensuring no gaps in between. + for i := 0; i < len(versions)-1; i++ { + if versions[i] != versions[i+1]-1 { + return fmt.Errorf( + "incomplete database schema upgrade: intermediate version v%d is missing,"+ + " please make sure you have applied all database migrations after upgrading Icinga DB", + versions[i]+1) + } + } + + if latestVersion := versions[len(versions)-1]; latestVersion != expectedDbSchemaVersion { // Since these error messages are trivial and mostly caused by users, we don't need // to print a stack trace here. However, since errors.Errorf() does this automatically, // we need to use fmt instead. return fmt.Errorf( "unexpected database schema version: v%d (expected v%d), please make sure you have applied all database"+ - " migrations after upgrading Icinga DB", version, expectedDbSchemaVersion, + " migrations after upgrading Icinga DB", latestVersion, expectedDbSchemaVersion, ) }