frostfs-sdk-go/object/relations/relations.go
Denis Kirillov 9964a83083 [#308] object/relations: List relations in split order
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2022-11-17 16:31:26 +03:00

128 lines
4.4 KiB
Go

package relations
import (
"context"
"errors"
"fmt"
"github.com/nspcc-dev/neofs-sdk-go/bearer"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/object"
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
// Tokens contains different tokens to perform requests in Relations implementations.
type Tokens struct {
Session *session.Object
Bearer *bearer.Token
}
type Relations interface {
// GetSplitInfo tries to get split info by some object id.
// This method must return split info on any object from split chain as well as on parent/linking object.
// If object doesn't have any split information returns ErrNoSplitInfo.
GetSplitInfo(ctx context.Context, cnrID cid.ID, rootID oid.ID, tokens Tokens) (*object.SplitInfo, error)
// ListChildrenByLinker returns list of children for link object.
// Result doesn't include link object itself.
ListChildrenByLinker(ctx context.Context, cnrID cid.ID, linkerID oid.ID, tokens Tokens) ([]oid.ID, error)
// GetLeftSibling return previous object id in object chain.
// If no previous object it returns ErrNoLeftSibling.
GetLeftSibling(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens Tokens) (oid.ID, error)
// FindSiblingByParentID returns all object that relates to the provided parent id.
FindSiblingByParentID(ctx context.Context, cnrID cid.ID, parentID oid.ID, tokens Tokens) ([]oid.ID, error)
}
var (
// ErrNoLeftSibling is an error that must be returned if object doesn't have left sibling in objects chain.
ErrNoLeftSibling = errors.New("no left siblings")
// ErrNoSplitInfo is an error that must be returned if requested object isn't virtual.
ErrNoSplitInfo = errors.New("no split info")
)
// ListAllRelations return all related phy objects for provided root object ID in split-chain order.
// Result doesn't include root object ID itself. If linking object is found its id will be the last one.
func ListAllRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObjID oid.ID, tokens Tokens) ([]oid.ID, error) {
return ListRelations(ctx, rels, cnrID, rootObjID, tokens, true)
}
// ListRelations return all related phy objects for provided root object ID in split-chain order.
// Result doesn't include root object ID itself.
func ListRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObjID oid.ID, tokens Tokens, includeLinking bool) ([]oid.ID, error) {
splitInfo, err := rels.GetSplitInfo(ctx, cnrID, rootObjID, tokens)
if err != nil {
if errors.Is(err, ErrNoSplitInfo) {
return []oid.ID{}, nil
}
return nil, err
}
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
// If any approach fails, we don't try the next since we assume that it will fail too.
if _, ok := splitInfo.Link(); !ok {
// the list is expected to contain last part and (probably) split info
list, err := rels.FindSiblingByParentID(ctx, cnrID, rootObjID, tokens)
if err != nil {
return nil, fmt.Errorf("failed to find object children: %w", err)
}
for _, id := range list {
split, err := rels.GetSplitInfo(ctx, cnrID, id, tokens)
if err != nil {
if errors.Is(err, ErrNoSplitInfo) {
continue
}
return nil, fmt.Errorf("failed to get split info: %w", err)
}
if link, ok := split.Link(); ok {
splitInfo.SetLink(link)
}
if last, ok := split.LastPart(); ok {
splitInfo.SetLastPart(last)
}
}
}
if idLinking, ok := splitInfo.Link(); ok {
children, err := rels.ListChildrenByLinker(ctx, cnrID, idLinking, tokens)
if err != nil {
return nil, fmt.Errorf("failed to get linking object's header: %w", err)
}
if includeLinking {
children = append(children, idLinking)
}
return children, nil
}
idMember, ok := splitInfo.LastPart()
if !ok {
return nil, errors.New("missing any data in received object split information")
}
chain := []oid.ID{idMember}
chainSet := map[oid.ID]struct{}{idMember: {}}
for {
idMember, err = rels.GetLeftSibling(ctx, cnrID, idMember, tokens)
if err != nil {
if errors.Is(err, ErrNoLeftSibling) {
break
}
return nil, fmt.Errorf("failed to read split chain member's header: %w", err)
}
if _, ok = chainSet[idMember]; ok {
return nil, fmt.Errorf("duplicated member in the split chain %s", idMember)
}
chain = append([]oid.ID{idMember}, chain...)
chainSet[idMember] = struct{}{}
}
return chain, nil
}