2018-04-17 04:50:20 +01:00
|
|
|
// Copyright (C) 2018 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
2018-04-06 17:32:34 +01:00
|
|
|
package boltdb
|
|
|
|
|
|
|
|
import (
|
2018-08-03 14:15:52 +01:00
|
|
|
"fmt"
|
2018-04-10 22:46:48 +01:00
|
|
|
"time"
|
2018-04-17 04:50:20 +01:00
|
|
|
|
|
|
|
"github.com/boltdb/bolt"
|
2018-04-21 00:54:18 +01:00
|
|
|
"go.uber.org/zap"
|
2018-08-03 14:15:52 +01:00
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
"storj.io/storj/storage"
|
2018-04-10 22:46:48 +01:00
|
|
|
)
|
2018-04-06 17:32:34 +01:00
|
|
|
|
2018-08-01 15:15:38 +01:00
|
|
|
// Client is the entrypoint into a bolt data store
|
|
|
|
type Client struct {
|
2018-06-13 19:22:32 +01:00
|
|
|
logger *zap.Logger
|
|
|
|
db *bolt.DB
|
|
|
|
Path string
|
|
|
|
Bucket []byte
|
|
|
|
}
|
|
|
|
|
2018-04-21 00:54:18 +01:00
|
|
|
const (
|
|
|
|
// fileMode sets permissions so owner can read and write
|
|
|
|
fileMode = 0600
|
2018-06-13 19:22:32 +01:00
|
|
|
// PointerBucket is the string representing the bucket used for `PointerEntries`
|
|
|
|
PointerBucket = "pointers"
|
|
|
|
// OverlayBucket is the string representing the bucket used for a bolt-backed overlay dht cache
|
|
|
|
OverlayBucket = "overlay"
|
2018-07-30 20:25:18 +01:00
|
|
|
// KBucket is the string representing the bucket used for the kademlia routing table k-bucket ids
|
|
|
|
KBucket = "kbuckets"
|
|
|
|
// NodeBucket is the string representing the bucket used for the kademlia routing table node ids
|
2018-08-03 14:15:52 +01:00
|
|
|
NodeBucket = "nodes"
|
|
|
|
maxKeyLookup = 100
|
2018-04-21 00:54:18 +01:00
|
|
|
)
|
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
var (
|
|
|
|
defaultTimeout = 1 * time.Second
|
|
|
|
)
|
2018-04-06 17:32:34 +01:00
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
// NewClient instantiates a new BoltDB client given a zap logger, db file path, and a bucket name
|
2018-08-01 15:15:38 +01:00
|
|
|
func NewClient(logger *zap.Logger, path, bucket string) (*Client, error) {
|
2018-04-21 00:54:18 +01:00
|
|
|
db, err := bolt.Open(path, fileMode, &bolt.Options{Timeout: defaultTimeout})
|
2018-04-06 17:32:34 +01:00
|
|
|
if err != nil {
|
2018-04-18 19:21:25 +01:00
|
|
|
return nil, err
|
2018-04-06 17:32:34 +01:00
|
|
|
}
|
|
|
|
|
2018-08-16 15:32:28 +01:00
|
|
|
err = db.Update(func(tx *bolt.Tx) error {
|
|
|
|
_, err = tx.CreateBucketIfNotExists([]byte(bucket))
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
2018-08-27 18:28:16 +01:00
|
|
|
_ = db.Close()
|
2018-08-16 15:32:28 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:15:38 +01:00
|
|
|
return &Client{
|
2018-04-21 00:54:18 +01:00
|
|
|
logger: logger,
|
|
|
|
db: db,
|
|
|
|
Path: path,
|
2018-06-13 19:22:32 +01:00
|
|
|
Bucket: []byte(bucket),
|
2018-04-06 17:32:34 +01:00
|
|
|
}, nil
|
|
|
|
}
|
2018-04-10 22:46:48 +01:00
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
// Put adds a value to the provided key in boltdb, returning an error on failure.
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) Put(key storage.Key, value storage.Value) error {
|
2018-06-13 19:22:32 +01:00
|
|
|
c.logger.Debug("entering bolt put")
|
|
|
|
return c.db.Update(func(tx *bolt.Tx) error {
|
2018-08-16 15:32:28 +01:00
|
|
|
b := tx.Bucket(c.Bucket)
|
2018-06-13 19:22:32 +01:00
|
|
|
return b.Put(key, value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get looks up the provided key from boltdb returning either an error or the result.
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) Get(pathKey storage.Key) (storage.Value, error) {
|
2018-06-13 19:22:32 +01:00
|
|
|
c.logger.Debug("entering bolt get: " + string(pathKey))
|
|
|
|
var pointerBytes []byte
|
|
|
|
err := c.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
b := tx.Bucket(c.Bucket)
|
|
|
|
v := b.Get(pathKey)
|
2018-08-14 16:22:29 +01:00
|
|
|
if len(v) == 0 {
|
|
|
|
return storage.ErrKeyNotFound.New(pathKey.String())
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
pointerBytes = v
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2018-06-29 21:06:25 +01:00
|
|
|
if err != nil {
|
|
|
|
// TODO: log
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pointerBytes, nil
|
2018-06-13 19:22:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// List returns either a list of keys for which boltdb has values or an error.
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) List(startingKey storage.Key, limit storage.Limit) (storage.Keys, error) {
|
2018-06-13 19:22:32 +01:00
|
|
|
c.logger.Debug("entering bolt list")
|
2018-07-30 20:25:18 +01:00
|
|
|
return c.listHelper(false, startingKey, limit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReverseList returns either a list of keys for which boltdb has values or an error.
|
|
|
|
// Starts from startingKey and iterates backwards
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) ReverseList(startingKey storage.Key, limit storage.Limit) (storage.Keys, error) {
|
2018-07-30 20:25:18 +01:00
|
|
|
c.logger.Debug("entering bolt reverse list")
|
|
|
|
return c.listHelper(true, startingKey, limit)
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) listHelper(reverseList bool, startingKey storage.Key, limit storage.Limit) (storage.Keys, error) {
|
2018-06-13 19:22:32 +01:00
|
|
|
var paths storage.Keys
|
|
|
|
err := c.db.Update(func(tx *bolt.Tx) error {
|
2018-06-29 21:06:25 +01:00
|
|
|
cur := tx.Bucket(c.Bucket).Cursor()
|
|
|
|
var k []byte
|
2018-07-30 20:25:18 +01:00
|
|
|
start := firstOrLast(reverseList, cur)
|
|
|
|
iterate := prevOrNext(reverseList, cur)
|
2018-06-29 21:06:25 +01:00
|
|
|
if startingKey == nil {
|
2018-07-30 20:25:18 +01:00
|
|
|
k, _ = start()
|
2018-06-29 21:06:25 +01:00
|
|
|
} else {
|
|
|
|
k, _ = cur.Seek(startingKey)
|
|
|
|
}
|
2018-07-30 20:25:18 +01:00
|
|
|
for ; k != nil; k, _ = iterate() {
|
2018-06-29 21:06:25 +01:00
|
|
|
paths = append(paths, k)
|
2018-08-27 18:28:16 +01:00
|
|
|
if limit > 0 && int(limit) == len(paths) {
|
2018-06-29 21:06:25 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2018-06-13 19:22:32 +01:00
|
|
|
})
|
|
|
|
return paths, err
|
|
|
|
}
|
|
|
|
|
2018-07-30 20:25:18 +01:00
|
|
|
func firstOrLast(reverseList bool, cur *bolt.Cursor) func() ([]byte, []byte) {
|
|
|
|
if reverseList {
|
|
|
|
return cur.Last
|
|
|
|
}
|
|
|
|
return cur.First
|
|
|
|
}
|
|
|
|
|
|
|
|
func prevOrNext(reverseList bool, cur *bolt.Cursor) func() ([]byte, []byte) {
|
|
|
|
if reverseList {
|
|
|
|
return cur.Prev
|
|
|
|
}
|
|
|
|
return cur.Next
|
|
|
|
}
|
|
|
|
|
2018-08-26 04:00:49 +01:00
|
|
|
//ListV2 is the new definition and will replace `List` definition
|
|
|
|
func (c *Client) ListV2(opts storage.ListOptions) (storage.Items, storage.More, error) {
|
|
|
|
//TODO write the implementation
|
|
|
|
panic("to do")
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:22:32 +01:00
|
|
|
// Delete deletes a key/value pair from boltdb, for a given the key
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) Delete(pathKey storage.Key) error {
|
2018-06-13 19:22:32 +01:00
|
|
|
c.logger.Debug("entering bolt delete: " + string(pathKey))
|
|
|
|
return c.db.Update(func(tx *bolt.Tx) error {
|
|
|
|
return tx.Bucket(c.Bucket).Delete(pathKey)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-04-21 00:54:18 +01:00
|
|
|
// Close closes a BoltDB client
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) Close() error {
|
2018-04-10 22:46:48 +01:00
|
|
|
return c.db.Close()
|
|
|
|
}
|
2018-08-01 15:15:38 +01:00
|
|
|
|
2018-08-03 14:15:52 +01:00
|
|
|
// GetAll finds all values for the provided keys up to 100 keys
|
|
|
|
// if more keys are provided than the maximum an error will be returned.
|
2018-08-01 15:15:38 +01:00
|
|
|
func (c *Client) GetAll(keys storage.Keys) (storage.Values, error) {
|
2018-08-03 14:15:52 +01:00
|
|
|
lk := len(keys)
|
|
|
|
if lk > maxKeyLookup {
|
|
|
|
return nil, Error.New(fmt.Sprintf("requested %d keys, maximum is %d", lk, maxKeyLookup))
|
|
|
|
}
|
|
|
|
|
|
|
|
vals := make(storage.Values, lk)
|
|
|
|
for i, v := range keys {
|
|
|
|
val, err := c.Get(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
vals[i] = val
|
|
|
|
}
|
|
|
|
return vals, nil
|
2018-08-01 15:15:38 +01:00
|
|
|
}
|