Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions flow/alerting/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ var (
// e.g. could not open file "pg_logical/snapshots/2-8B023150.snap.8007.tmp": No such file or directory
PostgresCouldNotOpenSnapshotRe = regexp.MustCompile(`could not open file ".*\.snap\..*\.tmp"`)
PostgresNeonDonorWalLaggingRe = regexp.MustCompile(`requested WAL up to [0-9A-F]+/[0-9A-F]+, but current donor \S+ has only up to`)
MySqlRdsBinlogFileNotFoundRe = regexp.MustCompile(`File '/rdsdbdata/log/binlog/mysql-bin-changelog.\d+' not found`)
MongoPoolClearedErrorRe = regexp.MustCompile(`connection pool for .+ was cleared because another operation failed with`)
// pg_dump automated schema migration failing because the destination lacks an extension the source uses,
// e.g. `extension "vector" is not available` or `could not open extension control file ".../vector.control"`.
PostgresExtensionNotAvailableRe = regexp.MustCompile(
`extension ".*?" is not available|could not open extension control file`,
)
MySqlRdsBinlogFileNotFoundRe = regexp.MustCompile(`File '/rdsdbdata/log/binlog/mysql-bin-changelog.\d+' not found`)
MongoPoolClearedErrorRe = regexp.MustCompile(`connection pool for .+ was cleared because another operation failed with`)
)

func (e ErrorAction) String() string {
Expand Down Expand Up @@ -209,6 +214,11 @@ var (
ErrorNotifyConstraintViolation = ErrorClass{
Class: "NOTIFY_CONSTRAINT_VIOLATION", action: NotifyUser,
}
// The pg_dump automated schema migration failed because the destination is missing an extension
// that the source schema depends on; the user must install it on the destination.
ErrorNotifyPostgresExtensionNotAvailable = ErrorClass{
Class: "NOTIFY_POSTGRES_EXTENSION_NOT_AVAILABLE", action: NotifyUser,
}
ErrorNotifyInvalidSynchronizedStandbySlots = ErrorClass{
Class: "NOTIFY_INVALID_SYNCHRONIZED_STANDBY_SLOTS", action: NotifyUser,
}
Expand Down Expand Up @@ -1404,6 +1414,15 @@ func GetErrorClass(ctx context.Context, err error) (ErrorClass, ErrorInfo) {
}
}

// pg_dump automated schema migration pipes into psql, so a missing destination extension surfaces
// as plain stderr text rather than a *pgconn.PgError.
if PostgresExtensionNotAvailableRe.MatchString(err.Error()) {
return ErrorNotifyPostgresExtensionNotAvailable, ErrorInfo{
Source: ErrorSourcePostgres,
Code: "EXTENSION_NOT_AVAILABLE",
}
}

return ErrorOther, ErrorInfo{
Source: ErrorSourceOther,
Code: "UNKNOWN",
Expand Down
21 changes: 21 additions & 0 deletions flow/alerting/classifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,27 @@ func TestPostgresUniqueViolationOnNormalize(t *testing.T) {
}, errInfo, "Unexpected error info")
}

func TestPostgresExtensionNotAvailableOnSchemaDump(t *testing.T) {
for name, message := range map[string]string{
"not available": `psql failed: exit status 3
stderr:
psql:<stdin>:42: ERROR: extension "vector" is not available`,
"missing control file": `psql failed: exit status 3
stderr:
psql:<stdin>:42: ERROR: could not open extension control file "/usr/share/postgresql/16/extension/vector.control": No such file or directory`,
} {
t.Run(name, func(t *testing.T) {
err := fmt.Errorf("pg_dump schema migration failed: %s", message)
errorClass, errInfo := GetErrorClass(t.Context(), err)
assert.Equal(t, ErrorNotifyPostgresExtensionNotAvailable, errorClass, "Unexpected error class")
assert.Equal(t, ErrorInfo{
Source: ErrorSourcePostgres,
Code: "EXTENSION_NOT_AVAILABLE",
}, errInfo, "Unexpected error info")
})
}
}

func TestPostgresLogicalDecodingNotSupportedOnStandby(t *testing.T) {
err := &pgconn.PgError{
Severity: "ERROR",
Expand Down
Loading