[MM-42167] Add configuration tables to migration system (#19671)

* add configuration tables to migration system

* config: move config migrations into new system completely

* add mysql specific bits

* use helper db instance instead

* fix typo

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2022-03-08 15:07:37 +03:00
коммит произвёл GitHub
родитель 4da27d8e30
Коммит e49cc0313a
15 изменённых файлов: 632 добавлений и 50 удалений

Просмотреть файл

@@ -5,8 +5,11 @@ package config
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"path/filepath"
"strings"
"github.com/jmoiron/sqlx"
@@ -17,8 +20,15 @@ import (
// Load the Postgres driver
_ "github.com/lib/pq"
"github.com/mattermost/mattermost-server/v6/config/migrations"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/morph"
"github.com/mattermost/morph/drivers"
ms "github.com/mattermost/morph/drivers/mysql"
ps "github.com/mattermost/morph/drivers/postgres"
mbindata "github.com/mattermost/morph/sources/go_bindata"
)
// MaxWriteLength defines the maximum length accepted for write to the Configurations or
@@ -27,6 +37,12 @@ import (
// It is imposed by MySQL's default max_allowed_packet value of 4Mb.
const MaxWriteLength = 4 * 1024 * 1024
// We use the something different from the default migration table name of morph
const migrationsTableName = "db_config_migrations"
// The timeout value for each migration file to run.
const migrationsTimeoutInSeconds = 100000
// DatabaseStore is a config store backed by a database.
// Not to be used directly. Only to be used as a backing store for config.Store
type DatabaseStore struct {
@@ -63,7 +79,7 @@ func NewDatabaseStore(dsn string) (ds *DatabaseStore, err error) {
dataSourceName: dataSourceName,
db: db,
}
if err = initializeConfigurationsTable(ds.db); err != nil {
if err = ds.initializeConfigurationsTable(); err != nil {
err = errors.Wrap(err, "failed to initialize")
return nil, err
}
@@ -74,61 +90,75 @@ func NewDatabaseStore(dsn string) (ds *DatabaseStore, err error) {
// initializeConfigurationsTable ensures the requisite tables in place to form the backing store.
//
// Uses MEDIUMTEXT on MySQL, and TEXT on sane databases.
func initializeConfigurationsTable(db *sqlx.DB) error {
mysqlCharset := ""
if db.DriverName() == "mysql" {
mysqlCharset = "DEFAULT CHARACTER SET utf8mb4"
func (ds *DatabaseStore) initializeConfigurationsTable() error {
var assetNamesForDriver []string
for _, assetName := range migrations.AssetNames() {
if strings.HasPrefix(assetName, ds.driverName) {
assetNamesForDriver = append(assetNamesForDriver, filepath.Base(assetName))
}
}
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS Configurations (
Id VARCHAR(26) PRIMARY KEY,
Value TEXT NOT NULL,
CreateAt BIGINT NOT NULL,
Active BOOLEAN NULL UNIQUE
)
` + mysqlCharset)
src, err := mbindata.WithInstance(&mbindata.AssetSource{
Names: assetNamesForDriver,
AssetFunc: func(name string) ([]byte, error) {
return migrations.Asset(filepath.Join(ds.driverName, name))
},
})
if err != nil {
return errors.Wrap(err, "failed to create Configurations table")
return err
}
defer src.Close()
cfg := drivers.Config{
MigrationsTable: migrationsTableName,
StatementTimeoutInSecs: migrationsTimeoutInSeconds,
}
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS ConfigurationFiles (
Name VARCHAR(64) PRIMARY KEY,
Data TEXT NOT NULL,
CreateAt BIGINT NOT NULL,
UpdateAt BIGINT NOT NULL
)
` + mysqlCharset)
var driver drivers.Driver
switch ds.driverName {
case model.DatabaseDriverMysql:
dataSource, rErr := resetReadTimeout(ds.dataSourceName)
if rErr != nil {
return fmt.Errorf("failed to reset read timeout from datasource: %w", rErr)
}
dataSource, err = appendMultipleStatementsFlag(dataSource)
if err != nil {
return err
}
var db *sqlx.DB
db, err = sqlx.Open(ds.driverName, dataSource)
if err != nil {
return errors.Wrapf(err, "failed to connect to %s database", ds.driverName)
}
driver, err = ms.WithInstance(db.DB, &ms.Config{
Config: cfg,
})
defer db.Close()
case model.DatabaseDriverPostgres:
driver, err = ps.WithInstance(ds.db.DB, &ps.Config{
Config: cfg,
})
default:
err = fmt.Errorf("unsupported database type %s for migration", ds.driverName)
}
if err != nil {
return errors.Wrap(err, "failed to create ConfigurationFiles table")
return err
}
// Change from TEXT (65535 limit) to MEDIUM TEXT (16777215) on MySQL. This is a
// backwards-compatible migration for any existing schema.
// Also fix using the wrong encoding initially
if db.DriverName() == "mysql" {
_, err = db.Exec(`ALTER TABLE Configurations MODIFY Value MEDIUMTEXT`)
if err != nil {
return errors.Wrap(err, "failed to alter Configurations table")
}
_, err = db.Exec(`ALTER TABLE Configurations CONVERT TO CHARACTER SET utf8mb4`)
if err != nil {
return errors.Wrap(err, "failed to alter Configurations table character set")
}
_, err = db.Exec(`ALTER TABLE ConfigurationFiles MODIFY Data MEDIUMTEXT`)
if err != nil {
return errors.Wrap(err, "failed to alter ConfigurationFiles table")
}
_, err = db.Exec(`ALTER TABLE ConfigurationFiles CONVERT TO CHARACTER SET utf8mb4`)
if err != nil {
return errors.Wrap(err, "failed to alter ConfigurationFiles table character set")
}
opts := []morph.EngineOption{
morph.WithLock("mm-config-lock-key"),
}
engine, err := morph.New(context.Background(), driver, src, opts...)
if err != nil {
return err
}
defer engine.Close()
return nil
return engine.ApplyAll()
}
// parseDSN splits up a connection string into a driver name and data source name.