diff --git a/pkg/local_object_storage/metabase/metadata.go b/pkg/local_object_storage/metabase/metadata.go index a6db08ad43..e4e4c6bee1 100644 --- a/pkg/local_object_storage/metabase/metadata.go +++ b/pkg/local_object_storage/metabase/metadata.go @@ -30,8 +30,8 @@ var ( maxUint256Neg = new(big.Int).Neg(maxUint256) ) -// TODO: fill on Init -// TODO: system attributes +// TODO: fill on migration +// TODO: sort integers in buckets naturally func putMetadata(tx *bbolt.Tx, cnr cid.ID, id oid.ID, ver version.Version, owner user.ID, typ object.Type, creationEpoch uint64, payloadLen uint64, pldHash, pldHmmHash, splitID []byte, parentID, firstID oid.ID, attrs []object.Attribute) error { mb, err := tx.CreateBucketIfNotExists(metaBucketKey(cnr)) @@ -86,7 +86,6 @@ func putMetadata(tx *bbolt.Tx, cnr cid.ID, id oid.ID, ver version.Version, owner return nil } putInt := func(attr string, n *big.Int) error { - // TODO: check uint256 overflow kLn, vOff := makeKeyB(metaPrefixBI, attr, 1+32) // sign + 256-bit. Sign makes some sensible order if n.Sign() >= 0 { k[vOff] = 1 diff --git a/pkg/local_object_storage/metabase/metadata_test.go b/pkg/local_object_storage/metabase/metadata_test.go index 3603b28208..1b76b8d972 100644 --- a/pkg/local_object_storage/metabase/metadata_test.go +++ b/pkg/local_object_storage/metabase/metadata_test.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "math/rand" "slices" + "strconv" "testing" "github.com/nspcc-dev/neofs-sdk-go/checksum" @@ -20,6 +21,12 @@ import ( "go.etcd.io/bbolt" ) +func sortObjectIDs(ids []oid.ID) []oid.ID { + s := slices.Clone(ids) + slices.SortFunc(s, func(a, b oid.ID) int { return bytes.Compare(a[:], b[:]) }) + return s +} + func assertAttrPrefixed[T string | []byte](t testing.TB, mb *bbolt.Bucket, id oid.ID, prefix byte, attr string, val T) { k := []byte{prefix} k = append(k, attr...) @@ -138,6 +145,10 @@ func TestPutMetadata(t *testing.T) { require.NoError(t, err) } +func appendAttribute(obj *object.Object, k, v string) { + obj.SetAttributes(append(obj.Attributes(), *object.NewAttribute(k, v))...) +} + func TestDB_SearchObjects(t *testing.T) { db := newDB(t) t.Run("invalid input", func(t *testing.T) { @@ -191,8 +202,7 @@ func TestDB_SearchObjects(t *testing.T) { require.NoError(t, err, i) } - idsSorted := slices.Clone(ids) - slices.SortFunc(idsSorted, func(a, b oid.ID) int { return bytes.Compare(a[:], b[:]) }) + idsSorted := sortObjectIDs(ids) t.Run("all at once", func(t *testing.T) { for _, tc := range []struct { @@ -259,4 +269,497 @@ func TestDB_SearchObjects(t *testing.T) { require.EqualError(t, err, "view BoltDB: unexpected object key len 31, expected 33") }) }) + t.Run("filters", func(t *testing.T) { + // this test is focused on correct filters' application only, so only sorting by + // IDs is checked + const nRoot = 2 + const nPhy = 2 * nRoot + const nAll = nRoot + nPhy + all := []uint{0, 1, 2, 3, 4, 5, 6} + group1 := []uint{0, 2, 4} + group2 := []uint{1, 3, 5} + ids := [nAll]oid.ID{ + // RSYscGLzKw1nkeVRGpowYTGgtgodXJrMyyiHTGGJW3S + {6, 66, 212, 15, 99, 92, 193, 89, 165, 111, 36, 160, 35, 150, 126, 177, 208, 51, 229, 148, 1, 245, 188, 147, 68, 92, 227, 128, 184, 49, 150, 25}, + // 6dMvfyLF7HZ1WsBRgrLUDZP4pLkvNRjB6HWGeNXP4fJp + {83, 155, 1, 16, 139, 16, 27, 84, 238, 110, 215, 181, 245, 231, 129, 220, 192, 80, 168, 236, 35, 215, 29, 238, 133, 31, 176, 13, 250, 67, 126, 185}, + // 6hkrsFBPpAKTAKHeC5gycCZsz2BQdKtAn9ADriNdWf4E + {84, 187, 66, 103, 55, 176, 48, 220, 171, 101, 83, 187, 75, 89, 244, 128, 14, 43, 160, 118, 226, 60, 180, 113, 95, 41, 15, 27, 151, 143, 183, 187}, + // BQY3VShN1BmU6XDKiQaDo2tk7s7rkYuaGeVgmcHcWsRY + {154, 156, 84, 7, 36, 243, 19, 205, 118, 179, 244, 56, 251, 80, 184, 244, 97, 142, 113, 120, 167, 50, 111, 94, 219, 78, 151, 180, 89, 102, 52, 15}, + // DsKLie7U2BVph5XkZttG8EERxt9DFQXkrowr6LFkxp8h + {191, 48, 5, 72, 64, 44, 163, 71, 127, 144, 18, 30, 134, 67, 189, 210, 243, 2, 101, 225, 63, 47, 174, 128, 41, 238, 107, 14, 87, 136, 50, 162}, + // Gv9XcEW7KREB8cnjFbW8HBdJesMnbNKknfGdBNsVtQmB + {236, 124, 186, 165, 234, 207, 5, 237, 62, 82, 41, 15, 133, 132, 132, 73, 55, 16, 69, 101, 214, 174, 160, 228, 101, 161, 18, 204, 241, 208, 155, 118}, + } + // HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23 + cnr := cid.ID{245, 188, 86, 80, 170, 97, 147, 48, 75, 27, 115, 238, 61, 151, 182, 191, 95, 33, 160, 253, 239, 70, 174, 188, 220, 84, 57, 222, 9, 104, 4, 48} + // cnrStr := "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23" + owners := [nRoot]user.ID{ + // NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj + {53, 220, 52, 178, 96, 0, 121, 121, 217, 160, 223, 119, 75, 71, 2, 233, 33, 138, 241, 182, 208, 164, 240, 222, 30}, + // NiUWeE8gb8njJmymdZTh229ojGeJ24WHSm + {53, 247, 122, 86, 36, 254, 120, 76, 10, 73, 62, 4, 132, 174, 224, 77, 32, 37, 224, 73, 102, 37, 121, 117, 46}, + } + checksums := [nAll][32]byte{ + // 8a61b9ff3de0983ed7ad7aa21db22ff91e5a2a07128cd45e3646282f90e4efd7 + {138, 97, 185, 255, 61, 224, 152, 62, 215, 173, 122, 162, 29, 178, 47, 249, 30, 90, 42, 7, 18, 140, 212, 94, 54, 70, 40, 47, 144, 228, 239, 215}, + // d501baff2dec96b7dec7d634e5ec13ed8be33048bfa4e8285a37dabc0537e677 + {213, 1, 186, 255, 45, 236, 150, 183, 222, 199, 214, 52, 229, 236, 19, 237, 139, 227, 48, 72, 191, 164, 232, 40, 90, 55, 218, 188, 5, 55, 230, 119}, + // 302b0610844a4da6874f566798018e9d79031a4cc8bf72357d8fc5413a54473e + {48, 43, 6, 16, 132, 74, 77, 166, 135, 79, 86, 103, 152, 1, 142, 157, 121, 3, 26, 76, 200, 191, 114, 53, 125, 143, 197, 65, 58, 84, 71, 62}, + // 9bcee80d024eb36a3dbb8e7948d1a9b672a82929950a85ccd350e31e34560672 + {155, 206, 232, 13, 2, 78, 179, 106, 61, 187, 142, 121, 72, 209, 169, 182, 114, 168, 41, 41, 149, 10, 133, 204, 211, 80, 227, 30, 52, 86, 6, 114}, + // 35d6c9f1aa664aa163f2ec0bffe48af0bd4e8bc640626c12759f187876007529 + {53, 214, 201, 241, 170, 102, 74, 161, 99, 242, 236, 11, 255, 228, 138, 240, 189, 78, 139, 198, 64, 98, 108, 18, 117, 159, 24, 120, 118, 0, 117, 41}, + // cc6c36b379e9a77a845a021498e2e92875131af404f825aa56bea91602785ef2 + {204, 108, 54, 179, 121, 233, 167, 122, 132, 90, 2, 20, 152, 226, 233, 40, 117, 19, 26, 244, 4, 248, 37, 170, 86, 190, 169, 22, 2, 120, 94, 242}, + } + hmmChecksums := [nAll][64]byte{ + // a73a37d54475df580b324d70f3d1ac922200af91f196dd9cb0f8f1cca5fefdf0cb3dbc4aaac639416e3fdd4c540e616e6b44ac6b56a3b194e8011925192a8be2 + {167, 58, 55, 213, 68, 117, 223, 88, 11, 50, 77, 112, 243, 209, 172, 146, 34, 0, 175, 145, 241, 150, 221, 156, 176, 248, 241, 204, 165, 254, 253, 240, 203, 61, 188, 74, 170, 198, 57, 65, 110, 63, 221, 76, 84, 14, 97, 110, 107, 68, 172, 107, 86, 163, 177, 148, 232, 1, 25, 37, 25, 42, 139, 226}, + // f72b6eb562c6dd5e69930ab51ca8a98b13bfa18013cd89df3254dbc615f86b8f8c042649fe76e01f54bea7216957fe6716ec0a33d6b6de25ec15a53f295196d1 + {247, 43, 110, 181, 98, 198, 221, 94, 105, 147, 10, 181, 28, 168, 169, 139, 19, 191, 161, 128, 19, 205, 137, 223, 50, 84, 219, 198, 21, 248, 107, 143, 140, 4, 38, 73, 254, 118, 224, 31, 84, 190, 167, 33, 105, 87, 254, 103, 22, 236, 10, 51, 214, 182, 222, 37, 236, 21, 165, 63, 41, 81, 150, 209}, + // 55a8577889ed275d15509b202b084fb7876c08408b8c61a1ba9ab26834f08c667ccde2acf55fcfc1755cb2a6f8316e1c6185bd48549b150767979cf76ede4b1c + {85, 168, 87, 120, 137, 237, 39, 93, 21, 80, 155, 32, 43, 8, 79, 183, 135, 108, 8, 64, 139, 140, 97, 161, 186, 154, 178, 104, 52, 240, 140, 102, 124, 205, 226, 172, 245, 95, 207, 193, 117, 92, 178, 166, 248, 49, 110, 28, 97, 133, 189, 72, 84, 155, 21, 7, 103, 151, 156, 247, 110, 222, 75, 28}, + // 4d97f1f4f17119efae4579ef916ca1535e68c4fa381c431ab4112cb5671ddb21e44dc78f02ae2b26c95d5f74bb5eb4350e00cdc5b270f60bf46deaafc1b84575 + {77, 151, 241, 244, 241, 113, 25, 239, 174, 69, 121, 239, 145, 108, 161, 83, 94, 104, 196, 250, 56, 28, 67, 26, 180, 17, 44, 181, 103, 29, 219, 33, 228, 77, 199, 143, 2, 174, 43, 38, 201, 93, 95, 116, 187, 94, 180, 53, 14, 0, 205, 197, 178, 112, 246, 11, 244, 109, 234, 175, 193, 184, 69, 117}, + // 80089235980bfbf6c01a93c4f507b2f1ff2ec8b0c29cfe6970ce95cbeb1739bef6a43626783d58f56c224cfb606c360301f632a198db63f599fca7be2e0c2566 + {128, 8, 146, 53, 152, 11, 251, 246, 192, 26, 147, 196, 245, 7, 178, 241, 255, 46, 200, 176, 194, 156, 254, 105, 112, 206, 149, 203, 235, 23, 57, 190, 246, 164, 54, 38, 120, 61, 88, 245, 108, 34, 76, 251, 96, 108, 54, 3, 1, 246, 50, 161, 152, 219, 99, 245, 153, 252, 167, 190, 46, 12, 37, 102}, + // f3b6eedc3f30b99309582a7e0ca09dd6a9234ce95bfa578ddfa6ef2a0fe9c56e8f6a86c82ce565d9216c02110c0fe44079a68275243ad2f9be6bf7dacdeed97c + {243, 182, 238, 220, 63, 48, 185, 147, 9, 88, 42, 126, 12, 160, 157, 214, 169, 35, 76, 233, 91, 250, 87, 141, 223, 166, 239, 42, 15, 233, 197, 110, 143, 106, 134, 200, 44, 229, 101, 217, 33, 108, 2, 17, 12, 15, 228, 64, 121, 166, 130, 117, 36, 58, 210, 249, 190, 107, 247, 218, 205, 238, 217, 124}, + } + groupAttrs := [nRoot]object.Attribute{ + *object.NewAttribute("group_attr_1", "group_val_1"), + *object.NewAttribute("group_attr_2", "group_val_2"), + } + types := [nRoot]object.Type{object.TypeRegular, object.TypeStorageGroup} + splitIDs := [nRoot][]byte{ + // 8b69e76d-5e95-4639-8213-46786c41ab73 + {139, 105, 231, 109, 94, 149, 70, 57, 130, 19, 70, 120, 108, 65, 171, 115}, + // 60c6b1ff-5e6d-4c0f-8699-15d54bf8a2e1 + {96, 198, 177, 255, 94, 109, 76, 15, 134, 153, 21, 213, 75, 248, 162, 225}, + } + firstIDs := [nRoot]oid.ID{ + // 61hnJaKip8c1QxvC2iT4Txfpxf37QBNRaw1XCeq72DbC + {74, 120, 139, 195, 149, 106, 19, 73, 151, 116, 227, 3, 83, 169, 108, 129, 20, 206, 146, 192, 140, 2, 85, 14, 244, 109, 247, 28, 51, 101, 212, 183}, + // Cdf8vnK5xTxmkdc1GcjkxaEQFtEmwHPRky4KRQik6rQH + {172, 212, 150, 43, 17, 126, 75, 161, 99, 197, 238, 169, 62, 209, 96, 183, 79, 236, 237, 83, 141, 73, 125, 166, 186, 82, 68, 27, 147, 18, 24, 2}, + } + + initObj := func(obj *object.Object, nGlobal, nGroup int) { + ver := version.New(100+uint32(nGroup), 200+uint32(nGroup)) + obj.SetVersion(&ver) + obj.SetContainerID(cnr) + obj.SetID(ids[nGlobal]) + obj.SetType(types[nGroup]) + obj.SetOwnerID(&owners[nGroup]) + obj.SetCreationEpoch(10 + uint64(nGroup)) + obj.SetPayloadSize(20 + uint64(nGroup)) + obj.SetPayloadChecksum(checksum.NewSHA256(checksums[nGlobal])) + obj.SetPayloadHomomorphicHash(checksum.NewTillichZemor(hmmChecksums[nGlobal])) + si := strconv.Itoa(nGlobal) + obj.SetAttributes( + *object.NewAttribute("attr_common", "val_common"), + *object.NewAttribute("unique_attr_"+si, "unique_val_"+si), + groupAttrs[nGroup], + *object.NewAttribute("global_non_integer", "non an integer"), + ) + } + + var pars [nRoot]object.Object + for i := range nRoot { + initObj(&pars[i], i, i) + } + + var phys [nPhy]object.Object + for i := range phys { + nGroup := i % nRoot + initObj(&phys[i], nRoot+i, nGroup) + if nGroup == 1 { + phys[i].SetSplitID(object.NewSplitIDFromV2(splitIDs[nGroup])) + phys[i].SetFirstID(firstIDs[nGroup]) + phys[i].SetParent(&pars[nGroup]) + } + } + + appendAttribute(&pars[0], "attr_int", "-115792089237316195423570985008687907853269984665640564039457584007913129639935") + appendAttribute(&phys[0], "attr_int", "-18446744073709551615") + appendAttribute(&phys[1], "attr_int", "0") + appendAttribute(&phys[2], "attr_int", "18446744073709551615") + appendAttribute(&pars[1], "attr_int", "115792089237316195423570985008687907853269984665640564039457584007913129639935") + + for i := range phys { + require.NoError(t, db.Put(&phys[i], nil, nil)) + } + + check := func(k string, m object.SearchMatchType, v string, matchInds []uint) { + var fs object.SearchFilters + fs.AddFilter("attr_common", "val_common", object.MatchStringEqual) + fs.AddFilter(k, v, m) + + res, cursor, err := db.Search(cnr, fs, []string{"attr_common"}, "", nAll) + require.NoError(t, err) + require.Empty(t, cursor) + require.Len(t, res, len(matchInds)) + for i, ind := range matchInds { + require.Equal(t, ids[ind], res[i].ID) + require.Len(t, res[i].Attributes, 1) + } + } + + t.Run("all", func(t *testing.T) { + check("attr_common", object.MatchStringEqual, "val_common", all) + }) + t.Run("container", func(t *testing.T) { + // Since container ID is a selection parameter itself, only EQ and PREFIX with + // CID prefix match. For other prefixes, result is always empty, and it can be + // obtained without touching the BoltDB. + check("$Object:containerID", object.MatchStringEqual, "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23", all) + check("$Object:containerID", object.MatchStringEqual, "EiyzAP7fv1DwtBycvZXQRvLfuxy6GFZm8mErhyR2KhKy", nil) + check("$Object:containerID", object.MatchStringEqual, "", nil) + check("$Object:containerID", object.MatchStringNotEqual, "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23", nil) + check("$Object:containerID", object.MatchStringNotEqual, "EiyzAP7fv1DwtBycvZXQRvLfuxy6GFZm8mErhyR2KhKy", all) + check("$Object:containerID", object.MatchStringNotEqual, "", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check("$Object:containerID", m, "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23", nil) + } + check("$Object:containerID", object.MatchCommonPrefix, "", all) + check("$Object:containerID", object.MatchCommonPrefix, "H", all) + check("$Object:containerID", object.MatchCommonPrefix, "HYFTEXkzpDWkXU6anQByuSPv", all) + check("$Object:containerID", object.MatchCommonPrefix, "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg23", all) + check("$Object:containerID", object.MatchCommonPrefix, "HYFTEXkzpDWkXU6anQByuSPvV3imjzTKJBaAyD4VYg234", nil) + check("$Object:containerID", object.MatchCommonPrefix, "X", nil) + // TODO: also check that BoltDB is untouched on mismatch + }) + t.Run("user attributes", func(t *testing.T) { + // unique + for i := range all { + si := strconv.Itoa(i) + key := "unique_attr_" + si + val := "val_" + si + check(key, object.MatchStringEqual, val, []uint{uint(i)}) + check(key, object.MatchStringNotEqual, "other_val", []uint{uint(i)}) + for j := range val { + check(key, object.MatchCommonPrefix, val[:j], []uint{uint(i)}) + } + for _, op := range []object.SearchMatchType{ + object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(key, op, val, nil) + } + } + // group + const val1 = "group_val_1" + check("group_attr_1", object.MatchStringEqual, val1, group1) + check("group_attr_1", object.MatchStringNotEqual, val1, nil) + check("group_attr_1", object.MatchNotPresent, val1, group2) + for i := range val1 { + check("group_attr_1", object.MatchCommonPrefix, val1[:i], group1) + } + const val2 = "group_val_2" + check("group_attr_2", object.MatchStringEqual, val2, group2) + check("group_attr_2", object.MatchStringNotEqual, val2, nil) + check("group_attr_2", object.MatchNotPresent, val2, group1) + for i := range val1 { + check("group_attr_2", object.MatchCommonPrefix, val2[:i], group2) + } + for _, op := range []object.SearchMatchType{ + object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check("group_attr_1", op, val1, nil) + check("group_attr_2", op, val2, nil) + } + }) + t.Run("ROOT", func(t *testing.T) { + check("$Object:ROOT", 0, "", []uint{0, 1}) + for _, op := range []object.SearchMatchType{ + object.MatchStringEqual, object.MatchStringNotEqual, object.MatchNotPresent, object.MatchCommonPrefix, + object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check("$Object:ROOT", op, "", nil) + } + }) + t.Run("PHY", func(t *testing.T) { + check("$Object;PHY", 0, "", []uint{0, 1, 2, 3}) + for _, op := range []object.SearchMatchType{ + object.MatchStringEqual, object.MatchStringNotEqual, object.MatchNotPresent, object.MatchCommonPrefix, + object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check("$Object:PHY", op, "", nil) + } + }) + t.Run("ID", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:objectID", m, v, matchInds) + } + check(object.MatchStringEqual, "RSYscGLzKw1nkeVRGpowYTGgtgodXJrMyyiHTGGJW3S", []uint{0}) + check(object.MatchStringEqual, "6dMvfyLF7HZ1WsBRgrLUDZP4pLkvNRjB6HWGeNXP4fJp", []uint{1}) + check(object.MatchStringEqual, "6hkrsFBPpAKTAKHeC5gycCZsz2BQdKtAn9ADriNdWf4E", []uint{2}) + check(object.MatchStringEqual, "BQY3VShN1BmU6XDKiQaDo2tk7s7rkYuaGeVgmcHcWsRY", []uint{3}) + check(object.MatchStringEqual, "DsKLie7U2BVph5XkZttG8EERxt9DFQXkrowr6LFkxp8h", []uint{4}) + check(object.MatchStringEqual, "Gv9XcEW7KREB8cnjFbW8HBdJesMnbNKknfGdBNsVtQmB", []uint{5}) + check(object.MatchStringNotEqual, "RSYscGLzKw1nkeVRGpowYTGgtgodXJrMyyiHTGGJW3S", []uint{1, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "6dMvfyLF7HZ1WsBRgrLUDZP4pLkvNRjB6HWGeNXP4fJp", []uint{0, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "6hkrsFBPpAKTAKHeC5gycCZsz2BQdKtAn9ADriNdWf4E", []uint{0, 1, 3, 4, 5}) + check(object.MatchStringNotEqual, "BQY3VShN1BmU6XDKiQaDo2tk7s7rkYuaGeVgmcHcWsRY", []uint{0, 1, 2, 4, 5}) + check(object.MatchStringNotEqual, "DsKLie7U2BVph5XkZttG8EERxt9DFQXkrowr6LFkxp8h", []uint{0, 1, 2, 3, 5}) + check(object.MatchStringNotEqual, "Gv9XcEW7KREB8cnjFbW8HBdJesMnbNKknfGdBNsVtQmB", []uint{0, 1, 2, 3, 4}) + check(object.MatchStringEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", nil) // other + check(object.MatchStringNotEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "RSYscGLzKw1nkeVRGpowYTGgtgodXJrMyyiHTGGJW3S", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "R", []uint{0}) + check(object.MatchCommonPrefix, "6", []uint{1, 2}) + check(object.MatchCommonPrefix, "6h", []uint{2}) + check(object.MatchCommonPrefix, "6h1", nil) + check(object.MatchCommonPrefix, "6hkrsFBPpAKTAKHeC5gycCZsz2BQdKtAn9ADriNdWf4E", []uint{2}) + check(object.MatchCommonPrefix, "6hkrsFBPpAKTAKHeC5gycCZsz2BQdKtAn9ADriNdWf4E1", nil) + }) + t.Run("version", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:version", m, v, matchInds) + } + check(object.MatchStringEqual, "v100.200", group1) + check(object.MatchStringNotEqual, "v100.200", group2) + check(object.MatchStringEqual, "v101.201", group2) + check(object.MatchStringNotEqual, "v101.201", group1) + check(object.MatchStringEqual, "v102.202", nil) // other + check(object.MatchStringNotEqual, "v102.202", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "v100.200", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "v", all) + check(object.MatchCommonPrefix, "v1", all) + check(object.MatchCommonPrefix, "v10", all) + check(object.MatchCommonPrefix, "v100", group1) + check(object.MatchCommonPrefix, "v100.200", all) + check(object.MatchCommonPrefix, "v100.2001", nil) + check(object.MatchCommonPrefix, "v101", group2) + check(object.MatchCommonPrefix, "v101.201", group2) + check(object.MatchCommonPrefix, "v2", nil) + }) + t.Run("owner", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:owner", m, v, matchInds) + } + check(object.MatchStringEqual, "NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj", group1) + check(object.MatchStringNotEqual, "NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj", group2) + check(object.MatchStringEqual, "NiUWeE8gb8njJmymdZTh229ojGeJ24WHSm", group2) + check(object.MatchStringNotEqual, "NiUWeE8gb8njJmymdZTh229ojGeJ24WHSm", group1) + check(object.MatchStringEqual, "NhP5vErYP9WCfPjtCb78xqPV5MgHyhVNeL", nil) // other + check(object.MatchStringNotEqual, "NhP5vErYP9WCfPjtCb78xqPV5MgHyhVNeL", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "N", all) + check(object.MatchCommonPrefix, "Nf", group1) + check(object.MatchCommonPrefix, "NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj", group1) + check(object.MatchCommonPrefix, "NfzJyPrn1hRGuVJNvMYLTfWZGW2ZVR9Qmj1", nil) + }) + t.Run("type", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:objectType", m, v, matchInds) + } + check(object.MatchStringEqual, "REGULAR", group1) + check(object.MatchStringNotEqual, "REGULAR", group2) + check(object.MatchStringEqual, "STORAGE_GROUP", group2) + check(object.MatchStringNotEqual, "STORAGE_GROUP", group1) + check(object.MatchStringEqual, "STORAGE_GROUP", group2) + check(object.MatchStringEqual, "TOMBSTONE", nil) + check(object.MatchStringNotEqual, "TOMBSTONE", all) + check(object.MatchStringEqual, "0", nil) // numeric enum value + check(object.MatchStringEqual, "2", nil) + for _, op := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(op, "", nil) + check(op, "TOMBSTONE", nil) + check(op, "LOCK", nil) + check(op, "1", nil) + check(op, "3", nil) + } + for _, prefix := range []string{"", "T", "L"} { + check(object.MatchCommonPrefix, prefix, nil) + } + }) + t.Run("payload checksum", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:payloadHash", m, v, matchInds) + } + check(object.MatchStringEqual, "8a61b9ff3de0983ed7ad7aa21db22ff91e5a2a07128cd45e3646282f90e4efd7", []uint{0}) + check(object.MatchStringEqual, "d501baff2dec96b7dec7d634e5ec13ed8be33048bfa4e8285a37dabc0537e677", []uint{1}) + check(object.MatchStringEqual, "302b0610844a4da6874f566798018e9d79031a4cc8bf72357d8fc5413a54473e", []uint{2}) + check(object.MatchStringEqual, "9bcee80d024eb36a3dbb8e7948d1a9b672a82929950a85ccd350e31e34560672", []uint{3}) + check(object.MatchStringEqual, "35d6c9f1aa664aa163f2ec0bffe48af0bd4e8bc640626c12759f187876007529", []uint{4}) + check(object.MatchStringEqual, "cc6c36b379e9a77a845a021498e2e92875131af404f825aa56bea91602785ef2", []uint{5}) + check(object.MatchStringNotEqual, "8a61b9ff3de0983ed7ad7aa21db22ff91e5a2a07128cd45e3646282f90e4efd7", []uint{1, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "d501baff2dec96b7dec7d634e5ec13ed8be33048bfa4e8285a37dabc0537e677", []uint{0, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "302b0610844a4da6874f566798018e9d79031a4cc8bf72357d8fc5413a54473e", []uint{0, 1, 3, 4, 5}) + check(object.MatchStringNotEqual, "9bcee80d024eb36a3dbb8e7948d1a9b672a82929950a85ccd350e31e34560672", []uint{0, 1, 2, 4, 5}) + check(object.MatchStringNotEqual, "35d6c9f1aa664aa163f2ec0bffe48af0bd4e8bc640626c12759f187876007529", []uint{0, 1, 2, 3, 5}) + check(object.MatchStringNotEqual, "cc6c36b379e9a77a845a021498e2e92875131af404f825aa56bea91602785ef2", []uint{0, 1, 2, 3, 4}) + check(object.MatchStringEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", nil) // other + check(object.MatchStringNotEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "8a61b9ff3de0983ed7ad7aa21db22ff91e5a2a07128cd45e3646282f90e4efd7", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "8", []uint{0}) + check(object.MatchCommonPrefix, "8a61b9ff3de0983ed7ad7aa21db22ff91e5a2a07128cd45e3646282f90e4efd7", []uint{0}) + check(object.MatchCommonPrefix, "4", nil) + }) + t.Run("payload homomorphic checksum", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:homomorphicHash", m, v, matchInds) + } + check(object.MatchStringEqual, "a73a37d54475df580b324d70f3d1ac922200af91f196dd9cb0f8f1cca5fefdf0cb3dbc4aaac639416e3fdd4c540e616e6b44ac6b56a3b194e8011925192a8be2", []uint{0}) + check(object.MatchStringEqual, "f72b6eb562c6dd5e69930ab51ca8a98b13bfa18013cd89df3254dbc615f86b8f8c042649fe76e01f54bea7216957fe6716ec0a33d6b6de25ec15a53f295196d1", []uint{1}) + check(object.MatchStringEqual, "55a8577889ed275d15509b202b084fb7876c08408b8c61a1ba9ab26834f08c667ccde2acf55fcfc1755cb2a6f8316e1c6185bd48549b150767979cf76ede4b1c", []uint{2}) + check(object.MatchStringEqual, "4d97f1f4f17119efae4579ef916ca1535e68c4fa381c431ab4112cb5671ddb21e44dc78f02ae2b26c95d5f74bb5eb4350e00cdc5b270f60bf46deaafc1b84575", []uint{3}) + check(object.MatchStringEqual, "80089235980bfbf6c01a93c4f507b2f1ff2ec8b0c29cfe6970ce95cbeb1739bef6a43626783d58f56c224cfb606c360301f632a198db63f599fca7be2e0c2566", []uint{4}) + check(object.MatchStringEqual, "f3b6eedc3f30b99309582a7e0ca09dd6a9234ce95bfa578ddfa6ef2a0fe9c56e8f6a86c82ce565d9216c02110c0fe44079a68275243ad2f9be6bf7dacdeed97c", []uint{5}) + check(object.MatchStringNotEqual, "a73a37d54475df580b324d70f3d1ac922200af91f196dd9cb0f8f1cca5fefdf0cb3dbc4aaac639416e3fdd4c540e616e6b44ac6b56a3b194e8011925192a8be2", []uint{1, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "f72b6eb562c6dd5e69930ab51ca8a98b13bfa18013cd89df3254dbc615f86b8f8c042649fe76e01f54bea7216957fe6716ec0a33d6b6de25ec15a53f295196d1", []uint{0, 2, 3, 4, 5}) + check(object.MatchStringNotEqual, "55a8577889ed275d15509b202b084fb7876c08408b8c61a1ba9ab26834f08c667ccde2acf55fcfc1755cb2a6f8316e1c6185bd48549b150767979cf76ede4b1c", []uint{0, 1, 3, 4, 5}) + check(object.MatchStringNotEqual, "4d97f1f4f17119efae4579ef916ca1535e68c4fa381c431ab4112cb5671ddb21e44dc78f02ae2b26c95d5f74bb5eb4350e00cdc5b270f60bf46deaafc1b84575", []uint{0, 1, 2, 4, 5}) + check(object.MatchStringNotEqual, "80089235980bfbf6c01a93c4f507b2f1ff2ec8b0c29cfe6970ce95cbeb1739bef6a43626783d58f56c224cfb606c360301f632a198db63f599fca7be2e0c2566", []uint{0, 1, 2, 3, 5}) + check(object.MatchStringNotEqual, "f3b6eedc3f30b99309582a7e0ca09dd6a9234ce95bfa578ddfa6ef2a0fe9c56e8f6a86c82ce565d9216c02110c0fe44079a68275243ad2f9be6bf7dacdeed97c", []uint{0, 1, 2, 3, 4}) + check(object.MatchStringEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", nil) // other + check(object.MatchStringNotEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "a73a37d54475df580b324d70f3d1ac922200af91f196dd9cb0f8f1cca5fefdf0cb3dbc4aaac639416e3fdd4c540e616e6b44ac6b56a3b194e8011925192a8be2", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "a", []uint{0}) + check(object.MatchCommonPrefix, "f", []uint{1, 5}) + check(object.MatchCommonPrefix, "f3", []uint{5}) + check(object.MatchCommonPrefix, "f3b6eedc3f30b99309582a7e0ca09dd6a9234ce95bfa578ddfa6ef2a0fe9c56e8f6a86c82ce565d9216c02110c0fe44079a68275243ad2f9be6bf7dacdeed97c", []uint{5}) + check(object.MatchCommonPrefix, "f3b6eedc3f30b99309582a7e0ca09dd6a9234ce95bfa578ddfa6ef2a0fe9c56e8f6a86c82ce565d9216c02110c0fe44079a68275243ad2f9be6bf7dacdeed97c1", nil) + }) + t.Run("split ID", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:split.splitID", m, v, matchInds) + } + check(object.MatchStringEqual, "8b69e76d-5e95-4639-8213-46786c41ab73", group1) + check(object.MatchStringNotEqual, "8b69e76d-5e95-4639-8213-46786c41ab73", group2) + check(object.MatchStringEqual, "60c6b1ff-5e6d-4c0f-8699-15d54bf8a2e1", group2) + check(object.MatchStringNotEqual, "60c6b1ff-5e6d-4c0f-8699-15d54bf8a2e1", group1) + check(object.MatchStringEqual, "2a6346f2-97de-4c8d-91bf-20145cf302d6", nil) // other + check(object.MatchStringNotEqual, "2a6346f2-97de-4c8d-91bf-20145cf302d6", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "60c6b1ff-5e6d-4c0f-8699-15d54bf8a2e1", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "8", group1) + check(object.MatchCommonPrefix, "60", group2) + check(object.MatchCommonPrefix, "8b69e76d-5e95-4639-8213-46786c41ab73", group2) + check(object.MatchCommonPrefix, "8b69e76d-5e95-4639-8213-46786c41ab731", nil) + }) + t.Run("first ID", func(t *testing.T) { + check := func(m object.SearchMatchType, v string, matchInds []uint) { + check("$Object:split.first", m, v, matchInds) + } + check(object.MatchStringEqual, "61hnJaKip8c1QxvC2iT4Txfpxf37QBNRaw1XCeq72DbC", group1) + check(object.MatchStringEqual, "Cdf8vnK5xTxmkdc1GcjkxaEQFtEmwHPRky4KRQik6rQH", group2) + check(object.MatchStringNotEqual, "61hnJaKip8c1QxvC2iT4Txfpxf37QBNRaw1XCeq72DbC", group2) + check(object.MatchStringNotEqual, "Cdf8vnK5xTxmkdc1GcjkxaEQFtEmwHPRky4KRQik6rQH", group1) + check(object.MatchStringEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", nil) // other + check(object.MatchStringNotEqual, "Dfot9FnhkJy9m8pXrF1fL5fmKmbHK8wL8PqExoQFNTrz", all) + for _, m := range []object.SearchMatchType{ + object.MatchNotPresent, object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check(m, "61hnJaKip8c1QxvC2iT4Txfpxf37QBNRaw1XCeq72DbC", nil) + } + check(object.MatchCommonPrefix, "", all) + check(object.MatchCommonPrefix, "6", group1) + check(object.MatchCommonPrefix, "C", group2) + check(object.MatchCommonPrefix, "Cdf8vnK5xTxmkdc1GcjkxaEQFtEmwHPRky4KRQik6rQH", group2) + check(object.MatchCommonPrefix, "Cdf8vnK5xTxmkdc1GcjkxaEQFtEmwHPRky4KRQik6rQH1", nil) + }) + t.Run("integers", func(t *testing.T) { + for _, op := range []object.SearchMatchType{ + object.MatchNumGT, object.MatchNumGE, object.MatchNumLT, object.MatchNumLE, + } { + check("global_non_integer", op, "123", nil) + // TODO: also check that BoltDB is untouched in following cases + check("attr_common", op, "text", nil) + check("attr_common", op, "1.5", nil) + check("attr_common", op, "115792089237316195423570985008687907853269984665640564039457584007913129639936", nil) + check("attr_common", op, "-115792089237316195423570985008687907853269984665640564039457584007913129639936", nil) + } + check("attr_common", object.MatchNumLT, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + check("attr_common", object.MatchNumLT, "-18446744073709551615", []uint{0}) + check("attr_common", object.MatchNumLT, "0", []uint{0, 2}) + check("attr_common", object.MatchNumLT, "18446744073709551615", []uint{0, 2, 3}) + check("attr_common", object.MatchNumLT, "115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{0, 2, 3}) + check("attr_common", object.MatchNumLE, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{0}) + check("attr_common", object.MatchNumLE, "-18446744073709551615", []uint{0, 2}) + check("attr_common", object.MatchNumLE, "0", []uint{0, 2, 3}) + check("attr_common", object.MatchNumLE, "18446744073709551615", []uint{0, 2, 3, 4}) + check("attr_common", object.MatchNumLE, "115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{0, 1, 2, 3, 4}) + check("attr_common", object.MatchNumGT, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{1, 2, 3, 4}) + check("attr_common", object.MatchNumGT, "-18446744073709551615", []uint{1, 3, 4}) + check("attr_common", object.MatchNumGT, "0", []uint{1, 4}) + check("attr_common", object.MatchNumGT, "18446744073709551615", []uint{1}) + check("attr_common", object.MatchNumGT, "115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + check("attr_common", object.MatchNumGE, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{0, 1, 2, 3, 4}) + check("attr_common", object.MatchNumGE, "-18446744073709551615", []uint{1, 2, 3, 4}) + check("attr_common", object.MatchNumGE, "0", []uint{1, 3, 4}) + check("attr_common", object.MatchNumGE, "18446744073709551615", []uint{1, 2}) + check("attr_common", object.MatchNumGE, "115792089237316195423570985008687907853269984665640564039457584007913129639935", []uint{0}) + for _, tc := range []struct { + name, key string + val1, val2 string + }{ + {name: "creation epoch", key: "$Object:creationEpoch", val1: "10", val2: "11"}, + {name: "creation epoch", key: "$Object:payloadLength", val1: "20", val2: "21"}, + } { + t.Run(tc.name, func(t *testing.T) { + check(tc.key, object.MatchNumLT, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + check(tc.key, object.MatchNumLT, "0", nil) + check(tc.key, object.MatchNumLT, tc.val1, nil) + check(tc.key, object.MatchNumLT, tc.val2, group1) + check(tc.key, object.MatchNumLT, "18446744073709551615", all) + check(tc.key, object.MatchNumLT, "115792089237316195423570985008687907853269984665640564039457584007913129639935", all) + check(tc.key, object.MatchNumLE, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + check(tc.key, object.MatchNumLE, "0", nil) + check(tc.key, object.MatchNumLE, tc.val1, group1) + check(tc.key, object.MatchNumLE, tc.val2, all) + check(tc.key, object.MatchNumLE, "18446744073709551615", all) + check(tc.key, object.MatchNumLE, "115792089237316195423570985008687907853269984665640564039457584007913129639935", all) + check(tc.key, object.MatchNumGT, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", all) + check(tc.key, object.MatchNumGT, "0", all) + check(tc.key, object.MatchNumGT, tc.val1, group2) + check(tc.key, object.MatchNumGT, tc.val2, nil) + check(tc.key, object.MatchNumGT, "18446744073709551615", nil) + check(tc.key, object.MatchNumGT, "115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + check(tc.key, object.MatchNumGE, "-115792089237316195423570985008687907853269984665640564039457584007913129639935", all) + check(tc.key, object.MatchNumGE, "0", all) + check(tc.key, object.MatchNumGE, tc.val1, all) + check(tc.key, object.MatchNumGE, tc.val2, group2) + check(tc.key, object.MatchNumGE, "18446744073709551615", nil) + check(tc.key, object.MatchNumGE, "115792089237316195423570985008687907853269984665640564039457584007913129639935", nil) + }) + } + }) + }) }