Skip to content

Commit 64d1330

Browse files
committed
CheckSchema: Loosen schema upgrade check
In f49fac0, a strict schema upgrade history was enforced. This might result in reports due to misaligned, but fixed schema upgrades. Now, this check was loosened to only enforce that all schema updates between the lowest and latest schema version were applied. If they are not in order, only a warning is being produced.
1 parent c4d7ed3 commit 64d1330

File tree

2 files changed

+44
-21
lines changed

2 files changed

+44
-21
lines changed

cmd/icingadb/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ func run() int {
8888
_ = db.Close()
8989

9090
logger.Info("The database schema was successfully imported")
91+
92+
case errors.Is(err, icingadb.ErrSchemaImperfect):
93+
logger.Warnw("Database schema should be checked", zap.Error(err))
94+
9195
case err != nil:
9296
logger.Fatalf("%+v", err)
9397
}

pkg/icingadb/schema.go

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@ import (
44
"context"
55
stderrors "errors"
66
"fmt"
7+
"maps"
8+
"os"
9+
"path"
10+
"slices"
11+
712
"github.com/icinga/icinga-go-library/backoff"
813
"github.com/icinga/icinga-go-library/database"
914
"github.com/icinga/icinga-go-library/retry"
1015
"github.com/icinga/icingadb/internal"
1116
"github.com/jmoiron/sqlx"
1217
"github.com/pkg/errors"
13-
"maps"
14-
"os"
15-
"path"
16-
"slices"
1718
)
1819

1920
// ErrSchemaNotExists implies that no Icinga DB schema has been imported.
@@ -23,11 +24,16 @@ var ErrSchemaNotExists = stderrors.New("no database schema exists")
2324
// missed the schema upgrade.
2425
var ErrSchemaMismatch = stderrors.New("unexpected database schema version")
2526

27+
// ErrSchemaImperfect implies some non critical failure condition of the database schema.
28+
var ErrSchemaImperfect = stderrors.New("imperfect database schema")
29+
2630
// CheckSchema verifies the correct database schema is present.
2731
//
2832
// This function returns the following error types, possibly wrapped:
2933
// - If no schema exists, the error returned is ErrSchemaNotExists.
3034
// - If the schema version does not match the expected version, the error returned is ErrSchemaMismatch.
35+
// - If there are non fatal database schema conditions, ErrSchemaImperfect is returned. This error must
36+
// be reported back to the user, but should not lead in a program termination.
3137
// - Otherwise, the original error is returned, for example in case of general database problems.
3238
func CheckSchema(ctx context.Context, db *database.DB) error {
3339
var schemaVersions map[uint16]string
@@ -53,7 +59,7 @@ func CheckSchema(ctx context.Context, db *database.DB) error {
5359
err := retry.WithBackoff(
5460
ctx,
5561
func(ctx context.Context) error {
56-
query := "SELECT version FROM icingadb_schema ORDER BY version ASC"
62+
query := "SELECT version FROM icingadb_schema ORDER BY id ASC"
5763
if err := db.SelectContext(ctx, &versions, query); err != nil {
5864
return database.CantPerformQuery(err, query)
5965
}
@@ -66,38 +72,51 @@ func CheckSchema(ctx context.Context, db *database.DB) error {
6672
return errors.Wrap(err, "can't check database schema version")
6773
}
6874

75+
// In the following, multiple error conditions are checked.
76+
//
77+
// Since their error messages are trivial and mostly caused by users, we don't need
78+
// to print a stack trace here. However, since errors.Errorf() does this automatically,
79+
// we need to use fmt.Errorf() instead.
80+
81+
// Check if any schema was imported.
6982
if len(versions) == 0 {
7083
return fmt.Errorf("%w: no database schema version is stored in the database", ErrSchemaMismatch)
7184
}
7285

73-
// Check if each schema update between the initial import and the latest version was applied or, in other words,
74-
// that no schema update was left out. The loop goes over the ascending sorted array of schema versions, verifying
75-
// that each element's successor is the increment of this version, ensuring no gaps in between.
76-
for i := 0; i < len(versions)-1; i++ {
77-
if versions[i] != versions[i+1]-1 {
78-
missing := versions[i] + 1
86+
// Check if the latest schema version was imported.
87+
if latestVersion := slices.Max(versions); latestVersion != expectedDbSchemaVersion {
88+
return fmt.Errorf("%w: v%d (expected v%d), "+
89+
"please apply the %s.sql schema upgrade file to your database after upgrading Icinga DB: "+
90+
"https://icinga.com/docs/icinga-db/latest/doc/04-Upgrading/",
91+
ErrSchemaMismatch, latestVersion, expectedDbSchemaVersion, schemaVersions[expectedDbSchemaVersion])
92+
}
7993

94+
// Check if all schema updates between the oldest schema version and the expected version were applied.
95+
for version := slices.Min(versions); version < expectedDbSchemaVersion; version++ {
96+
if !slices.Contains(versions, version) {
8097
release := "UNKNOWN"
81-
if releaseVersion, ok := schemaVersions[missing]; ok {
98+
if releaseVersion, ok := schemaVersions[version]; ok {
8299
release = releaseVersion
83100
}
84101

85102
return fmt.Errorf(
86103
"%w: incomplete database schema upgrade: intermediate version v%d (%s) is missing, "+
87104
"please inspect the icingadb_schema database table and ensure that all database "+
88105
"migrations were applied in order after upgrading Icinga DB",
89-
ErrSchemaMismatch, missing, release)
106+
ErrSchemaMismatch, version, release)
90107
}
91108
}
92109

93-
if latestVersion := versions[len(versions)-1]; latestVersion != expectedDbSchemaVersion {
94-
// Since these error messages are trivial and mostly caused by users, we don't need
95-
// to print a stack trace here. However, since errors.Errorf() does this automatically,
96-
// we need to use fmt instead.
97-
return fmt.Errorf("%w: v%d (expected v%d), "+
98-
"please apply the %s.sql schema upgrade file to your database after upgrading Icinga DB: "+
99-
"https://icinga.com/docs/icinga-db/latest/doc/04-Upgrading/",
100-
ErrSchemaMismatch, latestVersion, expectedDbSchemaVersion, schemaVersions[expectedDbSchemaVersion])
110+
// Extend the prior check by checking if the schema updates were applied in a monotonic increasing order.
111+
// However, this returns an ErrSchemaImperfect error instead of an ErrSchemaMismatch.
112+
for i := 0; i < len(versions)-1; i++ {
113+
if versions[i] != versions[i+1]-1 {
114+
return fmt.Errorf(
115+
"%w: unexpected schema upgrade order after schema version %d, "+
116+
"please inspect the icingadb_schema database table and ensure that all database "+
117+
"migrations were applied in order after upgrading Icinga DB",
118+
ErrSchemaImperfect, versions[i])
119+
}
101120
}
102121

103122
return nil

0 commit comments

Comments
 (0)