Files
CyberStrikeAI/internal/database/schema_legacy.go
T

165 lines
4.4 KiB
Go

package database
import (
"context"
"database/sql"
"fmt"
"strings"
"go.uber.org/zap"
)
// This is a historical foreign-key semantic migration, not a column repair
// list. Rebuild from the latest definition while retaining extra columns,
// indexes, triggers and dependent rows in old installations.
func (db *DB) migrateVulnerabilitiesConversationFK() error {
ok, err := vulnerabilitiesConversationFKOnDeleteSetNull(db.DB)
if err != nil || ok {
return err
}
reference, err := openSchemaReference(applicationSchema)
if err != nil {
return err
}
defer reference.Close()
var latest string
if err := reference.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='vulnerabilities'`).Scan(&latest); err != nil {
return err
}
createNew := "CREATE TABLE vulnerabilities_new " + latest[strings.Index(latest, "("):]
// Changing foreign_keys inside a transaction is ineffective. Pin one
// connection and disable it before the transaction to avoid cascading drops.
ctx := context.Background()
conn, err := db.Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
if _, err := conn.ExecContext(ctx, "PRAGMA foreign_keys=OFF"); err != nil {
return err
}
defer conn.ExecContext(ctx, "PRAGMA foreign_keys=ON")
tx, err := conn.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(createNew); err != nil {
return err
}
var original string
if err := tx.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='vulnerabilities'`).Scan(&original); err != nil {
return err
}
definitions, err := columnDefinitions(original)
if err != nil {
return err
}
oldColumns, err := schemaColumns(tx, "vulnerabilities")
if err != nil {
return err
}
newColumns, err := schemaColumns(tx, "vulnerabilities_new")
if err != nil {
return err
}
present := map[string]bool{}
for _, c := range newColumns {
present[strings.ToLower(c.name)] = true
}
var names, values []string
for _, c := range oldColumns {
key := strings.ToLower(c.name)
if !present[key] {
if _, err := tx.Exec("ALTER TABLE vulnerabilities_new ADD COLUMN " + definitions[key]); err != nil {
return fmt.Errorf("保留漏洞额外字段 %s 失败: %w", c.name, err)
}
}
if c.hidden != 0 {
continue
}
quoted := quoteSchemaName(c.name)
names = append(names, quoted)
if key == "retest_log" {
values = append(values, "COALESCE("+quoted+", '')")
} else {
values = append(values, quoted)
}
}
rows, err := tx.Query(`SELECT sql FROM sqlite_master WHERE tbl_name='vulnerabilities' AND type IN ('index','trigger') AND sql IS NOT NULL ORDER BY type, name`)
if err != nil {
return err
}
var objects []string
for rows.Next() {
var ddl string
if err := rows.Scan(&ddl); err != nil {
rows.Close()
return err
}
objects = append(objects, ddl)
}
err = rows.Err()
rows.Close()
if err != nil {
return err
}
copyRows := "INSERT INTO vulnerabilities_new (" + strings.Join(names, ",") + ") SELECT " + strings.Join(values, ",") + " FROM vulnerabilities"
if _, err := tx.Exec(copyRows); err != nil {
return fmt.Errorf("保留漏洞数据失败: %w", err)
}
if _, err := tx.Exec("DROP TABLE vulnerabilities"); err != nil {
return err
}
if _, err := tx.Exec("ALTER TABLE vulnerabilities_new RENAME TO vulnerabilities"); err != nil {
return err
}
for _, ddl := range objects {
if _, err := tx.Exec(ddl); err != nil {
return err
}
}
rows, err = tx.Query("PRAGMA foreign_key_check")
if err != nil {
return err
}
violation := rows.Next()
err = rows.Err()
rows.Close()
if err != nil {
return err
}
if violation {
return fmt.Errorf("漏洞外键迁移校验失败,已保留原数据")
}
if err := tx.Commit(); err != nil {
return err
}
db.logger.Info("vulnerabilities 表已迁移:删除对话时保留漏洞记录", zap.Int("preserved_columns", len(oldColumns)))
return nil
}
func vulnerabilitiesConversationFKOnDeleteSetNull(db *sql.DB) (bool, error) {
rows, err := db.Query(`PRAGMA foreign_key_list(vulnerabilities)`)
if err != nil {
return false, err
}
defer rows.Close()
found := false
for rows.Next() {
var id, seq int
var table, from, to, onUpdate, onDelete, match string
if err := rows.Scan(&id, &seq, &table, &from, &to, &onUpdate, &onDelete, &match); err != nil {
return false, err
}
if from == "conversation_id" {
found = true
if !strings.EqualFold(onDelete, "SET NULL") {
return false, nil
}
}
}
return found, rows.Err()
}