修复SQLite索引迁移识别
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-06 06:37:52 +08:00
parent 8bd6f1edfb
commit 7e52a01d3d
2 changed files with 60 additions and 3 deletions

View File

@ -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
}

View File

@ -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
}