github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/store/sqlstore/store.go (about)

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