2020-10-28 14:49:30 +00:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
2020-11-03 12:12:56 +00:00
|
|
|
"bytes"
|
|
|
|
"encoding/gob"
|
|
|
|
|
2020-10-28 14:49:30 +00:00
|
|
|
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
|
|
|
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"go.etcd.io/bbolt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type bucketItem struct {
|
|
|
|
key, val string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
primaryBucket = []byte("objects")
|
|
|
|
indexBucket = []byte("index")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Put saves object in DB.
|
|
|
|
//
|
|
|
|
// Object payload expected to be cut.
|
|
|
|
func (db *DB) Put(obj *object.Object) error {
|
|
|
|
return db.boltDB.Update(func(tx *bbolt.Tx) error {
|
2020-10-29 16:12:38 +00:00
|
|
|
par := false
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
for ; obj != nil; obj, par = obj.GetParent(), true {
|
|
|
|
// create primary bucket (addr: header)
|
|
|
|
primaryBucket, err := tx.CreateBucketIfNotExists(primaryBucket)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "(%T) could not create primary bucket", db)
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
data, err := obj.ToV2().StableMarshal(nil)
|
2020-10-28 14:49:30 +00:00
|
|
|
if err != nil {
|
2020-10-29 16:12:38 +00:00
|
|
|
return errors.Wrapf(err, "(%T) could not marshal the object", db)
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
addrKey := addressKey(obj.Address())
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
// put header to primary bucket
|
|
|
|
if err := primaryBucket.Put(addrKey, data); err != nil {
|
|
|
|
return errors.Wrapf(err, "(%T) could not put item to primary bucket", db)
|
|
|
|
}
|
|
|
|
|
|
|
|
// create bucket for indices
|
|
|
|
indexBucket, err := tx.CreateBucketIfNotExists(indexBucket)
|
2020-10-28 14:49:30 +00:00
|
|
|
if err != nil {
|
2020-10-29 16:12:38 +00:00
|
|
|
return errors.Wrapf(err, "(%T) could not create index bucket", db)
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
// calculate indexed values for object
|
|
|
|
indices := objectIndices(obj, par)
|
|
|
|
|
|
|
|
for i := range indices {
|
|
|
|
// create index bucket
|
|
|
|
keyBucket, err := indexBucket.CreateBucketIfNotExists([]byte(indices[i].key))
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "(%T) could not create bucket for header key", db)
|
|
|
|
}
|
|
|
|
|
2020-11-03 12:12:56 +00:00
|
|
|
v := nonEmptyKeyBytes([]byte(indices[i].val))
|
|
|
|
|
|
|
|
strs, err := decodeAddressList(keyBucket.Get(v))
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "(%T) could not decode address list", db)
|
|
|
|
}
|
2020-10-29 16:12:38 +00:00
|
|
|
|
2020-11-03 12:12:56 +00:00
|
|
|
data, err := encodeAddressList(append(strs, string(addrKey)))
|
2020-10-29 16:12:38 +00:00
|
|
|
if err != nil {
|
2020-11-03 12:12:56 +00:00
|
|
|
return errors.Wrapf(err, "(%T) could not encode address list", db)
|
2020-10-29 16:12:38 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 12:12:56 +00:00
|
|
|
if err := keyBucket.Put(v, data); err != nil {
|
2020-10-29 16:12:38 +00:00
|
|
|
return errors.Wrapf(err, "(%T) could not put item to header bucket", db)
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
2020-10-29 16:12:38 +00:00
|
|
|
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-29 11:40:53 +00:00
|
|
|
func nonEmptyKeyBytes(key []byte) []byte {
|
|
|
|
return append([]byte{0}, key...)
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 11:38:50 +00:00
|
|
|
func cutKeyBytes(key []byte) []byte {
|
|
|
|
return key[1:]
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func addressKey(addr *objectSDK.Address) []byte {
|
|
|
|
return []byte(addr.String())
|
|
|
|
}
|
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
func objectIndices(obj *object.Object, parent bool) []bucketItem {
|
2020-10-28 14:49:30 +00:00
|
|
|
as := obj.GetAttributes()
|
|
|
|
|
2020-10-29 16:12:38 +00:00
|
|
|
res := make([]bucketItem, 0, 5+len(as))
|
|
|
|
|
|
|
|
rootVal := v2object.BooleanPropertyValueTrue
|
|
|
|
if obj.HasParent() {
|
|
|
|
rootVal = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
leafVal := v2object.BooleanPropertyValueTrue
|
|
|
|
if parent {
|
|
|
|
leafVal = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
childfreeVal := v2object.BooleanPropertyValueTrue
|
|
|
|
if len(obj.GetChildren()) > 0 {
|
|
|
|
childfreeVal = ""
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
|
|
|
res = append(res,
|
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterHeaderVersion,
|
|
|
|
val: obj.GetVersion().String(),
|
|
|
|
},
|
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterHeaderContainerID,
|
|
|
|
val: obj.GetContainerID().String(),
|
|
|
|
},
|
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterHeaderOwnerID,
|
|
|
|
val: obj.GetOwnerID().String(),
|
|
|
|
},
|
2020-10-29 16:12:38 +00:00
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterPropertyRoot,
|
|
|
|
val: rootVal,
|
|
|
|
},
|
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterPropertyLeaf,
|
|
|
|
val: leafVal,
|
|
|
|
},
|
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterPropertyChildfree,
|
|
|
|
val: childfreeVal,
|
|
|
|
},
|
2020-11-05 16:54:24 +00:00
|
|
|
bucketItem{
|
|
|
|
key: v2object.FilterHeaderParent,
|
|
|
|
val: obj.GetParentID().String(),
|
|
|
|
},
|
2020-10-28 14:49:30 +00:00
|
|
|
// TODO: add remaining fields after neofs-api#72
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, a := range as {
|
|
|
|
res = append(res, bucketItem{
|
|
|
|
key: a.GetKey(),
|
|
|
|
val: a.GetValue(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
2020-11-03 12:12:56 +00:00
|
|
|
|
|
|
|
// FIXME: gob is a temporary solution, use protobuf.
|
|
|
|
func decodeAddressList(data []byte) ([]string, error) {
|
|
|
|
if len(data) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var strs []string
|
|
|
|
|
|
|
|
decoder := gob.NewDecoder(bytes.NewReader(data))
|
|
|
|
if err := decoder.Decode(&strs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return strs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func encodeAddressList(l []string) ([]byte, error) {
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
encoder := gob.NewEncoder(buf)
|
|
|
|
|
|
|
|
if err := encoder.Encode(l); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|