84 lines
3.3 KiB
Go
84 lines
3.3 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
)
|
|
|
|
// SchemaVerification 是发布和恢复前的只读结构核验结果。
|
|
// 它不修改数据库,也不代表已经完成备份恢复或高可用验收。
|
|
type SchemaVerification struct {
|
|
// SchemaVersion 是迁移账本中的最后版本。
|
|
SchemaVersion uint64 `json:"schema_version"`
|
|
// MigrationCount 是已记录且通过校验的迁移数量。
|
|
MigrationCount int `json:"migration_count"`
|
|
// ForeignKeyCount 是 Orbit 表上的数据库外键数量,必须为零。
|
|
ForeignKeyCount int `json:"foreign_key_count"`
|
|
// RequiredIndexCount 是有界观测所需固定索引数量,当前必须为六。
|
|
RequiredIndexCount int `json:"required_index_count"`
|
|
// Complete 表示迁移、外键和观测索引均满足当前二进制要求。
|
|
Complete bool `json:"complete"`
|
|
}
|
|
|
|
var requiredOperationalIndexes = []struct {
|
|
table string
|
|
name string
|
|
}{
|
|
{"pulse_tasks", "ix_task_observe_state_available"},
|
|
{"orbit_deliveries", "ix_delivery_observe_state_available"},
|
|
{"weave_instances", "ix_workflow_observe_state_updated"},
|
|
{"orbit_events", "ix_event_observe_type_occurred"},
|
|
{"pulse_tasks", "ix_task_observe_terminal_completed"},
|
|
{"weave_instances", "ix_workflow_observe_terminal_completed"},
|
|
}
|
|
|
|
// VerifyOperationalSchema performs the read-only preflight used before release
|
|
// or restore. It never runs migration or repairs a dirty database.
|
|
func VerifyOperationalSchema(ctx context.Context, db *sql.DB) (SchemaVerification, error) {
|
|
if db == nil {
|
|
return SchemaVerification{}, errors.New("database is required")
|
|
}
|
|
status, err := Status(ctx, db)
|
|
if err != nil {
|
|
return SchemaVerification{}, errors.New("schema status unavailable")
|
|
}
|
|
result := SchemaVerification{MigrationCount: len(status)}
|
|
if len(status) > 0 {
|
|
result.SchemaVersion = status[len(status)-1].Version
|
|
}
|
|
for _, item := range status {
|
|
if item.Dirty {
|
|
return result, errors.New("schema has a dirty migration")
|
|
}
|
|
}
|
|
var foreignKeys int
|
|
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE() AND LEFT(TABLE_NAME,6) IN ('orbit_','pulse_','weave_')`).Scan(&foreignKeys); err != nil {
|
|
return result, errors.New("foreign key status unavailable")
|
|
}
|
|
result.ForeignKeyCount = foreignKeys
|
|
for _, required := range requiredOperationalIndexes {
|
|
var count int
|
|
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND INDEX_NAME=?`, required.table, required.name).Scan(&count); err != nil {
|
|
return result, errors.New("operational index status unavailable")
|
|
}
|
|
if count > 0 {
|
|
result.RequiredIndexCount++
|
|
}
|
|
}
|
|
wanted, err := Migrations()
|
|
if err != nil || len(status) != len(wanted) {
|
|
return result, errors.New("schema version mismatch")
|
|
}
|
|
for i, migration := range wanted {
|
|
if status[i].Version != migration.Version || status[i].Name != migration.Name || status[i].Checksum != migration.Checksum {
|
|
return result, errors.New("schema checksum mismatch")
|
|
}
|
|
}
|
|
result.Complete = result.ForeignKeyCount == 0 && result.RequiredIndexCount == len(requiredOperationalIndexes)
|
|
if !result.Complete {
|
|
return result, errors.New("schema preflight requirements are not satisfied")
|
|
}
|
|
return result, nil
|
|
}
|