diff --git a/db.go b/db.go index d1b722a..6592b38 100644 --- a/db.go +++ b/db.go @@ -102,10 +102,12 @@ type DB struct { statlock sync.RWMutex // Protects stats access. ops struct { - writeAt func(b []byte, off int64) (n int, err error) + writeAt func(b []byte, off int64) (n int, err error) } - readOnly bool // Read only mode. Update()/Begin(true) would return ErrDatabaseReadOnly immediately. + // Read only mode. + // When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately. + readOnly bool } // Path returns the path to currently open database file. @@ -440,9 +442,11 @@ func (db *DB) beginTx() (*Tx, error) { } func (db *DB) beginRWTx() (*Tx, error) { + // If the database was opened with Options.ReadOnly, return an error. if db.readOnly { return nil, ErrDatabaseReadOnly } + // Obtain writer lock. This is released by the transaction when it closes. // This enforces only one writer transaction at a time. db.rwlock.Lock() diff --git a/db_test.go b/db_test.go index 64eb923..6af6423 100644 --- a/db_test.go +++ b/db_test.go @@ -228,11 +228,12 @@ func TestDB_Open_FileTooSmall(t *testing.T) { // and that a database can not be opened in read-write mode and in read-only // mode at the same time. func TestOpen_ReadOnly(t *testing.T) { - var bucket = []byte(`bucket`) - var key = []byte(`key`) - var value = []byte(`value`) + bucket, key, value := []byte(`bucket`), []byte(`key`), []byte(`value`) + path := tempfile() defer os.Remove(path) + + // Open in read-write mode. db, err := bolt.Open(path, 0666, nil) ok(t, db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket(bucket) @@ -245,33 +246,44 @@ func TestOpen_ReadOnly(t *testing.T) { assert(t, !db.IsReadOnly(), "") ok(t, err) ok(t, db.Close()) + // Open in read-only mode. db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true}) ok(t, err) defer db0.Close() - // Try opening in regular mode. + + // Opening in read-write mode should return an error. _, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100}) assert(t, err != nil, "") + // And again (in read-only mode). db1, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true}) ok(t, err) defer db1.Close() + + // Verify both read-only databases are accessible. for _, db := range []*bolt.DB{db0, db1} { // Verify is is in read only mode indeed. assert(t, db.IsReadOnly(), "") + + // Read-only databases should not allow updates. assert(t, bolt.ErrDatabaseReadOnly == db.Update(func(*bolt.Tx) error { panic(`should never get here`) }), "") + + // Read-only databases should not allow beginning writable txns. _, err = db.Begin(true) assert(t, bolt.ErrDatabaseReadOnly == err, "") + // Verify the data. ok(t, db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucket) if b == nil { return fmt.Errorf("expected bucket `%s`", string(bucket)) } + got := string(b.Get(key)) expected := string(value) if got != expected {