package meta_test import ( "context" "encoding/hex" "math/rand" "strconv" "testing" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/testutil" meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase" v2object "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object" cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/erasurecode" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/transformer" usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) func TestDB_SelectUserAttributes(t *testing.T) { t.Parallel() t.Run("with_index", func(t *testing.T) { testSelectUserAttributes(t, true) }) t.Run("without_index", func(t *testing.T) { testSelectUserAttributes(t, false) }) } func testSelectUserAttributes(t *testing.T, index bool) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() raw1 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw1, "foo", "bar") testutil.AddAttribute(raw1, "x", "y") var putPrm meta.PutPrm putPrm.SetIndexAttributes(index) putPrm.SetObject(raw1) _, err := db.Put(context.Background(), putPrm) require.NoError(t, err) raw2 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw2, "foo", "bar") testutil.AddAttribute(raw2, "x", "z") putPrm.SetObject(raw2) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) raw3 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw3, "a", "b") putPrm.SetObject(raw3) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) raw4 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw4, objectSDK.AttributeFilePath, "/test/1/2") putPrm.SetObject(raw4) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) raw5 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw5, objectSDK.AttributeFilePath, "/test/1/3") putPrm.SetObject(raw5) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) raw6 := testutil.GenerateObjectWithCID(cnr) testutil.AddAttribute(raw6, objectSDK.AttributeFilePath, "/test/2/3") putPrm.SetObject(raw6) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) raw7 := testutil.GenerateObjectWithCID(cnr) var attr objectSDK.Attribute attr.SetKey(objectSDK.AttributeFilePath) attr.SetValue("/test/3/4") attrs := raw7.Attributes() attrs = append(attrs, attr) ech := objectSDK.NewECHeader(objectSDK.ECParentInfo{ ID: oidtest.ID(), Attributes: attrs, }, 0, 3, []byte{}, 0) raw7.SetECHeader(ech) putPrm.SetObject(raw7) _, err = db.Put(context.Background(), putPrm) require.NoError(t, err) var raw7Parent oid.Address raw7Parent.SetContainer(cnr) raw7Parent.SetObject(ech.Parent()) fs := objectSDK.SearchFilters{} fs.AddFilter("foo", "bar", objectSDK.MatchStringEqual) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw1), object.AddressOf(raw2), ) fs = objectSDK.SearchFilters{} fs.AddFilter("x", "y", objectSDK.MatchStringEqual) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter("x", "y", objectSDK.MatchStringNotEqual) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter("a", "b", objectSDK.MatchStringEqual) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw3)) fs = objectSDK.SearchFilters{} fs.AddFilter("c", "d", objectSDK.MatchStringEqual) testSelect2(t, db, cnr, fs, index) fs = objectSDK.SearchFilters{} fs.AddFilter("foo", "", objectSDK.MatchNotPresent) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw3), object.AddressOf(raw4), object.AddressOf(raw5), object.AddressOf(raw6), object.AddressOf(raw7), ) fs = objectSDK.SearchFilters{} fs.AddFilter("a", "", objectSDK.MatchNotPresent) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw1), object.AddressOf(raw2), object.AddressOf(raw4), object.AddressOf(raw5), object.AddressOf(raw6), object.AddressOf(raw7), ) fs = objectSDK.SearchFilters{} testSelect2(t, db, cnr, fs, index, object.AddressOf(raw1), object.AddressOf(raw2), object.AddressOf(raw3), object.AddressOf(raw4), object.AddressOf(raw5), object.AddressOf(raw6), object.AddressOf(raw7), ) fs = objectSDK.SearchFilters{} fs.AddFilter("key", "", objectSDK.MatchNotPresent) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw1), object.AddressOf(raw2), object.AddressOf(raw3), object.AddressOf(raw4), object.AddressOf(raw5), object.AddressOf(raw6), object.AddressOf(raw7), ) fs = objectSDK.SearchFilters{} fs.AddFilter(objectSDK.AttributeFilePath, "/test", objectSDK.MatchCommonPrefix) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw4), object.AddressOf(raw5), object.AddressOf(raw6), raw7Parent, ) fs = objectSDK.SearchFilters{} fs.AddFilter(objectSDK.AttributeFilePath, "/test/1", objectSDK.MatchCommonPrefix) testSelect2(t, db, cnr, fs, index, object.AddressOf(raw4), object.AddressOf(raw5), ) fs = objectSDK.SearchFilters{} fs.AddFilter(objectSDK.AttributeFilePath, "/test/3/4", objectSDK.MatchStringEqual) testSelect2(t, db, cnr, fs, index, raw7Parent, ) } func TestDB_SelectRootPhyParent(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() // prepare small := testutil.GenerateObjectWithCID(cnr) err := putBig(db, small) require.NoError(t, err) ts := testutil.GenerateObjectWithCID(cnr) ts.SetType(objectSDK.TypeTombstone) err = putBig(db, ts) require.NoError(t, err) leftChild := testutil.GenerateObjectWithCID(cnr) leftChild.InitRelations() err = putBig(db, leftChild) require.NoError(t, err) lock := testutil.GenerateObjectWithCID(cnr) lock.SetType(objectSDK.TypeLock) err = putBig(db, lock) require.NoError(t, err) parent := testutil.GenerateObjectWithCID(cnr) rightChild := testutil.GenerateObjectWithCID(cnr) rightChild.SetParent(parent) idParent, _ := parent.ID() rightChild.SetParentID(idParent) err = putBig(db, rightChild) require.NoError(t, err) link := testutil.GenerateObjectWithCID(cnr) link.SetParent(parent) link.SetParentID(idParent) idLeftChild, _ := leftChild.ID() idRightChild, _ := rightChild.ID() link.SetChildren(idLeftChild, idRightChild) err = putBig(db, link) require.NoError(t, err) t.Run("root objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddRootFilter() testSelect(t, db, cnr, fs, object.AddressOf(small), object.AddressOf(parent), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterPropertyRoot, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("phy objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddPhyFilter() testSelect(t, db, cnr, fs, object.AddressOf(small), object.AddressOf(ts), object.AddressOf(leftChild), object.AddressOf(rightChild), object.AddressOf(link), object.AddressOf(lock), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterPropertyPhy, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("regular objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, v2object.TypeRegular.String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(small), object.AddressOf(leftChild), object.AddressOf(rightChild), object.AddressOf(link), object.AddressOf(parent), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, v2object.TypeRegular.String(), objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(ts), object.AddressOf(lock), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("tombstone objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, v2object.TypeTombstone.String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(ts)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, v2object.TypeTombstone.String(), objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(small), object.AddressOf(leftChild), object.AddressOf(rightChild), object.AddressOf(link), object.AddressOf(parent), object.AddressOf(lock), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderObjectType, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("objects with parent", func(t *testing.T) { idParent, _ := parent.ID() fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderParent, idParent.EncodeToString(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(rightChild), object.AddressOf(link), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderParent, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("all objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} testSelect(t, db, cnr, fs, object.AddressOf(small), object.AddressOf(ts), object.AddressOf(leftChild), object.AddressOf(rightChild), object.AddressOf(link), object.AddressOf(parent), object.AddressOf(lock), ) }) } func TestDB_SelectInhume(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() raw1 := testutil.GenerateObjectWithCID(cnr) err := putBig(db, raw1) require.NoError(t, err) raw2 := testutil.GenerateObjectWithCID(cnr) err = putBig(db, raw2) require.NoError(t, err) fs := objectSDK.SearchFilters{} testSelect(t, db, cnr, fs, object.AddressOf(raw1), object.AddressOf(raw2), ) err = metaInhume(db, object.AddressOf(raw2), oidtest.ID()) require.NoError(t, err) fs = objectSDK.SearchFilters{} testSelect(t, db, cnr, fs, object.AddressOf(raw1), ) } func TestDB_SelectPayloadHash(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() raw1 := testutil.GenerateObjectWithCID(cnr) err := putBig(db, raw1) require.NoError(t, err) raw2 := testutil.GenerateObjectWithCID(cnr) err = putBig(db, raw2) require.NoError(t, err) cs, _ := raw1.PayloadChecksum() payloadHash := hex.EncodeToString(cs.Value()) fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash, objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash[:len(payloadHash)-1], objectSDK.MatchCommonPrefix) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash, objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) t.Run("invalid hashes", func(t *testing.T) { fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash[:len(payloadHash)-1], objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw1), object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash[:len(payloadHash)-2]+"x", objectSDK.MatchCommonPrefix) testSelect(t, db, cnr, fs) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadHash, payloadHash[:len(payloadHash)-3]+"x0", objectSDK.MatchCommonPrefix) testSelect(t, db, cnr, fs) }) } func TestDB_SelectWithSlowFilters(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() v20 := new(version.Version) v20.SetMajor(2) var v21 version.Version v21.SetMajor(2) v21.SetMinor(1) raw1 := testutil.GenerateObjectWithCID(cnr) raw1.SetPayloadSize(10) raw1.SetCreationEpoch(11) raw1.SetVersion(v20) err := putBig(db, raw1) require.NoError(t, err) raw2 := testutil.GenerateObjectWithCID(cnr) raw2.SetPayloadSize(20) raw2.SetCreationEpoch(21) raw2.SetVersion(&v21) err = putBig(db, raw2) require.NoError(t, err) t.Run("object with TZHash", func(t *testing.T) { cs, _ := raw1.PayloadHomomorphicHash() fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderHomomorphicHash, hex.EncodeToString(cs.Value()), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderHomomorphicHash, hex.EncodeToString(cs.Value()), objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderHomomorphicHash, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("object with payload length", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadLength, "20", objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadLength, "20", objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderPayloadLength, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("object with creation epoch", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderCreationEpoch, "11", objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderCreationEpoch, "11", objectSDK.MatchStringNotEqual) testSelect(t, db, cnr, fs, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderCreationEpoch, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderCreationEpoch, "1", objectSDK.MatchCommonPrefix) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) }) t.Run("object with version", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectVersionFilter(objectSDK.MatchStringEqual, v21) testSelect(t, db, cnr, fs, object.AddressOf(raw2)) fs = objectSDK.SearchFilters{} fs.AddObjectVersionFilter(objectSDK.MatchStringNotEqual, v21) testSelect(t, db, cnr, fs, object.AddressOf(raw1)) fs = objectSDK.SearchFilters{} fs.AddObjectVersionFilter(objectSDK.MatchNotPresent, version.Version{}) testSelect(t, db, cnr, fs) }) } func TestDB_SelectObjectID(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() // prepare parent := testutil.GenerateObjectWithCID(cnr) regular := testutil.GenerateObjectWithCID(cnr) idParent, _ := parent.ID() regular.SetParentID(idParent) regular.SetParent(parent) err := putBig(db, regular) require.NoError(t, err) ts := testutil.GenerateObjectWithCID(cnr) ts.SetType(objectSDK.TypeTombstone) err = putBig(db, ts) require.NoError(t, err) lock := testutil.GenerateObjectWithCID(cnr) lock.SetType(objectSDK.TypeLock) err = putBig(db, lock) require.NoError(t, err) t.Run("not found objects", func(t *testing.T) { raw := testutil.GenerateObjectWithCID(cnr) id, _ := raw.ID() fs := objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id) testSelect(t, db, cnr, fs) fs = objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringNotEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("regular objects", func(t *testing.T) { id, _ := regular.ID() fs := objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(regular)) fs = objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringNotEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(parent), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("tombstone objects", func(t *testing.T) { id, _ := ts.ID() fs := objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(ts)) fs = objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringNotEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(lock), ) }) t.Run("parent objects", func(t *testing.T) { id, _ := parent.ID() fs := objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(parent)) fs = objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringNotEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("lock objects", func(t *testing.T) { id, _ := lock.ID() fs := objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(lock)) fs = objectSDK.SearchFilters{} fs.AddObjectIDFilter(objectSDK.MatchStringNotEqual, id) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(ts), ) }) } func TestDB_SelectOwnerID(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() // prepare parent := testutil.GenerateObjectWithCID(cnr) regular := testutil.GenerateObjectWithCID(cnr) idParent, _ := parent.ID() regular.SetParentID(idParent) regular.SetParent(parent) err := putBig(db, regular) require.NoError(t, err) ts := testutil.GenerateObjectWithCID(cnr) ts.SetType(objectSDK.TypeTombstone) err = putBig(db, ts) require.NoError(t, err) lock := testutil.GenerateObjectWithCID(cnr) lock.SetType(objectSDK.TypeLock) err = putBig(db, lock) require.NoError(t, err) t.Run("not found objects", func(t *testing.T) { raw := testutil.GenerateObjectWithCID(cnr) fs := objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringEqual, raw.OwnerID()) testSelect(t, db, cnr, fs) fs = objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringNotEqual, raw.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("regular objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringEqual, regular.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(regular)) fs = objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringNotEqual, regular.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(parent), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("tombstone objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringEqual, ts.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(ts)) fs = objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringNotEqual, ts.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(lock), ) }) t.Run("parent objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringEqual, parent.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(parent)) fs = objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringNotEqual, parent.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(ts), object.AddressOf(lock), ) }) t.Run("lock objects", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringEqual, lock.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(lock)) fs = objectSDK.SearchFilters{} fs.AddObjectOwnerIDFilter(objectSDK.MatchStringNotEqual, lock.OwnerID()) testSelect(t, db, cnr, fs, object.AddressOf(regular), object.AddressOf(parent), object.AddressOf(ts), ) }) } func TestDB_SelectECWithFastAndSlowFilters(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() ecChunk1 := oidtest.ID() ecChunk2 := oidtest.ID() ecParent := oidtest.ID() var ecParentAddr oid.Address ecParentAddr.SetContainer(cnr) ecParentAddr.SetObject(ecParent) var ecParentAttr []objectSDK.Attribute var attr objectSDK.Attribute attr.SetKey(objectSDK.AttributeFilePath) attr.SetValue("/1/2/3") ecParentAttr = append(ecParentAttr, attr) chunkObj := testutil.GenerateObjectWithCID(cnr) chunkObj.SetID(ecChunk1) chunkObj.SetPayload([]byte{0, 1, 2, 3, 4}) chunkObj.SetPayloadSize(uint64(5)) chunkObj.SetECHeader(objectSDK.NewECHeader(objectSDK.ECParentInfo{ID: ecParent, Attributes: ecParentAttr}, 0, 3, []byte{}, 0)) chunkObj2 := testutil.GenerateObjectWithCID(cnr) chunkObj2.SetID(ecChunk2) chunkObj2.SetPayload([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) chunkObj2.SetPayloadSize(uint64(10)) chunkObj2.SetECHeader(objectSDK.NewECHeader(objectSDK.ECParentInfo{ID: ecParent, Attributes: ecParentAttr}, 1, 3, []byte{}, 0)) // put object with EC var prm meta.PutPrm prm.SetObject(chunkObj) _, err := db.Put(context.Background(), prm) require.NoError(t, err) prm.SetObject(chunkObj2) _, err = db.Put(context.Background(), prm) require.NoError(t, err) fs := objectSDK.SearchFilters{} fs.AddRootFilter() fs.AddFilter(objectSDK.AttributeFilePath, "/1/2/3", objectSDK.MatchCommonPrefix) testSelect(t, db, cnr, fs, ecParentAddr) } type testTarget struct { objects []*objectSDK.Object } func (tt *testTarget) WriteObject(_ context.Context, obj *objectSDK.Object) error { tt.objects = append(tt.objects, obj) return nil } func cutObject(t *testing.T, p transformer.ChunkedObjectWriter, hdr *objectSDK.Object, size int) *transformer.AccessIdentifiers { ctx := context.Background() require.NoError(t, p.WriteHeader(ctx, hdr)) payload := make([]byte, size) rand.New(rand.NewSource(0)).Read(payload) _, err := p.Write(ctx, payload) require.NoError(t, err) ids, err := p.Close(ctx) require.NoError(t, err) return ids } func TestDB_RawHead_SplitInfo(t *testing.T) { t.Parallel() const ( partSize = 10 partCount = 2 dataCount = 2 parityCount = 1 ) db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() pk, err := keys.NewPrivateKey() require.NoError(t, err) tt := new(testTarget) p := transformer.NewPayloadSizeLimiter(transformer.Params{ Key: &pk.PrivateKey, NextTargetInit: func() transformer.ObjectWriter { return tt }, NetworkState: epochState{e: 1}, MaxSize: partSize, }) hdr := objectSDK.New() hdr.SetContainerID(cnr) hdr.SetOwnerID(usertest.ID()) ids := cutObject(t, p, hdr, partSize*partCount) require.Equal(t, len(tt.objects), partCount+1) t.Run("rep", func(t *testing.T) { testGetRawSplitInfo(t, cnr, ids, tt.objects[partCount], tt.objects[partCount-1]) }) t.Run("with ec", func(t *testing.T) { ec, err := erasurecode.NewConstructor(dataCount, parityCount) require.NoError(t, err) cs, err := ec.Split(tt.objects[partCount-1], &pk.PrivateKey) require.NoError(t, err) testGetRawSplitInfo(t, cnr, ids, tt.objects[partCount], cs[0]) }) } func testGetRawSplitInfo(t *testing.T, cnr cidSDK.ID, ids *transformer.AccessIdentifiers, linking, lastPart *objectSDK.Object) { expectedLinkID, ok := linking.ID() require.True(t, ok) t.Run("first last, then linking", func(t *testing.T) { db := newDB(t) defer func() { require.NoError(t, db.Close()) }() require.NoError(t, metaPut(db, lastPart, nil)) require.NoError(t, metaPut(db, linking, nil)) var addr oid.Address addr.SetContainer(cnr) addr.SetObject(*ids.ParentID) _, err := metaGet(db, addr, true) var siErr *objectSDK.SplitInfoError require.ErrorAs(t, err, &siErr) lastID, ok := siErr.SplitInfo().LastPart() require.True(t, ok) require.Equal(t, ids.SelfID, lastID) linkID, ok := siErr.SplitInfo().Link() require.True(t, ok) require.Equal(t, expectedLinkID, linkID) }) t.Run("first linking, then last", func(t *testing.T) { db := newDB(t) defer func() { require.NoError(t, db.Close()) }() require.NoError(t, metaPut(db, linking, nil)) require.NoError(t, metaPut(db, lastPart, nil)) var addr oid.Address addr.SetContainer(cnr) addr.SetObject(*ids.ParentID) _, err := metaGet(db, addr, true) var siErr *objectSDK.SplitInfoError require.ErrorAs(t, err, &siErr) lastID, ok := siErr.SplitInfo().LastPart() require.True(t, ok) require.Equal(t, ids.SelfID, lastID) linkID, ok := siErr.SplitInfo().Link() require.True(t, ok) require.Equal(t, expectedLinkID, linkID) }) t.Run("only last part", func(t *testing.T) { db := newDB(t) defer func() { require.NoError(t, db.Close()) }() require.NoError(t, metaPut(db, lastPart, nil)) var addr oid.Address addr.SetContainer(cnr) addr.SetObject(*ids.ParentID) _, err := metaGet(db, addr, true) var siErr *objectSDK.SplitInfoError require.ErrorAs(t, err, &siErr) lastPart, ok := siErr.SplitInfo().LastPart() require.True(t, ok) require.Equal(t, ids.SelfID, lastPart) }) } func TestDB_SelectSplitID_EC(t *testing.T) { t.Parallel() const ( partSize = 10 partCount = 2 dataCount = 2 parityCount = 1 ) db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() pk, err := keys.NewPrivateKey() require.NoError(t, err) tt := new(testTarget) p := transformer.NewPayloadSizeLimiter(transformer.Params{ Key: &pk.PrivateKey, NextTargetInit: func() transformer.ObjectWriter { return tt }, NetworkState: epochState{e: 1}, MaxSize: partSize, }) hdr := objectSDK.New() hdr.SetContainerID(cnr) hdr.SetOwnerID(usertest.ID()) cutObject(t, p, hdr, partSize*partCount) require.Equal(t, len(tt.objects), partCount+1) split := tt.objects[0].SplitID() require.NotNil(t, split) ec, err := erasurecode.NewConstructor(dataCount, parityCount) require.NoError(t, err) for i := range partCount { cs, err := ec.Split(tt.objects[i], &pk.PrivateKey) require.NoError(t, err) require.NoError(t, putBig(db, cs[0])) } t.Run("not present", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("split id", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, split.String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(tt.objects[0]), object.AddressOf(tt.objects[1]), ) }) t.Run("empty split", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, "", objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs) }) t.Run("unknown split id", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, objectSDK.NewSplitID().String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs) }) } func TestDB_SelectSplitID(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() child1 := testutil.GenerateObjectWithCID(cnr) child2 := testutil.GenerateObjectWithCID(cnr) child3 := testutil.GenerateObjectWithCID(cnr) split1 := objectSDK.NewSplitID() split2 := objectSDK.NewSplitID() child1.SetSplitID(split1) child2.SetSplitID(split1) child3.SetSplitID(split2) require.NoError(t, putBig(db, child1)) require.NoError(t, putBig(db, child2)) require.NoError(t, putBig(db, child3)) t.Run("not present", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, "", objectSDK.MatchNotPresent) testSelect(t, db, cnr, fs) }) t.Run("split id", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, split1.String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(child1), object.AddressOf(child2), ) fs = objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, split2.String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs, object.AddressOf(child3)) }) t.Run("empty split", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, "", objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs) }) t.Run("unknown split id", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddFilter(v2object.FilterHeaderSplitID, objectSDK.NewSplitID().String(), objectSDK.MatchStringEqual) testSelect(t, db, cnr, fs) }) } func TestDB_SelectContainerID(t *testing.T) { t.Parallel() db := newDB(t) defer func() { require.NoError(t, db.Close()) }() cnr := cidtest.ID() obj1 := testutil.GenerateObjectWithCID(cnr) err := putBig(db, obj1) require.NoError(t, err) obj2 := testutil.GenerateObjectWithCID(cnr) err = putBig(db, obj2) require.NoError(t, err) t.Run("same cid", func(t *testing.T) { fs := objectSDK.SearchFilters{} fs.AddObjectContainerIDFilter(objectSDK.MatchStringEqual, cnr) testSelect(t, db, cnr, fs, object.AddressOf(obj1), object.AddressOf(obj2), ) fs = objectSDK.SearchFilters{} fs.AddObjectContainerIDFilter(objectSDK.MatchStringNotEqual, cnr) testSelect(t, db, cnr, fs, object.AddressOf(obj1), object.AddressOf(obj2), ) fs = objectSDK.SearchFilters{} fs.AddObjectContainerIDFilter(objectSDK.MatchNotPresent, cnr) testSelect(t, db, cnr, fs) }) t.Run("not same cid", func(t *testing.T) { newCnr := cidtest.ID() fs := objectSDK.SearchFilters{} fs.AddObjectContainerIDFilter(objectSDK.MatchStringEqual, newCnr) testSelect(t, db, cnr, fs) }) } func BenchmarkSelect(b *testing.B) { const objCount = 1000 db := newDB(b) defer func() { require.NoError(b, db.Close()) }() cid := cidtest.ID() for i := range objCount { var attr objectSDK.Attribute attr.SetKey("myHeader") attr.SetValue(strconv.Itoa(i)) obj := testutil.GenerateObjectWithCID(cid) obj.SetAttributes(attr) require.NoError(b, metaPut(db, obj, nil)) } b.Run("string equal", func(b *testing.B) { fs := objectSDK.SearchFilters{} fs.AddFilter("myHeader", strconv.Itoa(objCount/2), objectSDK.MatchStringEqual) benchmarkSelect(b, db, cid, fs, 1) }) b.Run("string not equal", func(b *testing.B) { fs := objectSDK.SearchFilters{} fs.AddFilter("myHeader", strconv.Itoa(objCount/2), objectSDK.MatchStringNotEqual) benchmarkSelect(b, db, cid, fs, objCount-1) }) b.Run("common prefix", func(b *testing.B) { prefix := "99" n := 1 /* 99 */ + 10 /* 990..999 */ fs := objectSDK.SearchFilters{} fs.AddFilter("myHeader", prefix, objectSDK.MatchCommonPrefix) benchmarkSelect(b, db, cid, fs, n) }) b.Run("unknown", func(b *testing.B) { fs := objectSDK.SearchFilters{} fs.AddFilter("myHeader", strconv.Itoa(objCount/2), objectSDK.MatchUnknown) benchmarkSelect(b, db, cid, fs, 0) }) } func TestExpiredObjects(t *testing.T) { t.Parallel() db := newDB(t, meta.WithEpochState(epochState{currEpoch})) defer func() { require.NoError(t, db.Close()) }() checkExpiredObjects(t, db, func(exp, nonExp *objectSDK.Object) { cidExp, _ := exp.ContainerID() cidNonExp, _ := nonExp.ContainerID() objs, err := metaSelect(db, cidExp, objectSDK.SearchFilters{}, false) require.NoError(t, err) require.Empty(t, objs) // expired object should not be returned objs, err = metaSelect(db, cidNonExp, objectSDK.SearchFilters{}, false) require.NoError(t, err) require.NotEmpty(t, objs) }) } func benchmarkSelect(b *testing.B, db *meta.DB, cid cidSDK.ID, fs objectSDK.SearchFilters, expected int) { var prm meta.SelectPrm prm.SetContainerID(cid) prm.SetFilters(fs) for range b.N { res, err := db.Select(context.Background(), prm) if err != nil { b.Fatal(err) } if len(res.AddressList()) != expected { b.Fatalf("expected %d items, got %d", expected, len(res.AddressList())) } } } func metaSelect(db *meta.DB, cnr cidSDK.ID, fs objectSDK.SearchFilters, useAttributeIndex bool) ([]oid.Address, error) { var prm meta.SelectPrm prm.SetFilters(fs) prm.SetContainerID(cnr) prm.SetUseAttributeIndex(useAttributeIndex) res, err := db.Select(context.Background(), prm) return res.AddressList(), err }