mirror of https://github.com/hak5/bolt.git
move Copy and CopyFile from DB to Tx
parent
644a949855
commit
519d65228e
60
db.go
60
db.go
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -483,65 +482,6 @@ func (db *DB) View(fn func(*Tx) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Copy writes the entire database to a writer.
|
||||
// A reader transaction is maintained during the copy so it is safe to continue
|
||||
// using the database while a copy is in progress.
|
||||
func (db *DB) Copy(w io.Writer) error {
|
||||
// Maintain a reader transaction so pages don't get reclaimed.
|
||||
t, err := db.Begin(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open reader on the database.
|
||||
f, err := os.Open(db.path)
|
||||
if err != nil {
|
||||
_ = t.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the meta pages.
|
||||
db.metalock.Lock()
|
||||
_, err = io.CopyN(w, f, int64(db.pageSize*2))
|
||||
db.metalock.Unlock()
|
||||
if err != nil {
|
||||
_ = t.Rollback()
|
||||
_ = f.Close()
|
||||
return fmt.Errorf("meta copy: %s", err)
|
||||
}
|
||||
|
||||
// Copy data pages.
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
_ = t.Rollback()
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// Close read transaction and exit.
|
||||
if err := t.Rollback(); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// CopyFile copies the entire database to file at the given path.
|
||||
// A reader transaction is maintained during the copy so it is safe to continue
|
||||
// using the database while a copy is in progress.
|
||||
func (db *DB) CopyFile(path string, mode os.FileMode) error {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = db.Copy(f)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// Stats retrieves ongoing performance stats for the database.
|
||||
// This is only updated when a transaction closes.
|
||||
func (db *DB) Stats() Stats {
|
||||
|
|
61
db_test.go
61
db_test.go
|
@ -238,30 +238,6 @@ func TestDB_View_Error(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// Ensure that the database can be copied to a file path.
|
||||
func TestDB_CopyFile(t *testing.T) {
|
||||
withOpenDB(func(db *DB, path string) {
|
||||
var dest = tempfile()
|
||||
db.Update(func(tx *Tx) error {
|
||||
tx.CreateBucket([]byte("widgets"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, db.CopyFile(dest, 0600))
|
||||
|
||||
db2, err := Open(dest, 0600)
|
||||
assert.NoError(t, err)
|
||||
defer db2.Close()
|
||||
|
||||
db2.View(func(tx *Tx) error {
|
||||
assert.Equal(t, []byte("bar"), tx.Bucket([]byte("widgets")).Get([]byte("foo")))
|
||||
assert.Equal(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure that an error is returned when a database write fails.
|
||||
func TestDB_Commit_WriteFail(t *testing.T) {
|
||||
t.Skip("pending") // TODO(benbjohnson)
|
||||
|
@ -450,39 +426,6 @@ func ExampleDB_Begin_ReadOnly() {
|
|||
// zephyr likes purple
|
||||
}
|
||||
|
||||
func ExampleDB_CopyFile() {
|
||||
// Open the database.
|
||||
db, _ := Open(tempfile(), 0666)
|
||||
defer os.Remove(db.Path())
|
||||
defer db.Close()
|
||||
|
||||
// Create a bucket and a key.
|
||||
db.Update(func(tx *Tx) error {
|
||||
tx.CreateBucket([]byte("widgets"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||
return nil
|
||||
})
|
||||
|
||||
// Copy the database to another file.
|
||||
toFile := tempfile()
|
||||
db.CopyFile(toFile, 0666)
|
||||
defer os.Remove(toFile)
|
||||
|
||||
// Open the cloned database.
|
||||
db2, _ := Open(toFile, 0666)
|
||||
defer db2.Close()
|
||||
|
||||
// Ensure that the key exists in the copy.
|
||||
db2.View(func(tx *Tx) error {
|
||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Output:
|
||||
// The value for 'foo' in the clone is: bar
|
||||
}
|
||||
|
||||
// tempfile returns a temporary file path.
|
||||
func tempfile() string {
|
||||
f, _ := ioutil.TempFile("", "bolt-")
|
||||
|
@ -523,7 +466,7 @@ func mustCheck(db *DB) {
|
|||
if err := db.Check(); err != nil {
|
||||
// Copy db off first.
|
||||
var path = tempfile()
|
||||
db.CopyFile(path, 0600)
|
||||
db.View(func(tx *Tx) error { return tx.CopyFile(path, 0600) })
|
||||
|
||||
if errors, ok := err.(ErrorList); ok {
|
||||
for _, err := range errors {
|
||||
|
@ -564,7 +507,7 @@ func truncDuration(d time.Duration) string {
|
|||
// copyAndFailNow copies a database to a new location and then fails then test.
|
||||
func copyAndFailNow(t *testing.T, db *DB) {
|
||||
path := tempfile()
|
||||
db.CopyFile(path, 0600)
|
||||
db.View(func(tx *Tx) error { return tx.CopyFile(path, 0600) })
|
||||
fmt.Println("db copied to: ", path)
|
||||
t.FailNow()
|
||||
}
|
||||
|
|
51
tx.go
51
tx.go
|
@ -3,6 +3,8 @@ package bolt
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
@ -227,6 +229,55 @@ func (tx *Tx) close() {
|
|||
tx.db = nil
|
||||
}
|
||||
|
||||
// Copy writes the entire database to a writer.
|
||||
// A reader transaction is maintained during the copy so it is safe to continue
|
||||
// using the database while a copy is in progress.
|
||||
func (tx *Tx) Copy(w io.Writer) error {
|
||||
|
||||
// Open reader on the database.
|
||||
f, err := os.Open(tx.db.path)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the meta pages.
|
||||
tx.db.metalock.Lock()
|
||||
_, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
|
||||
tx.db.metalock.Unlock()
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
_ = f.Close()
|
||||
return fmt.Errorf("meta copy: %s", err)
|
||||
}
|
||||
|
||||
// Copy data pages.
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
_ = tx.Rollback()
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// CopyFile copies the entire database to file at the given path.
|
||||
// A reader transaction is maintained during the copy so it is safe to continue
|
||||
// using the database while a copy is in progress.
|
||||
func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Copy(f)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
// Check performs several consistency checks on the database for this transaction.
|
||||
// An error is returned if any inconsistency is found or if executed on a read-only transaction.
|
||||
func (tx *Tx) Check() error {
|
||||
|
|
58
tx_test.go
58
tx_test.go
|
@ -313,6 +313,31 @@ func TestTx_Check_Corrupt(t *testing.T) {
|
|||
assert.Equal(t, "check fail: 1 errors occurred: page 3: already freed", msg)
|
||||
}
|
||||
|
||||
// Ensure that the database can be copied to a file path.
|
||||
func TestTx_CopyFile(t *testing.T) {
|
||||
withOpenDB(func(db *DB, path string) {
|
||||
var dest = tempfile()
|
||||
db.Update(func(tx *Tx) error {
|
||||
tx.CreateBucket([]byte("widgets"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
|
||||
return nil
|
||||
})
|
||||
|
||||
assert.NoError(t, db.View(func(tx *Tx) error { return tx.CopyFile(dest, 0600) }))
|
||||
|
||||
db2, err := Open(dest, 0600)
|
||||
assert.NoError(t, err)
|
||||
defer db2.Close()
|
||||
|
||||
db2.View(func(tx *Tx) error {
|
||||
assert.Equal(t, []byte("bar"), tx.Bucket([]byte("widgets")).Get([]byte("foo")))
|
||||
assert.Equal(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func ExampleTx_Rollback() {
|
||||
// Open the database.
|
||||
db, _ := Open(tempfile(), 0666)
|
||||
|
@ -346,3 +371,36 @@ func ExampleTx_Rollback() {
|
|||
// Output:
|
||||
// The value for 'foo' is still: bar
|
||||
}
|
||||
|
||||
func ExampleTx_CopyFile() {
|
||||
// Open the database.
|
||||
db, _ := Open(tempfile(), 0666)
|
||||
defer os.Remove(db.Path())
|
||||
defer db.Close()
|
||||
|
||||
// Create a bucket and a key.
|
||||
db.Update(func(tx *Tx) error {
|
||||
tx.CreateBucket([]byte("widgets"))
|
||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||
return nil
|
||||
})
|
||||
|
||||
// Copy the database to another file.
|
||||
toFile := tempfile()
|
||||
db.View(func(tx *Tx) error { return tx.CopyFile(toFile, 0666) })
|
||||
defer os.Remove(toFile)
|
||||
|
||||
// Open the cloned database.
|
||||
db2, _ := Open(toFile, 0666)
|
||||
defer db2.Close()
|
||||
|
||||
// Ensure that the key exists in the copy.
|
||||
db2.View(func(tx *Tx) error {
|
||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Output:
|
||||
// The value for 'foo' in the clone is: bar
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue