Merge pull request #87 from benbjohnson/errors

Error refactoring
master
Ben Johnson 2014-03-24 08:32:38 -06:00
commit 0866abf733
8 changed files with 95 additions and 107 deletions

View File

@ -2,6 +2,40 @@ package bolt
import (
"bytes"
"errors"
)
var (
// ErrBucketNotFound is returned when trying to access a bucket that has
// not been created yet.
ErrBucketNotFound = errors.New("bucket not found")
// ErrBucketExists is returned when creating a bucket that already exists.
ErrBucketExists = errors.New("bucket already exists")
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
ErrBucketNameRequired = errors.New("bucket name required")
// ErrBucketNameTooLarge is returned when creating a bucket with a name
// that is longer than MaxBucketNameSize.
ErrBucketNameTooLarge = errors.New("bucket name too large")
// ErrBucketNotWritable is returned when changing data on a bucket
// reference that was created from a read-only transaction.
ErrBucketNotWritable = errors.New("bucket not writable")
// ErrKeyRequired is returned when inserting a zero-length key.
ErrKeyRequired = errors.New("key required")
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
ErrKeyTooLarge = errors.New("key too large")
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
ErrValueTooLarge = errors.New("value too large")
// ErrSequenceOverflow is returned when the next sequence number will be
// larger than the maximum integer size.
ErrSequenceOverflow = errors.New("sequence overflow")
)
// Bucket represents a collection of key/value pairs inside the database.

View File

@ -2,6 +2,7 @@ package bolt
import (
"bytes"
"errors"
"fmt"
"os"
"strconv"
@ -203,12 +204,12 @@ func TestBucketForEachShortCircuit(t *testing.T) {
err := tx.Bucket("widgets").ForEach(func(k, v []byte) error {
index++
if bytes.Equal(k, []byte("baz")) {
return &Error{"marker", nil}
return errors.New("marker")
}
return nil
})
assert.Equal(t, err, &Error{"marker", nil})
assert.Equal(t, index, 2)
assert.Equal(t, errors.New("marker"), err)
assert.Equal(t, 2, index)
return nil
})
})

25
db.go
View File

@ -1,6 +1,7 @@
package bolt
import (
"errors"
"fmt"
"io"
"os"
@ -15,6 +16,16 @@ const minMmapSize = 1 << 22 // 4MB
// The largest step that can be taken when remapping the mmap.
const maxMmapStep = 1 << 30 // 1GB
var (
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
// is opened or after it is closed.
ErrDatabaseNotOpen = errors.New("database not open")
// ErrDatabaseOpen is returned when opening a database that is
// already open.
ErrDatabaseOpen = errors.New("database already open")
)
// DB represents a collection of buckets persisted to a file on disk.
// All data access is performed through transactions which can be obtained through the DB.
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
@ -89,7 +100,7 @@ func (db *DB) Open(path string, mode os.FileMode) error {
// Initialize the database if it doesn't exist.
if info, err := db.file.Stat(); err != nil {
return &Error{"stat error", err}
return fmt.Errorf("stat error: %s", err)
} else if info.Size() == 0 {
// Initialize new files with meta pages.
if err := db.init(); err != nil {
@ -101,7 +112,7 @@ func (db *DB) Open(path string, mode os.FileMode) error {
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
m := db.pageInBuffer(buf[:], 0).meta()
if err := m.validate(); err != nil {
return &Error{"meta error", err}
return fmt.Errorf("meta error: %s", err)
}
db.pageSize = int(m.pageSize)
}
@ -140,9 +151,9 @@ func (db *DB) mmap(minsz int) error {
info, err := db.file.Stat()
if err != nil {
return &Error{"mmap stat error", err}
return fmt.Errorf("mmap stat error: %s", err)
} else if int(info.Size()) < db.pageSize*2 {
return &Error{"file size too small", err}
return fmt.Errorf("file size too small")
}
// Ensure the size is at least the minimum size.
@ -163,10 +174,10 @@ func (db *DB) mmap(minsz int) error {
// Validate the meta pages.
if err := db.meta0.validate(); err != nil {
return &Error{"meta0 error", err}
return fmt.Errorf("meta0 error: %s", err)
}
if err := db.meta1.validate(); err != nil {
return &Error{"meta1 error", err}
return fmt.Errorf("meta1 error: %s", err)
}
return nil
@ -529,7 +540,7 @@ func (db *DB) allocate(count int) (*page, error) {
var minsz = int((p.id+pgid(count))+1) * db.pageSize
if minsz >= len(db.data) {
if err := db.mmap(minsz); err != nil {
return nil, &Error{"mmap allocate error", err}
return nil, fmt.Errorf("mmap allocate error: %s", err)
}
}

View File

@ -1,6 +1,7 @@
package bolt
import (
"errors"
"io"
"io/ioutil"
"math/rand"
@ -8,6 +9,7 @@ import (
"strconv"
"strings"
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
@ -53,14 +55,12 @@ func TestDBReopen(t *testing.T) {
// Ensure that the database returns an error if the file handle cannot be open.
func TestDBOpenFileError(t *testing.T) {
withDBFile(func(db *DB, path string) {
exp := &os.PathError{
Op: "open",
Path: path + "/youre-not-my-real-parent",
Err: syscall.ENOTDIR,
}
withDB(func(db *DB, path string) {
err := db.Open(path+"/youre-not-my-real-parent", 0666)
assert.Equal(t, err, exp)
if err, _ := err.(*os.PathError); assert.Error(t, err) {
assert.Equal(t, path+"/youre-not-my-real-parent", err.Path)
assert.Equal(t, "open", err.Op)
}
})
}
@ -78,13 +78,14 @@ func TestDBMetaInitWriteError(t *testing.T) {
// Ensure that a database that is too small returns an error.
func TestDBFileTooSmall(t *testing.T) {
withDBFile(func(db *DB, path string) {
// corrupt the database
err := os.Truncate(path, int64(os.Getpagesize()))
assert.NoError(t, err)
withOpenDB(func(db *DB, path string) {
db.Close()
err = db.Open(path, 0666)
assert.Equal(t, err, &Error{"file size too small", nil})
// corrupt the database
assert.NoError(t, os.Truncate(path, int64(os.Getpagesize())))
err := db.Open(path, 0666)
assert.Equal(t, errors.New("file size too small"), err)
})
}
@ -108,7 +109,7 @@ func TestDBCorruptMeta0(t *testing.T) {
// Open the database.
err = db.Open(path, 0666)
assert.Equal(t, err, &Error{"meta error", ErrInvalid})
assert.Equal(t, err, errors.New("meta error: invalid database"))
})
}

View File

@ -1,71 +0,0 @@
package bolt
var (
// ErrInvalid is returned when a data file is not a Bolt-formatted database.
ErrInvalid = &Error{"Invalid database", nil}
// ErrVersionMismatch is returned when the data file was created with a
// different version of Bolt.
ErrVersionMismatch = &Error{"version mismatch", nil}
// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
// is opened or after it is closed.
ErrDatabaseNotOpen = &Error{"database not open", nil}
// ErrDatabaseOpen is returned when opening a database that is
// already open.
ErrDatabaseOpen = &Error{"database already open", nil}
// ErrTxNotWritable is returned when performing a write operation on a
// read-only transaction.
ErrTxNotWritable = &Error{"tx not writable", nil}
// ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back.
ErrTxClosed = &Error{"tx closed", nil}
// ErrBucketNotFound is returned when trying to access a bucket that has
// not been created yet.
ErrBucketNotFound = &Error{"bucket not found", nil}
// ErrBucketExists is returned when creating a bucket that already exists.
ErrBucketExists = &Error{"bucket already exists", nil}
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
ErrBucketNameRequired = &Error{"bucket name required", nil}
// ErrBucketNameTooLarge is returned when creating a bucket with a name
// that is longer than MaxBucketNameSize.
ErrBucketNameTooLarge = &Error{"bucket name too large", nil}
// ErrBucketNotWritable is returned when changing data on a bucket
// reference that was created from a read-only transaction.
ErrBucketNotWritable = &Error{"bucket not writable", nil}
// ErrKeyRequired is returned when inserting a zero-length key.
ErrKeyRequired = &Error{"key required", nil}
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
ErrKeyTooLarge = &Error{"key too large", nil}
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
ErrValueTooLarge = &Error{"value too large", nil}
// ErrSequenceOverflow is returned when the next sequence number will be
// larger than the maximum integer size.
ErrSequenceOverflow = &Error{"sequence overflow", nil}
)
// Error represents an error condition caused by Bolt.
type Error struct {
message string
cause error
}
// Error returns a string representation of the error.
func (e *Error) Error() string {
if e.cause != nil {
return e.message + ": " + e.cause.Error()
}
return e.message
}

View File

@ -1,12 +0,0 @@
package bolt
import (
"github.com/stretchr/testify/assert"
"testing"
)
// Ensure that nested errors are appropriately formatted.
func TestError(t *testing.T) {
e := &Error{"one error", &Error{"two error", nil}}
assert.Equal(t, e.Error(), "one error: two error")
}

13
meta.go
View File

@ -1,7 +1,20 @@
package bolt
import (
"errors"
)
const magic uint32 = 0xED0CDAED
var (
// ErrInvalid is returned when a data file is not a Bolt-formatted database.
ErrInvalid = errors.New("invalid database")
// ErrVersionMismatch is returned when the data file was created with a
// different version of Bolt.
ErrVersionMismatch = errors.New("version mismatch")
)
type meta struct {
magic uint32
version uint32

11
tx.go
View File

@ -1,10 +1,21 @@
package bolt
import (
"errors"
"sort"
"unsafe"
)
var (
// ErrTxNotWritable is returned when performing a write operation on a
// read-only transaction.
ErrTxNotWritable = errors.New("tx not writable")
// ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back.
ErrTxClosed = errors.New("tx closed")
)
// txid represents the internal transaction identifier.
type txid uint64