From dd84d3218c21bac59faab0d5635f55ce1b154506 Mon Sep 17 00:00:00 2001
From: Pavel Safonov
Date: Mon, 19 Sep 2022 15:45:54 +0300
Subject: [PATCH] postgres: pg_btree begin impl
---
format/format.go | 1 +
.../postgres/flavours/postgres14/pg_btree.go | 146 ++++++++++++++++++
format/postgres/pg_btree.go | 37 +++++
3 files changed, 184 insertions(+)
create mode 100644 format/postgres/flavours/postgres14/pg_btree.go
create mode 100644 format/postgres/pg_btree.go
diff --git a/format/format.go b/format/format.go
index 55ac3181..ddb8aaed 100644
--- a/format/format.go
+++ b/format/format.go
@@ -103,6 +103,7 @@ const (
OPUS_PACKET = "opus_packet"
PCAP = "pcap"
PCAPNG = "pcapng"
+ PG_BTREE = "pg_btree"
PG_WAL = "pg_wal"
PG_WALPAGE = "pg_wal_page"
PG_MULTIXACTOFF = "pg_multixact_offsets"
diff --git a/format/postgres/flavours/postgres14/pg_btree.go b/format/postgres/flavours/postgres14/pg_btree.go
new file mode 100644
index 00000000..04dbc9f6
--- /dev/null
+++ b/format/postgres/flavours/postgres14/pg_btree.go
@@ -0,0 +1,146 @@
+package postgres14
+
+import (
+ "github.com/wader/fq/format/postgres/common"
+ "github.com/wader/fq/pkg/decode"
+)
+
+const (
+ BTREE_MAGIC = 0x053162
+)
+
+// struct BTMetaPageData {
+/* 0 | 4 */ // uint32 btm_magic
+/* 4 | 4 */ // uint32 btm_version
+/* 8 | 4 */ // BlockNumber btm_root
+/* 12 | 4 */ // uint32 btm_level
+/* 16 | 4 */ // BlockNumber btm_fastroot
+/* 20 | 4 */ // uint32 btm_fastlevel
+/* 24 | 4 */ // uint32 btm_last_cleanup_num_delpages
+/* XXX 4-byte hole */
+/* 32 | 8 */ // float8 btm_last_cleanup_num_heap_tuples
+/* 40 | 1 */ // _Bool btm_allequalimage
+/* XXX 7-byte padding */
+//
+/* total size (bytes): 48 */
+
+func DecodePgBTree(d *decode.D) any {
+ d.SeekAbs(0)
+
+ btree := &BTreeD{
+ PageSize: common.HeapPageSize,
+ }
+ decodeBTreePages(btree, d)
+
+ return nil
+}
+
+type BTreeD struct {
+ PageSize uint64
+ page *BTreePage
+}
+
+type BTreePage struct {
+ heap HeapPage
+ bytesPosBegin uint64 // bytes pos of page's beginning
+ bytesPosEnd uint64 // bytes pos of page's ending
+ bytesPosSpecial uint64 // bytes pos of page's special
+}
+
+type HeapPage struct {
+ PdLower uint16
+ PdUpper uint16
+ PdSpecial uint16
+ PdPagesizeVersion uint16
+}
+
+func decodeBTreePages(btree *BTreeD, d *decode.D) {
+ for i := 0; ; i++ {
+ if end, _ := d.TryEnd(); end {
+ return
+ }
+
+ page := &BTreePage{}
+ if btree.page != nil {
+ // use prev page
+ page.bytesPosBegin = btree.page.bytesPosEnd
+ }
+ page.bytesPosEnd = common.TypeAlign(btree.PageSize, page.bytesPosBegin+1)
+ btree.page = page
+
+ if i == 0 {
+ // first page contains meta information
+ d.FieldStruct("heap_page", func(d *decode.D) {
+ decodeBTreeMetaPage(btree, d)
+ })
+ continue
+ }
+
+ if i > 0 {
+ return
+ }
+ }
+}
+
+func decodeBTreeMetaPage(btree *BTreeD, d *decode.D) {
+ d.FieldStruct("page_header", func(d *decode.D) {
+ decodePageHeader(btree, d)
+ })
+ d.FieldStruct("meta_page_data", func(d *decode.D) {
+ decodeBTMetaPageData(btree, d)
+ })
+}
+
+func decodePageHeader(btree *BTreeD, d *decode.D) {
+ heap := btree.page.heap
+
+ d.FieldStruct("pd_lsn", func(d *decode.D) {
+ /* 0 | 4 */ // uint32 xlogid;
+ /* 4 | 4 */ // uint32 xrecoff;
+ d.FieldU32("xlogid", common.HexMapper)
+ d.FieldU32("xrecoff", common.HexMapper)
+ })
+ d.FieldU16("pd_checksum")
+ d.FieldU16("pd_flags")
+ heap.PdLower = uint16(d.FieldU16("pd_lower"))
+ heap.PdUpper = uint16(d.FieldU16("pd_upper"))
+ heap.PdSpecial = uint16(d.FieldU16("pd_special"))
+ heap.PdPagesizeVersion = uint16(d.FieldU16("pd_pagesize_version"))
+ d.FieldU32("pd_prune_xid")
+
+ // ItemIdData pd_linp[];
+ //page.ItemsEnd = int64(page.PagePosBegin*8) + int64(page.PdLower*8)
+ //d.FieldArray("pd_linp", func(d *decode.D) {
+ // DecodeItemIds(heap, d)
+ //})
+}
+
+func decodeBTMetaPageData(btree *BTreeD, d *decode.D) {
+ /* 0 | 4 */ // uint32 btm_magic
+ /* 4 | 4 */ // uint32 btm_version
+ /* 8 | 4 */ // BlockNumber btm_root
+ /* 12 | 4 */ // uint32 btm_level
+ /* 16 | 4 */ // BlockNumber btm_fastroot
+ /* 20 | 4 */ // uint32 btm_fastlevel
+ /* 24 | 4 */ // uint32 btm_last_cleanup_num_delpages
+ /* XXX 4-byte hole */
+ /* 32 | 8 */ // float8 btm_last_cleanup_num_heap_tuples
+ /* 40 | 1 */ // _Bool btm_allequalimage
+ /* XXX 7-byte padding */
+
+ btmMagic := d.FieldU32("btm_magic")
+ d.FieldU32("btm_version")
+ d.FieldU32("btm_root")
+ d.FieldU32("btm_level")
+ d.FieldU32("btm_fastroot")
+ d.FieldU32("btm_fastlevel")
+ d.FieldU32("btm_last_cleanup_num_delpages")
+ d.FieldU32("hole0")
+ d.FieldF64("btm_last_cleanup_num_heap_tuples")
+ d.FieldU8("btm_allequalimage")
+ d.FieldU56("padding0")
+
+ if btmMagic != BTREE_MAGIC {
+ d.Fatalf("invalid btmMagic = %X, must be %X\n", btmMagic, BTREE_MAGIC)
+ }
+}
diff --git a/format/postgres/pg_btree.go b/format/postgres/pg_btree.go
new file mode 100644
index 00000000..ac62406b
--- /dev/null
+++ b/format/postgres/pg_btree.go
@@ -0,0 +1,37 @@
+package postgres
+
+import (
+ "github.com/wader/fq/format"
+ "github.com/wader/fq/format/postgres/flavours/postgres14"
+ "github.com/wader/fq/pkg/decode"
+ "github.com/wader/fq/pkg/interp"
+)
+
+func init() {
+ interp.RegisterFormat(decode.Format{
+ Name: format.PG_BTREE,
+ Description: "PostgreSQL btree index file",
+ DecodeFn: decodePgBTree,
+ DecodeInArg: format.PostgresIn{
+ Flavour: "default",
+ },
+ RootArray: true,
+ RootName: "pages",
+ })
+}
+
+func decodePgBTree(d *decode.D, in any) any {
+ d.Endian = decode.LittleEndian
+
+ pgIn, ok := in.(format.PostgresIn)
+ if !ok {
+ d.Fatalf("DecodeInArg must be PostgresIn!\n")
+ }
+
+ switch pgIn.Flavour {
+ case PG_FLAVOUR_POSTGRES14, PG_FLAVOUR_POSTGRES:
+ return postgres14.DecodePgBTree(d)
+ }
+
+ return nil
+}