aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--bucket.go1
-rw-r--r--cursor.go7
-rw-r--r--tx.go27
4 files changed, 31 insertions, 13 deletions
diff --git a/README.md b/README.md
index 8cc9f3c..18ba570 100644
--- a/README.md
+++ b/README.md
@@ -258,6 +258,10 @@ set to a key which is different than the key not existing.
Use the `Bucket.Delete()` function to delete a key from the bucket.
+Please note that values returned from `Get()` are only valid while the
+transaction is open. If you need to use a value outside of the transaction
+then you must use `copy()` to copy it to another byte slice.
+
### Iterating over keys
@@ -370,7 +374,7 @@ func (*Bucket) DeleteBucket(key []byte) error
### Database backups
-Bolt is a single file so it's easy to backup. You can use the `Tx.Copy()`
+Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()`
function to write a consistent view of the database to a writer. If you call
this from a read-only transaction, it will perform a hot backup and not block
your other database reads and writes. It will also use `O_DIRECT` when available
@@ -385,7 +389,8 @@ func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
- return tx.Copy(w)
+ _, err := tx.WriteTo(w)
+ return err
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/bucket.go b/bucket.go
index 470689b..6766992 100644
--- a/bucket.go
+++ b/bucket.go
@@ -252,6 +252,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
// Get retrieves the value for a key in the bucket.
// Returns a nil value if the key does not exist or if the key is a nested bucket.
+// The returned value is only valid for the life of the transaction.
func (b *Bucket) Get(key []byte) []byte {
k, v, flags := b.Cursor().seek(key)
diff --git a/cursor.go b/cursor.go
index 0d8ed16..006c548 100644
--- a/cursor.go
+++ b/cursor.go
@@ -10,6 +10,8 @@ import (
// Cursors see nested buckets with value == nil.
// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
//
+// Keys and values returned from the cursor are only valid for the life of the transaction.
+//
// Changing data while traversing with a cursor may cause it to be invalidated
// and return unexpected keys and/or values. You must reposition your cursor
// after mutating data.
@@ -25,6 +27,7 @@ func (c *Cursor) Bucket() *Bucket {
// First moves the cursor to the first item in the bucket and returns its key and value.
// If the bucket is empty then a nil key and value are returned.
+// The returned key and value are only valid for the life of the transaction.
func (c *Cursor) First() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed")
c.stack = c.stack[:0]
@@ -41,6 +44,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
// Last moves the cursor to the last item in the bucket and returns its key and value.
// If the bucket is empty then a nil key and value are returned.
+// The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Last() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed")
c.stack = c.stack[:0]
@@ -58,6 +62,7 @@ func (c *Cursor) Last() (key []byte, value []byte) {
// Next moves the cursor to the next item in the bucket and returns its key and value.
// If the cursor is at the end of the bucket then a nil key and value are returned.
+// The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Next() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed")
k, v, flags := c.next()
@@ -69,6 +74,7 @@ func (c *Cursor) Next() (key []byte, value []byte) {
// Prev moves the cursor to the previous item in the bucket and returns its key and value.
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
+// The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Prev() (key []byte, value []byte) {
_assert(c.bucket.tx.db != nil, "tx closed")
@@ -100,6 +106,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) {
// Seek moves the cursor to a given key and returns it.
// If the key does not exist then the next key is used. If no keys
// follow, a nil key is returned.
+// The returned key and value are only valid for the life of the transaction.
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
k, v, flags := c.seek(seek)
diff --git a/tx.go b/tx.go
index c041d73..fda6a21 100644
--- a/tx.go
+++ b/tx.go
@@ -252,37 +252,42 @@ func (tx *Tx) close() {
}
// 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.
-// Copy will write exactly tx.Size() bytes into the writer.
+// This function exists for backwards compatibility. Use WriteTo() in
func (tx *Tx) Copy(w io.Writer) error {
- var f *os.File
- var err error
+ _, err := tx.WriteTo(w)
+ return err
+}
+// WriteTo writes the entire database to a writer.
+// If err == nil then exactly tx.Size() bytes will be written into the writer.
+func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
// Attempt to open reader directly.
+ var f *os.File
if f, err = os.OpenFile(tx.db.path, os.O_RDONLY|odirect, 0); err != nil {
// Fallback to a regular open if that doesn't work.
if f, err = os.OpenFile(tx.db.path, os.O_RDONLY, 0); err != nil {
- return err
+ return 0, err
}
}
// Copy the meta pages.
tx.db.metalock.Lock()
- _, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
+ n, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
tx.db.metalock.Unlock()
if err != nil {
_ = f.Close()
- return fmt.Errorf("meta copy: %s", err)
+ return n, fmt.Errorf("meta copy: %s", err)
}
// Copy data pages.
- if _, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)); err != nil {
+ wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2))
+ n += wn
+ if err != nil {
_ = f.Close()
- return err
+ return n, err
}
- return f.Close()
+ return n, f.Close()
}
// CopyFile copies the entire database to file at the given path.