Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RO db should allow access to tx.Page(...) thus bbolt pages #371

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 74 additions & 52 deletions cmd/bbolt/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,63 @@ import (
crypto "crypto/rand"
"encoding/binary"
"fmt"
"go.etcd.io/bbolt/internal/btesting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io"
"math/rand"
"os"
"strconv"
"strings"
"testing"

"github.com/stretchr/testify/require"
bolt "go.etcd.io/bbolt"
main "go.etcd.io/bbolt/cmd/bbolt"
"go.etcd.io/bbolt/internal/btesting"
)

func MustCreateTestDB(t *testing.T) string {
db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: 4096})

if err := db.Update(func(tx *bolt.Tx) error {
// Create "foo" bucket.
b, err := tx.CreateBucket([]byte("foo"))
if err != nil {
return err
}
for i := 0; i < 10; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}

// Create "bar" bucket.
b, err = tx.CreateBucket([]byte("bar"))
if err != nil {
return err
}
for i := 0; i < 100; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}

// Create "baz" bucket.
b, err = tx.CreateBucket([]byte("baz"))
if err != nil {
return err
}
if err := b.Put([]byte("key"), []byte("value")); err != nil {
return err
}

return nil
}); err != nil {
t.Fatal(err)
}
db.Close()
return db.Path()
}

// Ensure the "info" command can print information about a database.
func TestInfoCommand_Run(t *testing.T) {
db := btesting.MustCreateDB(t)
Expand Down Expand Up @@ -74,52 +119,9 @@ func TestStatsCommand_Run_EmptyDatabase(t *testing.T) {

// Ensure the "stats" command can execute correctly.
func TestStatsCommand_Run(t *testing.T) {
// Ignore
if os.Getpagesize() != 4096 {
t.Skip("system does not use 4KB page size")
}
dbPath := MustCreateTestDB(t)

db := btesting.MustCreateDB(t)

if err := db.Update(func(tx *bolt.Tx) error {
// Create "foo" bucket.
b, err := tx.CreateBucket([]byte("foo"))
if err != nil {
return err
}
for i := 0; i < 10; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}

// Create "bar" bucket.
b, err = tx.CreateBucket([]byte("bar"))
if err != nil {
return err
}
for i := 0; i < 100; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}

// Create "baz" bucket.
b, err = tx.CreateBucket([]byte("baz"))
if err != nil {
return err
}
if err := b.Put([]byte("key"), []byte("value")); err != nil {
return err
}

return nil
}); err != nil {
t.Fatal(err)
}
db.Close()

defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())
defer requireDBNoChange(t, dbData(t, dbPath), dbPath)

// Generate expected result.
exp := "Aggregate statistics for 3 buckets\n\n" +
Expand All @@ -143,11 +145,31 @@ func TestStatsCommand_Run(t *testing.T) {

// Run the command.
m := NewMain()
if err := m.Run("stats", db.Path()); err != nil {
t.Fatal(err)
} else if m.Stdout.String() != exp {
t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String())
}
require.NoError(t, m.Run("stats", dbPath))
require.EqualValues(t, exp, m.Stdout.String())
}

func TestPagesCommand_Run(t *testing.T) {
dbPath := MustCreateTestDB(t)

defer requireDBNoChange(t, dbData(t, dbPath), dbPath)

// Generate expected result.
exp := `ID TYPE ITEMS OVRFLW
======== ========== ====== ======
0 meta? 0
1 meta? 0
2 freelist? 0
3 leaf? 0
4 leaf? 100
5 leaf? 3
6 freelist? 2
`
// Run the command.
m := NewMain()
require.NoError(t, m.Run("pages", dbPath))
assert.EqualValues(t, exp, m.Stdout.String())
assert.EqualValues(t, strings.ReplaceAll(exp, " ", "_"), strings.ReplaceAll(m.Stdout.String(), " ", "_"))
}

// Ensure the "buckets" command can print a list of buckets.
Expand Down
2 changes: 2 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return db, nil
}

// TODO(ptabor): Introduce bbolt.option to load freepages even in RO transactions,
// such that debugging tools (bbolt CLI) can distinguish whether page is reachable or not.
db.loadFreelist()

// Flush freelist when transitioning from no sync to sync so
Expand Down
13 changes: 10 additions & 3 deletions tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,17 @@ func (tx *Tx) Page(id int) (*PageInfo, error) {
}

// Determine the type (or if it's free).
if tx.db.freelist.freed(pgid(id)) {
info.Type = "free"
if tx.db.freelist != nil {
if tx.db.freelist.freed(pgid(id)) {
info.Type = "free"
} else {
info.Type = p.typ()
}
} else {
info.Type = p.typ()
// If DB is open in RO mode, we might not have freelist available.
// So we cannot distinguish whether page is free or not.
// TODO(ptabor): Introduce bbolt.option to load freepages even in RO.
info.Type = p.typ() + "?"
}

return info, nil
Expand Down