Skip to content

Commit

Permalink
Improve nakama migrate errors
Browse files Browse the repository at this point in the history
Helps with heroiclabs#1096
  • Loading branch information
toqueteos committed Aug 9, 2024
1 parent dfddb0a commit d429f29
Showing 1 changed file with 45 additions and 31 deletions.
76 changes: 45 additions & 31 deletions server/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import (
"go.uber.org/zap"
)

const dbErrorDatabaseDoesNotExist = pgerrcode.InvalidCatalogName
const (
dbErrorDatabaseInvalidPassword = pgerrcode.InvalidPassword
dbErrorDatabaseDoesNotExist = pgerrcode.InvalidCatalogName
)

var ErrDatabaseDriverMismatch = errors.New("database driver mismatch")

Expand All @@ -50,7 +53,7 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
}
query := parsedURL.Query()
var queryUpdated bool
if len(query.Get("sslmode")) == 0 {
if query.Get("sslmode") == "" {
query.Set("sslmode", "prefer")
queryUpdated = true
}
Expand All @@ -77,16 +80,14 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
logger.Fatal("Failed to open database", zap.Error(err))
}

dbPing(ctx, logger, db, dbName)

if create {
var nakamaDBExists bool
if err = db.QueryRow("SELECT EXISTS (SELECT 1 from pg_database WHERE datname = $1)", dbName).Scan(&nakamaDBExists); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == dbErrorDatabaseDoesNotExist {
nakamaDBExists = false
} else {
db.Close()
logger.Fatal("Failed to check if db exists", zap.String("db", dbName), zap.Error(err))
}
nakamaDBExists = false
db.Close()
logger.Fatal("Failed to check if db exists", zap.String("db", dbName), zap.Error(err))
}

if !nakamaDBExists {
Expand All @@ -104,45 +105,32 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
logger.Fatal("Failed to create database", zap.Error(err))
}
db.Close()

parsedURL.Path = fmt.Sprintf("/%s", dbName)
db, err = sql.Open("pgx", parsedURL.String())
if err != nil {
db.Close()
logger.Fatal("Failed to open database", zap.Error(err))
}
}
}

logger.Debug("Complete database connection URL", zap.String("raw_url", parsedURL.String()))
db, err = sql.Open("pgx", parsedURL.String())
if err != nil {
logger.Fatal("Error connecting to database", zap.Error(err))
}
// Limit max time allowed across database ping and version fetch to 15 seconds total.
pingCtx, pingCtxCancelFn := context.WithTimeout(ctx, 15*time.Second)
defer pingCtxCancelFn()
if err = db.PingContext(pingCtx); err != nil {
if strings.HasSuffix(err.Error(), "does not exist (SQLSTATE 3D000)") {
logger.Fatal("Database schema not found, run `nakama migrate up`", zap.Error(err))
}
logger.Fatal("Error pinging database", zap.Error(err))
logger.Fatal("Failed to open database", zap.Error(err))
}

dbPing(ctx, logger, db, dbName)

db.SetConnMaxLifetime(time.Millisecond * time.Duration(config.GetDatabase().ConnMaxLifetimeMs))
db.SetMaxOpenConns(config.GetDatabase().MaxOpenConns)
db.SetMaxIdleConns(config.GetDatabase().MaxIdleConns)

var dbVersion string
if err = db.QueryRowContext(pingCtx, "SELECT version()").Scan(&dbVersion); err != nil {
logger.Fatal("Error querying database version", zap.Error(err))
versionCtx, versionCtxCancelFn := context.WithTimeout(ctx, 5*time.Second)
defer versionCtxCancelFn()
if err = db.QueryRowContext(versionCtx, "SELECT version()").Scan(&dbVersion); err != nil {
logger.Fatal("Failed to query database version", zap.Error(err))
}

logger.Info("Database information", zap.String("version", dbVersion))
if strings.Split(dbVersion, " ")[0] == "CockroachDB" {
isCockroach = true
} else {
isCockroach = false
}
isCockroach = strings.HasPrefix(dbVersion, "CockroachDB ")

// Periodically check database hostname for underlying address changes.
go func() {
Expand Down Expand Up @@ -237,6 +225,32 @@ func DbConnect(ctx context.Context, logger *zap.Logger, config Config, create bo
return db
}

func dbPing(ctx context.Context, logger *zap.Logger, db *sql.DB, dbName string) error {
pingCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

err := db.PingContext(pingCtx)
if err != nil {
db.Close()

errLogger := logger.With(zap.String("db", dbName), zap.Error(err))

var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
switch pgErr.Code {
case dbErrorDatabaseInvalidPassword:
errLogger.Fatal("Invalid credentials")
case dbErrorDatabaseDoesNotExist:
errLogger.Fatal("Database schema not found, run `nakama migrate up`")
}
} else {
errLogger.Fatal("Failed to ping database")
}
}

return nil
}

func dbResolveAddress(ctx context.Context, logger *zap.Logger, host string) ([]string, map[string]struct{}) {
resolveCtx, resolveCtxCancelFn := context.WithTimeout(ctx, 15*time.Second)
defer resolveCtxCancelFn()
Expand Down

0 comments on commit d429f29

Please sign in to comment.