diff --git a/cmd/bolt/main.go b/cmd/bolt/main.go index c29d5df..248884e 100644 --- a/cmd/bolt/main.go +++ b/cmd/bolt/main.go @@ -4,6 +4,7 @@ import ( "bytes" "log" "os" + "strconv" "github.com/boltdb/bolt" "github.com/codegangsta/cli" @@ -30,6 +31,11 @@ func NewApp() *cli.App { Usage: "retrieve a list of all keys in a bucket", Action: KeysCommand, }, + { + Name: "pages", + Usage: "dump page information for a database", + Action: PagesCommand, + }, } return app } @@ -108,6 +114,45 @@ func KeysCommand(c *cli.Context) { } } +// PagesCommand prints a list of all pages in a database. +func PagesCommand(c *cli.Context) { + path := c.Args().Get(0) + if _, err := os.Stat(path); os.IsNotExist(err) { + fatal(err) + return + } + + db, err := bolt.Open(path, 0600) + if err != nil { + fatal(err) + return + } + defer db.Close() + + logger.Println("ID TYPE ITEMS OVRFLW") + logger.Println("======== ========== ====== ======") + + db.Do(func(tx *bolt.Tx) error { + var id int + for { + p, err := tx.Page(id) + if err != nil { + fatalf("page error: %d: %s", id, err) + } else if p == nil { + break + } + + var overflow string + if p.OverflowCount > 0 { + overflow = strconv.Itoa(p.OverflowCount) + } + logger.Printf("%-8d %-10s %-6d %-6s", p.ID, p.Type, p.Count, overflow) + id += 1 + p.OverflowCount + } + return nil + }) +} + var logger = log.New(os.Stderr, "", 0) var logBuffer *bytes.Buffer diff --git a/freelist.go b/freelist.go index 636ed22..d0b1492 100644 --- a/freelist.go +++ b/freelist.go @@ -70,6 +70,23 @@ func (f *freelist) release(txid txid) { sort.Sort(reverseSortedPgids(f.ids)) } +// isFree returns whether a given page is in the free list. +func (f *freelist) isFree(pgid pgid) bool { + for _, id := range f.ids { + if id == pgid { + return true + } + } + for _, m := range f.pending { + for _, id := range m { + if id == pgid { + return true + } + } + } + return false +} + // read initializes the freelist from a freelist page. func (f *freelist) read(p *page) { ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count] diff --git a/page.go b/page.go index 5b60c4d..0d46f09 100644 --- a/page.go +++ b/page.go @@ -119,3 +119,11 @@ func (n *leafPageElement) value() []byte { buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) return buf[n.pos+n.ksize : n.pos+n.ksize+n.vsize] } + +// PageInfo represents human readable information about a page. +type PageInfo struct { + ID int + Type string + Count int + OverflowCount int +} diff --git a/tx.go b/tx.go index ae6d103..5b2b14d 100644 --- a/tx.go +++ b/tx.go @@ -420,3 +420,30 @@ func (t *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) { } } } + +// Page returns page information for a given page number. +// This is only available from writable transactions. +func (t *Tx) Page(id int) (*PageInfo, error) { + if !t.writable { + return nil, ErrTxNotWritable + } else if pgid(id) >= t.meta.pgid { + return nil, nil + } + + // Build the page info. + p := t.page(pgid(id)) + info := &PageInfo{ + ID: id, + Count: int(p.count), + OverflowCount: int(p.overflow), + } + + // Determine the type (or if it's free). + if t.db.freelist.isFree(pgid(id)) { + info.Type = "free" + } else { + info.Type = p.typ() + } + + return info, nil +}