Upgrade import/export to use nested buckets.

This commit is contained in:
Ben Johnson 2014-04-11 14:59:46 -06:00
parent 714436100a
commit 10fed5f74d
8 changed files with 78 additions and 27 deletions

View File

@ -13,9 +13,9 @@ func TestBuckets(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket("woojits") tx.CreateBucket([]byte("woojits"))
tx.CreateBucket("widgets") tx.CreateBucket([]byte("widgets"))
tx.CreateBucket("whatchits") tx.CreateBucket([]byte("whatchits"))
return nil return nil
}) })
db.Close() db.Close()

View File

@ -23,25 +23,33 @@ func Export(path string) {
} }
defer db.Close() defer db.Close()
db.View(func(tx *bolt.Tx) error { err = db.View(func(tx *bolt.Tx) error {
// Loop over every bucket and export it as a raw message. // Loop over every bucket and export it as a raw message.
var root []*rawMessage var root []*rawMessage
for _, b := range tx.Buckets() { err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
message, err := exportBucket(b) message, err := exportBucket(b)
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
message.Key = name
root = append(root, message) root = append(root, message)
return nil
})
if err != nil {
return err
} }
// Encode all buckets into JSON. // Encode all buckets into JSON.
output, err := json.Marshal(root) output, err := json.Marshal(root)
if err != nil { if err != nil {
fatal("encode: ", err) return fmt.Errorf("encode: ", err)
} }
print(string(output)) print(string(output))
return nil return nil
}) })
if err != nil {
fatal(err)
}
} }
func exportBucket(b *bolt.Bucket) (*rawMessage, error) { func exportBucket(b *bolt.Bucket) (*rawMessage, error) {
@ -50,11 +58,22 @@ func exportBucket(b *bolt.Bucket) (*rawMessage, error) {
err := b.ForEach(func(k, v []byte) error { err := b.ForEach(func(k, v []byte) error {
var err error var err error
// If there is no value then it is a bucket.
if v == nil {
child, err := exportBucket(b.Bucket(k))
if err != nil {
return fmt.Errorf("bucket: %s", err)
}
child.Key = k
children = append(children, child)
return nil
}
// Otherwise it's a regular key.
var child = &rawMessage{Key: k} var child = &rawMessage{Key: k}
if child.Value, err = json.Marshal(v); err != nil { if child.Value, err = json.Marshal(v); err != nil {
return fmt.Errorf("value: %s", err) return fmt.Errorf("value: %s", err)
} }
children = append(children, child) children = append(children, child)
return nil return nil
}) })
@ -64,7 +83,6 @@ func exportBucket(b *bolt.Bucket) (*rawMessage, error) {
// Encode bucket into a raw message. // Encode bucket into a raw message.
var root = rawMessage{Type: "bucket"} var root = rawMessage{Type: "bucket"}
root.Key = []byte(b.Name())
if root.Value, err = json.Marshal(children); err != nil { if root.Value, err = json.Marshal(children); err != nil {
return nil, fmt.Errorf("children: %s", err) return nil, fmt.Errorf("children: %s", err)
} }

View File

@ -13,19 +13,26 @@ func TestExport(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket("widgets") tx.CreateBucket([]byte("widgets"))
b := tx.Bucket("widgets") b := tx.Bucket([]byte("widgets"))
b.Put([]byte("foo"), []byte("0000")) b.Put([]byte("foo"), []byte("0000"))
b.Put([]byte("bar"), []byte("")) b.Put([]byte("bar"), []byte(""))
tx.CreateBucket("woojits") tx.CreateBucket([]byte("woojits"))
b = tx.Bucket("woojits") b = tx.Bucket([]byte("woojits"))
b.Put([]byte("baz"), []byte("XXXX")) b.Put([]byte("baz"), []byte("XXXX"))
b.CreateBucket([]byte("woojits/subbucket"))
b = b.Bucket([]byte("woojits/subbucket"))
b.Put([]byte("bat"), []byte("A"))
tx.CreateBucket([]byte("empty"))
return nil return nil
}) })
db.Close() db.Close()
output := run("export", path) output := run("export", path)
assert.Equal(t, `[{"type":"bucket","key":"d2lkZ2V0cw==","value":[{"key":"YmFy","value":""},{"key":"Zm9v","value":"MDAwMA=="}]},{"type":"bucket","key":"d29vaml0cw==","value":[{"key":"YmF6","value":"WFhYWA=="}]}]`, output) assert.Equal(t, `[{"type":"bucket","key":"ZW1wdHk=","value":[]},{"type":"bucket","key":"d2lkZ2V0cw==","value":[{"key":"YmFy","value":""},{"key":"Zm9v","value":"MDAwMA=="}]},{"type":"bucket","key":"d29vaml0cw==","value":[{"key":"YmF6","value":"WFhYWA=="},{"type":"bucket","key":"d29vaml0cy9zdWJidWNrZXQ=","value":[{"key":"YmF0","value":"QQ=="}]}]}]`, output)
}) })
} }

View File

@ -13,8 +13,8 @@ func TestGet(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket("widgets") tx.CreateBucket([]byte("widgets"))
tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
return nil return nil
}) })
db.Close() db.Close()
@ -45,7 +45,7 @@ func TestGetKeyNotFound(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
return tx.CreateBucket("widgets") return tx.CreateBucket([]byte("widgets"))
}) })
db.Close() db.Close()
output := run("get", path, "widgets", "foo") output := run("get", path, "widgets", "foo")

View File

@ -41,7 +41,7 @@ func Import(path string, input string) {
} }
// Create the bucket if it doesn't exist. // Create the bucket if it doesn't exist.
if err := tx.CreateBucketIfNotExists(string(message.Key)); err != nil { if err := tx.CreateBucketIfNotExists(message.Key); err != nil {
return fmt.Errorf("create bucket: %s", err) return fmt.Errorf("create bucket: %s", err)
} }
@ -52,7 +52,7 @@ func Import(path string, input string) {
} }
// Import all the values into the bucket. // Import all the values into the bucket.
b := tx.Bucket(string(message.Key)) b := tx.Bucket(message.Key)
if err := importBucket(b, children); err != nil { if err := importBucket(b, children); err != nil {
return fmt.Errorf("import bucket: %s", err) return fmt.Errorf("import bucket: %s", err)
} }
@ -67,7 +67,28 @@ func Import(path string, input string) {
func importBucket(b *bolt.Bucket, children []*rawMessage) error { func importBucket(b *bolt.Bucket, children []*rawMessage) error {
// Decode each message into a key/value pair. // Decode each message into a key/value pair.
for _, child := range children { for _, child := range children {
// Decode the base64 value. // Bucket messages are handled recursively.
if child.Type == "bucket" {
// Create the bucket if it doesn't exist.
if err := b.CreateBucketIfNotExists(child.Key); err != nil {
return fmt.Errorf("create bucket: %s", err)
}
// Decode child messages.
var subchildren []*rawMessage
if err := json.Unmarshal(child.Value, &subchildren); err != nil {
return fmt.Errorf("decode children: %s", err)
}
// Import subbucket.
subbucket := b.Bucket(child.Key)
if err := importBucket(subbucket, subchildren); err != nil {
return fmt.Errorf("import bucket: %s", err)
}
continue
}
// Non-bucket values are decoded from base64.
var value []byte var value []byte
if err := json.Unmarshal(child.Value, &value); err != nil { if err := json.Unmarshal(child.Value, &value); err != nil {
return fmt.Errorf("decode value: %s", err) return fmt.Errorf("decode value: %s", err)

View File

@ -15,7 +15,7 @@ func TestImport(t *testing.T) {
// Write input file. // Write input file.
input := tempfile() input := tempfile()
assert.NoError(t, ioutil.WriteFile(input, []byte(`[{"type":"bucket","key":"d2lkZ2V0cw==","value":[{"key":"YmFy","value":""},{"key":"Zm9v","value":"MDAwMA=="}]},{"type":"bucket","key":"d29vaml0cw==","value":[{"key":"YmF6","value":"WFhYWA=="}]}]`), 0600)) assert.NoError(t, ioutil.WriteFile(input, []byte(`[{"type":"bucket","key":"ZW1wdHk=","value":[]},{"type":"bucket","key":"d2lkZ2V0cw==","value":[{"key":"YmFy","value":""},{"key":"Zm9v","value":"MDAwMA=="}]},{"type":"bucket","key":"d29vaml0cw==","value":[{"key":"YmF6","value":"WFhYWA=="},{"type":"bucket","key":"d29vaml0cy9zdWJidWNrZXQ=","value":[{"key":"YmF0","value":"QQ=="}]}]}]`), 0600))
// Import database. // Import database.
path := tempfile() path := tempfile()
@ -26,15 +26,20 @@ func TestImport(t *testing.T) {
db, err := bolt.Open(path, 0600) db, err := bolt.Open(path, 0600)
assert.NoError(t, err) assert.NoError(t, err)
db.View(func(tx *bolt.Tx) error { db.View(func(tx *bolt.Tx) error {
b := tx.Bucket("widgets") assert.NotNil(t, tx.Bucket([]byte("empty")))
b := tx.Bucket([]byte("widgets"))
if assert.NotNil(t, b) { if assert.NotNil(t, b) {
assert.Equal(t, []byte("0000"), b.Get([]byte("foo"))) assert.Equal(t, []byte("0000"), b.Get([]byte("foo")))
assert.Equal(t, []byte(""), b.Get([]byte("bar"))) assert.Equal(t, []byte(""), b.Get([]byte("bar")))
} }
b = tx.Bucket("woojits") b = tx.Bucket([]byte("woojits"))
if assert.NotNil(t, b) { if assert.NotNil(t, b) {
assert.Equal(t, []byte("XXXX"), b.Get([]byte("baz"))) assert.Equal(t, []byte("XXXX"), b.Get([]byte("baz")))
b = b.Bucket([]byte("woojits/subbucket"))
assert.Equal(t, []byte("A"), b.Get([]byte("bat")))
} }
return nil return nil

View File

@ -13,10 +13,10 @@ func TestKeys(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket("widgets") tx.CreateBucket([]byte("widgets"))
tx.Bucket("widgets").Put([]byte("0002"), []byte("")) tx.Bucket([]byte("widgets")).Put([]byte("0002"), []byte(""))
tx.Bucket("widgets").Put([]byte("0001"), []byte("")) tx.Bucket([]byte("widgets")).Put([]byte("0001"), []byte(""))
tx.Bucket("widgets").Put([]byte("0003"), []byte("")) tx.Bucket([]byte("widgets")).Put([]byte("0003"), []byte(""))
return nil return nil
}) })
db.Close() db.Close()

View File

@ -13,7 +13,7 @@ func TestSet(t *testing.T) {
SetTestMode(true) SetTestMode(true)
open(func(db *bolt.DB, path string) { open(func(db *bolt.DB, path string) {
db.Update(func(tx *bolt.Tx) error { db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket("widgets") tx.CreateBucket([]byte("widgets"))
return nil return nil
}) })
db.Close() db.Close()