Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iterator: improve and document error handling #97

Merged
merged 4 commits into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

- Batch `Set()`, `Delete()`, and `Close()` may now error (@erikgrinaker)

- `Iterator.Close()` may now error (@erikgrinaker)

- Many iterator panics are now exposed via `Error()` instead (@erikgrinaker)

- `RemoteDB` iterators are now correctly primed with the first item when created, without calling
`Next()` (@erikgrinaker)

- The `SetDeleter` interface has been removed (@erikgrinaker)

## 0.5.1
Expand Down
14 changes: 8 additions & 6 deletions boltdb_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ func (itr *boltDBIterator) Valid() bool {
return false
}

if itr.Error() != nil {
itr.isInvalid = true
return false
}

// iterated to the end of the cursor
if itr.currentKey == nil {
itr.isInvalid = true
Expand Down Expand Up @@ -165,15 +170,12 @@ func (itr *boltDBIterator) Error() error {
}

// Close implements Iterator.
func (itr *boltDBIterator) Close() {
err := itr.tx.Rollback()
if err != nil {
panic(err)
}
func (itr *boltDBIterator) Close() error {
return itr.tx.Rollback()
}

func (itr *boltDBIterator) assertIsValid() {
if !itr.Valid() {
panic("boltdb-iterator is invalid")
panic("iterator is invalid")
}
}
22 changes: 8 additions & 14 deletions cleveldb_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ func (itr cLevelDBIterator) Valid() bool {
return false
}

// Panic on DB error. No way to recover.
itr.assertNoError()
// If source errors, invalid.
if itr.source.GetError() != nil {
itr.isInvalid = true
return false
}

// If source is invalid, invalid.
if !itr.source.Valid() {
Expand Down Expand Up @@ -93,21 +96,18 @@ func (itr cLevelDBIterator) Valid() bool {

// Key implements Iterator.
func (itr cLevelDBIterator) Key() []byte {
itr.assertNoError()
itr.assertIsValid()
return itr.source.Key()
}

// Value implements Iterator.
func (itr cLevelDBIterator) Value() []byte {
itr.assertNoError()
itr.assertIsValid()
return itr.source.Value()
}

// Next implements Iterator.
func (itr cLevelDBIterator) Next() {
itr.assertNoError()
itr.assertIsValid()
if itr.isReverse {
itr.source.Prev()
Expand All @@ -122,19 +122,13 @@ func (itr cLevelDBIterator) Error() error {
}

// Close implements Iterator.
func (itr cLevelDBIterator) Close() {
func (itr cLevelDBIterator) Close() error {
itr.source.Close()
}

func (itr cLevelDBIterator) assertNoError() {
err := itr.source.GetError()
if err != nil {
panic(err)
}
return nil
}

func (itr cLevelDBIterator) assertIsValid() {
if !itr.Valid() {
panic("cLevelDBIterator is invalid")
panic("iterator is invalid")
}
}
22 changes: 8 additions & 14 deletions goleveldb_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ func (itr *goLevelDBIterator) Valid() bool {
return false
}

// Panic on DB error. No way to recover.
itr.assertNoError()
// If source errors, invalid.
if err := itr.Error(); err != nil {
itr.isInvalid = true
return false
}

// If source is invalid, invalid.
if !itr.source.Valid() {
Expand Down Expand Up @@ -94,7 +97,6 @@ func (itr *goLevelDBIterator) Valid() bool {
func (itr *goLevelDBIterator) Key() []byte {
// Key returns a copy of the current key.
// See https://github.com/syndtr/goleveldb/blob/52c212e6c196a1404ea59592d3f1c227c9f034b2/leveldb/iterator/iter.go#L88
itr.assertNoError()
itr.assertIsValid()
return cp(itr.source.Key())
}
Expand All @@ -103,14 +105,12 @@ func (itr *goLevelDBIterator) Key() []byte {
func (itr *goLevelDBIterator) Value() []byte {
// Value returns a copy of the current value.
// See https://github.com/syndtr/goleveldb/blob/52c212e6c196a1404ea59592d3f1c227c9f034b2/leveldb/iterator/iter.go#L88
itr.assertNoError()
itr.assertIsValid()
return cp(itr.source.Value())
}

// Next implements Iterator.
func (itr *goLevelDBIterator) Next() {
itr.assertNoError()
itr.assertIsValid()
if itr.isReverse {
itr.source.Prev()
Expand All @@ -125,19 +125,13 @@ func (itr *goLevelDBIterator) Error() error {
}

// Close implements Iterator.
func (itr *goLevelDBIterator) Close() {
func (itr *goLevelDBIterator) Close() error {
itr.source.Release()
}

func (itr *goLevelDBIterator) assertNoError() {
err := itr.source.Error()
if err != nil {
panic(err)
}
return nil
}

func (itr goLevelDBIterator) assertIsValid() {
if !itr.Valid() {
panic("goLevelDBIterator is invalid")
panic("iterator is invalid")
}
}
20 changes: 11 additions & 9 deletions memdb_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ func newMemDBIterator(db *MemDB, start []byte, end []byte, reverse bool) *memDBI
}

// Close implements Iterator.
func (i *memDBIterator) Close() {
func (i *memDBIterator) Close() error {
i.cancel()
for range i.ch { // drain channel
}
i.item = nil
return nil
}

// Domain implements Iterator.
Expand All @@ -112,12 +113,11 @@ func (i *memDBIterator) Valid() bool {

// Next implements Iterator.
func (i *memDBIterator) Next() {
i.assertIsValid()
item, ok := <-i.ch
switch {
case ok:
i.item = item
case i.item == nil:
panic("called Next() on invalid iterator")
default:
i.item = nil
}
Expand All @@ -130,16 +130,18 @@ func (i *memDBIterator) Error() error {

// Key implements Iterator.
func (i *memDBIterator) Key() []byte {
if i.item == nil {
panic("called Key() on invalid iterator")
}
i.assertIsValid()
return i.item.key
}

// Value implements Iterator.
func (i *memDBIterator) Value() []byte {
if i.item == nil {
panic("called Value() on invalid iterator")
}
i.assertIsValid()
return i.item.value
}

func (i *memDBIterator) assertIsValid() {
if !i.Valid() {
panic("iterator is invalid")
}
}
61 changes: 34 additions & 27 deletions prefixdb_iterator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package db

import "bytes"
import (
"bytes"
"fmt"
)

// IteratePrefix is a convenience function for iterating over a key domain
// restricted by prefix.
Expand Down Expand Up @@ -36,6 +39,7 @@ type prefixDBIterator struct {
end []byte
source Iterator
valid bool
err error
}

var _ Iterator = (*prefixDBIterator)(nil)
Expand Down Expand Up @@ -73,14 +77,23 @@ func (itr *prefixDBIterator) Domain() (start []byte, end []byte) {

// Valid implements Iterator.
func (itr *prefixDBIterator) Valid() bool {
return itr.valid && itr.source.Valid()
if !itr.valid || itr.err != nil || !itr.source.Valid() {
return false
}

key := itr.source.Key()
if len(key) < len(itr.prefix) || !bytes.Equal(key[:len(itr.prefix)], itr.prefix) {
itr.err = fmt.Errorf("received invalid key from backend: %x (expected prefix %x)",
key, itr.prefix)
return false
}

return true
}

// Next implements Iterator.
func (itr *prefixDBIterator) Next() {
if !itr.valid {
panic("prefixIterator invalid; cannot call Next()")
}
itr.assertIsValid()
itr.source.Next()

if !itr.source.Valid() || !bytes.HasPrefix(itr.source.Key(), itr.prefix) {
Expand All @@ -89,39 +102,33 @@ func (itr *prefixDBIterator) Next() {
}

// Next implements Iterator.
func (itr *prefixDBIterator) Key() (key []byte) {
if !itr.valid {
panic("prefixIterator invalid; cannot call Key()")
}
key = itr.source.Key()
return stripPrefix(key, itr.prefix)
func (itr *prefixDBIterator) Key() []byte {
itr.assertIsValid()
key := itr.source.Key()
return key[len(itr.prefix):] // we have checked the key in Valid()
}

// Value implements Iterator.
func (itr *prefixDBIterator) Value() (value []byte) {
if !itr.valid {
panic("prefixIterator invalid; cannot call Value()")
}
value = itr.source.Value()
return value
func (itr *prefixDBIterator) Value() []byte {
itr.assertIsValid()
return itr.source.Value()
}

// Error implements Iterator.
func (itr *prefixDBIterator) Error() error {
return itr.source.Error()
if err := itr.source.Error(); err != nil {
return err
}
return itr.err
}

// Close implements Iterator.
func (itr *prefixDBIterator) Close() {
itr.source.Close()
func (itr *prefixDBIterator) Close() error {
return itr.source.Close()
}

func stripPrefix(key []byte, prefix []byte) (stripped []byte) {
if len(key) < len(prefix) {
panic("should not happen")
}
if !bytes.Equal(key[:len(prefix)], prefix) {
panic("should not happen")
func (itr *prefixDBIterator) assertIsValid() {
if !itr.Valid() {
panic("iterator is invalid")
}
return key[len(prefix):]
}
Loading