-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmigrate.go
More file actions
142 lines (128 loc) · 5.16 KB
/
Copy pathmigrate.go
File metadata and controls
142 lines (128 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package thing
import (
"context"
"fmt"
"reflect"
"strings"
"sync"
"github.com/burugo/thing/drivers/schema"
log "github.com/burugo/thing/internal/logging"
internalSchema "github.com/burugo/thing/internal/schema"
)
// AllowDropColumn controls whether AutoMigrate will execute 'DROP COLUMN' statements.
// Defaults to false (columns will not be dropped).
var AllowDropColumn = false
// AutoMigrateOptions controls AutoMigrateWithOptions behavior.
type AutoMigrateOptions struct {
// AllowDropColumn controls whether DROP COLUMN statements are executed.
AllowDropColumn bool
}
// GenerateMigrationSQL 生成建表 SQL,但不执行,支持批量模型
func GenerateMigrationSQL(models ...interface{}) ([]string, error) {
db := configuredDB()
if db == nil {
return nil, fmt.Errorf("GenerateMigrationSQL: globalDB is nil, please call thing.Configure(db, cache)")
}
dialect := db.DialectName()
return internalSchema.AutoMigrateWithDialect(dialect, models...)
}
// IntrospectorFactory is a function that returns a schema.Introspector for a given DBAdapter.
type IntrospectorFactory func(DBAdapter) schema.Introspector
var (
introspectorFactoryMu sync.RWMutex
introspectorFactories = make(map[string]IntrospectorFactory)
)
// RegisterIntrospectorFactory registers a factory for a given dialect (e.g. "sqlite", "mysql", "postgres").
func RegisterIntrospectorFactory(dialect string, factory IntrospectorFactory) {
introspectorFactoryMu.Lock()
defer introspectorFactoryMu.Unlock()
introspectorFactories[dialect] = factory
}
// getIntrospectorFactory returns the registered factory for a dialect, or nil if not found.
func getIntrospectorFactory(dialect string) IntrospectorFactory {
introspectorFactoryMu.RLock()
defer introspectorFactoryMu.RUnlock()
return introspectorFactories[dialect]
}
func configuredDB() DBAdapter {
configMutex.RLock()
defer configMutex.RUnlock()
return globalDB
}
// introspectTable returns the current schema.TableInfo for a table using the registered driver introspector.
func introspectTable(ctx context.Context, db DBAdapter, tableName string, dialect string) (*schema.TableInfo, error) {
factory := getIntrospectorFactory(dialect)
if factory == nil {
return nil, fmt.Errorf("no introspector registered for dialect: %s", dialect)
}
introspector := factory(db)
return introspector.GetTableInfo(ctx, tableName)
}
// AutoMigrate 生成并执行建表 SQL,支持批量建表和 schema diff
func AutoMigrate(models ...interface{}) error {
return AutoMigrateWithOptions(context.Background(), AutoMigrateOptions{AllowDropColumn: AllowDropColumn}, models...)
}
// AutoMigrateWithOptions generates and executes schema migration SQL with an explicit context and options.
func AutoMigrateWithOptions(ctx context.Context, opts AutoMigrateOptions, models ...interface{}) error {
if ctx == nil {
return fmt.Errorf("AutoMigrateWithOptions: context must be non-nil")
}
db := configuredDB()
if db == nil {
return fmt.Errorf("AutoMigrate: globalDB is nil, please call thing.Configure(db, cache)")
}
dialect := db.DialectName()
for _, model := range models {
// 1. 获取模型元信息
typeOf := reflect.TypeOf(model)
if typeOf.Kind() == reflect.Pointer {
typeOf = typeOf.Elem()
}
info, err := internalSchema.GetCachedModelInfo(typeOf)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to get model info for %s: %w", typeOf.Name(), err)
}
tableName := info.TableName
// 2. introspect table (returns nil, nil if not exists)
dbTableInfo, err := introspectTable(ctx, db, tableName, dialect)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to introspect table %s: %w", tableName, err)
}
if dbTableInfo == nil {
// 表不存在,直接建表
createSQL, err := internalSchema.GenerateCreateTableSQL(info, dialect)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to generate CREATE TABLE SQL for %s: %w", tableName, err)
}
// --- LOGGING SQL ---
// 使用 %+q 格式化字符串,可以更清晰地显示包含换行的 SQL
log.Debugf("AutoMigrate executing CREATE TABLE SQL: %+q", createSQL)
_, err = db.Exec(ctx, createSQL)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to execute CREATE TABLE SQL: %w\nSQL: %s", err, createSQL)
}
continue
}
// 表已存在,做 schema diff
alterSQLs, err := internalSchema.GenerateAlterTableSQL(info, internalSchema.ConvertTableInfo(dbTableInfo), dialect)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to generate ALTER TABLE SQL for %s: %w", tableName, err)
}
for _, alterSQL := range alterSQLs {
isDropColumnSQL := strings.Contains(strings.ToUpper(alterSQL), "DROP COLUMN") // 更通用的检查
if isDropColumnSQL && !opts.AllowDropColumn {
// 如果是 DROP COLUMN 语句,并且 AllowDropColumn 为 false,则跳过
log.Infof("AutoMigrate skipping column drop because AllowDropColumn is false: %s", alterSQL)
continue
}
// --- LOGGING SQL ---
// 使用 %+q 格式化字符串
log.Debugf("AutoMigrate executing ALTER TABLE SQL: %+q", alterSQL)
_, err = db.Exec(ctx, alterSQL)
if err != nil {
return fmt.Errorf("AutoMigrate: failed to execute ALTER TABLE SQL: %w\nSQL: %s", err, alterSQL)
}
}
}
return nil
}