Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package popx
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/ory/pop/v6"
|
|
)
|
|
|
|
// Migration handles the data for a given database migration
|
|
type Migration struct {
|
|
// Path to the migration (./migrations/123_create_widgets.up.sql)
|
|
Path string
|
|
// Version of the migration (123)
|
|
Version string
|
|
// Name of the migration (create_widgets)
|
|
Name string
|
|
// Direction of the migration (up|down)
|
|
Direction string
|
|
// Type of migration (sql|go)
|
|
Type string
|
|
// DB type (all|postgres|mysql...)
|
|
DBType string
|
|
// Runner function to run/execute the migration. Will be wrapped in a
|
|
// database transaction. Mutually exclusive with RunnerNoTx
|
|
Runner func(Migration, *pop.Connection) error
|
|
// Content is the raw content of the migration file
|
|
Content string
|
|
// Autocommit indicates whether the migration should be run in autocommit mode
|
|
Autocommit bool
|
|
}
|
|
|
|
func (m Migration) Valid() error {
|
|
if m.Runner == nil {
|
|
return errors.Errorf("no runner defined for %s", m.Path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Migrations is a collection of Migration
|
|
type Migrations []Migration
|
|
|
|
func (mfs Migrations) Len() int { return len(mfs) }
|
|
func (mfs Migrations) Less(i, j int) bool { return compareMigration(mfs[i], mfs[j]) < 0 }
|
|
func (mfs Migrations) Swap(i, j int) { mfs[i], mfs[j] = mfs[j], mfs[i] }
|
|
|
|
func compareMigration(a, b Migration) int {
|
|
if a.Version != b.Version {
|
|
return strings.Compare(a.Version, b.Version)
|
|
}
|
|
// Force "all" to be greater.
|
|
if a.DBType == "all" && b.DBType != "all" {
|
|
return 1
|
|
} else if a.DBType != "all" && b.DBType == "all" {
|
|
return -1
|
|
}
|
|
return strings.Compare(a.DBType, b.DBType)
|
|
}
|
|
|
|
// migrationRank ranks a migration's DBType for the given dialect and its ordered
|
|
// fallbacks. Higher numbers indicate a better match:
|
|
//
|
|
// -1 not applicable
|
|
// 0 "all" (generic, dialect-independent)
|
|
// 1..n a fallback dialect (earlier fallbacks rank higher)
|
|
// n+1 the exact dialect
|
|
//
|
|
// This lets a migration box prefer a dialect-specific override, then a fallback
|
|
// dialect (e.g. postgres for a postgres-wire-compatible database), then the
|
|
// generic file.
|
|
func migrationRank(dbType, dialect string, fallbacks []string) int {
|
|
switch dbType {
|
|
case dialect:
|
|
return len(fallbacks) + 1
|
|
case "all":
|
|
return 0
|
|
}
|
|
for i, fb := range fallbacks {
|
|
if dbType == fb {
|
|
return len(fallbacks) - i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (mfs Migrations) sortAndFilter(dialect string, fallbacks ...string) Migrations {
|
|
byVersion := make(map[string]Migration, len(mfs))
|
|
for _, migration := range mfs {
|
|
rank := migrationRank(migration.DBType, dialect, fallbacks)
|
|
if rank < 0 {
|
|
// Not applicable to this dialect.
|
|
continue
|
|
}
|
|
if previousMigration, ok := byVersion[migration.Version]; ok {
|
|
if rank < migrationRank(previousMigration.DBType, dialect, fallbacks) {
|
|
// Previous migration is a better match, skip this one.
|
|
continue
|
|
}
|
|
}
|
|
byVersion[migration.Version] = migration
|
|
}
|
|
|
|
filtered := make(Migrations, 0, len(byVersion))
|
|
for k := range byVersion {
|
|
filtered = append(filtered, byVersion[k])
|
|
}
|
|
sort.Sort(filtered)
|
|
return filtered
|
|
}
|
|
|
|
func (mfs Migrations) find(version, dialect string, fallbacks ...string) *Migration {
|
|
var candidate *Migration
|
|
bestRank := -1
|
|
for i := range mfs {
|
|
if mfs[i].Version != version {
|
|
continue
|
|
}
|
|
if rank := migrationRank(mfs[i].DBType, dialect, fallbacks); rank > bestRank {
|
|
bestRank = rank
|
|
candidate = &mfs[i]
|
|
}
|
|
}
|
|
return candidate
|
|
}
|