复审 p2-1items(tip cc6904b)要求的三处,逐条:
1) SQL 双引号改单引号(唯一返工项):
- preinsert_store.go 的 revertMergedSQL:`merged_into = ""` → `''`。今天功能等价,
但靠的是 SQLite「双引号标识符解析不到 ⇒ 回退成字符串字面量」的 legacy 行为;
这是一条**改数据**的语句,还被 RevertMerged 与整理侧回滚共用,不该依赖回退。
- retention_migration_test.go 的 `WHERE retention_tier = ""` 一并统一成 `''`,
并在原位留下「为什么」。
2) 两处过期注释:
- retention_schema.go:「反过来(先自举、后迁移)也成立」是**假结论**,改为
「生产顺序成立;反向顺序在 0019 处即失败(duplicate column name: status,既有问题)」。
- store.go 的 ExpiresAt:「本字段目前只写不读」已半过期(Add 现在会把最终落库
时刻回填到返回值),改成「读回语义是半开的」:Add 返回的条目有值,从库里读回的
恒为零值。
3) 人工路径(塔裁定「人工动作默认应为 long」):包内的 `RememberManual`(「记住 X」)
显式钉 `RetentionTier = long`,留存规则因此不许把人工明确要求记住的东西降成临时层
(extract_test.go 加断言)。面板网关那条(adminAddMemory 直调 Add、不传档位)
按塔的口径不在本片、留给后续切片 —— 本片对它的唯一影响见回报。
不动(按塔的裁定):preinsert.go 514 行(dev 既有 tech debt)、
design/03 §10.5 与 design/23 §18 的章节(塔的账)、gateway 人工路径。
回归:go build ./... + go vet ./... + go test -count=1 ./internal/runtime/memory/... ./internal/db/...
全绿,内存包 -race 亦绿。
58 lines
2.7 KiB
Go
58 lines
2.7 KiB
Go
package memory
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// 本文件是「留多久 / 干什么用」轴(迁移 0026)的 schema 前滚:给**既有**的
|
||
// memory_items 补 `retention_tier` 列,再建读侧索引。
|
||
//
|
||
// 为什么需要在这里前滚:`createMemoryItemsTable` 是 `CREATE TABLE IF NOT EXISTS`,
|
||
// 它只对**新库**生效 —— 既有部署的表早就建好了,新列不会自己长出来。这与
|
||
// `ensureProjectIDColumn` / `ensureTidyMemoryColumns` 是同一手法、同一原因
|
||
// (记忆存储必须自包含、可在任意 *sql.DB 上使用)。
|
||
//
|
||
// 🔴 与迁移的分工(两份真源,必须同形):权威机制是
|
||
// `internal/db/migrations/0026_memory_retention_tier`;这里是「任意 *sql.DB」的
|
||
// 前滚。启动顺序是「先 MigrateUp、后 NewStore」(cmd/cyrene/wire.go),因此生产
|
||
// 库里 0026 先落地、这里走到 `duplicate column name` 是**预期路径**。
|
||
//
|
||
// ⚠️ **反向顺序不成立**(别把上面那句读成「两个方向都行」):先 NewStore、后
|
||
// MigrateUp 会在 **0019** 就先炸(`duplicate column name: status` —— 自举早已把
|
||
// 整理列建好),这是既有问题、与本片无关,因此本仓的可迁移顺序只有「先迁移」。
|
||
//
|
||
// 顺序**必须**是「建表 → 前滚列 → 建索引」:索引依赖的列在既有库上还不存在时,
|
||
// 先建索引会报 "no such column: retention_tier"(tidy_schema.go 记过同型的教训)。
|
||
|
||
// ensureRetentionSchema 幂等地给 memory_items 补上档位列与读侧索引。
|
||
func ensureRetentionSchema(ctx context.Context, db *sql.DB) error {
|
||
if err := ensureRetentionTierColumn(ctx, db); err != nil {
|
||
return err
|
||
}
|
||
if _, err := db.ExecContext(ctx, createMemoryItemsRetentionTierIndex); err != nil {
|
||
return fmt.Errorf("memory: retention tier index: %w", err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ensureRetentionTierColumn 在列缺失时补加 `retention_tier`,已存在时忽略
|
||
// SQLite 的 "duplicate column name"(那是预期路径,不是错误)。
|
||
//
|
||
// 默认值 = 空串 = 尚未分型:既有行因此**行为逐字不变**(不设过期、读路径照常可见),
|
||
// 本片也刻意**不**在启动时回填它们的档位 —— 回填是一次写全表的动作,该由整理侧
|
||
// 显式做(「按空串过滤」正是那个入口),不该藏在组合根的启动路径里。
|
||
func ensureRetentionTierColumn(ctx context.Context, db *sql.DB) error {
|
||
_, err := db.ExecContext(ctx, `ALTER TABLE memory_items
|
||
ADD COLUMN retention_tier TEXT NOT NULL DEFAULT ''`)
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
if strings.Contains(err.Error(), "duplicate column name") {
|
||
return nil
|
||
}
|
||
return fmt.Errorf("memory: roll forward retention_tier column: %w", err)
|
||
}
|