Tricks
Bucket self-increasing key
Use NextSequence() to create self-incrementing keys, as shown in the following example
// CreateUser saves u to the store. The new user ID is set on u once the data is persisted. func (s *Store) CreateUser(u *User) error { return s.db.Update(func(tx *bolt.Tx) error { // Retrieve the users bucket. // This should be created when the DB is first opened. b := tx.Bucket([]byte("users")) // Generate ID for the user. // This returns an error only if the Tx is closed or not writeable. // That can't happen in an Update() call so I ignore the error check. id, _ := b.NextSequence() u.ID = int(id) // Marshal user data into bytes. buf, err := json.Marshal(u) if err != nil { return err } // Persist bytes to users bucket. return b.Put(itob(u.ID), buf) }) } // itob returns an 8-byte big endian representation of v. func itob(v int) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(v)) return b } type User struct { ID int ... }
Nested bucket
Very simple, buckets can achieve nested storage
func (*Bucket) CreateBucket(key []byte) (*Bucket, error) func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) func (*Bucket) DeleteBucket(key []byte) error
Example
Suppose you have a multi-tenant application where the root level bucket is the account bucket. The bucket contains a sequence of accounts, which are buckets themselves. In a sequential bucket, there may be many related buckets (Users, Note s, etc.).
// createUser creates a new user in the given account. func createUser(accountID int, u *User) error { // Start the transaction. tx, err := db.Begin(true) if err != nil { return err } defer tx.Rollback() // Retrieve the root bucket for the account. // Assume this has already been created when the account was set up. root := tx.Bucket([]byte(strconv.FormatUint(accountID, 10))) // Setup the users bucket. bkt, err := root.CreateBucketIfNotExists([]byte("USERS")) if err != nil { return err } // Generate an ID for the new user. userID, err := bkt.NextSequence() if err != nil { return err } u.ID = userID // Marshal and save the encoded user. if buf, err := json.Marshal(u); err != nil { return err } else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil { return err } // Commit the transaction. if err := tx.Commit(); err != nil { return err } return nil }
Traversal key values
In buckets, key-value pairs are byte-ordered according to the value of the key.
Iterate it using Bucket.Cursor()
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
Cursor has five ways to iterate
- First() Move to the first key.
- Last() Move to the last key.
- Seek() Move to a specific key.
- Next() Move to the next key.
- Prev() Move to the previous key.
Each method returns (key []byte, value []byte) two values
Returns two nil values when the value specified by the method does not exist, in the following cases:
- When iterating to the last key-value pair, call Cursor.Next() again
- Call Cursor.Prev() when the current reference is the first key-value pair
- When the initial position is specified using 4.Next() and 5. Prev() methods instead of 1.First() 2.Last() 3. Seek().
Special case: when the key is non-nil but the value is nil, it means that this is a nested bucket, and the value value is a Bucket.Bucket() method is used to access the bucket, and the parameter is the key value.
db.View(func(tx *bolt.Tx) error { c := b.Cursor() fmt.Println(c.First()) k, v := c.Prev() fmt.Println(k == nil, v == nil) // true,true if k != nil && v == nil { subBucket := b.Bucket() // doanything } return nil })
Prefix traversal
By using Cursor, we can do some special traversals, such as traversing key-value pairs with specific prefixes.
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys c := tx.Bucket([]byte("MyBucket")).Cursor() prefix := []byte("1234") for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
Range traversal
Traversing in a range, such as: using a sorted time encoding (RFC3339), can traverse the data of a specific date range.
db.View(func(tx *bolt.Tx) error { // Assume our events bucket exists and has RFC3339 encoded time keys. c := tx.Bucket([]byte("Events")).Cursor() // Our time range spans the 90's decade. min := []byte("1990-01-01T00:00:00Z") max := []byte("2000-01-01T00:00:00Z") // Iterate over the 90's. for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { fmt.Printf("%s: %s\n", k, v) } return nil })
: The RFC3339 Nano implemented by Golang is not sortable
ForEach
In the case of value in the bucket, you can use ForEach() traversal
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) b.ForEach(func(k, v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) return nil })
: The key-value pairs traversed in ForEach() require copy() to be used outside of the transaction.