From 5797d6ace3d4e7f5207deba35e73c729de019c25 Mon Sep 17 00:00:00 2001 From: John Tzikas Date: Wed, 18 Nov 2020 00:01:20 +0200 Subject: [PATCH] Add logic to skip duplicate table errors when initializing db (#16270) * Add logic to skip duplicate table errors when initializing db * Update store/sqlstore/supplier.go Co-authored-by: Gabe Jackson * Handle mysql and pg table already exist errors * Apply PR suggestions * Fix typo on IsDuplicate * Update mysql error link to point to the official one Co-authored-by: Gabe Jackson --- store/sqlstore/supplier.go | 41 ++++++++++++++++++++++++++------- store/sqlstore/supplier_test.go | 21 +++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 921a7fbfb1..3fb03cbc46 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -7,7 +7,6 @@ import ( "context" dbsql "database/sql" "encoding/json" - "errors" "fmt" "os" "strings" @@ -15,6 +14,8 @@ import ( "sync/atomic" "time" + "github.com/pkg/errors" + sq "github.com/Masterminds/squirrel" "github.com/dyatlov/go-opengraph/opengraph" "github.com/go-sql-driver/mysql" @@ -28,10 +29,12 @@ import ( ) const ( - INDEX_TYPE_FULL_TEXT = "full_text" - INDEX_TYPE_DEFAULT = "default" - DB_PING_ATTEMPTS = 18 - DB_PING_TIMEOUT_SECS = 10 + INDEX_TYPE_FULL_TEXT = "full_text" + INDEX_TYPE_DEFAULT = "default" + PG_DUP_TABLE_ERROR_CODE = "42P07" // see https://github.com/lib/pq/blob/master/error.go#L268 + MYSQL_DUP_TABLE_ERROR_CODE = uint16(1050) // see https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html#error_er_table_exists_error + DB_PING_ATTEMPTS = 18 + DB_PING_TIMEOUT_SECS = 10 ) const ( @@ -176,9 +179,12 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter supplier.stores.productNotices = newSqlProductNoticesStore(supplier) err := supplier.GetMaster().CreateTablesIfNotExists() if err != nil { - mlog.Critical("Error creating database tables.", mlog.Err(err)) - time.Sleep(time.Second) - os.Exit(EXIT_CREATE_TABLE) + if IsDuplicate(err) { + mlog.Warn("Duplicate key error occurred; assuming table already created and proceeding.", mlog.Err(err)) + } else { + mlog.Critical("Error creating database tables.", mlog.Err(err)) + os.Exit(EXIT_CREATE_TABLE) + } } err = upgradeDatabase(supplier, model.CurrentVersion) @@ -1319,3 +1325,22 @@ func convertMySQLFullTextColumnsToPostgres(columnNames string) string { return concatenatedColumnNames } + +// IsDuplicate checks whether an error is a duplicate key error, which comes when processes are competing on creating the same +// tables in the database. +func IsDuplicate(err error) bool { + var pqErr *pq.Error + var mysqlErr *mysql.MySQLError + switch { + case errors.As(errors.Cause(err), &pqErr): + if pqErr.Code == PG_DUP_TABLE_ERROR_CODE { + return true + } + case errors.As(errors.Cause(err), &mysqlErr): + if mysqlErr.Number == MYSQL_DUP_TABLE_ERROR_CODE { + return true + } + } + + return false +} diff --git a/store/sqlstore/supplier_test.go b/store/sqlstore/supplier_test.go index 9168b14fbd..87fc4474bc 100644 --- a/store/sqlstore/supplier_test.go +++ b/store/sqlstore/supplier_test.go @@ -4,12 +4,16 @@ package sqlstore_test import ( + "fmt" "regexp" "sync" "testing" + "github.com/go-sql-driver/mysql" + "github.com/lib/pq" "github.com/mattermost/gorp" _ "github.com/mattn/go-sqlite3" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -314,6 +318,23 @@ func TestGetAllConns(t *testing.T) { } } +func TestIsDuplicate(t *testing.T) { + testErrors := map[error]bool{ + &pq.Error{Code: "42P06"}: false, + &pq.Error{Code: sqlstore.PG_DUP_TABLE_ERROR_CODE}: true, + &mysql.MySQLError{Number: uint16(1000)}: false, + &mysql.MySQLError{Number: sqlstore.MYSQL_DUP_TABLE_ERROR_CODE}: true, + errors.New("Random error"): false, + } + + for err, expected := range testErrors { + t.Run(fmt.Sprintf("Should return %t for %s", expected, err.Error()), func(t *testing.T) { + t.Parallel() + assert.Equal(t, expected, sqlstore.IsDuplicate(err)) + }) + } +} + func makeSqlSettings(driver string) *model.SqlSettings { switch driver { case model.DATABASE_DRIVER_POSTGRES: