|
|
@@ -334,3 +334,85 @@ func TestP1ReadOnlyDSN(t *testing.T) {
|
|
|
t.Fatalf("持久化后应 2 行, 实际 %d (%s)", cnt, fmt.Sprint(cnt))
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// TestP1Vacuum VACUUM 语句: 整库重建后行数据原样、文件可继续读写。
|
|
|
+// 顺带验证三种典型调用形态: 裸语句 / 多行空白 / 大小写混合。
|
|
|
+func TestP1Vacuum(t *testing.T) {
|
|
|
+ db := openTestDB(t)
|
|
|
+ if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ for i := 1; i <= 10; i++ {
|
|
|
+ if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, i, i*10); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 删掉一半再 VACUUM: 有垃圾可收, 更接近真实场景
|
|
|
+ if _, err := db.Exec(`DELETE FROM t WHERE id > ?`, 5); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ res, err := db.Exec(`VACUUM`)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("vacuum: %v", err)
|
|
|
+ }
|
|
|
+ if n, err := res.RowsAffected(); err != nil || n != 5 {
|
|
|
+ t.Fatalf("RowsAffected 应为复制的 5 行, 实际 %d (%v)", n, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 行数据原样
|
|
|
+ var cnt int
|
|
|
+ if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if cnt != 5 {
|
|
|
+ t.Fatalf("VACUUM 后应 5 行, 实际 %d", cnt)
|
|
|
+ }
|
|
|
+ var v int
|
|
|
+ if err := db.QueryRow(`SELECT v FROM t WHERE id = ?`, 3).Scan(&v); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if v != 30 {
|
|
|
+ t.Fatalf("行数据应为 30, 实际 %d", v)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重建后可继续读写
|
|
|
+ if _, err := db.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 100, 1); err != nil {
|
|
|
+ t.Fatalf("VACUUM 后插入失败: %v", err)
|
|
|
+ }
|
|
|
+ // 大小写/空白形态
|
|
|
+ if _, err := db.Exec(" vacuum "); err != nil {
|
|
|
+ t.Fatalf("小写带空白 VACUUM: %v", err)
|
|
|
+ }
|
|
|
+ if err := db.QueryRow(`SELECT COUNT(*) FROM t`).Scan(&cnt); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if cnt != 6 {
|
|
|
+ t.Fatalf("第二次 VACUUM 后应 6 行, 实际 %d", cnt)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestP1VacuumInTxn 活跃事务中 VACUUM 必须被拒 (引擎层 ErrTxnActive 透传)。
|
|
|
+func TestP1VacuumInTxn(t *testing.T) {
|
|
|
+ db := openTestDB(t)
|
|
|
+ if _, err := db.Exec(`CREATE TABLE t (id BIGINT PRIMARY KEY, v INT)`); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ tx, err := db.Begin()
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if _, err := tx.Exec(`INSERT INTO t (id, v) VALUES (?, ?)`, 1, 1); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if _, err := tx.Exec(`VACUUM`); err == nil {
|
|
|
+ t.Fatal("事务中 VACUUM 应被拒绝")
|
|
|
+ }
|
|
|
+ if err := tx.Rollback(); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ // 回滚后 VACUUM 恢复可用
|
|
|
+ if _, err := db.Exec(`VACUUM`); err != nil {
|
|
|
+ t.Fatalf("回滚后 VACUUM: %v", err)
|
|
|
+ }
|
|
|
+}
|