mirror of
https://github.com/etcd-io/bbolt.git
synced 2025-05-01 13:13:32 +00:00
This commit changes the local Tx variables from "t" to "tx". This is partly for consistency with external documentation but also because it just annoys me for some reason.
305 lines
8.1 KiB
Go
305 lines
8.1 KiB
Go
package bolt
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
)
|
|
|
|
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order.
|
|
// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
|
|
type Cursor struct {
|
|
tx *Tx
|
|
root pgid
|
|
stack []elemRef
|
|
}
|
|
|
|
// 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.
|
|
func (c *Cursor) First() (key []byte, value []byte) {
|
|
_assert(c.tx.db != nil, "tx closed")
|
|
c.stack = c.stack[:0]
|
|
p, n := c.tx.pageNode(c.root)
|
|
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
|
c.first()
|
|
return c.keyValue()
|
|
}
|
|
|
|
// 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.
|
|
func (c *Cursor) Last() (key []byte, value []byte) {
|
|
_assert(c.tx.db != nil, "tx closed")
|
|
c.stack = c.stack[:0]
|
|
p, n := c.tx.pageNode(c.root)
|
|
ref := elemRef{page: p, node: n}
|
|
ref.index = ref.count() - 1
|
|
c.stack = append(c.stack, ref)
|
|
c.last()
|
|
return c.keyValue()
|
|
}
|
|
|
|
// 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.
|
|
func (c *Cursor) Next() (key []byte, value []byte) {
|
|
_assert(c.tx.db != nil, "tx closed")
|
|
|
|
// Attempt to move over one element until we're successful.
|
|
// Move up the stack as we hit the end of each page in our stack.
|
|
for i := len(c.stack) - 1; i >= 0; i-- {
|
|
elem := &c.stack[i]
|
|
if elem.index < elem.count()-1 {
|
|
elem.index++
|
|
break
|
|
}
|
|
c.stack = c.stack[:i]
|
|
}
|
|
|
|
// If we've hit the end then return nil.
|
|
if len(c.stack) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Move down the stack to find the first element of the first leaf under this branch.
|
|
c.first()
|
|
return c.keyValue()
|
|
}
|
|
|
|
// 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.
|
|
func (c *Cursor) Prev() (key []byte, value []byte) {
|
|
_assert(c.tx.db != nil, "tx closed")
|
|
|
|
// Attempt to move back one element until we're successful.
|
|
// Move up the stack as we hit the beginning of each page in our stack.
|
|
for i := len(c.stack) - 1; i >= 0; i-- {
|
|
elem := &c.stack[i]
|
|
if elem.index > 0 {
|
|
elem.index--
|
|
break
|
|
}
|
|
c.stack = c.stack[:i]
|
|
}
|
|
|
|
// If we've hit the end then return nil.
|
|
if len(c.stack) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// Move down the stack to find the last element of the last leaf under this branch.
|
|
c.last()
|
|
return c.keyValue()
|
|
}
|
|
|
|
// 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 value is returned.
|
|
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
|
_assert(c.tx.db != nil, "tx closed")
|
|
|
|
// Start from root page/node and traverse to correct page.
|
|
c.stack = c.stack[:0]
|
|
c.search(seek, c.root)
|
|
ref := &c.stack[len(c.stack)-1]
|
|
|
|
// If the cursor is pointing to the end of page/node then return nil.
|
|
if ref.index >= ref.count() {
|
|
return nil, nil
|
|
}
|
|
|
|
return c.keyValue()
|
|
}
|
|
|
|
// first moves the cursor to the first leaf element under the last page in the stack.
|
|
func (c *Cursor) first() {
|
|
for {
|
|
// Exit when we hit a leaf page.
|
|
ref := &c.stack[len(c.stack)-1]
|
|
if ref.isLeaf() {
|
|
break
|
|
}
|
|
|
|
// Keep adding pages pointing to the first element to the stack.
|
|
var pgid pgid
|
|
if ref.node != nil {
|
|
pgid = ref.node.inodes[ref.index].pgid
|
|
} else {
|
|
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
|
|
}
|
|
p, n := c.tx.pageNode(pgid)
|
|
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
|
}
|
|
}
|
|
|
|
// last moves the cursor to the last leaf element under the last page in the stack.
|
|
func (c *Cursor) last() {
|
|
for {
|
|
// Exit when we hit a leaf page.
|
|
ref := &c.stack[len(c.stack)-1]
|
|
if ref.isLeaf() {
|
|
break
|
|
}
|
|
|
|
// Keep adding pages pointing to the last element in the stack.
|
|
var pgid pgid
|
|
if ref.node != nil {
|
|
pgid = ref.node.inodes[ref.index].pgid
|
|
} else {
|
|
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
|
|
}
|
|
p, n := c.tx.pageNode(pgid)
|
|
|
|
var nextRef = elemRef{page: p, node: n}
|
|
nextRef.index = nextRef.count() - 1
|
|
c.stack = append(c.stack, nextRef)
|
|
}
|
|
}
|
|
|
|
// search recursively performs a binary search against a given page/node until it finds a given key.
|
|
func (c *Cursor) search(key []byte, pgid pgid) {
|
|
p, n := c.tx.pageNode(pgid)
|
|
if p != nil {
|
|
_assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: "+p.typ())
|
|
}
|
|
e := elemRef{page: p, node: n}
|
|
c.stack = append(c.stack, e)
|
|
|
|
// If we're on a leaf page/node then find the specific node.
|
|
if e.isLeaf() {
|
|
c.nsearch(key)
|
|
return
|
|
}
|
|
|
|
if n != nil {
|
|
c.searchNode(key, n)
|
|
return
|
|
}
|
|
c.searchPage(key, p)
|
|
}
|
|
|
|
func (c *Cursor) searchNode(key []byte, n *node) {
|
|
var exact bool
|
|
index := sort.Search(len(n.inodes), func(i int) bool {
|
|
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
|
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
|
ret := bytes.Compare(n.inodes[i].key, key)
|
|
if ret == 0 {
|
|
exact = true
|
|
}
|
|
return ret != -1
|
|
})
|
|
if !exact && index > 0 {
|
|
index--
|
|
}
|
|
c.stack[len(c.stack)-1].index = index
|
|
|
|
// Recursively search to the next page.
|
|
c.search(key, n.inodes[index].pgid)
|
|
}
|
|
|
|
func (c *Cursor) searchPage(key []byte, p *page) {
|
|
// Binary search for the correct range.
|
|
inodes := p.branchPageElements()
|
|
|
|
var exact bool
|
|
index := sort.Search(int(p.count), func(i int) bool {
|
|
// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
|
|
// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
|
|
ret := bytes.Compare(inodes[i].key(), key)
|
|
if ret == 0 {
|
|
exact = true
|
|
}
|
|
return ret != -1
|
|
})
|
|
if !exact && index > 0 {
|
|
index--
|
|
}
|
|
c.stack[len(c.stack)-1].index = index
|
|
|
|
// Recursively search to the next page.
|
|
c.search(key, inodes[index].pgid)
|
|
}
|
|
|
|
// nsearch searches the leaf node on the top of the stack for a key.
|
|
func (c *Cursor) nsearch(key []byte) {
|
|
e := &c.stack[len(c.stack)-1]
|
|
p, n := e.page, e.node
|
|
|
|
// If we have a node then search its inodes.
|
|
if n != nil {
|
|
index := sort.Search(len(n.inodes), func(i int) bool {
|
|
return bytes.Compare(n.inodes[i].key, key) != -1
|
|
})
|
|
e.index = index
|
|
return
|
|
}
|
|
|
|
// If we have a page then search its leaf elements.
|
|
inodes := p.leafPageElements()
|
|
index := sort.Search(int(p.count), func(i int) bool {
|
|
return bytes.Compare(inodes[i].key(), key) != -1
|
|
})
|
|
e.index = index
|
|
}
|
|
|
|
// keyValue returns the key and value of the current leaf element.
|
|
func (c *Cursor) keyValue() ([]byte, []byte) {
|
|
ref := &c.stack[len(c.stack)-1]
|
|
if ref.count() == 0 || ref.index >= ref.count() {
|
|
return nil, nil
|
|
}
|
|
|
|
// Retrieve value from node.
|
|
if ref.node != nil {
|
|
inode := &ref.node.inodes[ref.index]
|
|
return inode.key, inode.value
|
|
}
|
|
|
|
// Or retrieve value from page.
|
|
elem := ref.page.leafPageElement(uint16(ref.index))
|
|
return elem.key(), elem.value()
|
|
}
|
|
|
|
// node returns the node that the cursor is currently positioned on.
|
|
func (c *Cursor) node(tx *Tx) *node {
|
|
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
|
|
|
|
// If the top of the stack is a leaf node then just return it.
|
|
if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
|
|
return ref.node
|
|
}
|
|
|
|
// Start from root and traverse down the hierarchy.
|
|
var n = c.stack[0].node
|
|
if n == nil {
|
|
n = tx.node(c.stack[0].page.id, nil)
|
|
}
|
|
for _, ref := range c.stack[:len(c.stack)-1] {
|
|
_assert(!n.isLeaf, "expected branch node")
|
|
n = n.childAt(int(ref.index))
|
|
}
|
|
_assert(n.isLeaf, "expected leaf node")
|
|
return n
|
|
}
|
|
|
|
// elemRef represents a reference to an element on a given page/node.
|
|
type elemRef struct {
|
|
page *page
|
|
node *node
|
|
index int
|
|
}
|
|
|
|
// isLeaf returns whether the ref is pointing at a leaf page/node.
|
|
func (r *elemRef) isLeaf() bool {
|
|
if r.node != nil {
|
|
return r.node.isLeaf
|
|
}
|
|
return (r.page.flags & leafPageFlag) != 0
|
|
}
|
|
|
|
// count returns the number of inodes or page elements.
|
|
func (r *elemRef) count() int {
|
|
if r.node != nil {
|
|
return len(r.node.inodes)
|
|
}
|
|
return int(r.page.count)
|
|
}
|