| 
									
										
										
										
											2015-01-20 21:15:48 +08:00
										 |  |  | package migrator | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-02-08 04:05:10 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-05-01 02:06:33 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2025-03-31 21:19:32 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	_ "github.com/go-sql-driver/mysql" | 
					
						
							| 
									
										
										
										
											2023-06-15 04:13:36 +08:00
										 |  |  | 	"github.com/golang-migrate/migrate/v4/database" | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	_ "github.com/lib/pq" | 
					
						
							| 
									
										
										
										
											2024-02-08 04:05:10 +08:00
										 |  |  | 	"github.com/mattn/go-sqlite3" | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/attribute" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/codes" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/trace" | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 	"go.uber.org/atomic" | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-02 23:13:01 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util/xorm" | 
					
						
							| 
									
										
										
										
											2021-11-25 03:56:07 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/metrics/metricutil" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							| 
									
										
										
										
											2021-11-25 03:56:07 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	ErrMigratorIsLocked   = fmt.Errorf("migrator is locked") | 
					
						
							|  |  |  | 	ErrMigratorIsUnlocked = fmt.Errorf("migrator is unlocked") | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/sqlstore/migrator") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | type Migrator struct { | 
					
						
							| 
									
										
										
										
											2022-06-04 07:59:49 +08:00
										 |  |  | 	DBEngine     *xorm.Engine | 
					
						
							|  |  |  | 	Dialect      Dialect | 
					
						
							|  |  |  | 	migrations   []Migration | 
					
						
							|  |  |  | 	migrationIds map[string]struct{} | 
					
						
							|  |  |  | 	Logger       log.Logger | 
					
						
							|  |  |  | 	Cfg          *setting.Cfg | 
					
						
							|  |  |  | 	isLocked     atomic.Bool | 
					
						
							| 
									
										
										
										
											2023-04-19 23:34:19 +08:00
										 |  |  | 	logMap       map[string]MigrationLog | 
					
						
							| 
									
										
										
										
											2023-05-23 02:31:07 +08:00
										 |  |  | 	tableName    string | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	metrics migratorMetrics | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-20 15:50:08 +08:00
										 |  |  | type MigrationLog struct { | 
					
						
							|  |  |  | 	Id          int64 | 
					
						
							| 
									
										
										
										
											2020-11-11 13:21:08 +08:00
										 |  |  | 	MigrationID string `xorm:"migration_id"` | 
					
						
							|  |  |  | 	SQL         string `xorm:"sql"` | 
					
						
							| 
									
										
										
										
											2015-01-20 15:50:08 +08:00
										 |  |  | 	Success     bool | 
					
						
							|  |  |  | 	Error       string | 
					
						
							|  |  |  | 	Timestamp   time.Time | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | type migratorMetrics struct { | 
					
						
							|  |  |  | 	migCount         *prometheus.CounterVec | 
					
						
							|  |  |  | 	migDuration      *prometheus.HistogramVec | 
					
						
							|  |  |  | 	totalMigDuration *prometheus.HistogramVec | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-11 20:08:39 +08:00
										 |  |  | func NewMigrator(engine *xorm.Engine, cfg *setting.Cfg) *Migrator { | 
					
						
							| 
									
										
										
										
											2023-05-23 02:31:07 +08:00
										 |  |  | 	return NewScopedMigrator(engine, cfg, "") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewScopedMigrator should only be used for the transition to a new storage engine
 | 
					
						
							|  |  |  | func NewScopedMigrator(engine *xorm.Engine, cfg *setting.Cfg, scope string) *Migrator { | 
					
						
							|  |  |  | 	mg := &Migrator{ | 
					
						
							|  |  |  | 		Cfg:          cfg, | 
					
						
							|  |  |  | 		DBEngine:     engine, | 
					
						
							|  |  |  | 		migrations:   make([]Migration, 0), | 
					
						
							|  |  |  | 		migrationIds: make(map[string]struct{}), | 
					
						
							| 
									
										
										
										
											2023-06-15 04:13:36 +08:00
										 |  |  | 		Dialect:      NewDialect(engine.DriverName()), | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		metrics: migratorMetrics{ | 
					
						
							|  |  |  | 			migCount: prometheus.NewCounterVec(prometheus.CounterOpts{ | 
					
						
							|  |  |  | 				Namespace: "grafana_database", | 
					
						
							|  |  |  | 				Subsystem: scope, | 
					
						
							|  |  |  | 				Name:      "migrations_total", | 
					
						
							|  |  |  | 				Help:      "Total number of SQL migrations", | 
					
						
							|  |  |  | 			}, []string{"success"}), | 
					
						
							|  |  |  | 			migDuration: metricutil.NewHistogramVec(prometheus.HistogramOpts{ | 
					
						
							|  |  |  | 				Namespace: "grafana_database", | 
					
						
							|  |  |  | 				Subsystem: scope, | 
					
						
							|  |  |  | 				Name:      "migration_duration_seconds", | 
					
						
							|  |  |  | 				Help:      "Individual SQL migration duration in seconds", | 
					
						
							|  |  |  | 			}, []string{"success"}), | 
					
						
							|  |  |  | 			totalMigDuration: metricutil.NewHistogramVec(prometheus.HistogramOpts{ | 
					
						
							|  |  |  | 				Namespace: "grafana_database", | 
					
						
							|  |  |  | 				Subsystem: scope, | 
					
						
							|  |  |  | 				Name:      "all_migrations_duration_seconds", | 
					
						
							|  |  |  | 				Help:      "Duration of the entire SQL migration process in seconds", | 
					
						
							|  |  |  | 			}, []string{"success"}), | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-05-23 02:31:07 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if scope == "" { | 
					
						
							|  |  |  | 		mg.tableName = "migration_log" | 
					
						
							|  |  |  | 		mg.Logger = log.New("migrator") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		mg.tableName = scope + "_migration_log" | 
					
						
							| 
									
										
										
										
											2023-11-03 22:30:52 +08:00
										 |  |  | 		mg.Logger = log.New(scope + "-migrator") | 
					
						
							| 
									
										
										
										
											2023-05-23 02:31:07 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	return mg | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | // Collect implements Prometheus.Collector.
 | 
					
						
							|  |  |  | func (mg *Migrator) Collect(ch chan<- prometheus.Metric) { | 
					
						
							|  |  |  | 	mg.metrics.migCount.Collect(ch) | 
					
						
							|  |  |  | 	mg.metrics.migDuration.Collect(ch) | 
					
						
							|  |  |  | 	mg.metrics.totalMigDuration.Collect(ch) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Describe implements Prometheus.Collector.
 | 
					
						
							|  |  |  | func (mg *Migrator) Describe(ch chan<- *prometheus.Desc) { | 
					
						
							|  |  |  | 	mg.metrics.migCount.Describe(ch) | 
					
						
							|  |  |  | 	mg.metrics.migDuration.Describe(ch) | 
					
						
							|  |  |  | 	mg.metrics.totalMigDuration.Describe(ch) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 02:31:07 +08:00
										 |  |  | // AddCreateMigration adds the initial migration log table -- this should likely be
 | 
					
						
							|  |  |  | // automatic and first, but enough tests exists that do not expect that we can keep it explicit
 | 
					
						
							|  |  |  | func (mg *Migrator) AddCreateMigration() { | 
					
						
							|  |  |  | 	mg.AddMigration("create "+mg.tableName+" table", NewAddTableMigration(Table{ | 
					
						
							|  |  |  | 		Name: mg.tableName, | 
					
						
							|  |  |  | 		Columns: []*Column{ | 
					
						
							|  |  |  | 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, | 
					
						
							|  |  |  | 			{Name: "migration_id", Type: DB_NVarchar, Length: 255}, | 
					
						
							|  |  |  | 			{Name: "sql", Type: DB_Text}, | 
					
						
							|  |  |  | 			{Name: "success", Type: DB_Bool}, | 
					
						
							|  |  |  | 			{Name: "error", Type: DB_Text}, | 
					
						
							|  |  |  | 			{Name: "timestamp", Type: DB_DateTime}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	})) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-14 22:30:12 +08:00
										 |  |  | func (mg *Migrator) MigrationsCount() int { | 
					
						
							|  |  |  | 	return len(mg.migrations) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | func (mg *Migrator) AddMigration(id string, m Migration) { | 
					
						
							| 
									
										
										
										
											2022-06-04 07:59:49 +08:00
										 |  |  | 	if _, ok := mg.migrationIds[id]; ok { | 
					
						
							|  |  |  | 		panic(fmt.Sprintf("migration id conflict: %s", id)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	m.SetId(id) | 
					
						
							|  |  |  | 	mg.migrations = append(mg.migrations, m) | 
					
						
							| 
									
										
										
										
											2022-06-04 07:59:49 +08:00
										 |  |  | 	mg.migrationIds[id] = struct{}{} | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-25 03:56:07 +08:00
										 |  |  | func (mg *Migrator) GetMigrationIDs(excludeNotLogged bool) []string { | 
					
						
							|  |  |  | 	result := make([]string, 0, len(mg.migrations)) | 
					
						
							|  |  |  | 	for _, migration := range mg.migrations { | 
					
						
							|  |  |  | 		if migration.SkipMigrationLog() && excludeNotLogged { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		result = append(result, migration.Id()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return result | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { | 
					
						
							| 
									
										
										
										
											2015-01-19 17:44:16 +08:00
										 |  |  | 	logMap := make(map[string]MigrationLog) | 
					
						
							|  |  |  | 	logItems := make([]MigrationLog, 0) | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 	if err := mg.DBEngine.Table(mg.tableName).Find(&logItems); err != nil { | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, logItem := range logItems { | 
					
						
							|  |  |  | 		if !logItem.Success { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-11-11 13:21:08 +08:00
										 |  |  | 		logMap[logItem.MigrationID] = logItem | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-19 23:34:19 +08:00
										 |  |  | 	mg.logMap = logMap | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	return logMap, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-19 23:34:19 +08:00
										 |  |  | func (mg *Migrator) RemoveMigrationLogs(migrationsIDs ...string) { | 
					
						
							|  |  |  | 	for _, id := range migrationsIDs { | 
					
						
							|  |  |  | 		delete(mg.logMap, id) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | // soft-deprecated: use RunMigrations instead (will be fully deprecated later)
 | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | func (mg *Migrator) Start(isDatabaseLockingEnabled bool, lockAttemptTimeout int) (err error) { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	return mg.RunMigrations(context.Background(), isDatabaseLockingEnabled, lockAttemptTimeout) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (mg *Migrator) RunMigrations(ctx context.Context, isDatabaseLockingEnabled bool, lockAttemptTimeout int) (err error) { | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 	if !isDatabaseLockingEnabled { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		return mg.run(ctx) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-15 04:13:36 +08:00
										 |  |  | 	dbName, err := mg.Dialect.GetDBName(mg.DBEngine.DataSourceName()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	key, err := database.GenerateAdvisoryLockId(dbName) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	logger := mg.Logger.FromContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 	return mg.InTransaction(func(sess *xorm.Session) error { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		logger.Info("Locking database") | 
					
						
							| 
									
										
										
										
											2023-06-15 04:13:36 +08:00
										 |  |  | 		lockCfg := LockCfg{ | 
					
						
							|  |  |  | 			Session: sess, | 
					
						
							|  |  |  | 			Key:     key, | 
					
						
							|  |  |  | 			Timeout: lockAttemptTimeout, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := casRestoreOnErr(&mg.isLocked, false, true, ErrMigratorIsLocked, mg.Dialect.Lock, lockCfg); err != nil { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 			logger.Error("Failed to lock database", "error", err) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		defer func() { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 			logger.Info("Unlocking database") | 
					
						
							| 
									
										
										
										
											2023-06-15 04:13:36 +08:00
										 |  |  | 			unlockErr := casRestoreOnErr(&mg.isLocked, true, false, ErrMigratorIsUnlocked, mg.Dialect.Unlock, lockCfg) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 			if unlockErr != nil { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 				logger.Error("Failed to unlock database", "error", unlockErr) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// migration will run inside a nested transaction
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		return mg.run(ctx) | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | func (mg *Migrator) run(ctx context.Context) (err error) { | 
					
						
							|  |  |  | 	ctx, span := tracer.Start(ctx, "Migrator.run") | 
					
						
							|  |  |  | 	defer span.End() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger := mg.Logger.FromContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger.Info("Starting DB migrations") | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 	migrationLogExists, err := mg.DBEngine.IsTableExist(mg.tableName) | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2025-03-10 22:59:06 +08:00
										 |  |  | 		return fmt.Errorf("failed to check table existence: %w", err) | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !migrationLogExists { | 
					
						
							|  |  |  | 		// Check if dialect can initialize database from a snapshot.
 | 
					
						
							|  |  |  | 		err := mg.Dialect.CreateDatabaseFromSnapshot(ctx, mg.DBEngine, mg.tableName) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2025-03-10 22:59:06 +08:00
										 |  |  | 			return fmt.Errorf("failed to create database from snapshot: %w", err) | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		migrationLogExists, err = mg.DBEngine.IsTableExist(mg.tableName) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2025-03-10 22:59:06 +08:00
										 |  |  | 			return fmt.Errorf("failed to check table existence after applying snapshot: %w", err) | 
					
						
							| 
									
										
										
										
											2025-03-06 23:11:20 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if migrationLogExists { | 
					
						
							|  |  |  | 		_, err = mg.GetMigrationLog() | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	successLabel := prometheus.Labels{"success": "true"} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-08 04:26:29 +08:00
										 |  |  | 	migrationsPerformed := 0 | 
					
						
							|  |  |  | 	migrationsSkipped := 0 | 
					
						
							|  |  |  | 	start := time.Now() | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	for _, m := range mg.migrations { | 
					
						
							| 
									
										
										
										
											2023-04-19 23:34:19 +08:00
										 |  |  | 		_, exists := mg.logMap[m.Id()] | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		if exists { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 			logger.Debug("Skipping migration: Already executed", "id", m.Id()) | 
					
						
							|  |  |  | 			span.AddEvent("Skipping migration: Already executed", | 
					
						
							|  |  |  | 				trace.WithAttributes(attribute.String("migration_id", m.Id())), | 
					
						
							|  |  |  | 			) | 
					
						
							| 
									
										
										
										
											2021-03-08 04:26:29 +08:00
										 |  |  | 			migrationsSkipped++ | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		migStart := time.Now() | 
					
						
							| 
									
										
										
										
											2015-01-19 17:44:16 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		if err := mg.doMigration(ctx, m); err != nil { | 
					
						
							|  |  |  | 			failLabel := prometheus.Labels{"success": "false"} | 
					
						
							|  |  |  | 			metricutil.ObserveWithExemplar(ctx, mg.metrics.migDuration.With(failLabel), time.Since(migStart).Seconds()) | 
					
						
							|  |  |  | 			mg.metrics.migCount.With(failLabel).Inc() | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		metricutil.ObserveWithExemplar(ctx, mg.metrics.migDuration.With(successLabel), time.Since(migStart).Seconds()) | 
					
						
							|  |  |  | 		mg.metrics.migCount.With(successLabel).Inc() | 
					
						
							| 
									
										
										
										
											2024-02-08 04:05:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		migrationsPerformed++ | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	metricutil.ObserveWithExemplar(ctx, mg.metrics.totalMigDuration.With(successLabel), time.Since(start).Seconds()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger.Info("migrations completed", "performed", migrationsPerformed, "skipped", migrationsSkipped, "duration", time.Since(start)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure migrations are synced
 | 
					
						
							|  |  |  | 	return mg.DBEngine.Sync2() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (mg *Migrator) doMigration(ctx context.Context, m Migration) error { | 
					
						
							|  |  |  | 	ctx, span := tracer.Start(ctx, "Migrator.doMigration", trace.WithAttributes( | 
					
						
							|  |  |  | 		attribute.String("migration_id", m.Id()), | 
					
						
							|  |  |  | 	)) | 
					
						
							|  |  |  | 	defer span.End() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger := mg.Logger.FromContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sql := m.SQL(mg.Dialect) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	record := MigrationLog{ | 
					
						
							|  |  |  | 		MigrationID: m.Id(), | 
					
						
							|  |  |  | 		SQL:         sql, | 
					
						
							|  |  |  | 		Timestamp:   time.Now(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := mg.InTransaction(func(sess *xorm.Session) error { | 
					
						
							|  |  |  | 		// propagate context
 | 
					
						
							|  |  |  | 		sess = sess.Context(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := mg.exec(ctx, m, sess) | 
					
						
							|  |  |  | 		// if we get an sqlite busy/locked error, sleep 100ms and try again
 | 
					
						
							|  |  |  | 		cnt := 0 | 
					
						
							|  |  |  | 		for cnt < 3 && (errors.Is(err, sqlite3.ErrLocked) || errors.Is(err, sqlite3.ErrBusy)) { | 
					
						
							|  |  |  | 			cnt++ | 
					
						
							|  |  |  | 			logger.Debug("Database locked, sleeping then retrying", "error", err, "sql", sql) | 
					
						
							|  |  |  | 			span.AddEvent("Database locked, sleeping then retrying", | 
					
						
							|  |  |  | 				trace.WithAttributes(attribute.String("error", err.Error())), | 
					
						
							|  |  |  | 				trace.WithAttributes(attribute.String("sql", sql)), | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 			time.Sleep(100 * time.Millisecond) | 
					
						
							|  |  |  | 			err = mg.exec(ctx, m, sess) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logger.Error("Exec failed", "error", err, "sql", sql) | 
					
						
							|  |  |  | 			record.Error = err.Error() | 
					
						
							| 
									
										
										
										
											2021-08-12 21:04:09 +08:00
										 |  |  | 			if !m.SkipMigrationLog() { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 				if _, err := sess.Table(mg.tableName).Insert(&record); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2021-03-08 04:26:29 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-10-22 20:08:18 +08:00
										 |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		record.Success = true | 
					
						
							|  |  |  | 		if !m.SkipMigrationLog() { | 
					
						
							|  |  |  | 			_, err = sess.Table(mg.tableName).Insert(&record) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return tracing.Errorf(span, "migration failed (id = %s): %w", m.Id(), err) | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	span.SetStatus(codes.Ok, "") | 
					
						
							| 
									
										
										
										
											2021-03-08 04:26:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | func (mg *Migrator) exec(ctx context.Context, m Migration, sess *xorm.Session) error { | 
					
						
							|  |  |  | 	logger := mg.Logger.FromContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-11 19:10:21 +08:00
										 |  |  | 	start := time.Now() | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	logger.Info("Executing migration", "id", m.Id()) | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-30 13:49:40 +08:00
										 |  |  | 	condition := m.GetCondition() | 
					
						
							|  |  |  | 	if condition != nil { | 
					
						
							| 
									
										
										
										
											2020-11-11 13:21:08 +08:00
										 |  |  | 		sql, args := condition.SQL(mg.Dialect) | 
					
						
							| 
									
										
										
										
											2018-12-19 04:47:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-19 06:02:08 +08:00
										 |  |  | 		if sql != "" { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 			logger.Debug("Executing migration condition SQL", "id", m.Id(), "sql", sql, "args", args) | 
					
						
							| 
									
										
										
										
											2018-12-19 06:02:08 +08:00
										 |  |  | 			results, err := sess.SQL(sql, args...).Query() | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 				logger.Error("Executing migration condition failed", "id", m.Id(), "error", err) | 
					
						
							| 
									
										
										
										
											2018-12-19 06:02:08 +08:00
										 |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !condition.IsFulfilled(results) { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 				logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id()) | 
					
						
							| 
									
										
										
										
											2018-12-19 06:02:08 +08:00
										 |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2015-02-24 18:46:34 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-09-30 13:49:40 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-02-24 18:46:34 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-21 19:30:39 +08:00
										 |  |  | 	var err error | 
					
						
							|  |  |  | 	if codeMigration, ok := m.(CodeMigration); ok { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		logger.Debug("Executing code migration", "id", m.Id()) | 
					
						
							| 
									
										
										
										
											2018-08-21 19:30:39 +08:00
										 |  |  | 		err = codeMigration.Exec(sess, mg) | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2020-11-11 13:21:08 +08:00
										 |  |  | 		sql := m.SQL(mg.Dialect) | 
					
						
							| 
									
										
										
										
											2025-03-31 21:19:32 +08:00
										 |  |  | 		if strings.TrimSpace(sql) == "" { | 
					
						
							|  |  |  | 			logger.Debug("Skipping empty sql migration", "id", m.Id()) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			logger.Debug("Executing sql migration", "id", m.Id(), "sql", sql) | 
					
						
							|  |  |  | 			_, err = sess.Exec(sql) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-08-21 19:30:39 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-30 13:49:40 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 		logger.Error("Executing migration failed", "id", m.Id(), "error", err, "duration", time.Since(start)) | 
					
						
							| 
									
										
										
										
											2016-09-30 13:49:40 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 06:22:19 +08:00
										 |  |  | 	logger.Info("Migration successfully executed", "id", m.Id(), "duration", time.Since(start)) | 
					
						
							| 
									
										
										
										
											2023-09-11 19:10:21 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type dbTransactionFunc func(sess *xorm.Session) error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-14 21:43:33 +08:00
										 |  |  | func (mg *Migrator) InTransaction(callback dbTransactionFunc) error { | 
					
						
							| 
									
										
										
										
											2021-11-25 03:56:07 +08:00
										 |  |  | 	sess := mg.DBEngine.NewSession() | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 	defer sess.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-04 00:02:10 +08:00
										 |  |  | 	// XXX: Spanner cannot execute DDL statements in transactions
 | 
					
						
							|  |  |  | 	if mg.Dialect.DriverName() == Spanner { | 
					
						
							|  |  |  | 		return callback(sess) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-22 20:08:18 +08:00
										 |  |  | 	if err := sess.Begin(); err != nil { | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-22 20:08:18 +08:00
										 |  |  | 	if err := callback(sess); err != nil { | 
					
						
							| 
									
										
										
										
											2021-01-07 18:36:13 +08:00
										 |  |  | 		if rollErr := sess.Rollback(); rollErr != nil { | 
					
						
							| 
									
										
										
										
											2022-06-07 04:30:31 +08:00
										 |  |  | 			return fmt.Errorf("failed to roll back transaction due to error: %s: %w", rollErr, err) | 
					
						
							| 
									
										
										
										
											2019-10-22 20:08:18 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2019-10-22 20:08:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := sess.Commit(); err != nil { | 
					
						
							| 
									
										
										
										
											2015-01-19 01:41:03 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func casRestoreOnErr(lock *atomic.Bool, o, n bool, casErr error, f func(LockCfg) error, lockCfg LockCfg) error { | 
					
						
							| 
									
										
										
										
											2022-12-14 19:32:45 +08:00
										 |  |  | 	if !lock.CompareAndSwap(o, n) { | 
					
						
							| 
									
										
										
										
											2022-02-16 00:54:27 +08:00
										 |  |  | 		return casErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := f(lockCfg); err != nil { | 
					
						
							|  |  |  | 		// Automatically unlock/lock on error
 | 
					
						
							|  |  |  | 		lock.Store(o) | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |