Moved morph dependency to new repo (#19618)
```release-note NONE ``` Co-authored-by: Ibrahim Serdar Acikgoz <serdaracikgoz86@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9534efe534
Коммит
2a59047d07
23
vendor/github.com/mattermost/morph/drivers/driver.go
сгенерированный
поставляемый
Обычный файл
23
vendor/github.com/mattermost/morph/drivers/driver.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,23 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
"github.com/mattermost/morph/models"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
MigrationsTable string
|
||||
// StatementTimeoutInSecs is used to set a timeout for each migration file.
|
||||
// Set below zero to disable timeout. Zero value will result in default value, which is 60 seconds.
|
||||
StatementTimeoutInSecs int
|
||||
MigrationMaxSize int
|
||||
}
|
||||
|
||||
type Driver interface {
|
||||
Ping() error
|
||||
// Close closes the underlying db connection. If the driver is created via Open() function
|
||||
// this method will also going to call Close() on the sql.db instance.
|
||||
Close() error
|
||||
Apply(migration *models.Migration, saveVersion bool) error
|
||||
AppliedMigrations() ([]*models.Migration, error)
|
||||
SetConfig(key string, value interface{}) error
|
||||
}
|
||||
25
vendor/github.com/mattermost/morph/drivers/error.go
сгенерированный
поставляемый
Обычный файл
25
vendor/github.com/mattermost/morph/drivers/error.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,25 @@
|
||||
package drivers
|
||||
|
||||
import "fmt"
|
||||
|
||||
type AppError struct {
|
||||
OrigErr error
|
||||
Driver string
|
||||
Message string
|
||||
}
|
||||
|
||||
type DatabaseError struct {
|
||||
OrigErr error
|
||||
Driver string
|
||||
Message string
|
||||
Command string
|
||||
Query []byte
|
||||
}
|
||||
|
||||
func (ae *AppError) Error() string {
|
||||
return fmt.Sprintf("driver: %s, message: %s, originalError: %v ", ae.Driver, ae.Message, ae.OrigErr)
|
||||
}
|
||||
|
||||
func (de *DatabaseError) Error() string {
|
||||
return fmt.Sprintf("driver: %s, message: %s, command: %s, originalError: %v, query: \n\n%s\n", de.Driver, de.Message, de.Command, de.OrigErr, string(de.Query))
|
||||
}
|
||||
85
vendor/github.com/mattermost/morph/drivers/lock.go
сгенерированный
поставляемый
Обычный файл
85
vendor/github.com/mattermost/morph/drivers/lock.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,85 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// MutexTableName is the name being used for the mutex table
|
||||
MutexTableName = "db_lock"
|
||||
|
||||
// minWaitInterval is the minimum amount of time to wait between locking attempts
|
||||
minWaitInterval = 1 * time.Second
|
||||
|
||||
// maxWaitInterval is the maximum amount of time to wait between locking attempts
|
||||
maxWaitInterval = 5 * time.Minute
|
||||
|
||||
// pollWaitInterval is the usual time to wait between unsuccessful locking attempts
|
||||
pollWaitInterval = 1 * time.Second
|
||||
|
||||
// jitterWaitInterval is the amount of jitter to add when waiting to avoid thundering herds
|
||||
jitterWaitInterval = minWaitInterval / 2
|
||||
|
||||
// TTL is the interval after which a locked mutex will expire unless refreshed
|
||||
TTL = time.Second * 15
|
||||
|
||||
// RefreshInterval is the interval on which the mutex will be refreshed when locked
|
||||
RefreshInterval = TTL / 2
|
||||
)
|
||||
|
||||
// MakeLockKey returns the prefixed key used to namespace mutex keys.
|
||||
func MakeLockKey(key string) (string, error) {
|
||||
if key == "" {
|
||||
return "", errors.New("must specify valid mutex key")
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// NextWaitInterval determines how long to wait until the next lock retry.
|
||||
func NextWaitInterval(lastWaitInterval time.Duration, err error) time.Duration {
|
||||
nextWaitInterval := lastWaitInterval
|
||||
|
||||
if nextWaitInterval <= 0 {
|
||||
nextWaitInterval = minWaitInterval
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
nextWaitInterval *= 2
|
||||
if nextWaitInterval > maxWaitInterval {
|
||||
nextWaitInterval = maxWaitInterval
|
||||
}
|
||||
} else {
|
||||
nextWaitInterval = pollWaitInterval
|
||||
}
|
||||
|
||||
// Add some jitter to avoid unnecessary collision between competing other instances.
|
||||
nextWaitInterval += time.Duration(rand.Int63n(int64(jitterWaitInterval)) - int64(jitterWaitInterval)/2)
|
||||
|
||||
return nextWaitInterval
|
||||
}
|
||||
|
||||
type Locker interface {
|
||||
Lock() error
|
||||
Unlock() error
|
||||
// LockWithContext locks m unless the context is canceled. If the mutex is already locked by any other
|
||||
// instance, including the current one, the calling goroutine blocks until the mutex can be locked,
|
||||
// or the context is canceled.
|
||||
//
|
||||
// The mutex is locked only if a nil error is returned.
|
||||
LockWithContext(ctx context.Context) error
|
||||
}
|
||||
|
||||
type Lockable interface {
|
||||
DriverName() string
|
||||
}
|
||||
|
||||
// IsLockable returns whether the given instance satisfies
|
||||
// drivers.Lockable or not.
|
||||
func IsLockable(x interface{}) bool {
|
||||
_, ok := x.(Lockable)
|
||||
return ok
|
||||
}
|
||||
269
vendor/github.com/mattermost/morph/drivers/mysql/lock.go
сгенерированный
поставляемый
Обычный файл
269
vendor/github.com/mattermost/morph/drivers/mysql/lock.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,269 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/morph/drivers"
|
||||
)
|
||||
|
||||
// Mutex is similar to sync.Mutex, except usable by morph to lock the db.
|
||||
//
|
||||
// Pick a unique name for each mutex your plugin requires.
|
||||
//
|
||||
// A Mutex must not be copied after first use.
|
||||
type Mutex struct {
|
||||
noCopy
|
||||
key string
|
||||
|
||||
// lock guards the variables used to manage the refresh task, and is not itself related to
|
||||
// the db lock.
|
||||
lock sync.Mutex
|
||||
stopRefresh chan bool
|
||||
refreshDone chan bool
|
||||
conn *sql.Conn
|
||||
}
|
||||
|
||||
// NewMutex creates a mutex with the given key name.
|
||||
//
|
||||
// returns error if key is empty.
|
||||
func NewMutex(key string, driver drivers.Driver) (*Mutex, error) {
|
||||
key, err := drivers.MakeLockKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), drivers.TTL)
|
||||
defer cancel()
|
||||
|
||||
ms, ok := driver.(*mysql)
|
||||
if !ok {
|
||||
return nil, errors.New("incorrect implementation of the driver")
|
||||
}
|
||||
|
||||
conn, err := ms.db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createTableIfNotExistsQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (Id varchar(64) NOT NULL, ExpireAt bigint(20) NOT NULL, PRIMARY KEY (Id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", drivers.MutexTableName)
|
||||
if _, err = conn.ExecContext(ctx, createTableIfNotExistsQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Mutex{
|
||||
key: key,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// lock makes a single attempt to lock the mutex, returning true only if successful.
|
||||
func (m *Mutex) tryLock(ctx context.Context) (bool, error) {
|
||||
now := time.Now()
|
||||
tx, err := m.conn.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("INSERT INTO %s (Id, ExpireAt) VALUES (?, ?)", drivers.MutexTableName)
|
||||
if _, err := tx.Exec(query, m.key, now.Add(drivers.TTL).Unix()); err != nil {
|
||||
err2 := m.releaseLock(tx, now)
|
||||
if err2 == nil { // lock has been released due to expiration
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("failed to lock mutex: %w", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return false, txErr
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Mutex) releaseLock(tx *sql.Tx, t time.Time) error {
|
||||
e, err := m.getExpireAt(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Unix() < e {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return errors.New("could not release the lock")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("UPDATE %s SET ExpireAt = ? WHERE Id = ?", drivers.MutexTableName)
|
||||
if err = executeTx(tx, query, t.Add(drivers.TTL).Unix(), m.key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback transaction: %w", txErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to set new expireat for mutex: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mutex) getExpireAt(tx *sql.Tx) (int64, error) {
|
||||
var expireAt int64
|
||||
query := fmt.Sprintf("SELECT ExpireAt FROM %s WHERE Id = ?", drivers.MutexTableName)
|
||||
err := tx.QueryRow(query, m.key).Scan(&expireAt)
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return -1, fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("failed to fetch mutex from db: %w", err)
|
||||
}
|
||||
|
||||
return expireAt, nil
|
||||
}
|
||||
|
||||
// refreshLock rewrites the lock key value with a new expiry, returning nil only if successful.
|
||||
func (m *Mutex) refreshLock(ctx context.Context) error {
|
||||
tx, err := m.conn.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e, err := m.getExpireAt(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp := time.Unix(e, 0)
|
||||
query := fmt.Sprintf("UPDATE %s SET ExpireAt = ? WHERE Id = ?", drivers.MutexTableName)
|
||||
if err = executeTx(tx, query, tmp.Add(drivers.TTL).Unix(), m.key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to refresh expireat for mutex: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks m. If the mutex is already locked by any other morph instance, including the current one,
|
||||
// the calling goroutine blocks until the mutex can be locked.
|
||||
func (m *Mutex) Lock() error {
|
||||
return m.LockWithContext(context.Background())
|
||||
}
|
||||
|
||||
// LockWithContext locks m unless the context is canceled. If the mutex is already locked by any other
|
||||
// instance, including the current one, the calling goroutine blocks until the mutex can be locked,
|
||||
// or the context is canceled.
|
||||
//
|
||||
// The mutex is locked only if a nil error is returned.
|
||||
func (m *Mutex) LockWithContext(ctx context.Context) error {
|
||||
var waitInterval time.Duration
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(waitInterval):
|
||||
}
|
||||
|
||||
ok, err := m.tryLock(ctx)
|
||||
if err != nil || !ok {
|
||||
waitInterval = drivers.NextWaitInterval(waitInterval, err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
stop := make(chan bool)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
t := time.NewTicker(drivers.RefreshInterval)
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
err := m.refreshLock(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
m.lock.Lock()
|
||||
m.stopRefresh = stop
|
||||
m.refreshDone = done
|
||||
m.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock unlocks m. It is a run-time error if m is not locked on entry to Unlock.
|
||||
//
|
||||
// Just like sync.Mutex, a locked Lock is not associated with a particular goroutine or a process.
|
||||
func (m *Mutex) Unlock() error {
|
||||
m.lock.Lock()
|
||||
if m.stopRefresh == nil {
|
||||
m.lock.Unlock()
|
||||
panic("mutex has not been acquired")
|
||||
}
|
||||
|
||||
close(m.stopRefresh)
|
||||
m.stopRefresh = nil
|
||||
<-m.refreshDone
|
||||
m.lock.Unlock()
|
||||
|
||||
defer m.conn.Close()
|
||||
|
||||
// If an error occurs deleting, the mutex will still expire, allowing later retry.
|
||||
query := fmt.Sprintf("DELETE FROM %s WHERE Id = ?", drivers.MutexTableName)
|
||||
_, err := m.conn.ExecContext(context.Background(), query, m.key)
|
||||
return err
|
||||
}
|
||||
|
||||
func executeTx(tx *sql.Tx, query string, args ...interface{}) error {
|
||||
if _, err := tx.Exec(query, args...); err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback transaction: %w", txErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// noCopy may be embedded into structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
335
vendor/github.com/mattermost/morph/drivers/mysql/mysql.go
сгенерированный
поставляемый
Обычный файл
335
vendor/github.com/mattermost/morph/drivers/mysql/mysql.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,335 @@
|
||||
// Initial code generated by generator.
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/mattermost/morph/drivers"
|
||||
"github.com/mattermost/morph/models"
|
||||
)
|
||||
|
||||
const driverName = "mysql"
|
||||
const defaultMigrationMaxSize = 10 * 1 << 20 // 10 MB
|
||||
|
||||
// add here any custom driver configuration
|
||||
var configParams = []string{
|
||||
"x-migration-max-size",
|
||||
"x-migrations-table",
|
||||
"x-statement-timeout",
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
drivers.Config
|
||||
databaseName string
|
||||
closeDBonClose bool
|
||||
}
|
||||
|
||||
type mysql struct {
|
||||
conn *sql.Conn
|
||||
db *sql.DB
|
||||
config *Config
|
||||
}
|
||||
|
||||
func WithInstance(dbInstance *sql.DB, config *Config) (drivers.Driver, error) {
|
||||
driverConfig := mergeConfigs(config, getDefaultConfig())
|
||||
|
||||
conn, err := dbInstance.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "grabbing_connection", OrigErr: err, Message: "failed to grab connection to the database"}
|
||||
}
|
||||
|
||||
if driverConfig.databaseName, err = currentDatabaseNameFromDB(conn, driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mysql{config: driverConfig, conn: conn, db: dbInstance}, nil
|
||||
}
|
||||
|
||||
func Open(connURL string) (drivers.Driver, error) {
|
||||
customParams, err := drivers.ExtractCustomParams(connURL, configParams)
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to parse custom parameters from url"}
|
||||
}
|
||||
|
||||
sanitizedConnURL, err := drivers.RemoveParamsFromURL(connURL, configParams)
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to sanitize url from custom parameters"}
|
||||
}
|
||||
|
||||
driverConfig, err := mergeConfigWithParams(customParams, getDefaultConfig())
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to merge custom params to driver config"}
|
||||
}
|
||||
|
||||
db, err := sql.Open(driverName, sanitizedConnURL)
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "opening_connection", OrigErr: err, Message: "failed to open connection with the database"}
|
||||
}
|
||||
|
||||
conn, err := db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "grabbing_connection", OrigErr: err, Message: "failed to grab connection to the database"}
|
||||
}
|
||||
|
||||
if driverConfig.databaseName, err = extractDatabaseNameFromURL(sanitizedConnURL); err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to extract database name from connection url"}
|
||||
}
|
||||
|
||||
driverConfig.closeDBonClose = true
|
||||
|
||||
return &mysql{
|
||||
conn: conn,
|
||||
db: db,
|
||||
config: driverConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (driver *mysql) Ping() error {
|
||||
ctx, cancel := drivers.GetContext(driver.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
return driver.conn.PingContext(ctx)
|
||||
}
|
||||
|
||||
func (mysql) DriverName() string {
|
||||
return driverName
|
||||
}
|
||||
|
||||
func (driver *mysql) Close() error {
|
||||
if driver.conn != nil {
|
||||
if err := driver.conn.Close(); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to close database connection",
|
||||
Command: "mysql_conn_close",
|
||||
Query: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if driver.db != nil && driver.config.closeDBonClose {
|
||||
if err := driver.db.Close(); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to close database",
|
||||
Command: "mysql_db_close",
|
||||
Query: nil,
|
||||
}
|
||||
}
|
||||
driver.db = nil
|
||||
}
|
||||
|
||||
driver.conn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *mysql) createSchemaTableIfNotExists() (err error) {
|
||||
ctx, cancel := drivers.GetContext(driver.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
createTableIfNotExistsQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (Version bigint(20) NOT NULL, Name varchar(64) NOT NULL, PRIMARY KEY (Version)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", driver.config.MigrationsTable)
|
||||
if _, err = driver.conn.ExecContext(ctx, createTableIfNotExistsQuery); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed while executing query",
|
||||
Command: "create_migrations_table_if_not_exists",
|
||||
Query: []byte(createTableIfNotExistsQuery),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *mysql) Apply(migration *models.Migration, saveVersion bool) (err error) {
|
||||
query, readErr := migration.Query()
|
||||
if readErr != nil {
|
||||
return &drivers.AppError{
|
||||
OrigErr: readErr,
|
||||
Driver: driverName,
|
||||
Message: fmt.Sprintf("failed to read migration query: %s", migration.Name),
|
||||
}
|
||||
}
|
||||
defer migration.Close()
|
||||
ctx, cancel := drivers.GetContext(driver.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
if _, err := driver.conn.ExecContext(ctx, query); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed when applying migration",
|
||||
Command: "apply_migration",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
|
||||
updateVersionContext, cancel := drivers.GetContext(driver.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
if !saveVersion {
|
||||
return nil
|
||||
}
|
||||
|
||||
updateVersionQuery := driver.addMigrationQuery(migration)
|
||||
if _, err := driver.conn.ExecContext(updateVersionContext, updateVersionQuery); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed when updating migrations table with the new version",
|
||||
Command: "update_version",
|
||||
Query: []byte(updateVersionQuery),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *mysql) AppliedMigrations() (migrations []*models.Migration, err error) {
|
||||
if driver.conn == nil {
|
||||
return nil, &drivers.AppError{
|
||||
OrigErr: errors.New("driver has no connection established"),
|
||||
Message: "database connection is missing",
|
||||
Driver: driverName,
|
||||
}
|
||||
}
|
||||
|
||||
if err := driver.createSchemaTableIfNotExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("SELECT version, name FROM %s", driver.config.MigrationsTable)
|
||||
ctx, cancel := drivers.GetContext(driver.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
var appliedMigrations []*models.Migration
|
||||
var version uint32
|
||||
var name string
|
||||
|
||||
rows, err := driver.conn.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to fetch applied migrations",
|
||||
Command: "select_applied_migrations",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&version, &name); err != nil {
|
||||
return nil, &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to scan applied migration row",
|
||||
Command: "scan_applied_migrations",
|
||||
}
|
||||
}
|
||||
|
||||
appliedMigrations = append(appliedMigrations, &models.Migration{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Direction: models.Up,
|
||||
})
|
||||
}
|
||||
|
||||
return appliedMigrations, nil
|
||||
}
|
||||
|
||||
func currentDatabaseNameFromDB(conn *sql.Conn, config *Config) (string, error) {
|
||||
query := "SELECT DATABASE()"
|
||||
|
||||
ctx, cancel := drivers.GetContext(config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
var databaseName string
|
||||
if err := conn.QueryRowContext(ctx, query).Scan(&databaseName); err != nil {
|
||||
return "", &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to fetch database name",
|
||||
Command: "current_database",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
|
||||
return databaseName, nil
|
||||
}
|
||||
|
||||
func mergeConfigs(config *Config, defaultConfig *Config) *Config {
|
||||
if config.MigrationsTable == "" {
|
||||
config.MigrationsTable = defaultConfig.MigrationsTable
|
||||
}
|
||||
|
||||
if config.StatementTimeoutInSecs == 0 {
|
||||
config.StatementTimeoutInSecs = defaultConfig.StatementTimeoutInSecs
|
||||
}
|
||||
|
||||
if config.MigrationMaxSize == 0 {
|
||||
config.MigrationMaxSize = defaultConfig.MigrationMaxSize
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func mergeConfigWithParams(params map[string]string, config *Config) (*Config, error) {
|
||||
var err error
|
||||
|
||||
for _, configKey := range configParams {
|
||||
if v, ok := params[configKey]; ok {
|
||||
switch configKey {
|
||||
case "x-migration-max-size":
|
||||
if config.MigrationMaxSize, err = strconv.Atoi(v); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to cast config param %s of %s", configKey, v))
|
||||
}
|
||||
case "x-migrations-table":
|
||||
config.MigrationsTable = v
|
||||
case "x-statement-timeout":
|
||||
if config.StatementTimeoutInSecs, err = strconv.Atoi(v); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to cast config param %s of %s", configKey, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (driver *mysql) addMigrationQuery(migration *models.Migration) string {
|
||||
if migration.Direction == models.Down {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE (Version=%d AND NAME='%s')", driver.config.MigrationsTable, migration.Version, migration.Name)
|
||||
}
|
||||
return fmt.Sprintf("INSERT INTO %s (Version, Name) VALUES (%d, '%s')", driver.config.MigrationsTable, migration.Version, migration.Name)
|
||||
}
|
||||
|
||||
func (driver *mysql) SetConfig(key string, value interface{}) error {
|
||||
if driver.config != nil {
|
||||
switch key {
|
||||
case "StatementTimeoutInSecs":
|
||||
n, ok := value.(int)
|
||||
if ok {
|
||||
driver.config.StatementTimeoutInSecs = n
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("incorrect value type for %s", key)
|
||||
case "MigrationsTable":
|
||||
n, ok := value.(string)
|
||||
if ok {
|
||||
driver.config.MigrationsTable = n
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("incorrect value type for %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("incorrect key name %q", key)
|
||||
}
|
||||
34
vendor/github.com/mattermost/morph/drivers/mysql/utils.go
сгенерированный
поставляемый
Обычный файл
34
vendor/github.com/mattermost/morph/drivers/mysql/utils.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,34 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
mysqlDriver "github.com/go-sql-driver/mysql"
|
||||
"github.com/mattermost/morph/drivers"
|
||||
)
|
||||
|
||||
func ExtractMysqlDSNParams(conn string) (map[string]string, error) {
|
||||
cfg, err := mysqlDriver.ParseDSN(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg.Params, nil
|
||||
}
|
||||
|
||||
func extractDatabaseNameFromURL(conn string) (string, error) {
|
||||
cfg, err := mysqlDriver.ParseDSN(conn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cfg.DBName, nil
|
||||
}
|
||||
|
||||
func getDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Config: drivers.Config{
|
||||
MigrationsTable: "db_migrations",
|
||||
StatementTimeoutInSecs: 60,
|
||||
MigrationMaxSize: defaultMigrationMaxSize,
|
||||
},
|
||||
}
|
||||
}
|
||||
269
vendor/github.com/mattermost/morph/drivers/postgres/lock.go
сгенерированный
поставляемый
Обычный файл
269
vendor/github.com/mattermost/morph/drivers/postgres/lock.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,269 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/morph/drivers"
|
||||
)
|
||||
|
||||
// Mutex is similar to sync.Mutex, except usable by morph to lock the db.
|
||||
//
|
||||
// Pick a unique name for each mutex your plugin requires.
|
||||
//
|
||||
// A Mutex must not be copied after first use.
|
||||
type Mutex struct {
|
||||
noCopy
|
||||
key string
|
||||
|
||||
// lock guards the variables used to manage the refresh task, and is not itself related to
|
||||
// the db lock.
|
||||
lock sync.Mutex
|
||||
stopRefresh chan bool
|
||||
refreshDone chan bool
|
||||
conn *sql.Conn
|
||||
}
|
||||
|
||||
// NewMutex creates a mutex with the given key name.
|
||||
//
|
||||
// returns error if key is empty.
|
||||
func NewMutex(key string, driver drivers.Driver) (*Mutex, error) {
|
||||
key, err := drivers.MakeLockKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), drivers.TTL)
|
||||
defer cancel()
|
||||
|
||||
ps, ok := driver.(*postgres)
|
||||
if !ok {
|
||||
return nil, errors.New("incorrect implementation of the driver")
|
||||
}
|
||||
|
||||
conn, err := ps.db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createTableIfNotExistsQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id varchar(64) PRIMARY KEY, expireat bigint);", drivers.MutexTableName)
|
||||
if _, err = conn.ExecContext(ctx, createTableIfNotExistsQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Mutex{
|
||||
key: key,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// lock makes a single attempt to lock the mutex, returning true only if successful.
|
||||
func (m *Mutex) tryLock(ctx context.Context) (bool, error) {
|
||||
now := time.Now()
|
||||
tx, err := m.conn.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("INSERT INTO %s (id, expireat) VALUES ($1, $2)", drivers.MutexTableName)
|
||||
if _, err := tx.Exec(query, m.key, now.Add(drivers.TTL).Unix()); err != nil {
|
||||
err2 := m.releaseLock(tx, now)
|
||||
if err2 == nil { // lock has been released due to expiration
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("failed to lock mutex: %w", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return false, txErr
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Mutex) releaseLock(tx *sql.Tx, t time.Time) error {
|
||||
e, err := m.getExpireAt(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t.Unix() < e {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return errors.New("could not release the lock")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("UPDATE %s SET expireat = $1 WHERE id = $2", drivers.MutexTableName)
|
||||
if err = executeTx(tx, query, t.Add(drivers.TTL).Unix(), m.key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback transaction: %w", txErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to set new expireat for mutex: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mutex) getExpireAt(tx *sql.Tx) (int64, error) {
|
||||
var expireAt int64
|
||||
query := fmt.Sprintf("SELECT expireat FROM %s WHERE id = $1", drivers.MutexTableName)
|
||||
err := tx.QueryRow(query, m.key).Scan(&expireAt)
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return -1, fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("failed to fetch mutex from db: %w", err)
|
||||
}
|
||||
|
||||
return expireAt, nil
|
||||
}
|
||||
|
||||
// refreshLock rewrites the lock key value with a new expiry, returning nil only if successful.
|
||||
func (m *Mutex) refreshLock(ctx context.Context) error {
|
||||
tx, err := m.conn.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e, err := m.getExpireAt(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmp := time.Unix(e, 0)
|
||||
query := fmt.Sprintf("UPDATE %s SET expireat = $1 WHERE id = $2", drivers.MutexTableName)
|
||||
if err = executeTx(tx, query, tmp.Add(drivers.TTL).Unix(), m.key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback: %w", txErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unable to refresh expireat for mutex: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks m. If the mutex is already locked by any other morph instance, including the current one,
|
||||
// the calling goroutine blocks until the mutex can be locked.
|
||||
func (m *Mutex) Lock() error {
|
||||
return m.LockWithContext(context.Background())
|
||||
}
|
||||
|
||||
// LockWithContext locks m unless the context is canceled. If the mutex is already locked by any other
|
||||
// instance, including the current one, the calling goroutine blocks until the mutex can be locked,
|
||||
// or the context is canceled.
|
||||
//
|
||||
// The mutex is locked only if a nil error is returned.
|
||||
func (m *Mutex) LockWithContext(ctx context.Context) error {
|
||||
var waitInterval time.Duration
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(waitInterval):
|
||||
}
|
||||
|
||||
ok, err := m.tryLock(ctx)
|
||||
if err != nil || !ok {
|
||||
waitInterval = drivers.NextWaitInterval(waitInterval, err)
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
stop := make(chan bool)
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
t := time.NewTicker(drivers.RefreshInterval)
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
err := m.refreshLock(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
m.lock.Lock()
|
||||
m.stopRefresh = stop
|
||||
m.refreshDone = done
|
||||
m.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock unlocks m. It is a run-time error if m is not locked on entry to Unlock.
|
||||
//
|
||||
// Just like sync.Mutex, a locked Lock is not associated with a particular goroutine or a process.
|
||||
func (m *Mutex) Unlock() error {
|
||||
m.lock.Lock()
|
||||
if m.stopRefresh == nil {
|
||||
m.lock.Unlock()
|
||||
panic("mutex has not been acquired")
|
||||
}
|
||||
|
||||
close(m.stopRefresh)
|
||||
m.stopRefresh = nil
|
||||
<-m.refreshDone
|
||||
m.lock.Unlock()
|
||||
|
||||
defer m.conn.Close()
|
||||
|
||||
// If an error occurs deleting, the mutex will still expire, allowing later retry.
|
||||
query := fmt.Sprintf("DELETE FROM %s WHERE id = $1", drivers.MutexTableName)
|
||||
_, err := m.conn.ExecContext(context.Background(), query, m.key)
|
||||
return err
|
||||
}
|
||||
|
||||
func executeTx(tx *sql.Tx, query string, args ...interface{}) error {
|
||||
if _, err := tx.Exec(query, args...); err != nil {
|
||||
if txErr := tx.Rollback(); txErr != nil {
|
||||
return fmt.Errorf("could not rollback transaction: %w", txErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// noCopy may be embedded into structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://golang.org/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
392
vendor/github.com/mattermost/morph/drivers/postgres/postgres.go
сгенерированный
поставляемый
Обычный файл
392
vendor/github.com/mattermost/morph/drivers/postgres/postgres.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,392 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/mattermost/morph/drivers"
|
||||
"github.com/mattermost/morph/models"
|
||||
)
|
||||
|
||||
var (
|
||||
driverName = "postgres"
|
||||
defaultMigrationMaxSize = 10 * 1 << 20 // 10 MB
|
||||
configParams = []string{
|
||||
"x-migration-max-size",
|
||||
"x-migrations-table",
|
||||
"x-statement-timeout",
|
||||
}
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
drivers.Config
|
||||
databaseName string
|
||||
schemaName string
|
||||
closeDBonClose bool
|
||||
}
|
||||
|
||||
type postgres struct {
|
||||
conn *sql.Conn
|
||||
db *sql.DB
|
||||
config *Config
|
||||
}
|
||||
|
||||
func WithInstance(dbInstance *sql.DB, config *Config) (drivers.Driver, error) {
|
||||
driverConfig := mergeConfigs(config, getDefaultConfig())
|
||||
|
||||
conn, err := dbInstance.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "grabbing_connection", OrigErr: err, Message: "failed to grab connection to the database"}
|
||||
}
|
||||
|
||||
if driverConfig.databaseName, err = currentDatabaseNameFromDB(conn, driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if driverConfig.schemaName, err = currentSchema(conn, driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &postgres{
|
||||
conn: conn,
|
||||
db: dbInstance,
|
||||
config: driverConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Open(connURL string) (drivers.Driver, error) {
|
||||
customParams, err := drivers.ExtractCustomParams(connURL, configParams)
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to parse custom parameters from url"}
|
||||
}
|
||||
|
||||
sanitizedConnURL, err := drivers.RemoveParamsFromURL(connURL, configParams)
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to sanitize url from custom parameters"}
|
||||
}
|
||||
|
||||
driverConfig, err := mergeConfigWithParams(customParams, getDefaultConfig())
|
||||
if err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to merge custom params to driver config"}
|
||||
}
|
||||
|
||||
db, err := sql.Open(driverName, sanitizedConnURL)
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "opening_connection", OrigErr: err, Message: "failed to open connection with the database"}
|
||||
}
|
||||
|
||||
conn, err := db.Conn(context.Background())
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{Driver: driverName, Command: "grabbing_connection", OrigErr: err, Message: "failed to grab connection to the database"}
|
||||
}
|
||||
|
||||
if driverConfig.databaseName, err = extractDatabaseNameFromURL(connURL); err != nil {
|
||||
return nil, &drivers.AppError{Driver: driverName, OrigErr: err, Message: "failed to extract database name from connection url"}
|
||||
}
|
||||
|
||||
if driverConfig.schemaName, err = currentSchema(conn, driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driverConfig.closeDBonClose = true
|
||||
|
||||
return &postgres{
|
||||
db: db,
|
||||
config: driverConfig,
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func currentSchema(conn *sql.Conn, config *Config) (string, error) {
|
||||
query := "SELECT CURRENT_SCHEMA()"
|
||||
|
||||
ctx, cancel := drivers.GetContext(config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
var schemaName string
|
||||
if err := conn.QueryRowContext(ctx, query).Scan(&schemaName); err != nil {
|
||||
return "", &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to fetch current schema",
|
||||
Command: "current_schema",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
return schemaName, nil
|
||||
}
|
||||
|
||||
func mergeConfigWithParams(params map[string]string, config *Config) (*Config, error) {
|
||||
var err error
|
||||
|
||||
for _, configKey := range configParams {
|
||||
if v, ok := params[configKey]; ok {
|
||||
switch configKey {
|
||||
case "x-migration-max-size":
|
||||
if config.MigrationMaxSize, err = strconv.Atoi(v); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to cast config param %s of %s", configKey, v))
|
||||
}
|
||||
case "x-migrations-table":
|
||||
config.MigrationsTable = v
|
||||
case "x-statement-timeout":
|
||||
if config.StatementTimeoutInSecs, err = strconv.Atoi(v); err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("failed to cast config param %s of %s", configKey, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func mergeConfigs(config, defaultConfig *Config) *Config {
|
||||
if config.MigrationsTable == "" {
|
||||
config.MigrationsTable = defaultConfig.MigrationsTable
|
||||
}
|
||||
|
||||
if config.StatementTimeoutInSecs == 0 {
|
||||
config.StatementTimeoutInSecs = defaultConfig.StatementTimeoutInSecs
|
||||
}
|
||||
|
||||
if config.MigrationMaxSize == 0 {
|
||||
config.MigrationMaxSize = defaultConfig.MigrationMaxSize
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (pg *postgres) Ping() error {
|
||||
ctx, cancel := drivers.GetContext(pg.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
return pg.conn.PingContext(ctx)
|
||||
}
|
||||
|
||||
func (pg *postgres) createSchemaTableIfNotExists() (err error) {
|
||||
ctx, cancel := drivers.GetContext(pg.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
createTableIfNotExistsQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (version bigint not null primary key, name varchar not null)", pg.config.MigrationsTable)
|
||||
if _, err = pg.conn.ExecContext(ctx, createTableIfNotExistsQuery); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed while executing query",
|
||||
Command: "create_migrations_table_if_not_exists",
|
||||
Query: []byte(createTableIfNotExistsQuery),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (postgres) DriverName() string {
|
||||
return driverName
|
||||
}
|
||||
|
||||
func (pg *postgres) Close() error {
|
||||
if pg.conn != nil {
|
||||
if err := pg.conn.Close(); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to close database connection",
|
||||
Command: "pg_conn_close",
|
||||
Query: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pg.db != nil && pg.config.closeDBonClose {
|
||||
if err := pg.db.Close(); err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to close database",
|
||||
Command: "pg_db_close",
|
||||
Query: nil,
|
||||
}
|
||||
}
|
||||
pg.db = nil
|
||||
}
|
||||
|
||||
pg.conn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pg *postgres) Apply(migration *models.Migration, saveVersion bool) (err error) {
|
||||
query, readErr := migration.Query()
|
||||
if readErr != nil {
|
||||
return &drivers.AppError{
|
||||
OrigErr: readErr,
|
||||
Driver: driverName,
|
||||
Message: fmt.Sprintf("failed to read migration query: %s", migration.Name),
|
||||
}
|
||||
}
|
||||
defer migration.Close()
|
||||
|
||||
ctx, cancel := drivers.GetContext(pg.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
transaction, err := pg.conn.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "error while opening a transaction to the database",
|
||||
Command: "begin_transaction",
|
||||
}
|
||||
}
|
||||
|
||||
if err = executeQuery(transaction, query); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if saveVersion {
|
||||
if err = executeQuery(transaction, pg.addMigrationQuery(migration)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
if err != nil {
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "error while committing a transaction to the database",
|
||||
Command: "commit_transaction",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pg *postgres) AppliedMigrations() (migrations []*models.Migration, err error) {
|
||||
if pg.conn == nil {
|
||||
return nil, &drivers.AppError{
|
||||
OrigErr: errors.New("driver has no connection established"),
|
||||
Message: "database connection is missing",
|
||||
Driver: driverName,
|
||||
}
|
||||
}
|
||||
|
||||
if err := pg.createSchemaTableIfNotExists(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("SELECT version, name FROM %s", pg.config.MigrationsTable)
|
||||
ctx, cancel := drivers.GetContext(pg.config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
var appliedMigrations []*models.Migration
|
||||
var version uint32
|
||||
var name string
|
||||
|
||||
rows, err := pg.conn.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to fetch applied migrations",
|
||||
Command: "select_applied_migrations",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&version, &name); err != nil {
|
||||
return nil, &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to scan applied migration row",
|
||||
Command: "scan_applied_migrations",
|
||||
}
|
||||
}
|
||||
|
||||
appliedMigrations = append(appliedMigrations, &models.Migration{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Direction: models.Up,
|
||||
})
|
||||
}
|
||||
|
||||
return appliedMigrations, nil
|
||||
}
|
||||
|
||||
func (pg *postgres) addMigrationQuery(migration *models.Migration) string {
|
||||
if migration.Direction == models.Down {
|
||||
return fmt.Sprintf("DELETE FROM %s WHERE (Version=%d AND NAME='%s')", pg.config.MigrationsTable, migration.Version, migration.Name)
|
||||
}
|
||||
return fmt.Sprintf("INSERT INTO %s (version, name) VALUES (%d, '%s')", pg.config.MigrationsTable, migration.Version, migration.Name)
|
||||
}
|
||||
|
||||
func executeQuery(transaction *sql.Tx, query string) error {
|
||||
if _, err := transaction.Exec(query); err != nil {
|
||||
if txErr := transaction.Rollback(); txErr != nil {
|
||||
err = errors.Wrap(errors.New(err.Error()+txErr.Error()), "failed to execute query in migration transaction")
|
||||
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Command: "rollback_transaction",
|
||||
}
|
||||
}
|
||||
|
||||
return &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to execute migration",
|
||||
Command: "executing_query",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func currentDatabaseNameFromDB(conn *sql.Conn, config *Config) (string, error) {
|
||||
query := "SELECT CURRENT_DATABASE()"
|
||||
|
||||
ctx, cancel := drivers.GetContext(config.StatementTimeoutInSecs)
|
||||
defer cancel()
|
||||
|
||||
var databaseName string
|
||||
if err := conn.QueryRowContext(ctx, query).Scan(&databaseName); err != nil {
|
||||
return "", &drivers.DatabaseError{
|
||||
OrigErr: err,
|
||||
Driver: driverName,
|
||||
Message: "failed to fetch database name",
|
||||
Command: "current_database",
|
||||
Query: []byte(query),
|
||||
}
|
||||
}
|
||||
return databaseName, nil
|
||||
}
|
||||
|
||||
func (pg *postgres) SetConfig(key string, value interface{}) error {
|
||||
if pg.config != nil {
|
||||
switch key {
|
||||
case "StatementTimeoutInSecs":
|
||||
n, ok := value.(int)
|
||||
if ok {
|
||||
pg.config.StatementTimeoutInSecs = n
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("incorrect value type for %s", key)
|
||||
case "MigrationsTable":
|
||||
n, ok := value.(string)
|
||||
if ok {
|
||||
pg.config.MigrationsTable = n
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("incorrect value type for %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("incorrect key name %q", key)
|
||||
}
|
||||
26
vendor/github.com/mattermost/morph/drivers/postgres/utils.go
сгенерированный
поставляемый
Обычный файл
26
vendor/github.com/mattermost/morph/drivers/postgres/utils.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,26 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/mattermost/morph/drivers"
|
||||
)
|
||||
|
||||
func extractDatabaseNameFromURL(URL string) (string, error) {
|
||||
uri, err := url.Parse(URL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uri.Path[1:], nil
|
||||
}
|
||||
|
||||
func getDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Config: drivers.Config{
|
||||
MigrationsTable: "db_migrations",
|
||||
StatementTimeoutInSecs: 60,
|
||||
MigrationMaxSize: defaultMigrationMaxSize,
|
||||
},
|
||||
}
|
||||
}
|
||||
58
vendor/github.com/mattermost/morph/drivers/utils.go
сгенерированный
поставляемый
Обычный файл
58
vendor/github.com/mattermost/morph/drivers/utils.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,58 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExtractCustomParams(conn string, params []string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
for _, param := range params {
|
||||
reg := regexp.MustCompile(fmt.Sprintf("%s=(\\w+)", param))
|
||||
match := reg.FindStringSubmatch(conn)
|
||||
if len(match) > 1 {
|
||||
result[param] = match[1]
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func RemoveParamsFromURL(conn string, params []string) (string, error) {
|
||||
prefixCorrection := regexp.MustCompile(`\?&+`)
|
||||
repeatedAmber := regexp.MustCompile("&+")
|
||||
|
||||
for _, param := range params {
|
||||
reg := regexp.MustCompile(fmt.Sprintf("%s=\\w+", param))
|
||||
conn = string(reg.ReplaceAll([]byte(conn), []byte(``)))
|
||||
}
|
||||
|
||||
parts := strings.Split(conn, "/")
|
||||
urlParams := parts[len(parts)-1]
|
||||
|
||||
urlParams = string(prefixCorrection.ReplaceAll([]byte(urlParams), []byte(`?`)))
|
||||
urlParams = string(repeatedAmber.ReplaceAll([]byte(urlParams), []byte(`&`)))
|
||||
parts[len(parts)-1] = urlParams
|
||||
|
||||
return strings.Join(parts, "/"), nil
|
||||
}
|
||||
|
||||
const advisoryLockIDSalt uint = 1486364155
|
||||
|
||||
func GenerateAdvisoryLockID(databaseName, schemaName string) (string, error) {
|
||||
databaseName = schemaName + databaseName + "\x00"
|
||||
sum := crc32.ChecksumIEEE([]byte(databaseName))
|
||||
sum = sum * uint32(advisoryLockIDSalt)
|
||||
return fmt.Sprint(sum), nil
|
||||
}
|
||||
|
||||
func GetContext(timeoutInSeconds int) (context.Context, context.CancelFunc) {
|
||||
if t := timeoutInSeconds; t > 0 {
|
||||
return context.WithTimeout(context.Background(), time.Second*time.Duration(t))
|
||||
}
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
Ссылка в новой задаче
Block a user