bolt usage

26 January 2017 in Notes

Most of the time I’m too lazy to use a real RDBMS and it would be overkill anyway. bolt offers a nice embedded alternative. It’s not really complicated to use and has excellent documentation. Some more examples can never hurt though.

encoding and decoding the values

Values in bolt are always stored as []byte, so types other than that need to be marshalled somehow. Which encoding is used depends on the use case, but for go-internal usage, encoding/gob is a nice fit i guess:

func decodeItem(x []byte) (*Item, error) {
    var i Item
    buf := bytes.NewBuffer(x)
    d := gob.NewDecoder(buf)
    if err := d.Decode(&i); err != nil {
        return nil, err
    }
    return &i, nil
}

func encodeItem(i *Item) ([]byte, error) {
    var x bytes.Buffer
    e := gob.NewEncoder(&x)
    err := e.Encode(i)
    if err != nil {
        return nil, err
    }
    return x.Bytes(), nil
}

These functions could also be methods of your type. If you have multiple types, it may be sensible to define an interface for this which can be used when storing values in bolt.

hashing

bolt is a key/value database, so we need a unique key for our item. This could be anything of the type []byte. When the Item doesn’t have a single unique property usable as key, I prefer to use a hash to generate a key instead of just concatenating things together (I’m not sure this makes sense, but somehow it feels more right). Select a hash function depending on the use case. If you have only few items, and don’t fear collisions, a fast hash like fnv could be enough, otherwise use some cryptographic hash:

func itemID(i *Item) []byte {
    h := fnv.New64a()
    h.Write([]byte(i.FieldOne))
    h.Write([]byte(e.FieldTwo))
    return h.Sum(nil)
}

store values in bolt

First we need to open a bolt database, it will be created if it doesn’t exist:

db, err := bolt.Open(path, 0600, nil)

The next steps now depend on if you want to store different types into the db. I’d store different types in different buckets but thats up to you. Note that you can also nest buckets.

Storing an item to bolt:

func updateItem(db *bolt.DB, i *Item) error {
    err := db.Update(func(tx *bolt.Tx) error {
        itemBucket, err := tx.CreateBucketIfNotExists([]byte("myBucketName"))
        if err != nil {
            return err
        }
        x, err := encodeItem(i)
        if err != nil {
            return err
        }
        return itemBucket.Put(itemID(i), x)
    })
    return err
}

retrieving values from bolt

func getItem(db *bolt.DB, id []byte) (*Item, error) {
    var i *Item
    err := db.View(func(tx *bolt.Tx) error {
        itemBucket := tx.Bucket([]byte("myBucketName"))
        if itemBucket == nil {
                return nil
        }

        x := itemBucket.Get(id)

        i, err2 := decodeItem(x)
        if err2 != nil {
                return err2
        }

        return nil
    })

    if err != nil {
            return nil, err
    }

    return i, nil
}