fix: postgresql migration not working with other languages (#1013)

* fix: word check on migration error

* refactor: Improve PostgreSQL migration error handling using error codes

* refactor: Improve PostgreSQL migration error handling and transaction management

* refactor: Remove unused compareWordsInString function and its tests

* style: Clean up migrations.go imports and comments

* style: Fix gofmt formatting in migrations.go

* chore: remove migrations_test file

* ci: address golangci-lint warning

* test: stop tests if creating database fails

* fix: ensure migration transaction is commited or rolled back
This commit is contained in:
Felipe Martin 2024-12-11 13:10:56 +01:00 committed by GitHub
parent 7b6fad897b
commit 4aa0f51f10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 20 deletions

View file

@ -20,12 +20,12 @@ linters-settings:
linters: linters:
disable-all: true disable-all: true
enable: enable:
- copyloopvar
- gofmt - gofmt
- gosimple - gosimple
# - govet # Re-enable when all shadow declarations are fixed # - govet # Re-enable when all shadow declarations are fixed
- ineffassign - ineffassign
- predeclared - predeclared
- exportloopref
- staticcheck - staticcheck
- unconvert - unconvert
- unused - unused

View file

@ -45,7 +45,7 @@ func testDatabase(t *testing.T, dbFactory testDatabaseFactory) {
t.Run(testName, func(tInner *testing.T) { t.Run(testName, func(tInner *testing.T) {
ctx := context.TODO() ctx := context.TODO()
db, err := dbFactory(t, ctx) db, err := dbFactory(t, ctx)
assert.NoError(tInner, err, "Error recreating database") require.NoError(tInner, err, "Error recreating database")
testCase(tInner, db) testCase(tInner, db)
}) })
} }

View file

@ -1,3 +1,4 @@
// Package database implements database operations and migrations
package database package database
import ( import (
@ -13,13 +14,14 @@ import (
//go:embed migrations/* //go:embed migrations/*
var migrationFiles embed.FS var migrationFiles embed.FS
// migration represents a database schema migration
type migration struct { type migration struct {
fromVersion semver.Version fromVersion semver.Version
toVersion semver.Version toVersion semver.Version
migrationFunc func(db *sql.DB) error migrationFunc func(db *sql.DB) error
} }
// txFunc is a function that runs in a transaction. // txFn is a function that runs in a transaction.
type txFn func(tx *sql.Tx) error type txFn func(tx *sql.Tx) error
// runInTransaction runs the given function in a transaction. // runInTransaction runs the given function in a transaction.

View file

@ -12,7 +12,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
_ "github.com/lib/pq" "github.com/lib/pq"
) )
var postgresMigrations = []migration{ var postgresMigrations = []migration{
@ -25,16 +25,21 @@ var postgresMigrations = []migration{
if err != nil { if err != nil {
return fmt.Errorf("failed to start transaction: %w", err) return fmt.Errorf("failed to start transaction: %w", err)
} }
defer tx.Rollback()
_, err = tx.Exec(`ALTER TABLE bookmark ADD COLUMN has_content BOOLEAN DEFAULT FALSE NOT NULL`) _, err = tx.Exec(`ALTER TABLE bookmark ADD COLUMN has_content BOOLEAN DEFAULT FALSE NOT NULL`)
if err != nil && strings.Contains(err.Error(), `column "has_content" of relation "bookmark" already exists`) { if err != nil {
tx.Rollback() // Check if this is a "column already exists" error (PostgreSQL error code 42701)
} else if err != nil { // If it's not, return error.
return fmt.Errorf("failed to add has_content column to bookmark table: %w", err) // This is needed for users upgrading from >1.5.4 directly into this version.
} else if err == nil { pqErr, ok := err.(*pq.Error)
if errCommit := tx.Commit(); errCommit != nil { if ok && pqErr.Code == "42701" {
return fmt.Errorf("failed to commit transaction: %w", errCommit) tx.Rollback()
} else {
return fmt.Errorf("failed to add has_content column to bookmark table: %w", err)
}
} else {
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
} }
} }
@ -42,16 +47,21 @@ var postgresMigrations = []migration{
if err != nil { if err != nil {
return fmt.Errorf("failed to start transaction: %w", err) return fmt.Errorf("failed to start transaction: %w", err)
} }
defer tx.Rollback()
_, err = tx.Exec(`ALTER TABLE account ADD COLUMN config JSONB NOT NULL DEFAULT '{}'`) _, err = tx.Exec(`ALTER TABLE account ADD COLUMN config JSONB NOT NULL DEFAULT '{}'`)
if err != nil && strings.Contains(err.Error(), `column "config" of relation "account" already exists`) { if err != nil {
tx.Rollback() // Check if this is a "column already exists" error (PostgreSQL error code 42701)
} else if err != nil { // If it's not, return error
return fmt.Errorf("failed to add config column to account table: %w", err) // This is needed for users upgrading from >1.5.4 directly into this version.
} else if err == nil { pqErr, ok := err.(*pq.Error)
if errCommit := tx.Commit(); errCommit != nil { if ok && pqErr.Code == "42701" {
return fmt.Errorf("failed to commit transaction: %w", errCommit) tx.Rollback()
} else {
return fmt.Errorf("failed to add config column to account table: %w", err)
}
} else {
if err := tx.Commit(); err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
} }
} }