This commit is contained in:
parent
8bd6f1edfb
commit
7e52a01d3d
@ -1,5 +1,10 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DefaultMigrations() []Migration {
|
||||
return []Migration{
|
||||
WithCompatibleChecksums(
|
||||
@ -22,3 +27,27 @@ func DefaultMigrations() []Migration {
|
||||
DefineCustom(15, "expand_security_setup", "expand_security_setup_v1", applySecuritySetupSchema),
|
||||
}
|
||||
}
|
||||
|
||||
var sqliteIndexDefinitionPattern = regexp.MustCompile(`(?i)^CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?["` + "`" + `]([^"` + "`" + `]+)["` + "`" + `]`)
|
||||
|
||||
// ManagedSQLiteIndexNames 返回版本化迁移允许存在的 SQLite 索引名称。
|
||||
// 自动生成的 sqlite_autoindex_* 不包含在列表中,由 SQLite 自身维护。
|
||||
func ManagedSQLiteIndexNames() map[string]struct{} {
|
||||
managed := make(map[string]struct{})
|
||||
for _, item := range DefaultMigrations() {
|
||||
for _, statement := range item.StatementsFor(DialectSQLite) {
|
||||
matches := sqliteIndexDefinitionPattern.FindStringSubmatch(strings.TrimSpace(statement))
|
||||
if len(matches) == 2 {
|
||||
managed[matches[1]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range []string{
|
||||
"uq_user_email_address_canonical",
|
||||
"idx_user_email_user_primary",
|
||||
"idx_verification_event_expires_at",
|
||||
} {
|
||||
managed[name] = struct{}{}
|
||||
}
|
||||
return managed
|
||||
}
|
||||
|
||||
@ -2,16 +2,16 @@ package dbmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
"git.pcux.cn/xuans/Smail/db/migration"
|
||||
)
|
||||
|
||||
// 仅迁移 Smail 版本化业务表;不能静默忽略自定义对象。
|
||||
func validatePortableObjects(ctx context.Context, database portableQuery, kind config.DatabaseType) error {
|
||||
queries := []string{"SELECT COUNT(*) FROM sqlite_master WHERE type IN ('view','trigger')"}
|
||||
if kind == config.DatabaseTypeSQLite {
|
||||
queries = append(queries, "SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND sql IS NOT NULL")
|
||||
}
|
||||
switch kind {
|
||||
case config.DatabaseTypeMySQL:
|
||||
queries = []string{
|
||||
@ -42,5 +42,33 @@ func validatePortableObjects(ctx context.Context, database portableQuery, kind c
|
||||
return ErrSQLUnsupportedObject
|
||||
}
|
||||
}
|
||||
if kind == config.DatabaseTypeSQLite {
|
||||
if err := validateSQLiteIndexes(ctx, database); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSQLiteIndexes(ctx context.Context, database portableQuery) error {
|
||||
rows, err := database.QueryContext(ctx, "SELECT name, sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL")
|
||||
if err != nil {
|
||||
return ErrSQLUnsupportedObject
|
||||
}
|
||||
defer rows.Close()
|
||||
managed := migration.ManagedSQLiteIndexNames()
|
||||
for rows.Next() {
|
||||
var name string
|
||||
var definition sql.NullString
|
||||
if err := rows.Scan(&name, &definition); err != nil || !definition.Valid || strings.TrimSpace(definition.String) == "" {
|
||||
return ErrSQLUnsupportedObject
|
||||
}
|
||||
if _, ok := managed[name]; !ok {
|
||||
return ErrSQLUnsupportedObject
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return ErrSQLUnsupportedObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user