179 lines
4.8 KiB
Go
179 lines
4.8 KiB
Go
// Copyright 2016 RapidLoop. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package skv provides a simple persistent key-value store for Go values. It
|
|
// can store a mapping of string to any gob-encodable Go value. It is
|
|
// lightweight and performant, and ideal for use in low-traffic websites,
|
|
// utilities and the like.
|
|
//
|
|
// The API is very simple - you can Put(), Get() or Delete() entries. These
|
|
// methods are goroutine-safe.
|
|
//
|
|
// skv uses BoltDB for storage and the encoding/gob package for encoding and
|
|
// decoding values. There are no other dependencies.
|
|
package skv
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/boltdb/bolt"
|
|
)
|
|
|
|
// KVStore represents the key value store. Use the Open() method to create
|
|
// one, and Close() it when done.
|
|
type KVStore struct {
|
|
db *bolt.DB
|
|
}
|
|
|
|
var (
|
|
// ErrNotFound is returned when the key supplied to a Get or Delete
|
|
// method does not exist in the database.
|
|
ErrNotFound = errors.New("skv: key not found")
|
|
|
|
// ErrBadValue is returned when the value supplied to the Put method
|
|
// is nil.
|
|
ErrBadValue = errors.New("skv: bad value")
|
|
)
|
|
|
|
// Open a key-value store. "path" is the full path to the database file, any
|
|
// leading directories must have been created already. File is created with
|
|
// mode 0640 if needed.
|
|
//
|
|
// Because of BoltDB restrictions, only one process may open the file at a
|
|
// time. Attempts to open the file from another process will fail with a
|
|
// timeout error.
|
|
func Open(path string, buckets []string) (*KVStore, error) {
|
|
opts := &bolt.Options{
|
|
Timeout: 500 * time.Millisecond,
|
|
}
|
|
if db, err := bolt.Open(path, 0640, opts); err != nil {
|
|
return nil, err
|
|
} else {
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
var err error
|
|
for _, bucket := range buckets {
|
|
_, err = tx.CreateBucketIfNotExists([]byte(bucket))
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
} else {
|
|
return &KVStore{db: db}, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put an entry into the store. The passed value is gob-encoded and stored.
|
|
// The key can be an empty string, but the value cannot be nil - if it is,
|
|
// Put() returns ErrBadValue.
|
|
//
|
|
// err := store.Put("key42", 156)
|
|
// err := store.Put("key42", "this is a string")
|
|
// m := map[string]int{
|
|
// "harry": 100,
|
|
// "emma": 101,
|
|
// }
|
|
// err := store.Put("key43", m)
|
|
func (kvs *KVStore) Put(key string, bucket string, value interface{}) error {
|
|
if value == nil {
|
|
return ErrBadValue
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := gob.NewEncoder(&buf).Encode(value); err != nil {
|
|
return nil
|
|
}
|
|
return kvs.db.Update(func(tx *bolt.Tx) error {
|
|
return tx.Bucket([]byte(bucket)).Put([]byte(key), buf.Bytes())
|
|
})
|
|
}
|
|
|
|
// Get an entry from the store. "value" must be a pointer-typed. If the key
|
|
// is not present in the store, Get returns ErrNotFound.
|
|
//
|
|
// type MyStruct struct {
|
|
// Numbers []int
|
|
// }
|
|
// var val MyStruct
|
|
// if err := store.Get("key42", &val); err == skv.ErrNotFound {
|
|
// // "key42" not found
|
|
// } else if err != nil {
|
|
// // an error occurred
|
|
// } else {
|
|
// // ok
|
|
// }
|
|
//
|
|
// The value passed to Get() can be nil, in which case any value read from
|
|
// the store is silently discarded.
|
|
//
|
|
// if err := store.Get("key42", nil); err == nil {
|
|
// fmt.Println("entry is present")
|
|
// }
|
|
func (kvs *KVStore) Get(key string, bucket string, value interface{}) error {
|
|
return kvs.db.View(func(tx *bolt.Tx) error {
|
|
c := tx.Bucket([]byte(bucket)).Cursor()
|
|
if k, v := c.Seek([]byte(key)); k == nil || string(k) != key {
|
|
return ErrNotFound
|
|
} else if value == nil {
|
|
return nil
|
|
} else {
|
|
d := gob.NewDecoder(bytes.NewReader(v))
|
|
return d.Decode(value)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Delete the entry with the given key. If no such key is present in the store,
|
|
// it returns ErrNotFound.
|
|
//
|
|
// store.Delete("key42")
|
|
func (kvs *KVStore) Delete(key string, bucket string) error {
|
|
return kvs.db.Update(func(tx *bolt.Tx) error {
|
|
c := tx.Bucket([]byte(bucket)).Cursor()
|
|
if k, _ := c.Seek([]byte(key)); k == nil || string(k) != key {
|
|
return ErrNotFound
|
|
} else {
|
|
return c.Delete()
|
|
}
|
|
})
|
|
}
|
|
|
|
//GetAll gets all values in a bucket
|
|
func (kvs *KVStore) GetAll(bucket string, iterateFunction func(k, v []byte) error) error {
|
|
err := kvs.db.View(func(tx *bolt.Tx) error {
|
|
tx.Bucket([]byte(bucket)).ForEach(iterateFunction)
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
// Close closes the key-value store file.
|
|
func (kvs *KVStore) Close() error {
|
|
return kvs.db.Close()
|
|
}
|
|
|
|
// Iterate over all existing keys and return a slice of the keys.
|
|
// If no keys are found, return an empty slice.
|
|
//
|
|
// store.GetKeys()
|
|
func (kvs *KVStore) GetKeys(bucketName string) ([]string, error) {
|
|
var kl []string
|
|
|
|
err := kvs.db.View(func(tx *bolt.Tx) error {
|
|
var err error
|
|
b := tx.Bucket([]byte(bucketName))
|
|
|
|
err = b.ForEach(func(k, v []byte) error {
|
|
//copy(kopie, k)
|
|
kl = append(kl, string(k))
|
|
return err
|
|
})
|
|
return err
|
|
})
|
|
return kl, err
|
|
}
|