# SQLite 双端数据与回退实验

Python 3.10+，使用标准库 sqlite3，无 pip、云账户或网络。数据为合成教学订单。
实际执行 SQLite INSERT/UPDATE/DELETE；变化日志、连续水位和单一写入口由程序模拟。
不实现真实 CDC、网络并发、数据库权限 Fence、跨库事务或外部支付补偿。

解压进入本目录，先读 input.json 并预测：

```sh
python3 lab.py --out runs/normal
python3 lab.py --out runs/swapped --fault swap-amounts
python3 lab.py --out runs/missing-delete --fault skip-delete
python3 -m unittest -v test_lab.py
```

每次 --out 必须不存在，防止覆盖证据。程序生成 source.sqlite、target.sqlite、report.json。
正常退出 0；故障 HOLD 退出 2，属于预期对照。8 项测试须全部通过。
若 sqlite3 模块缺失，使用带 SQLite 支持的 Python 安装；不需要 sqlite3 命令行程序。

## 输入与规则

orders: ID、整数分、状态。sourceEvents 为 1–3；targetEvents 为 4–6。
每事件包含 seq、id、before、after，null 表示行不存在。每个事件按完整内容记回执。
目标必须从水位 0 的固定快照顺序接收 1–3；这是稳定快照的教学替代，未模拟并发取快照。
重复同一事件不再次改变行；复用序号却改变内容、跳序、旧值冲突都会拒绝。
订单变更和回执在同一个本地事务提交。写入隔离只约束 lab.py API，直接 SQL 可绕过。

## 参考与练习

normal: forward=4 行/80000 分/水位 3；final=4 行/51000 分/水位 6，O1/O2/O5/O6，仅源可写。
swapped: 总数仍相同，O1/O2 内容不同，切换 HOLD，两端客户写入关闭。
missing-delete: 反放事件 4 后缺 5，6 被拒；源水位 4、目标 6，O4 未删、O6 未建。
reference/*.json 是同一代码实跑结果；不要覆盖它们。

将 input.json 复制为 unseen.json，只改目标事件 6 的金额为 15000；先算再运行：

```sh
python3 lab.py --data unseen.json --out runs/unseen
```

应得到 final 57000 分、4 行、水位 6。再把事件 4 的 before 改为 9999，观察冲突拒绝。
提交原始输入、三条路径输出、按键差异表、第一次偏离预期的位置，以及未证明的真实系统能力。
详细教材见站点 /learn/cloud-migration/case.html。

## 检查数据库

```sh
python3 -c 'import sqlite3; c=sqlite3.connect("runs/normal/source.sqlite"); print(c.execute("SELECT * FROM orders ORDER BY id").fetchall()); print(c.execute("SELECT seq FROM applied ORDER BY seq").fetchall())'
```

结果不一致时先查输入文件、watermark、differences、reason，再查原始事件；不要直接改总额凑平。
结果目录可自行保留或删除，程序不会访问该目录之外的数据库，不会连接真实服务。
