2020-10-28 14:49:30 +00:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
2020-12-08 07:51:34 +00:00
|
|
|
"encoding/binary"
|
|
|
|
"encoding/hex"
|
2021-06-28 14:01:31 +00:00
|
|
|
"io/fs"
|
2020-12-08 07:51:34 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2021-08-18 07:26:14 +00:00
|
|
|
"strings"
|
2022-03-21 13:34:31 +00:00
|
|
|
"time"
|
2020-12-08 07:51:34 +00:00
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
2020-11-09 10:22:36 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
|
2021-11-10 07:08:33 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
2020-10-28 14:49:30 +00:00
|
|
|
"go.etcd.io/bbolt"
|
2020-11-09 10:22:36 +00:00
|
|
|
"go.uber.org/zap"
|
2020-10-28 14:49:30 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// DB represents local metabase of storage node.
|
|
|
|
type DB struct {
|
2020-11-09 10:14:52 +00:00
|
|
|
*cfg
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-12-08 07:51:34 +00:00
|
|
|
matchers map[object.SearchMatchType]func(string, []byte, string) bool
|
|
|
|
|
|
|
|
boltDB *bbolt.DB
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 10:14:52 +00:00
|
|
|
// Option is an option of DB constructor.
|
|
|
|
type Option func(*cfg)
|
|
|
|
|
|
|
|
type cfg struct {
|
2020-12-08 07:51:34 +00:00
|
|
|
boltOptions *bbolt.Options // optional
|
|
|
|
|
2022-03-21 13:34:31 +00:00
|
|
|
boltBatchSize int
|
|
|
|
boltBatchDelay time.Duration
|
|
|
|
|
2020-12-08 07:51:34 +00:00
|
|
|
info Info
|
2020-11-09 10:22:36 +00:00
|
|
|
|
|
|
|
log *logger.Logger
|
2020-11-09 10:14:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func defaultCfg() *cfg {
|
2020-11-09 10:22:36 +00:00
|
|
|
return &cfg{
|
2020-12-08 07:51:34 +00:00
|
|
|
info: Info{
|
|
|
|
Permission: os.ModePerm, // 0777
|
|
|
|
},
|
2022-03-21 13:34:31 +00:00
|
|
|
boltBatchDelay: bbolt.DefaultMaxBatchDelay,
|
|
|
|
boltBatchSize: bbolt.DefaultMaxBatchSize,
|
|
|
|
log: zap.L(),
|
2020-11-09 10:22:36 +00:00
|
|
|
}
|
2020-11-09 10:14:52 +00:00
|
|
|
}
|
|
|
|
|
2020-12-08 07:51:34 +00:00
|
|
|
// New creates and returns new Metabase instance.
|
|
|
|
func New(opts ...Option) *DB {
|
2020-11-09 10:14:52 +00:00
|
|
|
c := defaultCfg()
|
|
|
|
|
|
|
|
for i := range opts {
|
|
|
|
opts[i](c)
|
|
|
|
}
|
|
|
|
|
2020-10-28 14:49:30 +00:00
|
|
|
return &DB{
|
2020-11-19 13:53:22 +00:00
|
|
|
cfg: c,
|
2020-12-08 07:51:34 +00:00
|
|
|
matchers: map[object.SearchMatchType]func(string, []byte, string) bool{
|
2021-02-01 21:00:40 +00:00
|
|
|
object.MatchUnknown: unknownMatcher,
|
|
|
|
object.MatchStringEqual: stringEqualMatcher,
|
|
|
|
object.MatchStringNotEqual: stringNotEqualMatcher,
|
2021-08-18 07:26:14 +00:00
|
|
|
object.MatchCommonPrefix: stringCommonPrefixMatcher,
|
2020-10-28 14:49:30 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2020-10-29 16:12:38 +00:00
|
|
|
|
2021-02-01 21:00:40 +00:00
|
|
|
func stringifyValue(key string, objVal []byte) string {
|
2020-10-29 16:12:38 +00:00
|
|
|
switch key {
|
|
|
|
default:
|
2021-02-01 21:00:40 +00:00
|
|
|
return string(objVal)
|
2020-12-08 07:51:34 +00:00
|
|
|
case v2object.FilterHeaderPayloadHash, v2object.FilterHeaderHomomorphicHash:
|
2021-02-01 21:00:40 +00:00
|
|
|
return hex.EncodeToString(objVal)
|
2020-12-08 07:51:34 +00:00
|
|
|
case v2object.FilterHeaderCreationEpoch, v2object.FilterHeaderPayloadLength:
|
2021-02-01 21:00:40 +00:00
|
|
|
return strconv.FormatUint(binary.LittleEndian.Uint64(objVal), 10)
|
2020-11-10 12:55:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-01 21:00:40 +00:00
|
|
|
func stringEqualMatcher(key string, objVal []byte, filterVal string) bool {
|
|
|
|
return stringifyValue(key, objVal) == filterVal
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringNotEqualMatcher(key string, objVal []byte, filterVal string) bool {
|
|
|
|
return stringifyValue(key, objVal) != filterVal
|
|
|
|
}
|
|
|
|
|
2021-08-18 07:26:14 +00:00
|
|
|
func stringCommonPrefixMatcher(key string, objVal []byte, filterVal string) bool {
|
|
|
|
return strings.HasPrefix(stringifyValue(key, objVal), filterVal)
|
|
|
|
}
|
|
|
|
|
2020-12-08 07:51:34 +00:00
|
|
|
func unknownMatcher(_ string, _ []byte, _ string) bool {
|
|
|
|
return false
|
2020-10-29 16:12:38 +00:00
|
|
|
}
|
2020-11-09 10:14:52 +00:00
|
|
|
|
2020-12-08 07:51:34 +00:00
|
|
|
// bucketKeyHelper returns byte representation of val that is used as a key
|
|
|
|
// in boltDB. Useful for getting filter values from unique and list indexes.
|
|
|
|
func bucketKeyHelper(hdr string, val string) []byte {
|
|
|
|
switch hdr {
|
|
|
|
case v2object.FilterHeaderPayloadHash:
|
|
|
|
v, err := hex.DecodeString(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
case v2object.FilterHeaderSplitID:
|
|
|
|
s := object.NewSplitID()
|
|
|
|
|
|
|
|
err := s.Parse(val)
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.ToV2()
|
|
|
|
default:
|
|
|
|
return []byte(val)
|
2020-11-09 10:14:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-09 10:22:36 +00:00
|
|
|
|
|
|
|
// WithLogger returns option to set logger of DB.
|
|
|
|
func WithLogger(l *logger.Logger) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.log = l
|
|
|
|
}
|
|
|
|
}
|
2020-12-08 07:51:34 +00:00
|
|
|
|
|
|
|
// WithBoltDBOptions returns option to specify BoltDB options.
|
|
|
|
func WithBoltDBOptions(opts *bbolt.Options) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.boltOptions = opts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPath returns option to set system path to Metabase.
|
|
|
|
func WithPath(path string) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.info.Path = path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPermissions returns option to specify permission bits
|
|
|
|
// of Metabase system path.
|
2021-06-28 14:01:31 +00:00
|
|
|
func WithPermissions(perm fs.FileMode) Option {
|
2020-12-08 07:51:34 +00:00
|
|
|
return func(c *cfg) {
|
|
|
|
c.info.Permission = perm
|
|
|
|
}
|
|
|
|
}
|
2022-03-21 13:34:31 +00:00
|
|
|
|
|
|
|
// WithBatchSize returns option to specify maximum concurrent operations
|
|
|
|
// to be processed in a single transactions.
|
|
|
|
// This option is missing from `bbolt.Options` but is set right after DB is open.
|
|
|
|
func WithBatchSize(s int) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.boltBatchSize = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBatchDelay returns option to specify maximum time to wait before
|
|
|
|
// the batch of concurrent transactions is processed.
|
|
|
|
// This option is missing from `bbolt.Options` but is set right after DB is open.
|
|
|
|
func WithBatchDelay(d time.Duration) Option {
|
|
|
|
return func(c *cfg) {
|
|
|
|
c.boltBatchDelay = d
|
|
|
|
}
|
|
|
|
}
|