move Copy and CopyFile from DB to Tx

master
Martin Kobetic 2014-05-21 15:08:37 +00:00
parent 644a949855
commit 519d65228e
4 changed files with 111 additions and 119 deletions

60
db.go
View File

@ -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 {

View File

@ -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
View File

@ -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 {

View File

@ -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
}