package bboltimport ()// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order.// 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.typeCursorstruct { bucket *Bucket stack []elemRef}// Bucket returns the bucket that this cursor was created from.func ( *Cursor) () *Bucket {return .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 ( *Cursor) () ( []byte, []byte) {_assert(.bucket.tx.db != nil, "tx closed") .stack = .stack[:0] , := .bucket.pageNode(.bucket.root) .stack = append(.stack, elemRef{page: , node: , index: 0}) .first()// If we land on an empty page then move to the next value. // https://github.com/boltdb/bolt/issues/450if .stack[len(.stack)-1].count() == 0 { .next() } , , := .keyValue()if ( & uint32(bucketLeafFlag)) != 0 {return , nil }return , }// 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 ( *Cursor) () ( []byte, []byte) {_assert(.bucket.tx.db != nil, "tx closed") .stack = .stack[:0] , := .bucket.pageNode(.bucket.root) := elemRef{page: , node: } .index = .count() - 1 .stack = append(.stack, ) .last() , , := .keyValue()if ( & uint32(bucketLeafFlag)) != 0 {return , nil }return , }// 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 ( *Cursor) () ( []byte, []byte) {_assert(.bucket.tx.db != nil, "tx closed") , , := .next()if ( & uint32(bucketLeafFlag)) != 0 {return , nil }return , }// 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 ( *Cursor) () ( []byte, []byte) {_assert(.bucket.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 := len(.stack) - 1; >= 0; -- { := &.stack[]if .index > 0 { .index--break } .stack = .stack[:] }// If we've hit the end then return nil.iflen(.stack) == 0 {returnnil, nil }// Move down the stack to find the last element of the last leaf under this branch. .last() , , := .keyValue()if ( & uint32(bucketLeafFlag)) != 0 {return , nil }return , }// 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 ( *Cursor) ( []byte) ( []byte, []byte) { , , := .seek()// If we ended up after the last element of a page then move to the next one.if := &.stack[len(.stack)-1]; .index >= .count() { , , = .next() }if == nil {returnnil, nil } elseif ( & uint32(bucketLeafFlag)) != 0 {return , nil }return , }// Delete removes the current key/value under the cursor from the bucket.// Delete fails if current key/value is a bucket or if the transaction is not writable.func ( *Cursor) () error {if .bucket.tx.db == nil {returnErrTxClosed } elseif !.bucket.Writable() {returnErrTxNotWritable } , , := .keyValue()// Return an error if current value is a bucket.if ( & bucketLeafFlag) != 0 {returnErrIncompatibleValue } .node().del()returnnil}// seek moves the cursor to a given key and returns it.// If the key does not exist then the next key is used.func ( *Cursor) ( []byte) ( []byte, []byte, uint32) {_assert(.bucket.tx.db != nil, "tx closed")// Start from root page/node and traverse to correct page. .stack = .stack[:0] .search(, .bucket.root)// If this is a bucket then return a nil value.return .keyValue()}// first moves the cursor to the first leaf element under the last page in the stack.func ( *Cursor) () {for {// Exit when we hit a leaf page.var = &.stack[len(.stack)-1]if .isLeaf() {break }// Keep adding pages pointing to the first element to the stack.varpgidif .node != nil { = .node.inodes[.index].pgid } else { = .page.branchPageElement(uint16(.index)).pgid } , := .bucket.pageNode() .stack = append(.stack, elemRef{page: , node: , index: 0}) }}// last moves the cursor to the last leaf element under the last page in the stack.func ( *Cursor) () {for {// Exit when we hit a leaf page. := &.stack[len(.stack)-1]if .isLeaf() {break }// Keep adding pages pointing to the last element in the stack.varpgidif .node != nil { = .node.inodes[.index].pgid } else { = .page.branchPageElement(uint16(.index)).pgid } , := .bucket.pageNode()var = elemRef{page: , node: } .index = .count() - 1 .stack = append(.stack, ) }}// next moves to the next leaf element and returns the key and value.// If the cursor is at the last leaf element then it stays there and returns nil.func ( *Cursor) () ( []byte, []byte, uint32) {for {// 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.varintfor = len(.stack) - 1; >= 0; -- { := &.stack[]if .index < .count()-1 { .index++break } }// If we've hit the root page then stop and return. This will leave the // cursor on the last element of the last page.if == -1 {returnnil, nil, 0 }// Otherwise start from where we left off in the stack and find the // first element of the first leaf page. .stack = .stack[:+1] .first()// If this is an empty page then restart and move back up the stack. // https://github.com/boltdb/bolt/issues/450if .stack[len(.stack)-1].count() == 0 {continue }return .keyValue() }}// search recursively performs a binary search against a given page/node until it finds a given key.func ( *Cursor) ( []byte, pgid) { , := .bucket.pageNode()if != nil && (.flags&(branchPageFlag|leafPageFlag)) == 0 {panic(fmt.Sprintf("invalid page type: %d: %x", .id, .flags)) } := elemRef{page: , node: } .stack = append(.stack, )// If we're on a leaf page/node then find the specific node.if .isLeaf() { .nsearch()return }if != nil { .searchNode(, )return } .searchPage(, )}func ( *Cursor) ( []byte, *node) {varbool := sort.Search(len(.inodes), func( 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. := bytes.Compare(.inodes[].key, )if == 0 { = true }return != -1 })if ! && > 0 { -- } .stack[len(.stack)-1].index = // Recursively search to the next page. .search(, .inodes[].pgid)}func ( *Cursor) ( []byte, *page) {// Binary search for the correct range. := .branchPageElements()varbool := sort.Search(int(.count), func( 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. := bytes.Compare([].key(), )if == 0 { = true }return != -1 })if ! && > 0 { -- } .stack[len(.stack)-1].index = // Recursively search to the next page. .search(, [].pgid)}// nsearch searches the leaf node on the top of the stack for a key.func ( *Cursor) ( []byte) { := &.stack[len(.stack)-1] , := .page, .node// If we have a node then search its inodes.if != nil { := sort.Search(len(.inodes), func( int) bool {returnbytes.Compare(.inodes[].key, ) != -1 }) .index = return }// If we have a page then search its leaf elements. := .leafPageElements() := sort.Search(int(.count), func( int) bool {returnbytes.Compare([].key(), ) != -1 }) .index = }// keyValue returns the key and value of the current leaf element.func ( *Cursor) () ([]byte, []byte, uint32) { := &.stack[len(.stack)-1]// If the cursor is pointing to the end of page/node then return nil.if .count() == 0 || .index >= .count() {returnnil, nil, 0 }// Retrieve value from node.if .node != nil { := &.node.inodes[.index]return .key, .value, .flags }// Or retrieve value from page. := .page.leafPageElement(uint16(.index))return .key(), .value(), .flags}// node returns the node that the cursor is currently positioned on.func ( *Cursor) () *node {_assert(len(.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 := &.stack[len(.stack)-1]; .node != nil && .isLeaf() {return .node }// Start from root and traverse down the hierarchy.var = .stack[0].nodeif == nil { = .bucket.node(.stack[0].page.id, nil) }for , := range .stack[:len(.stack)-1] {_assert(!.isLeaf, "expected branch node") = .childAt(.index) }_assert(.isLeaf, "expected leaf node")return}// 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 ( *elemRef) () bool {if .node != nil {return .node.isLeaf }return (.page.flags & leafPageFlag) != 0}// count returns the number of inodes or page elements.func ( *elemRef) () int {if .node != nil {returnlen(.node.inodes) }returnint(.page.count)}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.