package meta import ( 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 { par := false 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) } data, err := obj.Marshal() if err != nil { return errors.Wrapf(err, "(%T) could not marshal the object", db) } addrKey := addressKey(obj.Address()) if !par { // 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) if err != nil { return errors.Wrapf(err, "(%T) could not create index bucket", db) } // 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) } v := []byte(indices[i].val) // create address bucket for the value valBucket, err := keyBucket.CreateBucketIfNotExists(nonEmptyKeyBytes(v)) if err != nil { return errors.Wrapf(err, "(%T) could not create bucket for header value", db) } // put object address to value bucket if err := valBucket.Put(addrKey, nil); err != nil { return errors.Wrapf(err, "(%T) could not put item to header bucket", db) } } } return nil }) } func nonEmptyKeyBytes(key []byte) []byte { return append([]byte{0}, key...) } func cutKeyBytes(key []byte) []byte { return key[1:] } func addressKey(addr *objectSDK.Address) []byte { return []byte(addr.String()) } func objectIndices(obj *object.Object, parent bool) []bucketItem { as := obj.Attributes() res := make([]bucketItem, 0, 6+len(as)) // 6 predefined buckets and object attributes res = append(res, bucketItem{ key: v2object.FilterHeaderVersion, val: obj.Version().String(), }, bucketItem{ key: v2object.FilterHeaderContainerID, val: obj.ContainerID().String(), }, bucketItem{ key: v2object.FilterHeaderOwnerID, val: obj.OwnerID().String(), }, bucketItem{ key: v2object.FilterHeaderParent, val: obj.ParentID().String(), }, bucketItem{ key: v2object.FilterHeaderObjectID, val: obj.ID().String(), }, // TODO: add remaining fields after neofs-api#72 ) if obj.Type() == objectSDK.TypeRegular && !obj.HasParent() { res = append(res, bucketItem{ key: v2object.FilterPropertyRoot, }) } if !parent { res = append(res, bucketItem{ key: v2object.FilterPropertyPhy, }) } for _, a := range as { res = append(res, bucketItem{ key: a.Key(), val: a.Value(), }) } return res }