|
@@ -1,6 +1,7 @@
|
|
|
package core
|
|
package core
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "encoding/binary"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"time"
|
|
"time"
|
|
|
"unsafe"
|
|
"unsafe"
|
|
@@ -17,7 +18,8 @@ import (
|
|
|
// [16,48) 时间与事务: CreateTime/ModifyTime/NextTxnID/GlobalFlags (8 字节标量)
|
|
// [16,48) 时间与事务: CreateTime/ModifyTime/NextTxnID/GlobalFlags (8 字节标量)
|
|
|
// [48,76) 表/空闲链/WAL: TablePage/TableCount/FreePageStart/FreePageEnd/
|
|
// [48,76) 表/空闲链/WAL: TablePage/TableCount/FreePageStart/FreePageEnd/
|
|
|
// FreePageCount/FirstWALPage/LastWALPage (4 字节标量, 两两紧邻无填充)
|
|
// FreePageCount/FirstWALPage/LastWALPage (4 字节标量, 两两紧邻无填充)
|
|
|
-// [76,332) 扩展: ExtConfig[256]
|
|
|
|
|
|
|
+// [76,332) 扩展: ExtConfigHdr[8] (预留) + HeaderCrc uint32 (偏移 84,
|
|
|
|
|
+// 整页 XXH32 校验, 写入前由 PrepareWrite 生成) + ExtConfig[244]
|
|
|
// [332,4096) 介绍区: Comment[3764] — 占满头页余下全部空间 (建库默认写入
|
|
// [332,4096) 介绍区: Comment[3764] — 占满头页余下全部空间 (建库默认写入
|
|
|
// 当前版本的功能介绍与修复点, 见 VersionComment; 用户可用
|
|
// 当前版本的功能介绍与修复点, 见 VersionComment; 用户可用
|
|
|
// DB.SetComment 改写, 上限即本字段容量)
|
|
// DB.SetComment 改写, 上限即本字段容量)
|
|
@@ -25,44 +27,95 @@ import (
|
|
|
// 8 字节标量全部排在前半段 (偏移 16~48), 4 字节标量排在后半段 (偏移 48~76),
|
|
// 8 字节标量全部排在前半段 (偏移 16~48), 4 字节标量排在后半段 (偏移 48~76),
|
|
|
// 任何字段后面都不留填充字节。
|
|
// 任何字段后面都不留填充字节。
|
|
|
var (
|
|
var (
|
|
|
|
|
+ // 整体头页结构体必须严格等于4K
|
|
|
_ [PageSize4K - unsafe.Sizeof(HeaderPage{})]struct{}
|
|
_ [PageSize4K - unsafe.Sizeof(HeaderPage{})]struct{}
|
|
|
_ [unsafe.Sizeof(HeaderPage{}) - PageSize4K]struct{}
|
|
_ [unsafe.Sizeof(HeaderPage{}) - PageSize4K]struct{}
|
|
|
- // FreePageEnd 必须紧跟 FreePageStart (偏移 56+4=60): 空闲链头尾指针相邻,
|
|
|
|
|
- // 读头页时一次缓存行就能取到两者。
|
|
|
|
|
- _ [unsafe.Offsetof(HeaderPage{}.FreePageEnd) - unsafe.Offsetof(HeaderPage{}.FreePageStart) - 4]struct{}
|
|
|
|
|
- _ [unsafe.Offsetof(HeaderPage{}.FreePageStart) + 4 - unsafe.Offsetof(HeaderPage{}.FreePageEnd)]struct{}
|
|
|
|
|
- // 8 字节标量必须落在 8 的倍数偏移上 (零对齐填充的另一半: 偏移对了, 填充自然为零)。
|
|
|
|
|
- _ [16]byte = [unsafe.Offsetof(HeaderPage{}.CreateTime)]byte{}
|
|
|
|
|
- _ [24]byte = [unsafe.Offsetof(HeaderPage{}.ModifyTime)]byte{}
|
|
|
|
|
- _ [32]byte = [unsafe.Offsetof(HeaderPage{}.NextTxnID)]byte{}
|
|
|
|
|
- _ [40]byte = [unsafe.Offsetof(HeaderPage{}.GlobalFlags)]byte{}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // FreePageEnd 紧跟 FreePageStart,间隔4字节
|
|
|
|
|
+ _ [unsafe.Offsetof(HeaderPage{}.FreePageEnd) - (unsafe.Offsetof(HeaderPage{}.FreePageStart) + 4)]struct{}
|
|
|
|
|
+
|
|
|
|
|
+ // 锁定各个字段偏移
|
|
|
|
|
+ _ [16]byte = [unsafe.Offsetof(HeaderPage{}.CreateTime)]byte{}
|
|
|
|
|
+ _ [24]byte = [unsafe.Offsetof(HeaderPage{}.ModifyTime)]byte{}
|
|
|
|
|
+ _ [32]byte = [unsafe.Offsetof(HeaderPage{}.NextTxnID)]byte{}
|
|
|
|
|
+ _ [40]byte = [unsafe.Offsetof(HeaderPage{}.GlobalFlags)]byte{}
|
|
|
|
|
+ _ [84]byte = [unsafe.Offsetof(HeaderPage{}.HeaderCrc)]byte{}
|
|
|
_ [332]byte = [unsafe.Offsetof(HeaderPage{}.Comment)]byte{}
|
|
_ [332]byte = [unsafe.Offsetof(HeaderPage{}.Comment)]byte{}
|
|
|
- // Comment 必须恰好占满头页剩余空间 (字段区 332 字节, 介绍区 3764 字节)。
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Comment 区域大小校验
|
|
|
_ [PageSize4K - 332]byte = [unsafe.Sizeof(HeaderPage{}.Comment)]byte{}
|
|
_ [PageSize4K - 332]byte = [unsafe.Sizeof(HeaderPage{}.Comment)]byte{}
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// 数据库全局头页(PageID=0 固定)
|
|
// 数据库全局头页(PageID=0 固定)
|
|
|
type HeaderPage struct {
|
|
type HeaderPage struct {
|
|
|
- Magic [4]byte // 0 魔术字,识别文件类型
|
|
|
|
|
- Version uint32 // 4 版本
|
|
|
|
|
- PageSize uint32 // 8 每页大小(比如 4096)
|
|
|
|
|
- NextFreePage uint32 // 12 下一个可用空闲页
|
|
|
|
|
- CreateTime int64 // 16 建库时间 (纳秒)
|
|
|
|
|
- ModifyTime int64 // 24 最近修改时间 (纳秒)
|
|
|
|
|
- NextTxnID uint64 // 32 下一个事务 ID
|
|
|
|
|
- GlobalFlags uint64 // 40 全局状态
|
|
|
|
|
- TablePage uint32 // 48 【唯一重要】表链起始页ID
|
|
|
|
|
- TableCount uint32 // 52 表数量
|
|
|
|
|
- FreePageStart uint32 // 56 空闲页链表头
|
|
|
|
|
- FreePageEnd uint32 // 60 空闲页链表尾 (0 = 空闲链为空)。
|
|
|
|
|
- // 不变量: FreePageStart == 0 当且仅当 FreePageEnd == 0,
|
|
|
|
|
- // 两者必须同生同灭 (摘链/还链处维护)。
|
|
|
|
|
- FreePageCount uint32 // 64 空闲页数量
|
|
|
|
|
- FirstWALPage uint32 // 68 未恢复的 WAL 链首页 ID (0=无待恢复WAL)
|
|
|
|
|
- LastWALPage uint32 // 72 WAL 链尾页 ID (追加写入用)
|
|
|
|
|
- ExtConfig [256]byte // 76 扩展配置
|
|
|
|
|
|
|
+ Magic [4]byte // 0 魔术字,识别文件类型
|
|
|
|
|
+ Version uint32 // 4 版本
|
|
|
|
|
+ PageSize uint32 // 8 每页大小(比如 4096)
|
|
|
|
|
+ NextFreePage uint32 // 12 下一个可用空闲页
|
|
|
|
|
+ CreateTime int64 // 16 建库时间 (纳秒)
|
|
|
|
|
+ ModifyTime int64 // 24 最近修改时间 (纳秒)
|
|
|
|
|
+ NextTxnID uint64 // 32 下一个事务 ID
|
|
|
|
|
+ GlobalFlags uint64 // 40 全局状态
|
|
|
|
|
+ TablePage uint32 // 48 【唯一重要】表链起始页ID
|
|
|
|
|
+ TableCount uint32 // 52 表数量
|
|
|
|
|
+ FreePageStart uint32 // 56 空闲页链表头
|
|
|
|
|
+ FreePageEnd uint32 // 60 空闲页链表尾 (0 = 空闲链为空)。
|
|
|
|
|
+ // 不变量: FreePageStart == 0 当且仅当 FreePageEnd == 0,
|
|
|
|
|
+ // 两者必须同生同灭 (摘链/还链处维护)。
|
|
|
|
|
+ FreePageCount uint32 // 64 空闲页数量
|
|
|
|
|
+ FirstWALPage uint32 // 68 未恢复的 WAL 链首页 ID (0=无待恢复WAL)
|
|
|
|
|
+ LastWALPage uint32 // 72 WAL 链尾页 ID (追加写入用)
|
|
|
|
|
+ ExtConfigHdr [8]byte // 76 扩展配置: 头 8 字节 (预留, 保持 84 前不动)
|
|
|
|
|
+ HeaderCrc uint32 // 84 整页 XXH32 校验 (写入前由 PrepareWrite 生成;
|
|
|
|
|
+ // 旧 v11 库该字段为 0 = 无校验, 首次写头页自动生成)
|
|
|
|
|
+ ExtConfig [244]byte // 88 扩展配置其余
|
|
|
Comment [PageSize4K - 332]byte // 332 数据库介绍区 (3764B, 占满整页余下空间;
|
|
Comment [PageSize4K - 332]byte // 332 数据库介绍区 (3764B, 占满整页余下空间;
|
|
|
- // 建库默认写入 VersionComment, 可用 DB.SetComment 改写)
|
|
|
|
|
|
|
+ // 建库默认写入 VersionComment, 可用 DB.SetComment 改写)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// CalcHeaderCrc 计算整页 4096 字节的 XXH32。计算期间临时把 HeaderCrc 字段
|
|
|
|
|
+// 清零 (CRC 的覆盖范围是"除 CRC 自身外的整页"), 算完恢复原值 —— 本方法
|
|
|
|
|
+// 不改变结构体状态, 可随意调用。
|
|
|
|
|
+//
|
|
|
|
|
+// 序列化方式与 ReadHeader/writeHeaderLocked 一致 (unsafe 直映, 小端机
|
|
|
|
|
+// 内存布局即盘上布局), 所以这里算出的值与盘上字节严格对应。
|
|
|
|
|
+func (h *HeaderPage) CalcHeaderCrc() uint32 {
|
|
|
|
|
+ buf := (*[PageSize4K]byte)(unsafe.Pointer(h))[:]
|
|
|
|
|
+ off := int(unsafe.Offsetof(HeaderPage{}.HeaderCrc))
|
|
|
|
|
+ saved := binary.LittleEndian.Uint32(buf[off : off+4])
|
|
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:off+4], 0)
|
|
|
|
|
+ crc := XXH32(buf)
|
|
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:off+4], saved)
|
|
|
|
|
+ return crc
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Validate 打开数据库时的头页校验。
|
|
|
|
|
+//
|
|
|
|
|
+// - Magic 必查;
|
|
|
|
|
+// - CRC: 版本 11 且 HeaderCrc == 0 视为【旧 v11 库】—— 该字段在旧库里是
|
|
|
|
|
+// ExtConfig 预留区, 恒为 0 —— 跳过校验, 首次 PrepareWrite 会自动生成
|
|
|
|
|
+// CRC (版本号不变, 同号文件自动升级出校验能力);
|
|
|
|
|
+// 其余情况 (v11 已带 CRC, 或未来 v12+) 一律校验, 不匹配返回错误。
|
|
|
|
|
+func (h *HeaderPage) Validate() error {
|
|
|
|
|
+ if h.Magic != Magic {
|
|
|
|
|
+ return fmt.Errorf("invalid database file: bad magic %v, expected %v", h.Magic, Magic)
|
|
|
|
|
+ }
|
|
|
|
|
+ if h.Version == Version && h.HeaderCrc == 0 {
|
|
|
|
|
+ return nil // 旧 v11: 无 CRC, 跳过
|
|
|
|
|
+ }
|
|
|
|
|
+ if got := h.CalcHeaderCrc(); got != h.HeaderCrc {
|
|
|
|
|
+ return fmt.Errorf("头页 CRC 校验失败: 期望 %08x, 实际 %08x —— 文件可能损坏或不是 VRTX 数据库", h.HeaderCrc, got)
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// PrepareWrite 头页落盘前调用: 计算整页 XXH32 写入 HeaderCrc 字段。
|
|
|
|
|
+//
|
|
|
|
|
+// 所有头页写都汇聚在 File.writeHeaderLocked 一处, 该方法在那里被统一调用,
|
|
|
|
|
+// 因此任何路径 (Commit / 页分配 / SetComment / VACUUM ...) 出盘的头页
|
|
|
|
|
+// CRC 都是最新的。对旧 v11 库, 这一步同时就是"自动生成 CRC"的升级点。
|
|
|
|
|
+func (h *HeaderPage) PrepareWrite() {
|
|
|
|
|
+ h.HeaderCrc = h.CalcHeaderCrc()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// newPageWithReader 分配新页:优先从空闲链表头取, 否则递增分配。
|
|
// newPageWithReader 分配新页:优先从空闲链表头取, 否则递增分配。
|
|
@@ -221,11 +274,12 @@ func newHeader(desc string) (*HeaderPage, error) {
|
|
|
TableCount: 0, // 表数量=0
|
|
TableCount: 0, // 表数量=0
|
|
|
CreateTime: time.Now().UnixNano(), // 纳秒时间戳
|
|
CreateTime: time.Now().UnixNano(), // 纳秒时间戳
|
|
|
ModifyTime: time.Now().UnixNano(),
|
|
ModifyTime: time.Now().UnixNano(),
|
|
|
- FreePageStart: 0, // 初始无空闲页
|
|
|
|
|
- FreePageCount: 0, // 空闲页数量=0
|
|
|
|
|
- GlobalFlags: 0, // 无标志
|
|
|
|
|
- NextTxnID: 1, // 事务 ID 从 1 开始 (0 保留, 便于 WAL 页首页判定)
|
|
|
|
|
- ExtConfig: [256]byte{}, // 清空扩展配置
|
|
|
|
|
|
|
+ FreePageStart: 0, // 初始无空闲页
|
|
|
|
|
+ FreePageCount: 0, // 空闲页数量=0
|
|
|
|
|
+ GlobalFlags: 0, // 无标志
|
|
|
|
|
+ NextTxnID: 1, // 事务 ID 从 1 开始 (0 保留, 便于 WAL 页首页判定)
|
|
|
|
|
+ ExtConfigHdr: 0,
|
|
|
|
|
+ ExtConfig: [244]byte{}, // 清空扩展配置
|
|
|
Comment: comment, // 数据库描述
|
|
Comment: comment, // 数据库描述
|
|
|
}, nil
|
|
}, nil
|
|
|
}
|
|
}
|