github.com/mattermost/mattermost-server/v5@v5.39.3/store/sqlstore/store.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package sqlstore
     5  
     6  import (
     7  	"context"
     8  	dbsql "database/sql"
     9  	"encoding/json"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"sync/atomic"
    17  	"time"
    18  
    19  	sq "github.com/Masterminds/squirrel"
    20  	"github.com/dyatlov/go-opengraph/opengraph"
    21  	"github.com/go-sql-driver/mysql"
    22  	"github.com/golang-migrate/migrate/v4"
    23  	"github.com/golang-migrate/migrate/v4/database"
    24  	mysqlmigrate "github.com/golang-migrate/migrate/v4/database/mysql"
    25  	"github.com/golang-migrate/migrate/v4/database/postgres"
    26  	_ "github.com/golang-migrate/migrate/v4/source/file"
    27  	bindata "github.com/golang-migrate/migrate/v4/source/go_bindata"
    28  	"github.com/lib/pq"
    29  	_ "github.com/lib/pq"
    30  	"github.com/mattermost/gorp"
    31  	"github.com/pkg/errors"
    32  
    33  	"github.com/mattermost/mattermost-server/v5/db/migrations"
    34  	"github.com/mattermost/mattermost-server/v5/einterfaces"
    35  	"github.com/mattermost/mattermost-server/v5/model"
    36  	"github.com/mattermost/mattermost-server/v5/shared/i18n"
    37  	"github.com/mattermost/mattermost-server/v5/shared/mlog"
    38  	"github.com/mattermost/mattermost-server/v5/store"
    39  )
    40  
    41  type migrationDirection string
    42  
    43  const (
    44  	IndexTypeFullText                 = "full_text"
    45  	IndexTypeFullTextFunc             = "full_text_func"
    46  	IndexTypeDefault                  = "default"
    47  	PGDupTableErrorCode               = "42P07"      // see https://github.com/lib/pq/blob/master/error.go#L268
    48  	MySQLDupTableErrorCode            = uint16(1050) // see https://dev.mysql.com/doc/mysql-errors/5.7/en/server-error-reference.html#error_er_table_exists_error
    49  	PGForeignKeyViolationErrorCode    = "23503"
    50  	MySQLForeignKeyViolationErrorCode = 1452
    51  	PGDuplicateObjectErrorCode        = "42710"
    52  	MySQLDuplicateObjectErrorCode     = 1022
    53  	DBPingAttempts                    = 18
    54  	DBPingTimeoutSecs                 = 10
    55  	// This is a numerical version string by postgres. The format is
    56  	// 2 characters for major, minor, and patch version prior to 10.
    57  	// After 10, it's major and minor only.
    58  	// 10.1 would be 100001.
    59  	// 9.6.3 would be 90603.
    60  	MinimumRequiredPostgresVersion = 100000
    61  
    62  	migrationsDirectionUp   migrationDirection = "up"
    63  	migrationsDirectionDown migrationDirection = "down"
    64  
    65  	replicaLagPrefix = "replica-lag"
    66  )
    67  
    68  const (
    69  	ExitGenericFailure           = 1
    70  	ExitCreateTable              = 100
    71  	ExitDBOpen                   = 101
    72  	ExitPing                     = 102
    73  	ExitNoDriver                 = 103
    74  	ExitTableExists              = 104
    75  	ExitTableExistsMySQL         = 105
    76  	ExitColumnExists             = 106
    77  	ExitDoesColumnExistsPostgres = 107
    78  	ExitDoesColumnExistsMySQL    = 108
    79  	ExitDoesColumnExistsMissing  = 109
    80  	ExitCreateColumnPostgres     = 110
    81  	ExitCreateColumnMySQL        = 111
    82  	ExitCreateColumnMissing      = 112
    83  	ExitRemoveColumn             = 113
    84  	ExitRenameColumn             = 114
    85  	ExitMaxColumn                = 115
    86  	ExitAlterColumn              = 116
    87  	ExitCreateIndexPostgres      = 117
    88  	ExitCreateIndexMySQL         = 118
    89  	ExitCreateIndexFullMySQL     = 119
    90  	ExitCreateIndexMissing       = 120
    91  	ExitRemoveIndexPostgres      = 121
    92  	ExitRemoveIndexMySQL         = 122
    93  	ExitRemoveIndexMissing       = 123
    94  	ExitDoesIndexExists          = 124
    95  	ExitDoesIndexExistsMySQL     = 125
    96  	ExitRemoveTable              = 134
    97  	ExitAlterPrimaryKey          = 139
    98  )
    99  
   100  type SqlStoreStores struct {
   101  	team                 store.TeamStore
   102  	channel              store.ChannelStore
   103  	post                 store.PostStore
   104  	retentionPolicy      store.RetentionPolicyStore
   105  	thread               store.ThreadStore
   106  	user                 store.UserStore
   107  	bot                  store.BotStore
   108  	audit                store.AuditStore
   109  	cluster              store.ClusterDiscoveryStore
   110  	remoteCluster        store.RemoteClusterStore
   111  	compliance           store.ComplianceStore
   112  	session              store.SessionStore
   113  	oauth                store.OAuthStore
   114  	system               store.SystemStore
   115  	webhook              store.WebhookStore
   116  	command              store.CommandStore
   117  	commandWebhook       store.CommandWebhookStore
   118  	preference           store.PreferenceStore
   119  	license              store.LicenseStore
   120  	token                store.TokenStore
   121  	emoji                store.EmojiStore
   122  	status               store.StatusStore
   123  	fileInfo             store.FileInfoStore
   124  	uploadSession        store.UploadSessionStore
   125  	reaction             store.ReactionStore
   126  	job                  store.JobStore
   127  	userAccessToken      store.UserAccessTokenStore
   128  	plugin               store.PluginStore
   129  	channelMemberHistory store.ChannelMemberHistoryStore
   130  	role                 store.RoleStore
   131  	scheme               store.SchemeStore
   132  	TermsOfService       store.TermsOfServiceStore
   133  	productNotices       store.ProductNoticesStore
   134  	group                store.GroupStore
   135  	UserTermsOfService   store.UserTermsOfServiceStore
   136  	linkMetadata         store.LinkMetadataStore
   137  	sharedchannel        store.SharedChannelStore
   138  }
   139  
   140  type SqlStore struct {
   141  	// rrCounter and srCounter should be kept first.
   142  	// See https://github.com/mattermost/mattermost-server/v5/pull/7281
   143  	rrCounter         int64
   144  	srCounter         int64
   145  	master            *gorp.DbMap
   146  	Replicas          []*gorp.DbMap
   147  	searchReplicas    []*gorp.DbMap
   148  	replicaLagHandles []*dbsql.DB
   149  	stores            SqlStoreStores
   150  	settings          *model.SqlSettings
   151  	lockedToMaster    bool
   152  	context           context.Context
   153  	license           *model.License
   154  	licenseMutex      sync.RWMutex
   155  	metrics           einterfaces.MetricsInterface
   156  }
   157  
   158  type TraceOnAdapter struct{}
   159  
   160  // ColumnInfo holds information about a column.
   161  type ColumnInfo struct {
   162  	DataType          string
   163  	CharMaximumLength int
   164  }
   165  
   166  func (t *TraceOnAdapter) Printf(format string, v ...interface{}) {
   167  	originalString := fmt.Sprintf(format, v...)
   168  	newString := strings.ReplaceAll(originalString, "\n", " ")
   169  	newString = strings.ReplaceAll(newString, "\t", " ")
   170  	newString = strings.ReplaceAll(newString, "\"", "")
   171  	mlog.Debug(newString)
   172  }
   173  
   174  func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlStore {
   175  	store := &SqlStore{
   176  		rrCounter: 0,
   177  		srCounter: 0,
   178  		settings:  &settings,
   179  		metrics:   metrics,
   180  	}
   181  
   182  	store.initConnection()
   183  
   184  	if *settings.DriverName == model.DATABASE_DRIVER_POSTGRES {
   185  		ver, err := store.GetDbVersion(true)
   186  		if err != nil {
   187  			mlog.Critical("Cannot get DB version.", mlog.Err(err))
   188  			os.Exit(ExitGenericFailure)
   189  		}
   190  		intVer, err := strconv.Atoi(ver)
   191  		if err != nil {
   192  			mlog.Critical("Cannot parse DB version.", mlog.Err(err))
   193  			os.Exit(ExitGenericFailure)
   194  		}
   195  		if intVer < MinimumRequiredPostgresVersion {
   196  			mlog.Critical("Minimum Postgres version requirements not met.", mlog.String("Found", VersionString(intVer)), mlog.String("Wanted", VersionString(MinimumRequiredPostgresVersion)))
   197  			os.Exit(ExitGenericFailure)
   198  		}
   199  	}
   200  
   201  	err := store.migrate(migrationsDirectionUp)
   202  	if err != nil {
   203  		mlog.Critical("Failed to apply database migrations.", mlog.Err(err))
   204  		os.Exit(ExitGenericFailure)
   205  	}
   206  
   207  	store.stores.team = newSqlTeamStore(store)
   208  	store.stores.channel = newSqlChannelStore(store, metrics)
   209  	store.stores.post = newSqlPostStore(store, metrics)
   210  	store.stores.retentionPolicy = newSqlRetentionPolicyStore(store, metrics)
   211  	store.stores.user = newSqlUserStore(store, metrics)
   212  	store.stores.bot = newSqlBotStore(store, metrics)
   213  	store.stores.audit = newSqlAuditStore(store)
   214  	store.stores.cluster = newSqlClusterDiscoveryStore(store)
   215  	store.stores.remoteCluster = newSqlRemoteClusterStore(store)
   216  	store.stores.compliance = newSqlComplianceStore(store)
   217  	store.stores.session = newSqlSessionStore(store)
   218  	store.stores.oauth = newSqlOAuthStore(store)
   219  	store.stores.system = newSqlSystemStore(store)
   220  	store.stores.webhook = newSqlWebhookStore(store, metrics)
   221  	store.stores.command = newSqlCommandStore(store)
   222  	store.stores.commandWebhook = newSqlCommandWebhookStore(store)
   223  	store.stores.preference = newSqlPreferenceStore(store)
   224  	store.stores.license = newSqlLicenseStore(store)
   225  	store.stores.token = newSqlTokenStore(store)
   226  	store.stores.emoji = newSqlEmojiStore(store, metrics)
   227  	store.stores.status = newSqlStatusStore(store)
   228  	store.stores.fileInfo = newSqlFileInfoStore(store, metrics)
   229  	store.stores.uploadSession = newSqlUploadSessionStore(store)
   230  	store.stores.thread = newSqlThreadStore(store)
   231  	store.stores.job = newSqlJobStore(store)
   232  	store.stores.userAccessToken = newSqlUserAccessTokenStore(store)
   233  	store.stores.channelMemberHistory = newSqlChannelMemberHistoryStore(store)
   234  	store.stores.plugin = newSqlPluginStore(store)
   235  	store.stores.TermsOfService = newSqlTermsOfServiceStore(store, metrics)
   236  	store.stores.UserTermsOfService = newSqlUserTermsOfServiceStore(store)
   237  	store.stores.linkMetadata = newSqlLinkMetadataStore(store)
   238  	store.stores.sharedchannel = newSqlSharedChannelStore(store)
   239  	store.stores.reaction = newSqlReactionStore(store)
   240  	store.stores.role = newSqlRoleStore(store)
   241  	store.stores.scheme = newSqlSchemeStore(store)
   242  	store.stores.group = newSqlGroupStore(store)
   243  	store.stores.productNotices = newSqlProductNoticesStore(store)
   244  	err = store.GetMaster().CreateTablesIfNotExists()
   245  
   246  	if err != nil {
   247  		if IsDuplicate(err) {
   248  			mlog.Warn("Duplicate key error occurred; assuming table already created and proceeding.", mlog.Err(err))
   249  		} else {
   250  			mlog.Critical("Error creating database tables.", mlog.Err(err))
   251  			os.Exit(ExitCreateTable)
   252  		}
   253  	}
   254  
   255  	err = upgradeDatabase(store, model.CurrentVersion)
   256  	if err != nil {
   257  		mlog.Critical("Failed to upgrade database.", mlog.Err(err))
   258  		time.Sleep(time.Second)
   259  		os.Exit(ExitGenericFailure)
   260  	}
   261  
   262  	store.stores.channel.(*SqlChannelStore).createIndexesIfNotExists()
   263  	store.stores.post.(*SqlPostStore).createIndexesIfNotExists()
   264  	store.stores.retentionPolicy.(*SqlRetentionPolicyStore).createIndexesIfNotExists()
   265  	store.stores.thread.(*SqlThreadStore).createIndexesIfNotExists()
   266  	store.stores.user.(*SqlUserStore).createIndexesIfNotExists()
   267  	store.stores.bot.(*SqlBotStore).createIndexesIfNotExists()
   268  	store.stores.audit.(*SqlAuditStore).createIndexesIfNotExists()
   269  	store.stores.compliance.(*SqlComplianceStore).createIndexesIfNotExists()
   270  	store.stores.session.(*SqlSessionStore).createIndexesIfNotExists()
   271  	store.stores.oauth.(*SqlOAuthStore).createIndexesIfNotExists()
   272  	store.stores.system.(*SqlSystemStore).createIndexesIfNotExists()
   273  	store.stores.webhook.(*SqlWebhookStore).createIndexesIfNotExists()
   274  	store.stores.command.(*SqlCommandStore).createIndexesIfNotExists()
   275  	store.stores.commandWebhook.(*SqlCommandWebhookStore).createIndexesIfNotExists()
   276  	store.stores.preference.(*SqlPreferenceStore).createIndexesIfNotExists()
   277  	store.stores.license.(*SqlLicenseStore).createIndexesIfNotExists()
   278  	store.stores.token.(*SqlTokenStore).createIndexesIfNotExists()
   279  	store.stores.emoji.(*SqlEmojiStore).createIndexesIfNotExists()
   280  	store.stores.status.(*SqlStatusStore).createIndexesIfNotExists()
   281  	store.stores.fileInfo.(*SqlFileInfoStore).createIndexesIfNotExists()
   282  	store.stores.uploadSession.(*SqlUploadSessionStore).createIndexesIfNotExists()
   283  	store.stores.job.(*SqlJobStore).createIndexesIfNotExists()
   284  	store.stores.userAccessToken.(*SqlUserAccessTokenStore).createIndexesIfNotExists()
   285  	store.stores.plugin.(*SqlPluginStore).createIndexesIfNotExists()
   286  	store.stores.TermsOfService.(SqlTermsOfServiceStore).createIndexesIfNotExists()
   287  	store.stores.productNotices.(SqlProductNoticesStore).createIndexesIfNotExists()
   288  	store.stores.UserTermsOfService.(SqlUserTermsOfServiceStore).createIndexesIfNotExists()
   289  	store.stores.linkMetadata.(*SqlLinkMetadataStore).createIndexesIfNotExists()
   290  	store.stores.sharedchannel.(*SqlSharedChannelStore).createIndexesIfNotExists()
   291  	store.stores.group.(*SqlGroupStore).createIndexesIfNotExists()
   292  	store.stores.scheme.(*SqlSchemeStore).createIndexesIfNotExists()
   293  	store.stores.remoteCluster.(*sqlRemoteClusterStore).createIndexesIfNotExists()
   294  	store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
   295  
   296  	return store
   297  }
   298  
   299  func setupConnection(connType string, dataSource string, settings *model.SqlSettings) *gorp.DbMap {
   300  	db, err := dbsql.Open(*settings.DriverName, dataSource)
   301  	if err != nil {
   302  		mlog.Critical("Failed to open SQL connection to err.", mlog.Err(err))
   303  		time.Sleep(time.Second)
   304  		os.Exit(ExitDBOpen)
   305  	}
   306  
   307  	for i := 0; i < DBPingAttempts; i++ {
   308  		mlog.Info("Pinging SQL", mlog.String("database", connType))
   309  		ctx, cancel := context.WithTimeout(context.Background(), DBPingTimeoutSecs*time.Second)
   310  		defer cancel()
   311  		err = db.PingContext(ctx)
   312  		if err == nil {
   313  			break
   314  		} else {
   315  			if i == DBPingAttempts-1 {
   316  				mlog.Critical("Failed to ping DB, server will exit.", mlog.Err(err))
   317  				time.Sleep(time.Second)
   318  				os.Exit(ExitPing)
   319  			} else {
   320  				mlog.Error("Failed to ping DB", mlog.Err(err), mlog.Int("retrying in seconds", DBPingTimeoutSecs))
   321  				time.Sleep(DBPingTimeoutSecs * time.Second)
   322  			}
   323  		}
   324  	}
   325  
   326  	if strings.HasPrefix(connType, replicaLagPrefix) {
   327  		// If this is a replica lag connection, we just open one connection.
   328  		//
   329  		// Arguably, if the query doesn't require a special credential, it does take up
   330  		// one extra connection from the replica DB. But falling back to the replica
   331  		// data source when the replica lag data source is null implies an ordering constraint
   332  		// which makes things brittle and is not a good design.
   333  		// If connections are an overhead, it is advised to use a connection pool.
   334  		db.SetMaxOpenConns(1)
   335  		db.SetMaxIdleConns(1)
   336  	} else {
   337  		db.SetMaxIdleConns(*settings.MaxIdleConns)
   338  		db.SetMaxOpenConns(*settings.MaxOpenConns)
   339  	}
   340  	db.SetConnMaxLifetime(time.Duration(*settings.ConnMaxLifetimeMilliseconds) * time.Millisecond)
   341  	db.SetConnMaxIdleTime(time.Duration(*settings.ConnMaxIdleTimeMilliseconds) * time.Millisecond)
   342  
   343  	dbMap := getDBMap(settings, db)
   344  
   345  	return dbMap
   346  }
   347  
   348  func getDBMap(settings *model.SqlSettings, db *dbsql.DB) *gorp.DbMap {
   349  	connectionTimeout := time.Duration(*settings.QueryTimeout) * time.Second
   350  	var dbMap *gorp.DbMap
   351  	switch *settings.DriverName {
   352  	case model.DATABASE_DRIVER_MYSQL:
   353  		dbMap = &gorp.DbMap{
   354  			Db:            db,
   355  			TypeConverter: mattermConverter{},
   356  			Dialect:       gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"},
   357  			QueryTimeout:  connectionTimeout,
   358  		}
   359  	case model.DATABASE_DRIVER_POSTGRES:
   360  		dbMap = &gorp.DbMap{
   361  			Db:            db,
   362  			TypeConverter: mattermConverter{},
   363  			Dialect:       gorp.PostgresDialect{},
   364  			QueryTimeout:  connectionTimeout,
   365  		}
   366  	default:
   367  		mlog.Critical("Failed to create dialect specific driver")
   368  		time.Sleep(time.Second)
   369  		os.Exit(ExitNoDriver)
   370  		return nil
   371  	}
   372  	if settings.Trace != nil && *settings.Trace {
   373  		dbMap.TraceOn("sql-trace:", &TraceOnAdapter{})
   374  	}
   375  	return dbMap
   376  }
   377  
   378  func (ss *SqlStore) SetContext(context context.Context) {
   379  	ss.context = context
   380  }
   381  
   382  func (ss *SqlStore) Context() context.Context {
   383  	return ss.context
   384  }
   385  
   386  func (ss *SqlStore) initConnection() {
   387  	dataSource := *ss.settings.DataSource
   388  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   389  		// TODO: We ignore the readTimeout datasource parameter for MySQL since QueryTimeout
   390  		// covers that already. Ideally we'd like to do this only for the upgrade
   391  		// step. To be reviewed in MM-35789.
   392  		var err error
   393  		dataSource, err = resetReadTimeout(dataSource)
   394  		if err != nil {
   395  			mlog.Critical("Failed to reset read timeout from datasource.", mlog.Err(err))
   396  			os.Exit(ExitGenericFailure)
   397  		}
   398  	}
   399  
   400  	ss.master = setupConnection("master", dataSource, ss.settings)
   401  
   402  	if len(ss.settings.DataSourceReplicas) > 0 {
   403  		ss.Replicas = make([]*gorp.DbMap, len(ss.settings.DataSourceReplicas))
   404  		for i, replica := range ss.settings.DataSourceReplicas {
   405  			ss.Replicas[i] = setupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings)
   406  		}
   407  	}
   408  
   409  	if len(ss.settings.DataSourceSearchReplicas) > 0 {
   410  		ss.searchReplicas = make([]*gorp.DbMap, len(ss.settings.DataSourceSearchReplicas))
   411  		for i, replica := range ss.settings.DataSourceSearchReplicas {
   412  			ss.searchReplicas[i] = setupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings)
   413  		}
   414  	}
   415  
   416  	if len(ss.settings.ReplicaLagSettings) > 0 {
   417  		ss.replicaLagHandles = make([]*dbsql.DB, len(ss.settings.ReplicaLagSettings))
   418  		for i, src := range ss.settings.ReplicaLagSettings {
   419  			if src.DataSource == nil {
   420  				continue
   421  			}
   422  			gorpConn := setupConnection(fmt.Sprintf(replicaLagPrefix+"-%d", i), *src.DataSource, ss.settings)
   423  			ss.replicaLagHandles[i] = gorpConn.Db
   424  		}
   425  	}
   426  }
   427  
   428  func (ss *SqlStore) DriverName() string {
   429  	return *ss.settings.DriverName
   430  }
   431  
   432  func (ss *SqlStore) GetCurrentSchemaVersion() string {
   433  	version, _ := ss.GetMaster().SelectStr("SELECT Value FROM Systems WHERE Name='Version'")
   434  	return version
   435  }
   436  
   437  // GetDbVersion returns the version of the database being used.
   438  // If numerical is set to true, it attempts to return a numerical version string
   439  // that can be parsed by callers.
   440  func (ss *SqlStore) GetDbVersion(numerical bool) (string, error) {
   441  	var sqlVersion string
   442  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   443  		if numerical {
   444  			sqlVersion = `SHOW server_version_num`
   445  		} else {
   446  			sqlVersion = `SHOW server_version`
   447  		}
   448  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   449  		sqlVersion = `SELECT version()`
   450  	} else {
   451  		return "", errors.New("Not supported driver")
   452  	}
   453  
   454  	version, err := ss.GetReplica().SelectStr(sqlVersion)
   455  	if err != nil {
   456  		return "", err
   457  	}
   458  
   459  	return version, nil
   460  
   461  }
   462  
   463  func (ss *SqlStore) GetMaster() *gorp.DbMap {
   464  	return ss.master
   465  }
   466  
   467  func (ss *SqlStore) GetSearchReplica() *gorp.DbMap {
   468  	ss.licenseMutex.RLock()
   469  	license := ss.license
   470  	ss.licenseMutex.RUnlock()
   471  	if license == nil {
   472  		return ss.GetMaster()
   473  	}
   474  
   475  	if len(ss.settings.DataSourceSearchReplicas) == 0 {
   476  		return ss.GetReplica()
   477  	}
   478  
   479  	rrNum := atomic.AddInt64(&ss.srCounter, 1) % int64(len(ss.searchReplicas))
   480  	return ss.searchReplicas[rrNum]
   481  }
   482  
   483  func (ss *SqlStore) GetReplica() *gorp.DbMap {
   484  	ss.licenseMutex.RLock()
   485  	license := ss.license
   486  	ss.licenseMutex.RUnlock()
   487  	if len(ss.settings.DataSourceReplicas) == 0 || ss.lockedToMaster || license == nil {
   488  		return ss.GetMaster()
   489  	}
   490  
   491  	rrNum := atomic.AddInt64(&ss.rrCounter, 1) % int64(len(ss.Replicas))
   492  	return ss.Replicas[rrNum]
   493  }
   494  
   495  func (ss *SqlStore) TotalMasterDbConnections() int {
   496  	return ss.GetMaster().Db.Stats().OpenConnections
   497  }
   498  
   499  // ReplicaLagAbs queries all the replica databases to get the absolute replica lag value
   500  // and updates the Prometheus metric with it.
   501  func (ss *SqlStore) ReplicaLagAbs() error {
   502  	for i, item := range ss.settings.ReplicaLagSettings {
   503  		if item.QueryAbsoluteLag == nil || *item.QueryAbsoluteLag == "" {
   504  			continue
   505  		}
   506  		var binDiff float64
   507  		var node string
   508  		err := ss.replicaLagHandles[i].QueryRow(*item.QueryAbsoluteLag).Scan(&node, &binDiff)
   509  		if err != nil {
   510  			return err
   511  		}
   512  		// There is no nil check needed here because it's called from the metrics store.
   513  		ss.metrics.SetReplicaLagAbsolute(node, binDiff)
   514  	}
   515  	return nil
   516  }
   517  
   518  // ReplicaLagAbs queries all the replica databases to get the time-based replica lag value
   519  // and updates the Prometheus metric with it.
   520  func (ss *SqlStore) ReplicaLagTime() error {
   521  	for i, item := range ss.settings.ReplicaLagSettings {
   522  		if item.QueryTimeLag == nil || *item.QueryTimeLag == "" {
   523  			continue
   524  		}
   525  		var timeDiff float64
   526  		var node string
   527  		err := ss.replicaLagHandles[i].QueryRow(*item.QueryTimeLag).Scan(&node, &timeDiff)
   528  		if err != nil {
   529  			return err
   530  		}
   531  		// There is no nil check needed here because it's called from the metrics store.
   532  		ss.metrics.SetReplicaLagTime(node, timeDiff)
   533  	}
   534  	return nil
   535  }
   536  
   537  func (ss *SqlStore) TotalReadDbConnections() int {
   538  	if len(ss.settings.DataSourceReplicas) == 0 {
   539  		return 0
   540  	}
   541  
   542  	count := 0
   543  	for _, db := range ss.Replicas {
   544  		count = count + db.Db.Stats().OpenConnections
   545  	}
   546  
   547  	return count
   548  }
   549  
   550  func (ss *SqlStore) TotalSearchDbConnections() int {
   551  	if len(ss.settings.DataSourceSearchReplicas) == 0 {
   552  		return 0
   553  	}
   554  
   555  	count := 0
   556  	for _, db := range ss.searchReplicas {
   557  		count = count + db.Db.Stats().OpenConnections
   558  	}
   559  
   560  	return count
   561  }
   562  
   563  func (ss *SqlStore) MarkSystemRanUnitTests() {
   564  	props, err := ss.System().Get()
   565  	if err != nil {
   566  		return
   567  	}
   568  
   569  	unitTests := props[model.SYSTEM_RAN_UNIT_TESTS]
   570  	if unitTests == "" {
   571  		systemTests := &model.System{Name: model.SYSTEM_RAN_UNIT_TESTS, Value: "1"}
   572  		ss.System().Save(systemTests)
   573  	}
   574  }
   575  
   576  func (ss *SqlStore) DoesTableExist(tableName string) bool {
   577  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   578  		count, err := ss.GetMaster().SelectInt(
   579  			`SELECT count(relname) FROM pg_class WHERE relname=$1`,
   580  			strings.ToLower(tableName),
   581  		)
   582  
   583  		if err != nil {
   584  			mlog.Critical("Failed to check if table exists", mlog.Err(err))
   585  			time.Sleep(time.Second)
   586  			os.Exit(ExitTableExists)
   587  		}
   588  
   589  		return count > 0
   590  
   591  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   592  
   593  		count, err := ss.GetMaster().SelectInt(
   594  			`SELECT
   595  		    COUNT(0) AS table_exists
   596  			FROM
   597  			    information_schema.TABLES
   598  			WHERE
   599  			    TABLE_SCHEMA = DATABASE()
   600  			        AND TABLE_NAME = ?
   601  		    `,
   602  			tableName,
   603  		)
   604  
   605  		if err != nil {
   606  			mlog.Critical("Failed to check if table exists", mlog.Err(err))
   607  			time.Sleep(time.Second)
   608  			os.Exit(ExitTableExistsMySQL)
   609  		}
   610  
   611  		return count > 0
   612  
   613  	} else {
   614  		mlog.Critical("Failed to check if column exists because of missing driver")
   615  		time.Sleep(time.Second)
   616  		os.Exit(ExitColumnExists)
   617  		return false
   618  	}
   619  }
   620  
   621  func (ss *SqlStore) DoesColumnExist(tableName string, columnName string) bool {
   622  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   623  		count, err := ss.GetMaster().SelectInt(
   624  			`SELECT COUNT(0)
   625  			FROM   pg_attribute
   626  			WHERE  attrelid = $1::regclass
   627  			AND    attname = $2
   628  			AND    NOT attisdropped`,
   629  			strings.ToLower(tableName),
   630  			strings.ToLower(columnName),
   631  		)
   632  
   633  		if err != nil {
   634  			if err.Error() == "pq: relation \""+strings.ToLower(tableName)+"\" does not exist" {
   635  				return false
   636  			}
   637  
   638  			mlog.Critical("Failed to check if column exists", mlog.Err(err))
   639  			time.Sleep(time.Second)
   640  			os.Exit(ExitDoesColumnExistsPostgres)
   641  		}
   642  
   643  		return count > 0
   644  
   645  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   646  
   647  		count, err := ss.GetMaster().SelectInt(
   648  			`SELECT
   649  		    COUNT(0) AS column_exists
   650  		FROM
   651  		    information_schema.COLUMNS
   652  		WHERE
   653  		    TABLE_SCHEMA = DATABASE()
   654  		        AND TABLE_NAME = ?
   655  		        AND COLUMN_NAME = ?`,
   656  			tableName,
   657  			columnName,
   658  		)
   659  
   660  		if err != nil {
   661  			mlog.Critical("Failed to check if column exists", mlog.Err(err))
   662  			time.Sleep(time.Second)
   663  			os.Exit(ExitDoesColumnExistsMySQL)
   664  		}
   665  
   666  		return count > 0
   667  
   668  	} else {
   669  		mlog.Critical("Failed to check if column exists because of missing driver")
   670  		time.Sleep(time.Second)
   671  		os.Exit(ExitDoesColumnExistsMissing)
   672  		return false
   673  	}
   674  }
   675  
   676  func (ss *SqlStore) DoesIndexExist(indexName string, tableName string) bool {
   677  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   678  		_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
   679  		// It should fail if the index does not exist
   680  		return err == nil
   681  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   682  		count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
   683  		if err != nil {
   684  			mlog.Critical("Failed to check index", mlog.Err(err))
   685  			time.Sleep(time.Second)
   686  			os.Exit(ExitDoesIndexExistsMySQL)
   687  		}
   688  
   689  		if count <= 0 {
   690  			return false
   691  		}
   692  	} else {
   693  		mlog.Critical("Failed to check if index exists because of missing driver")
   694  		time.Sleep(time.Second)
   695  		os.Exit(ExitDoesIndexExists)
   696  	}
   697  
   698  	return true
   699  }
   700  
   701  // GetColumnInfo returns data type information about the given column.
   702  func (ss *SqlStore) GetColumnInfo(tableName, columnName string) (*ColumnInfo, error) {
   703  	var columnInfo ColumnInfo
   704  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   705  		err := ss.GetMaster().SelectOne(&columnInfo,
   706  			`SELECT data_type as DataType,
   707  					COALESCE(character_maximum_length, 0) as CharMaximumLength
   708  			 FROM information_schema.columns
   709  			 WHERE lower(table_name) = lower($1)
   710  			 AND lower(column_name) = lower($2)`,
   711  			tableName, columnName)
   712  		if err != nil {
   713  			return nil, err
   714  		}
   715  		return &columnInfo, nil
   716  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   717  		err := ss.GetMaster().SelectOne(&columnInfo,
   718  			`SELECT data_type as DataType,
   719  					COALESCE(character_maximum_length, 0) as CharMaximumLength
   720  			 FROM information_schema.columns
   721  			 WHERE table_schema = DATABASE()
   722  			 AND lower(table_name) = lower(?)
   723  			 AND lower(column_name) = lower(?)`,
   724  			tableName, columnName)
   725  		if err != nil {
   726  			return nil, err
   727  		}
   728  		return &columnInfo, nil
   729  	}
   730  	return nil, errors.New("Driver not supported for this method")
   731  }
   732  
   733  // IsVarchar returns true if the column type matches one of the varchar types
   734  // either in MySQL or PostgreSQL.
   735  func (ss *SqlStore) IsVarchar(columnType string) bool {
   736  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES && columnType == "character varying" {
   737  		return true
   738  	}
   739  
   740  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL && columnType == "varchar" {
   741  		return true
   742  	}
   743  
   744  	return false
   745  }
   746  
   747  func (ss *SqlStore) DoesTriggerExist(triggerName string) bool {
   748  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   749  		count, err := ss.GetMaster().SelectInt(`
   750  			SELECT
   751  				COUNT(0)
   752  			FROM
   753  				pg_trigger
   754  			WHERE
   755  				tgname = $1
   756  		`, triggerName)
   757  
   758  		if err != nil {
   759  			mlog.Critical("Failed to check if trigger exists", mlog.Err(err))
   760  			time.Sleep(time.Second)
   761  			os.Exit(ExitGenericFailure)
   762  		}
   763  
   764  		return count > 0
   765  
   766  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   767  		count, err := ss.GetMaster().SelectInt(`
   768  			SELECT
   769  				COUNT(0)
   770  			FROM
   771  				information_schema.triggers
   772  			WHERE
   773  				trigger_schema = DATABASE()
   774  			AND	trigger_name = ?
   775  		`, triggerName)
   776  
   777  		if err != nil {
   778  			mlog.Critical("Failed to check if trigger exists", mlog.Err(err))
   779  			time.Sleep(time.Second)
   780  			os.Exit(ExitGenericFailure)
   781  		}
   782  
   783  		return count > 0
   784  
   785  	} else {
   786  		mlog.Critical("Failed to check if column exists because of missing driver")
   787  		time.Sleep(time.Second)
   788  		os.Exit(ExitGenericFailure)
   789  		return false
   790  	}
   791  }
   792  
   793  func (ss *SqlStore) CreateColumnIfNotExists(tableName string, columnName string, mySqlColType string, postgresColType string, defaultValue string) bool {
   794  
   795  	if ss.DoesColumnExist(tableName, columnName) {
   796  		return false
   797  	}
   798  
   799  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   800  		_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'")
   801  		if err != nil {
   802  			mlog.Critical("Failed to create column", mlog.Err(err))
   803  			time.Sleep(time.Second)
   804  			os.Exit(ExitCreateColumnPostgres)
   805  		}
   806  
   807  		return true
   808  
   809  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   810  		_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'")
   811  		if err != nil {
   812  			mlog.Critical("Failed to create column", mlog.Err(err))
   813  			time.Sleep(time.Second)
   814  			os.Exit(ExitCreateColumnMySQL)
   815  		}
   816  
   817  		return true
   818  
   819  	} else {
   820  		mlog.Critical("Failed to create column because of missing driver")
   821  		time.Sleep(time.Second)
   822  		os.Exit(ExitCreateColumnMissing)
   823  		return false
   824  	}
   825  }
   826  
   827  func (ss *SqlStore) CreateColumnIfNotExistsNoDefault(tableName string, columnName string, mySqlColType string, postgresColType string) bool {
   828  
   829  	if ss.DoesColumnExist(tableName, columnName) {
   830  		return false
   831  	}
   832  
   833  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   834  		_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType)
   835  		if err != nil {
   836  			mlog.Critical("Failed to create column", mlog.Err(err))
   837  			time.Sleep(time.Second)
   838  			os.Exit(ExitCreateColumnPostgres)
   839  		}
   840  
   841  		return true
   842  
   843  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   844  		_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType)
   845  		if err != nil {
   846  			mlog.Critical("Failed to create column", mlog.Err(err))
   847  			time.Sleep(time.Second)
   848  			os.Exit(ExitCreateColumnMySQL)
   849  		}
   850  
   851  		return true
   852  
   853  	} else {
   854  		mlog.Critical("Failed to create column because of missing driver")
   855  		time.Sleep(time.Second)
   856  		os.Exit(ExitCreateColumnMissing)
   857  		return false
   858  	}
   859  }
   860  
   861  func (ss *SqlStore) RemoveColumnIfExists(tableName string, columnName string) bool {
   862  
   863  	if !ss.DoesColumnExist(tableName, columnName) {
   864  		return false
   865  	}
   866  
   867  	_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName)
   868  	if err != nil {
   869  		mlog.Critical("Failed to drop column", mlog.Err(err))
   870  		time.Sleep(time.Second)
   871  		os.Exit(ExitRemoveColumn)
   872  	}
   873  
   874  	return true
   875  }
   876  
   877  func (ss *SqlStore) RemoveTableIfExists(tableName string) bool {
   878  	if !ss.DoesTableExist(tableName) {
   879  		return false
   880  	}
   881  
   882  	_, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName)
   883  	if err != nil {
   884  		mlog.Critical("Failed to drop table", mlog.Err(err))
   885  		time.Sleep(time.Second)
   886  		os.Exit(ExitRemoveTable)
   887  	}
   888  
   889  	return true
   890  }
   891  
   892  func (ss *SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool {
   893  	if !ss.DoesColumnExist(tableName, oldColumnName) {
   894  		return false
   895  	}
   896  
   897  	var err error
   898  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   899  		_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType)
   900  	} else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   901  		_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " RENAME COLUMN " + oldColumnName + " TO " + newColumnName)
   902  	}
   903  
   904  	if err != nil {
   905  		mlog.Critical("Failed to rename column", mlog.Err(err))
   906  		time.Sleep(time.Second)
   907  		os.Exit(ExitRenameColumn)
   908  	}
   909  
   910  	return true
   911  }
   912  
   913  func (ss *SqlStore) GetMaxLengthOfColumnIfExists(tableName string, columnName string) string {
   914  	if !ss.DoesColumnExist(tableName, columnName) {
   915  		return ""
   916  	}
   917  
   918  	var result string
   919  	var err error
   920  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   921  		result, err = ss.GetMaster().SelectStr("SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.columns WHERE table_name = '" + tableName + "' AND COLUMN_NAME = '" + columnName + "'")
   922  	} else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   923  		result, err = ss.GetMaster().SelectStr("SELECT character_maximum_length FROM information_schema.columns WHERE table_name = '" + strings.ToLower(tableName) + "' AND column_name = '" + strings.ToLower(columnName) + "'")
   924  	}
   925  
   926  	if err != nil {
   927  		mlog.Critical("Failed to get max length of column", mlog.Err(err))
   928  		time.Sleep(time.Second)
   929  		os.Exit(ExitMaxColumn)
   930  	}
   931  
   932  	return result
   933  }
   934  
   935  func (ss *SqlStore) AlterColumnTypeIfExists(tableName string, columnName string, mySqlColType string, postgresColType string) bool {
   936  	if !ss.DoesColumnExist(tableName, columnName) {
   937  		return false
   938  	}
   939  
   940  	var err error
   941  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   942  		_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " MODIFY " + columnName + " " + mySqlColType)
   943  	} else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   944  		_, err = ss.GetMaster().ExecNoTimeout("ALTER TABLE " + strings.ToLower(tableName) + " ALTER COLUMN " + strings.ToLower(columnName) + " TYPE " + postgresColType)
   945  	}
   946  
   947  	if err != nil {
   948  		mlog.Critical("Failed to alter column type", mlog.Err(err))
   949  		time.Sleep(time.Second)
   950  		os.Exit(ExitAlterColumn)
   951  	}
   952  
   953  	return true
   954  }
   955  
   956  func (ss *SqlStore) RemoveDefaultIfColumnExists(tableName, columnName string) bool {
   957  	if !ss.DoesColumnExist(tableName, columnName) {
   958  		return false
   959  	}
   960  
   961  	_, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " DROP DEFAULT")
   962  	if err != nil {
   963  		mlog.Critical("Failed to drop column default", mlog.String("table", tableName), mlog.String("column", columnName), mlog.Err(err))
   964  		time.Sleep(time.Second)
   965  		os.Exit(ExitGenericFailure)
   966  		return false
   967  	}
   968  
   969  	return true
   970  }
   971  
   972  func (ss *SqlStore) AlterDefaultIfColumnExists(tableName string, columnName string, mySqlColDefault *string, postgresColDefault *string) bool {
   973  	if !ss.DoesColumnExist(tableName, columnName) {
   974  		return false
   975  	}
   976  
   977  	var defaultValue string
   978  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
   979  		// Some column types in MySQL cannot have defaults, so don't try to configure anything.
   980  		if mySqlColDefault == nil {
   981  			return true
   982  		}
   983  
   984  		defaultValue = *mySqlColDefault
   985  	} else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
   986  		// Postgres doesn't have the same limitation, but preserve the interface.
   987  		if postgresColDefault == nil {
   988  			return true
   989  		}
   990  
   991  		tableName = strings.ToLower(tableName)
   992  		columnName = strings.ToLower(columnName)
   993  		defaultValue = *postgresColDefault
   994  	} else {
   995  		mlog.Critical("Failed to alter column default because of missing driver")
   996  		time.Sleep(time.Second)
   997  		os.Exit(ExitGenericFailure)
   998  		return false
   999  	}
  1000  
  1001  	if defaultValue == "" {
  1002  		defaultValue = "''"
  1003  	}
  1004  
  1005  	query := "ALTER TABLE " + tableName + " ALTER COLUMN " + columnName + " SET DEFAULT " + defaultValue
  1006  	_, err := ss.GetMaster().ExecNoTimeout(query)
  1007  	if err != nil {
  1008  		mlog.Critical("Failed to alter column default", mlog.String("table", tableName), mlog.String("column", columnName), mlog.String("default value", defaultValue), mlog.Err(err))
  1009  		time.Sleep(time.Second)
  1010  		os.Exit(ExitGenericFailure)
  1011  		return false
  1012  	}
  1013  
  1014  	return true
  1015  }
  1016  
  1017  func (ss *SqlStore) AlterPrimaryKey(tableName string, columnNames []string) bool {
  1018  	var currentPrimaryKey string
  1019  	var err error
  1020  	// get the current primary key as a comma separated list of columns
  1021  	switch ss.DriverName() {
  1022  	case model.DATABASE_DRIVER_MYSQL:
  1023  		query := `
  1024  			SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) AS PK
  1025  		FROM
  1026  			information_schema.statistics
  1027  		WHERE
  1028  			table_schema = DATABASE()
  1029  		AND table_name = ?
  1030  		AND index_name = 'PRIMARY'
  1031  		GROUP BY
  1032  			index_name`
  1033  		currentPrimaryKey, err = ss.GetMaster().SelectStr(query, tableName)
  1034  	case model.DATABASE_DRIVER_POSTGRES:
  1035  		query := `
  1036  			SELECT string_agg(a.attname, ',') AS pk
  1037  		FROM
  1038  			pg_constraint AS c
  1039  		CROSS JOIN
  1040  			(SELECT unnest(conkey) FROM pg_constraint WHERE conrelid='` + strings.ToLower(tableName) + `'::REGCLASS AND contype='p') AS cols(colnum)
  1041  		INNER JOIN
  1042  			pg_attribute AS a ON a.attrelid = c.conrelid
  1043  		AND cols.colnum = a.attnum
  1044  		WHERE
  1045  			c.contype = 'p'
  1046  		AND c.conrelid = '` + strings.ToLower(tableName) + `'::REGCLASS`
  1047  		currentPrimaryKey, err = ss.GetMaster().SelectStr(query)
  1048  	}
  1049  	if err != nil {
  1050  		mlog.Critical("Failed to get current primary key", mlog.String("table", tableName), mlog.Err(err))
  1051  		time.Sleep(time.Second)
  1052  		os.Exit(ExitAlterPrimaryKey)
  1053  	}
  1054  
  1055  	primaryKey := strings.Join(columnNames, ",")
  1056  	if strings.EqualFold(currentPrimaryKey, primaryKey) {
  1057  		return false
  1058  	}
  1059  	// alter primary key
  1060  	var alterQuery string
  1061  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1062  		alterQuery = "ALTER TABLE " + tableName + " DROP PRIMARY KEY, ADD PRIMARY KEY (" + primaryKey + ")"
  1063  	} else if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1064  		alterQuery = "ALTER TABLE " + tableName + " DROP CONSTRAINT " + strings.ToLower(tableName) + "_pkey, ADD PRIMARY KEY (" + strings.ToLower(primaryKey) + ")"
  1065  	}
  1066  	_, err = ss.GetMaster().ExecNoTimeout(alterQuery)
  1067  	if err != nil {
  1068  		mlog.Critical("Failed to alter primary key", mlog.String("table", tableName), mlog.Err(err))
  1069  		time.Sleep(time.Second)
  1070  		os.Exit(ExitAlterPrimaryKey)
  1071  	}
  1072  	return true
  1073  }
  1074  
  1075  func (ss *SqlStore) CreateUniqueIndexIfNotExists(indexName string, tableName string, columnName string) bool {
  1076  	return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, IndexTypeDefault, true)
  1077  }
  1078  
  1079  func (ss *SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) bool {
  1080  	return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, IndexTypeDefault, false)
  1081  }
  1082  
  1083  func (ss *SqlStore) CreateCompositeIndexIfNotExists(indexName string, tableName string, columnNames []string) bool {
  1084  	return ss.createIndexIfNotExists(indexName, tableName, columnNames, IndexTypeDefault, false)
  1085  }
  1086  
  1087  func (ss *SqlStore) CreateUniqueCompositeIndexIfNotExists(indexName string, tableName string, columnNames []string) bool {
  1088  	return ss.createIndexIfNotExists(indexName, tableName, columnNames, IndexTypeDefault, true)
  1089  }
  1090  
  1091  func (ss *SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) bool {
  1092  	return ss.createIndexIfNotExists(indexName, tableName, []string{columnName}, IndexTypeFullText, false)
  1093  }
  1094  
  1095  func (ss *SqlStore) CreateFullTextFuncIndexIfNotExists(indexName string, tableName string, function string) bool {
  1096  	return ss.createIndexIfNotExists(indexName, tableName, []string{function}, IndexTypeFullTextFunc, false)
  1097  }
  1098  
  1099  func (ss *SqlStore) createIndexIfNotExists(indexName string, tableName string, columnNames []string, indexType string, unique bool) bool {
  1100  
  1101  	uniqueStr := ""
  1102  	if unique {
  1103  		uniqueStr = "UNIQUE "
  1104  	}
  1105  
  1106  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1107  		_, errExists := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
  1108  		// It should fail if the index does not exist
  1109  		if errExists == nil {
  1110  			return false
  1111  		}
  1112  
  1113  		query := ""
  1114  		if indexType == IndexTypeFullText {
  1115  			if len(columnNames) != 1 {
  1116  				mlog.Critical("Unable to create multi column full text index")
  1117  				os.Exit(ExitCreateIndexPostgres)
  1118  			}
  1119  			columnName := columnNames[0]
  1120  			postgresColumnNames := convertMySQLFullTextColumnsToPostgres(columnName)
  1121  			query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + postgresColumnNames + "))"
  1122  		} else if indexType == IndexTypeFullTextFunc {
  1123  			if len(columnNames) != 1 {
  1124  				mlog.Critical("Unable to create multi column full text index")
  1125  				os.Exit(ExitCreateIndexPostgres)
  1126  			}
  1127  			columnName := columnNames[0]
  1128  			query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + columnName + "))"
  1129  		} else {
  1130  			query = "CREATE " + uniqueStr + "INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")"
  1131  		}
  1132  
  1133  		_, err := ss.GetMaster().ExecNoTimeout(query)
  1134  		if err != nil {
  1135  			mlog.Critical("Failed to create index", mlog.Err(errExists), mlog.Err(err))
  1136  			time.Sleep(time.Second)
  1137  			os.Exit(ExitCreateIndexPostgres)
  1138  		}
  1139  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1140  
  1141  		count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
  1142  		if err != nil {
  1143  			mlog.Critical("Failed to check index", mlog.Err(err))
  1144  			time.Sleep(time.Second)
  1145  			os.Exit(ExitCreateIndexMySQL)
  1146  		}
  1147  
  1148  		if count > 0 {
  1149  			return false
  1150  		}
  1151  
  1152  		fullTextIndex := ""
  1153  		if indexType == IndexTypeFullText {
  1154  			fullTextIndex = " FULLTEXT "
  1155  		}
  1156  
  1157  		_, err = ss.GetMaster().ExecNoTimeout("CREATE  " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")")
  1158  		if err != nil {
  1159  			mlog.Critical("Failed to create index", mlog.String("table", tableName), mlog.String("index_name", indexName), mlog.Err(err))
  1160  			time.Sleep(time.Second)
  1161  			os.Exit(ExitCreateIndexFullMySQL)
  1162  		}
  1163  	} else {
  1164  		mlog.Critical("Failed to create index because of missing driver")
  1165  		time.Sleep(time.Second)
  1166  		os.Exit(ExitCreateIndexMissing)
  1167  	}
  1168  
  1169  	return true
  1170  }
  1171  
  1172  func (ss *SqlStore) CreateForeignKeyIfNotExists(
  1173  	tableName, columnName, refTableName, refColumnName string,
  1174  	onDeleteCascade bool,
  1175  ) (err error) {
  1176  	deleteClause := ""
  1177  	if onDeleteCascade {
  1178  		deleteClause = "ON DELETE CASCADE"
  1179  	}
  1180  	constraintName := "FK_" + tableName + "_" + refTableName
  1181  	sQuery := `
  1182  	ALTER TABLE ` + tableName + `
  1183  	ADD CONSTRAINT ` + constraintName + `
  1184  	FOREIGN KEY (` + columnName + `) REFERENCES ` + refTableName + ` (` + refColumnName + `)
  1185  	` + deleteClause + `;`
  1186  	_, err = ss.GetMaster().ExecNoTimeout(sQuery)
  1187  	if IsConstraintAlreadyExistsError(err) {
  1188  		err = nil
  1189  	}
  1190  	if err != nil {
  1191  		mlog.Warn("Could not create foreign key: " + err.Error())
  1192  	}
  1193  	return
  1194  }
  1195  
  1196  func (ss *SqlStore) RemoveIndexIfExists(indexName string, tableName string) bool {
  1197  
  1198  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1199  		_, err := ss.GetMaster().SelectStr("SELECT $1::regclass", indexName)
  1200  		// It should fail if the index does not exist
  1201  		if err != nil {
  1202  			return false
  1203  		}
  1204  
  1205  		_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName)
  1206  		if err != nil {
  1207  			mlog.Critical("Failed to remove index", mlog.Err(err))
  1208  			time.Sleep(time.Second)
  1209  			os.Exit(ExitRemoveIndexPostgres)
  1210  		}
  1211  
  1212  		return true
  1213  	} else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1214  
  1215  		count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName)
  1216  		if err != nil {
  1217  			mlog.Critical("Failed to check index", mlog.Err(err))
  1218  			time.Sleep(time.Second)
  1219  			os.Exit(ExitRemoveIndexMySQL)
  1220  		}
  1221  
  1222  		if count <= 0 {
  1223  			return false
  1224  		}
  1225  
  1226  		_, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName)
  1227  		if err != nil {
  1228  			mlog.Critical("Failed to remove index", mlog.Err(err))
  1229  			time.Sleep(time.Second)
  1230  			os.Exit(ExitRemoveIndexMySQL)
  1231  		}
  1232  	} else {
  1233  		mlog.Critical("Failed to create index because of missing driver")
  1234  		time.Sleep(time.Second)
  1235  		os.Exit(ExitRemoveIndexMissing)
  1236  	}
  1237  
  1238  	return true
  1239  }
  1240  
  1241  func IsConstraintAlreadyExistsError(err error) bool {
  1242  	switch dbErr := err.(type) {
  1243  	case *pq.Error:
  1244  		if dbErr.Code == PGDuplicateObjectErrorCode {
  1245  			return true
  1246  		}
  1247  	case *mysql.MySQLError:
  1248  		if dbErr.Number == MySQLDuplicateObjectErrorCode {
  1249  			return true
  1250  		}
  1251  	}
  1252  	return false
  1253  }
  1254  
  1255  func IsUniqueConstraintError(err error, indexName []string) bool {
  1256  	unique := false
  1257  	if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "23505" {
  1258  		unique = true
  1259  	}
  1260  
  1261  	if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == 1062 {
  1262  		unique = true
  1263  	}
  1264  
  1265  	field := false
  1266  	for _, contain := range indexName {
  1267  		if strings.Contains(err.Error(), contain) {
  1268  			field = true
  1269  			break
  1270  		}
  1271  	}
  1272  
  1273  	return unique && field
  1274  }
  1275  
  1276  func (ss *SqlStore) GetAllConns() []*gorp.DbMap {
  1277  	all := make([]*gorp.DbMap, len(ss.Replicas)+1)
  1278  	copy(all, ss.Replicas)
  1279  	all[len(ss.Replicas)] = ss.master
  1280  	return all
  1281  }
  1282  
  1283  // RecycleDBConnections closes active connections by setting the max conn lifetime
  1284  // to d, and then resets them back to their original duration.
  1285  func (ss *SqlStore) RecycleDBConnections(d time.Duration) {
  1286  	// Get old time.
  1287  	originalDuration := time.Duration(*ss.settings.ConnMaxLifetimeMilliseconds) * time.Millisecond
  1288  	// Set the max lifetimes for all connections.
  1289  	for _, conn := range ss.GetAllConns() {
  1290  		conn.Db.SetConnMaxLifetime(d)
  1291  	}
  1292  	// Wait for that period with an additional 2 seconds of scheduling delay.
  1293  	time.Sleep(d + 2*time.Second)
  1294  	// Reset max lifetime back to original value.
  1295  	for _, conn := range ss.GetAllConns() {
  1296  		conn.Db.SetConnMaxLifetime(originalDuration)
  1297  	}
  1298  }
  1299  
  1300  func (ss *SqlStore) Close() {
  1301  	ss.master.Db.Close()
  1302  	for _, replica := range ss.Replicas {
  1303  		replica.Db.Close()
  1304  	}
  1305  
  1306  	for _, replica := range ss.searchReplicas {
  1307  		replica.Db.Close()
  1308  	}
  1309  }
  1310  
  1311  func (ss *SqlStore) LockToMaster() {
  1312  	ss.lockedToMaster = true
  1313  }
  1314  
  1315  func (ss *SqlStore) UnlockFromMaster() {
  1316  	ss.lockedToMaster = false
  1317  }
  1318  
  1319  func (ss *SqlStore) Team() store.TeamStore {
  1320  	return ss.stores.team
  1321  }
  1322  
  1323  func (ss *SqlStore) Channel() store.ChannelStore {
  1324  	return ss.stores.channel
  1325  }
  1326  
  1327  func (ss *SqlStore) Post() store.PostStore {
  1328  	return ss.stores.post
  1329  }
  1330  
  1331  func (ss *SqlStore) RetentionPolicy() store.RetentionPolicyStore {
  1332  	return ss.stores.retentionPolicy
  1333  }
  1334  
  1335  func (ss *SqlStore) User() store.UserStore {
  1336  	return ss.stores.user
  1337  }
  1338  
  1339  func (ss *SqlStore) Bot() store.BotStore {
  1340  	return ss.stores.bot
  1341  }
  1342  
  1343  func (ss *SqlStore) Session() store.SessionStore {
  1344  	return ss.stores.session
  1345  }
  1346  
  1347  func (ss *SqlStore) Audit() store.AuditStore {
  1348  	return ss.stores.audit
  1349  }
  1350  
  1351  func (ss *SqlStore) ClusterDiscovery() store.ClusterDiscoveryStore {
  1352  	return ss.stores.cluster
  1353  }
  1354  
  1355  func (ss *SqlStore) RemoteCluster() store.RemoteClusterStore {
  1356  	return ss.stores.remoteCluster
  1357  }
  1358  
  1359  func (ss *SqlStore) Compliance() store.ComplianceStore {
  1360  	return ss.stores.compliance
  1361  }
  1362  
  1363  func (ss *SqlStore) OAuth() store.OAuthStore {
  1364  	return ss.stores.oauth
  1365  }
  1366  
  1367  func (ss *SqlStore) System() store.SystemStore {
  1368  	return ss.stores.system
  1369  }
  1370  
  1371  func (ss *SqlStore) Webhook() store.WebhookStore {
  1372  	return ss.stores.webhook
  1373  }
  1374  
  1375  func (ss *SqlStore) Command() store.CommandStore {
  1376  	return ss.stores.command
  1377  }
  1378  
  1379  func (ss *SqlStore) CommandWebhook() store.CommandWebhookStore {
  1380  	return ss.stores.commandWebhook
  1381  }
  1382  
  1383  func (ss *SqlStore) Preference() store.PreferenceStore {
  1384  	return ss.stores.preference
  1385  }
  1386  
  1387  func (ss *SqlStore) License() store.LicenseStore {
  1388  	return ss.stores.license
  1389  }
  1390  
  1391  func (ss *SqlStore) Token() store.TokenStore {
  1392  	return ss.stores.token
  1393  }
  1394  
  1395  func (ss *SqlStore) Emoji() store.EmojiStore {
  1396  	return ss.stores.emoji
  1397  }
  1398  
  1399  func (ss *SqlStore) Status() store.StatusStore {
  1400  	return ss.stores.status
  1401  }
  1402  
  1403  func (ss *SqlStore) FileInfo() store.FileInfoStore {
  1404  	return ss.stores.fileInfo
  1405  }
  1406  
  1407  func (ss *SqlStore) UploadSession() store.UploadSessionStore {
  1408  	return ss.stores.uploadSession
  1409  }
  1410  
  1411  func (ss *SqlStore) Reaction() store.ReactionStore {
  1412  	return ss.stores.reaction
  1413  }
  1414  
  1415  func (ss *SqlStore) Job() store.JobStore {
  1416  	return ss.stores.job
  1417  }
  1418  
  1419  func (ss *SqlStore) UserAccessToken() store.UserAccessTokenStore {
  1420  	return ss.stores.userAccessToken
  1421  }
  1422  
  1423  func (ss *SqlStore) ChannelMemberHistory() store.ChannelMemberHistoryStore {
  1424  	return ss.stores.channelMemberHistory
  1425  }
  1426  
  1427  func (ss *SqlStore) Plugin() store.PluginStore {
  1428  	return ss.stores.plugin
  1429  }
  1430  
  1431  func (ss *SqlStore) Thread() store.ThreadStore {
  1432  	return ss.stores.thread
  1433  }
  1434  
  1435  func (ss *SqlStore) Role() store.RoleStore {
  1436  	return ss.stores.role
  1437  }
  1438  
  1439  func (ss *SqlStore) TermsOfService() store.TermsOfServiceStore {
  1440  	return ss.stores.TermsOfService
  1441  }
  1442  
  1443  func (ss *SqlStore) ProductNotices() store.ProductNoticesStore {
  1444  	return ss.stores.productNotices
  1445  }
  1446  
  1447  func (ss *SqlStore) UserTermsOfService() store.UserTermsOfServiceStore {
  1448  	return ss.stores.UserTermsOfService
  1449  }
  1450  
  1451  func (ss *SqlStore) Scheme() store.SchemeStore {
  1452  	return ss.stores.scheme
  1453  }
  1454  
  1455  func (ss *SqlStore) Group() store.GroupStore {
  1456  	return ss.stores.group
  1457  }
  1458  
  1459  func (ss *SqlStore) LinkMetadata() store.LinkMetadataStore {
  1460  	return ss.stores.linkMetadata
  1461  }
  1462  
  1463  func (ss *SqlStore) SharedChannel() store.SharedChannelStore {
  1464  	return ss.stores.sharedchannel
  1465  }
  1466  
  1467  func (ss *SqlStore) DropAllTables() {
  1468  	ss.master.TruncateTables()
  1469  }
  1470  
  1471  func (ss *SqlStore) getQueryBuilder() sq.StatementBuilderType {
  1472  	builder := sq.StatementBuilder.PlaceholderFormat(sq.Question)
  1473  	if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES {
  1474  		builder = builder.PlaceholderFormat(sq.Dollar)
  1475  	}
  1476  	return builder
  1477  }
  1478  
  1479  func (ss *SqlStore) CheckIntegrity() <-chan model.IntegrityCheckResult {
  1480  	results := make(chan model.IntegrityCheckResult)
  1481  	go CheckRelationalIntegrity(ss, results)
  1482  	return results
  1483  }
  1484  
  1485  func (ss *SqlStore) UpdateLicense(license *model.License) {
  1486  	ss.licenseMutex.Lock()
  1487  	defer ss.licenseMutex.Unlock()
  1488  	ss.license = license
  1489  }
  1490  
  1491  func (ss *SqlStore) GetLicense() *model.License {
  1492  	return ss.license
  1493  }
  1494  
  1495  func (ss *SqlStore) migrate(direction migrationDirection) error {
  1496  	var driver database.Driver
  1497  	var err error
  1498  
  1499  	// When WithInstance is used in golang-migrate, the underlying driver connections are not tracked.
  1500  	// So we will have to open a fresh connection for migrations and explicitly close it when all is done.
  1501  	dataSource, err := ss.appendMultipleStatementsFlag(*ss.settings.DataSource)
  1502  	if err != nil {
  1503  		return err
  1504  	}
  1505  	conn := setupConnection("migrations", dataSource, ss.settings)
  1506  	defer conn.Db.Close()
  1507  
  1508  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1509  		driver, err = mysqlmigrate.WithInstance(conn.Db, &mysqlmigrate.Config{})
  1510  		if err != nil {
  1511  			return err
  1512  		}
  1513  	} else {
  1514  		driver, err = postgres.WithInstance(conn.Db, &postgres.Config{})
  1515  		if err != nil {
  1516  			return err
  1517  		}
  1518  	}
  1519  
  1520  	var assetNamesForDriver []string
  1521  	for _, assetName := range migrations.AssetNames() {
  1522  		if strings.HasPrefix(assetName, ss.DriverName()) {
  1523  			assetNamesForDriver = append(assetNamesForDriver, filepath.Base(assetName))
  1524  		}
  1525  	}
  1526  
  1527  	source := bindata.Resource(assetNamesForDriver, func(name string) ([]byte, error) {
  1528  		return migrations.Asset(filepath.Join(ss.DriverName(), name))
  1529  	})
  1530  
  1531  	sourceDriver, err := bindata.WithInstance(source)
  1532  	if err != nil {
  1533  		return err
  1534  	}
  1535  
  1536  	migrations, err := migrate.NewWithInstance("go-bindata",
  1537  		sourceDriver,
  1538  		ss.DriverName(),
  1539  		driver)
  1540  
  1541  	if err != nil {
  1542  		return err
  1543  	}
  1544  	defer migrations.Close()
  1545  
  1546  	switch direction {
  1547  	case migrationsDirectionUp:
  1548  		err = migrations.Up()
  1549  	case migrationsDirectionDown:
  1550  		err = migrations.Down()
  1551  	default:
  1552  		return errors.New(fmt.Sprintf("unsupported migration direction %s", direction))
  1553  	}
  1554  
  1555  	if err != nil && err != migrate.ErrNoChange && !errors.Is(err, os.ErrNotExist) {
  1556  		return err
  1557  	}
  1558  
  1559  	return nil
  1560  }
  1561  
  1562  func (ss *SqlStore) appendMultipleStatementsFlag(dataSource string) (string, error) {
  1563  	// We need to tell the MySQL driver that we want to use multiStatements
  1564  	// in order to make migrations work.
  1565  	if ss.DriverName() == model.DATABASE_DRIVER_MYSQL {
  1566  		config, err := mysql.ParseDSN(dataSource)
  1567  		if err != nil {
  1568  			return "", err
  1569  		}
  1570  
  1571  		if config.Params == nil {
  1572  			config.Params = map[string]string{}
  1573  		}
  1574  
  1575  		config.Params["multiStatements"] = "true"
  1576  		return config.FormatDSN(), nil
  1577  	}
  1578  
  1579  	return dataSource, nil
  1580  }
  1581  
  1582  func resetReadTimeout(dataSource string) (string, error) {
  1583  	config, err := mysql.ParseDSN(dataSource)
  1584  	if err != nil {
  1585  		return "", err
  1586  	}
  1587  	config.ReadTimeout = 0
  1588  	return config.FormatDSN(), nil
  1589  }
  1590  
  1591  type mattermConverter struct{}
  1592  
  1593  func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
  1594  
  1595  	switch t := val.(type) {
  1596  	case model.StringMap:
  1597  		return model.MapToJson(t), nil
  1598  	case map[string]string:
  1599  		return model.MapToJson(model.StringMap(t)), nil
  1600  	case model.StringArray:
  1601  		return model.ArrayToJson(t), nil
  1602  	case model.StringInterface:
  1603  		return model.StringInterfaceToJson(t), nil
  1604  	case map[string]interface{}:
  1605  		return model.StringInterfaceToJson(model.StringInterface(t)), nil
  1606  	case JSONSerializable:
  1607  		return t.ToJson(), nil
  1608  	case *opengraph.OpenGraph:
  1609  		return json.Marshal(t)
  1610  	}
  1611  
  1612  	return val, nil
  1613  }
  1614  
  1615  func (me mattermConverter) FromDb(target interface{}) (gorp.CustomScanner, bool) {
  1616  	switch target.(type) {
  1617  	case *model.StringMap:
  1618  		binder := func(holder, target interface{}) error {
  1619  			s, ok := holder.(*string)
  1620  			if !ok {
  1621  				return errors.New(i18n.T("store.sql.convert_string_map"))
  1622  			}
  1623  			b := []byte(*s)
  1624  			return json.Unmarshal(b, target)
  1625  		}
  1626  		return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
  1627  	case *map[string]string:
  1628  		binder := func(holder, target interface{}) error {
  1629  			s, ok := holder.(*string)
  1630  			if !ok {
  1631  				return errors.New(i18n.T("store.sql.convert_string_map"))
  1632  			}
  1633  			b := []byte(*s)
  1634  			return json.Unmarshal(b, target)
  1635  		}
  1636  		return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
  1637  	case *model.StringArray:
  1638  		binder := func(holder, target interface{}) error {
  1639  			s, ok := holder.(*string)
  1640  			if !ok {
  1641  				return errors.New(i18n.T("store.sql.convert_string_array"))
  1642  			}
  1643  			b := []byte(*s)
  1644  			return json.Unmarshal(b, target)
  1645  		}
  1646  		return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
  1647  	case *model.StringInterface:
  1648  		binder := func(holder, target interface{}) error {
  1649  			s, ok := holder.(*string)
  1650  			if !ok {
  1651  				return errors.New(i18n.T("store.sql.convert_string_interface"))
  1652  			}
  1653  			b := []byte(*s)
  1654  			return json.Unmarshal(b, target)
  1655  		}
  1656  		return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
  1657  	case *map[string]interface{}:
  1658  		binder := func(holder, target interface{}) error {
  1659  			s, ok := holder.(*string)
  1660  			if !ok {
  1661  				return errors.New(i18n.T("store.sql.convert_string_interface"))
  1662  			}
  1663  			b := []byte(*s)
  1664  			return json.Unmarshal(b, target)
  1665  		}
  1666  		return gorp.CustomScanner{Holder: new(string), Target: target, Binder: binder}, true
  1667  	}
  1668  
  1669  	return gorp.CustomScanner{}, false
  1670  }
  1671  
  1672  type JSONSerializable interface {
  1673  	ToJson() string
  1674  }
  1675  
  1676  func convertMySQLFullTextColumnsToPostgres(columnNames string) string {
  1677  	columns := strings.Split(columnNames, ", ")
  1678  	concatenatedColumnNames := ""
  1679  	for i, c := range columns {
  1680  		concatenatedColumnNames += c
  1681  		if i < len(columns)-1 {
  1682  			concatenatedColumnNames += " || ' ' || "
  1683  		}
  1684  	}
  1685  
  1686  	return concatenatedColumnNames
  1687  }
  1688  
  1689  // IsDuplicate checks whether an error is a duplicate key error, which comes when processes are competing on creating the same
  1690  // tables in the database.
  1691  func IsDuplicate(err error) bool {
  1692  	var pqErr *pq.Error
  1693  	var mysqlErr *mysql.MySQLError
  1694  	switch {
  1695  	case errors.As(errors.Cause(err), &pqErr):
  1696  		if pqErr.Code == PGDupTableErrorCode {
  1697  			return true
  1698  		}
  1699  	case errors.As(errors.Cause(err), &mysqlErr):
  1700  		if mysqlErr.Number == MySQLDupTableErrorCode {
  1701  			return true
  1702  		}
  1703  	}
  1704  
  1705  	return false
  1706  }
  1707  
  1708  // VersionString converts an integer representation of a DB version
  1709  // to a pretty-printed string.
  1710  // Postgres doesn't follow three-part version numbers from 10.0 onwards:
  1711  // https://www.postgresql.org/docs/13/libpq-status.html#LIBPQ-PQSERVERVERSION.
  1712  func VersionString(v int) string {
  1713  	minor := v % 10000
  1714  	major := v / 10000
  1715  	return strconv.Itoa(major) + "." + strconv.Itoa(minor)
  1716  }