package memory import ( "context" "database/sql" "fmt" "strings" ) // 本文件是「敏感标记」轴(迁移 0028)的 schema 前滚:给**既有**的 memory_items // 补 `sensitive` 列。手法与 retention_schema.go / temporal_schema.go 逐字同族, // 原因也一样:`createMemoryItemsTable` 是 `CREATE TABLE IF NOT EXISTS`,只对 // **新库**生效,既有部署的表不会自己长出新列;而记忆存储必须自包含、能在任意 // *sql.DB 上使用,所以这条前滚不能只靠 `internal/db/migrations`。 // // 🔴 与迁移的分工(两份真源,必须同形):权威机制是 // `internal/db/migrations/0028_memory_sensitive`;这里是「任意 *sql.DB」的前滚。 // 启动顺序是「先 MigrateUp、后 NewStore」(cmd/cyrene/wire.go),因此生产库里 // 迁移先落地、这里走到 `duplicate column name` 是**预期路径**。 // // 默认值 0(= 非敏感):既有行因此**行为逐字不变**(受限车道照旧看得到它们), // 本前滚刻意**不做**任何回填判定 —— 回填是独立的一次性数据动作 // (`BackfillSensitive`,sensitive_backfill.go):把「结构前滚」与「给历史数据 // 打标」分成两步,才能使两者的验收互不牵连(前滚永远幂等且零副作用;回填可 // dry-run、可重跑、可只跑一次)。 // // 本列**不建索引**:受限车道的过滤在内存里做(候选集本来就已 `status` + 时效 + // scope 过滤过,行数在千级),而「按 sensitive 取样巡检」是人工低频查询。将来 // 若真出现按它筛的读路径,两条真源(迁移与本文件)一起加。 // sensitiveColumn 是被测列名(迁移与自举 DDL 里的字面量)。 const sensitiveColumn = "sensitive" // ensureSensitiveSchema 幂等地给 memory_items 补上敏感标记列(列缺失时 ALTER, // 已存在时忽略 SQLite 的 "duplicate column name" —— 那是预期路径,不是错误)。 func ensureSensitiveSchema(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, `ALTER TABLE memory_items ADD COLUMN sensitive INTEGER NOT NULL DEFAULT 0`) if err == nil { return nil } if strings.Contains(err.Error(), "duplicate column name") { return nil } return fmt.Errorf("memory: roll forward sensitive column: %w", err) }