导出:删掉到访后,别把它指向的原始采样点留成悬空引用

真机报「备份恢复失败,你的当前数据未被更改」——这句说明它是先校验、整体回滚。
往下查的第一个醒悟:**Core Data 的 SQLite 表里根本没有外键约束**(关系由 Core Data
自己维护),所以之前跑的 PRAGMA foreign_key_check 报 0 违例毫无意义,它压根没得可查。

按关系列手查悬空引用,结果:

  原版:悬空 0
  导出:ZRAWVISIT.ZVISIT 有 2 条指向不存在的 ZVISIT —— (2051→到访46, 465→到访200)

正是用户在站点上删掉的那两条到访。ZRAWVISIT(原始 GPS 采样点)是不托管实体、
导出时原样保留,于是删到访就留下了指向已删记录的悬空关系;而真机删到访时
采样点是跟着级联删的,所以这是它自己绝不会产生的形状。

修法:visit.finish() 之后,删掉 ZVISIT 指向不存在到访的 ZRAWVISIT 行。
选「删」而不是「把 ZVISIT 置空」:真机的「未归并采样点」虽有 1461/2246 条(置空合法),
但留成未归并会让 app 重新推出一条到访,把用户删掉的记录复活。

顺带加固:
- verify_export_structure.py 新增 1.45 节「关系完整性」,手查 8 组关系列的悬空引用,
  有悬空就把结论判成「有差异」——这条 foreign_key_check 查不出来的坑进了标准验收清单。
- TestExportDropsOrphanedRawVisits:删一条带采样点的到访 → 导出 → 断言没有悬空;已做变异验证。
This commit is contained in:
tamakyi
2026-09-20 18:05:47 +08:00
parent 56c7283e38
commit 78865fc736
3 changed files with 90 additions and 0 deletions
+24
View File
@@ -225,6 +225,30 @@ def compare_rondbackup(orig, new, new2=None):
stale += 1
say("对照:原版自身「缓存 != MAX(到访到达)」的地点 %d 个(原版缓存陈旧,导出是修正)" % stale)
# 悬空关系引用。**必须手查**:Core Data 的关系由它自己维护,SQLite 里根本没有
# 外键约束,所以 PRAGMA foreign_key_check 永远是 0 违例,查不出这类问题。
# 典型场景:站点删掉一条到访,它的 ZRAWVISIT 采样点(不托管实体)就失去了目标。
head("1.45 关系完整性(有没有指向已删记录的悬空引用)")
REFS = [("ZRAWVISIT", "ZVISIT", "ZVISIT"), ("ZRAWVISIT", "ZLOCATION", "ZLOCATION"),
("ZRAWVISIT", "ZACTIVITY", "ZACTIVITY"), ("ZSTAYSEGMENT", "ZLOCATION", "ZLOCATION"),
("ZUSERPHOTO", "ZVISIT_", "ZVISIT"), ("ZVISITPHOTOFAVORITE", "ZVISIT_", "ZVISIT"),
("ZKEYWORD", "ZACTIVITY_", "ZACTIVITY"), ("ZPOICATEGORY", "ZACTIVITY_", "ZACTIVITY")]
for tag, con in (("原版", co), ("导出", cn)):
bad = 0
for t, col, target in REFS:
try:
n = con.execute("SELECT COUNT(*) FROM %s x WHERE x.%s IS NOT NULL AND x.%s<>0 "
"AND NOT EXISTS (SELECT 1 FROM %s y WHERE y.Z_PK=x.%s)"
% (t, col, col, target, col)).fetchone()[0]
except Exception:
continue
if n:
say(" !! %s:%s.%s 有 %d 条指向不存在的 %s(真机从不产生悬空关系)" % (tag, t, col, n, target))
FAILED[0] = True
bad += n
if not bad:
say(" %s:悬空引用 0 ✓" % tag)
head("1.5 确定性(同一份数据导出两次是否一致)")
if new2 and os.path.exists(new2):
def dig(p):