我维护着Chron,一个MCP服务器,将AI编码会话的审计日志写到本地的SQLite数据库.
每个消息都会得到时间戳,每个会话都记录产生它的工具:克洛德(Claude, Cursor, Codex).
不久前,我打开历史 发现克劳德的消息 和克塞尔的消息 在一个会话中相互连接。
两个不同的工具,两个不同的终端,一个会话记录.
对于一个整个工作都是归属的工具来说,这和虫子得到的一样糟糕.
设置 会话表看起来是这样的:当一个工具开始工作时,它用标题和它自己的名称召唤。
如果已经存在该标题的会议,请恢复。
否则就另起炉灶 Claude和Cursor都从任务中产生一个简短的描述性标题.
在同一repo中执行相同的任务,它们产生相同的标题.
类似的东西,然后保证 只有一行可以存在 该标题。
因此第二个工具没有获得自己的会话.
它恢复了第一个。
Claude的讯息和Cursor的讯息在同一会话ID下登陆,在该会话上,无论哪个工具碰巧先开始,都会报告.
限制是完全按照书面规定去做的。
这只是错误的制约。
标题从来不是会话的特征。
这对是的。
修补一个尝试,这行不通 明显的举动: 这是错误的,而且它是错误的,因为它通过一个偶然的测试。
无效。
在SQL中,从不等于.,这包括一个独特的指数内的比较.
约束停止了对列为NULL的一瞬间的限制:在一个独特的索引下两行相同.
这是标准的 SQL 行为,而不是 SQLite 的夸克,但很容易忘记您在复合键上添加一栏可失效的时刻.
这里之所以重要,是因为Chron有合法的NULL行:会话通过库 API 创建,没有工具集.
固定索引是一个表达式,而不是生列: NULL倒闭为空字符串,它确实比较了自己: 每个工具一行,在一个工具内复制,仍然被拒绝.
这就是我真正想要的财产。
当然,搜索代码必须匹配。
继续只用标题搜索,所以即使有正确的限制,它也会一直找到另一个工具的行:一个限制和依赖它的查询是一个单元.
换掉的只有一只 丢弃 SQLite SQLite 中的制约没有 。
所上线是表定义的一部分,因此去掉它意味着重建表:标准阴影表舞.
没什么奇怪的事 与原始错误相比的迁移错误在这里是值得的。
我首先写到移民警卫: 如果综合指数不存在,迁移。
它的读取非常灵敏, 并且它失败了 正是数据库需要它。
计程靴在移民检查前运行。
在仍带有遗留内容的现有数据库中,该声明是成功的。
它在旧的桌子上创造了综合指数.
旧的束缚仍然坐在那里,没有改变。
因此,当迁移检查运行时,索引存在,警卫得出结论"已经迁移",它跳过.
桌子永远保持。
新鲜的数据库是好的,因为它们从一开始就是正确的。
测试套房通过,因为测试建立了新的数据库.
每一个真正的用户升级 从旧版本,会一直保留错误。
警卫必须询问制约,而不是通常与之相关的副作用.
唯一能看出限制的方法就是所存储的 DDL: Regex 对抗 DDL 不优雅.
然而,这是事实。
测试必须从旧计划开始 而不是从新计划开始 我从这个UNIQUE的限制中得到了一个身份证明。
声称"标题表示一个会话".
这绝不是事实,数据库忠实地执行了虚假的说法,直到两个工具出现之日。
综合独有索引中可忽略的列通常不会做你想做的.
如果列可以是 NULL,则索引一个快取