2018-12-18 15:44:25 +00:00
|
|
|
package bolt
|
|
|
|
|
|
|
|
import (
|
2020-01-21 12:52:30 +00:00
|
|
|
"bytes"
|
2018-12-18 15:44:25 +00:00
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
2019-08-28 17:30:06 +00:00
|
|
|
bolt "github.com/coreos/bbolt"
|
2019-03-06 00:18:04 +00:00
|
|
|
"github.com/influxdata/influxdb/kit/tracing"
|
2019-03-05 20:54:32 +00:00
|
|
|
"github.com/influxdata/influxdb/kv"
|
2019-08-28 17:30:06 +00:00
|
|
|
"go.uber.org/zap"
|
2018-12-18 15:44:25 +00:00
|
|
|
)
|
|
|
|
|
2019-12-19 16:30:05 +00:00
|
|
|
// check that *KVStore implement kv.Store interface.
|
2019-12-28 01:24:26 +00:00
|
|
|
var _ kv.Store = (*KVStore)(nil)
|
2019-12-19 16:30:05 +00:00
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
// KVStore is a kv.Store backed by boltdb.
|
|
|
|
type KVStore struct {
|
2019-12-04 23:10:23 +00:00
|
|
|
path string
|
|
|
|
db *bolt.DB
|
|
|
|
log *zap.Logger
|
2018-12-18 15:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewKVStore returns an instance of KVStore with the file at
|
|
|
|
// the provided path.
|
2019-12-04 23:10:23 +00:00
|
|
|
func NewKVStore(log *zap.Logger, path string) *KVStore {
|
2018-12-18 15:44:25 +00:00
|
|
|
return &KVStore{
|
2019-12-04 23:10:23 +00:00
|
|
|
path: path,
|
|
|
|
log: log,
|
2018-12-18 15:44:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open creates boltDB file it doesn't exists and opens it otherwise.
|
|
|
|
func (s *KVStore) Open(ctx context.Context) error {
|
2019-03-06 00:18:04 +00:00
|
|
|
span, _ := tracing.StartSpanFromContext(ctx)
|
2019-03-05 00:38:10 +00:00
|
|
|
defer span.Finish()
|
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
// Ensure the required directory structure exists.
|
|
|
|
if err := os.MkdirAll(filepath.Dir(s.path), 0700); err != nil {
|
|
|
|
return fmt.Errorf("unable to create directory %s: %v", s.path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(s.path); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open database file.
|
|
|
|
db, err := bolt.Open(s.path, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to open boltdb file %v", err)
|
|
|
|
}
|
|
|
|
s.db = db
|
|
|
|
|
2019-12-04 23:10:23 +00:00
|
|
|
s.log.Info("Resources opened", zap.String("path", s.path))
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the connection to the bolt database
|
|
|
|
func (s *KVStore) Close() error {
|
|
|
|
if s.db != nil {
|
|
|
|
return s.db.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-19 23:47:19 +00:00
|
|
|
// Flush removes all bolt keys within each bucket.
|
2019-03-05 00:38:10 +00:00
|
|
|
func (s *KVStore) Flush(ctx context.Context) {
|
2019-02-19 23:47:19 +00:00
|
|
|
_ = s.db.Update(
|
|
|
|
func(tx *bolt.Tx) error {
|
|
|
|
return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
|
|
|
s.cleanBucket(tx, b)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *KVStore) cleanBucket(tx *bolt.Tx, b *bolt.Bucket) {
|
|
|
|
// nested bucket recursion base case:
|
|
|
|
if b == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c := b.Cursor()
|
|
|
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
|
|
_ = v
|
|
|
|
if err := c.Delete(); err != nil {
|
|
|
|
// clean out nexted buckets
|
|
|
|
s.cleanBucket(tx, b.Bucket(k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
// WithDB sets the boltdb on the store.
|
|
|
|
func (s *KVStore) WithDB(db *bolt.DB) {
|
|
|
|
s.db = db
|
|
|
|
}
|
|
|
|
|
|
|
|
// View opens up a view transaction against the store.
|
2019-03-05 00:38:10 +00:00
|
|
|
func (s *KVStore) View(ctx context.Context, fn func(tx kv.Tx) error) error {
|
2019-03-06 00:18:04 +00:00
|
|
|
span, ctx := tracing.StartSpanFromContext(ctx)
|
2019-03-05 00:38:10 +00:00
|
|
|
defer span.Finish()
|
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
return s.db.View(func(tx *bolt.Tx) error {
|
|
|
|
return fn(&Tx{
|
|
|
|
tx: tx,
|
2019-03-05 00:38:10 +00:00
|
|
|
ctx: ctx,
|
2018-12-18 15:44:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update opens up an update transaction against the store.
|
2019-03-05 00:38:10 +00:00
|
|
|
func (s *KVStore) Update(ctx context.Context, fn func(tx kv.Tx) error) error {
|
2019-03-06 00:18:04 +00:00
|
|
|
span, ctx := tracing.StartSpanFromContext(ctx)
|
2019-03-05 00:38:10 +00:00
|
|
|
defer span.Finish()
|
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
return s.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
return fn(&Tx{
|
|
|
|
tx: tx,
|
2019-03-05 00:38:10 +00:00
|
|
|
ctx: ctx,
|
2018-12-18 15:44:25 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tx is a light wrapper around a boltdb transaction. It implements kv.Tx.
|
|
|
|
type Tx struct {
|
|
|
|
tx *bolt.Tx
|
|
|
|
ctx context.Context
|
|
|
|
}
|
|
|
|
|
|
|
|
// Context returns the context for the transaction.
|
|
|
|
func (tx *Tx) Context() context.Context {
|
|
|
|
return tx.ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithContext sets the context for the transaction.
|
|
|
|
func (tx *Tx) WithContext(ctx context.Context) {
|
|
|
|
tx.ctx = ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
// createBucketIfNotExists creates a bucket with the provided byte slice.
|
|
|
|
func (tx *Tx) createBucketIfNotExists(b []byte) (*Bucket, error) {
|
|
|
|
bkt, err := tx.tx.CreateBucketIfNotExists(b)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Bucket{
|
|
|
|
bucket: bkt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bucket retrieves the bucket named b.
|
|
|
|
func (tx *Tx) Bucket(b []byte) (kv.Bucket, error) {
|
|
|
|
bkt := tx.tx.Bucket(b)
|
|
|
|
if bkt == nil {
|
|
|
|
return tx.createBucketIfNotExists(b)
|
|
|
|
}
|
|
|
|
return &Bucket{
|
|
|
|
bucket: bkt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bucket implements kv.Bucket.
|
|
|
|
type Bucket struct {
|
|
|
|
bucket *bolt.Bucket
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get retrieves the value at the provided key.
|
|
|
|
func (b *Bucket) Get(key []byte) ([]byte, error) {
|
|
|
|
val := b.bucket.Get(key)
|
|
|
|
if len(val) == 0 {
|
|
|
|
return nil, kv.ErrKeyNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put sets the value at the provided key.
|
|
|
|
func (b *Bucket) Put(key []byte, value []byte) error {
|
|
|
|
err := b.bucket.Put(key, value)
|
|
|
|
if err == bolt.ErrTxNotWritable {
|
|
|
|
return kv.ErrTxNotWritable
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete removes the provided key.
|
|
|
|
func (b *Bucket) Delete(key []byte) error {
|
|
|
|
err := b.bucket.Delete(key)
|
|
|
|
if err == bolt.ErrTxNotWritable {
|
|
|
|
return kv.ErrTxNotWritable
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-19 16:30:05 +00:00
|
|
|
// ForwardCursor retrieves a cursor for iterating through the entries
|
|
|
|
// in the key value store in a given direction (ascending / descending).
|
|
|
|
func (b *Bucket) ForwardCursor(seek []byte, opts ...kv.CursorOption) (kv.ForwardCursor, error) {
|
|
|
|
var (
|
|
|
|
cursor = b.bucket.Cursor()
|
|
|
|
key, value = cursor.Seek(seek)
|
2020-01-21 12:52:30 +00:00
|
|
|
config = kv.NewCursorConfig(opts...)
|
2019-12-19 16:30:05 +00:00
|
|
|
)
|
|
|
|
|
2020-01-21 12:52:30 +00:00
|
|
|
if config.Prefix != nil && !bytes.HasPrefix(seek, config.Prefix) {
|
|
|
|
return nil, fmt.Errorf("seek bytes %q not prefixed with %q: %w", string(seek), string(config.Prefix), kv.ErrSeekMissingPrefix)
|
|
|
|
}
|
|
|
|
|
2019-12-19 16:30:05 +00:00
|
|
|
return &Cursor{
|
|
|
|
cursor: cursor,
|
|
|
|
key: key,
|
|
|
|
value: value,
|
2020-01-21 12:52:30 +00:00
|
|
|
config: config,
|
2019-12-19 16:30:05 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-12-18 15:44:25 +00:00
|
|
|
// Cursor retrieves a cursor for iterating through the entries
|
|
|
|
// in the key value store.
|
2019-10-31 20:01:41 +00:00
|
|
|
func (b *Bucket) Cursor(opts ...kv.CursorHint) (kv.Cursor, error) {
|
2018-12-18 15:44:25 +00:00
|
|
|
return &Cursor{
|
|
|
|
cursor: b.bucket.Cursor(),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cursor is a struct for iterating through the entries
|
|
|
|
// in the key value store.
|
|
|
|
type Cursor struct {
|
|
|
|
cursor *bolt.Cursor
|
2019-12-19 16:30:05 +00:00
|
|
|
|
|
|
|
// previously seeked key/value
|
|
|
|
key, value []byte
|
|
|
|
|
|
|
|
config kv.CursorConfig
|
|
|
|
closed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close sets the closed to closed
|
|
|
|
func (c *Cursor) Close() error {
|
|
|
|
c.closed = true
|
|
|
|
|
|
|
|
return nil
|
2018-12-18 15:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Seek seeks for the first key that matches the prefix provided.
|
|
|
|
func (c *Cursor) Seek(prefix []byte) ([]byte, []byte) {
|
2019-12-19 16:30:05 +00:00
|
|
|
if c.closed {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2018-12-18 15:44:25 +00:00
|
|
|
k, v := c.cursor.Seek(prefix)
|
2019-02-19 23:47:19 +00:00
|
|
|
if len(k) == 0 && len(v) == 0 {
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return k, v
|
|
|
|
}
|
|
|
|
|
|
|
|
// First retrieves the first key value pair in the bucket.
|
|
|
|
func (c *Cursor) First() ([]byte, []byte) {
|
2019-12-19 16:30:05 +00:00
|
|
|
if c.closed {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2018-12-18 15:44:25 +00:00
|
|
|
k, v := c.cursor.First()
|
2019-02-19 23:47:19 +00:00
|
|
|
if len(k) == 0 && len(v) == 0 {
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return k, v
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last retrieves the last key value pair in the bucket.
|
|
|
|
func (c *Cursor) Last() ([]byte, []byte) {
|
2019-12-19 16:30:05 +00:00
|
|
|
if c.closed {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2018-12-18 15:44:25 +00:00
|
|
|
k, v := c.cursor.Last()
|
2019-02-19 23:47:19 +00:00
|
|
|
if len(k) == 0 && len(v) == 0 {
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return k, v
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next retrieves the next key in the bucket.
|
2019-12-19 16:30:05 +00:00
|
|
|
func (c *Cursor) Next() (k []byte, v []byte) {
|
2020-01-21 12:52:30 +00:00
|
|
|
if c.closed || (c.key != nil && c.missingPrefix(c.key)) {
|
2019-12-19 16:30:05 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
// get and unset previously seeked values if they exist
|
|
|
|
k, v, c.key, c.value = c.key, c.value, nil, nil
|
|
|
|
if len(k) > 0 && len(v) > 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
next := c.cursor.Next
|
|
|
|
if c.config.Direction == kv.CursorDescending {
|
|
|
|
next = c.cursor.Prev
|
|
|
|
}
|
|
|
|
|
|
|
|
k, v = next()
|
2020-01-21 12:52:30 +00:00
|
|
|
if (len(k) == 0 && len(v) == 0) || c.missingPrefix(k) {
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return k, v
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prev retrieves the previous key in the bucket.
|
2019-12-19 16:30:05 +00:00
|
|
|
func (c *Cursor) Prev() (k []byte, v []byte) {
|
2020-01-21 12:52:30 +00:00
|
|
|
if c.closed || (c.key != nil && c.missingPrefix(c.key)) {
|
2019-12-19 16:30:05 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
// get and unset previously seeked values if they exist
|
|
|
|
k, v, c.key, c.value = c.key, c.value, nil, nil
|
|
|
|
if len(k) > 0 && len(v) > 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
prev := c.cursor.Prev
|
|
|
|
if c.config.Direction == kv.CursorDescending {
|
|
|
|
prev = c.cursor.Next
|
|
|
|
}
|
|
|
|
|
|
|
|
k, v = prev()
|
2020-01-21 12:52:30 +00:00
|
|
|
if (len(k) == 0 && len(v) == 0) || c.missingPrefix(k) {
|
2018-12-18 15:44:25 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return k, v
|
|
|
|
}
|
2019-12-19 16:30:05 +00:00
|
|
|
|
2020-01-21 12:52:30 +00:00
|
|
|
func (c *Cursor) missingPrefix(key []byte) bool {
|
|
|
|
return c.config.Prefix != nil && !bytes.HasPrefix(key, c.config.Prefix)
|
|
|
|
}
|
|
|
|
|
2019-12-19 16:30:05 +00:00
|
|
|
// Err always returns nil as nothing can go wrong™ during iteration
|
|
|
|
func (c *Cursor) Err() error {
|
|
|
|
return nil
|
|
|
|
}
|