From ae07fc0147df33c4af9033b104822de2ad971d71 Mon Sep 17 00:00:00 2001 From: Thomas E Lackey Date: Tue, 19 Dec 2023 06:55:11 +0000 Subject: [PATCH] 129: Index multivalued attributes. (#128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes #129, by indexing each value of a multivalued attribute. This handles at least the most common use case, so that we can search on a single value of the attribute. ``` ❯ laconic -c ~/.laconic/local.yml cns record list --all --type ApplicationDeploymentRequest --tags b [ { "id": "bafyreidrp4pylixp44rkxu5il72qhwwc4ir5ctdnssps5rnelstloxivwm", "names": null, "owners": [ "FCCE01FCC2472AEDBCF33902907F33262445AC2C" ], "bondId": "4ef470a9207f00fc07663623d092a14c310794b616eb53b085cfe6976e82f56d", "createTime": "2023-12-18T22:13:23Z", "expiryTime": "2024-12-17T22:13:23Z", "attributes": { "type": "ApplicationDeploymentRequest", "version": "1.0.6", "application": "crn://cerc-io/applications/test-progressive-web-app@0.1.1", "config": { "env": { "CERC_WEBAPP_DEBUG": "57588a9d" } }, "tags": [ "a", "b", "c" ] } } ] ``` Reviewed-on: https://git.vdb.to/cerc-io/laconicd/pulls/128 Co-authored-by: Thomas E Lackey Co-committed-by: Thomas E Lackey --- .../examples/general_record_example.yml | 7 ++ x/registry/keeper/grpc_query_test.go | 64 +++++++++++++++++-- x/registry/keeper/keeper.go | 18 +++++- 3 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 x/registry/helpers/examples/general_record_example.yml diff --git a/x/registry/helpers/examples/general_record_example.yml b/x/registry/helpers/examples/general_record_example.yml new file mode 100644 index 00000000..5b3ef3b5 --- /dev/null +++ b/x/registry/helpers/examples/general_record_example.yml @@ -0,0 +1,7 @@ +record: + type: GeneralRecord + name: foo + version: 1.0.0 + tags: + - tagA + - tagB diff --git a/x/registry/keeper/grpc_query_test.go b/x/registry/keeper/grpc_query_test.go index 6d5073de..61a3a8be 100644 --- a/x/registry/keeper/grpc_query_test.go +++ b/x/registry/keeper/grpc_query_test.go @@ -3,12 +3,12 @@ package keeper_test import ( "context" "fmt" - "os" - "github.com/cerc-io/laconicd/x/registry/client/cli" "github.com/cerc-io/laconicd/x/registry/helpers" "github.com/cerc-io/laconicd/x/registry/keeper" registrytypes "github.com/cerc-io/laconicd/x/registry/types" + "os" + "reflect" ) func (suite *KeeperTestSuite) TestGrpcQueryParams() { @@ -39,6 +39,7 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() { examples := []string{ "/../helpers/examples/service_provider_example.yml", "/../helpers/examples/website_registration_example.yml", + "/../helpers/examples/general_record_example.yml", } testCases := []struct { msg string @@ -59,7 +60,7 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() { ®istrytypes.QueryListRecordsRequest{}, true, false, - 2, + 3, }, { "Filter with type", @@ -79,6 +80,42 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() { false, 1, }, + { + "Filter with tag (extant) (https://git.vdb.to/cerc-io/laconicd/issues/129)", + ®istrytypes.QueryListRecordsRequest{ + Attributes: []*registrytypes.QueryListRecordsRequest_KeyValueInput{ + { + Key: "tags", + Value: ®istrytypes.QueryListRecordsRequest_ValueInput{ + Type: "string", + String_: "tagA", + }, + }, + }, + All: true, + }, + true, + false, + 1, + }, + { + "Filter with tag (non-existent) (https://git.vdb.to/cerc-io/laconicd/issues/129)", + ®istrytypes.QueryListRecordsRequest{ + Attributes: []*registrytypes.QueryListRecordsRequest_KeyValueInput{ + { + Key: "tags", + Value: ®istrytypes.QueryListRecordsRequest_ValueInput{ + Type: "string", + String_: "NOEXIST", + }, + }, + }, + All: true, + }, + true, + false, + 0, + }, { "Filter test for key collision (https://git.vdb.to/cerc-io/laconicd/issues/122)", ®istrytypes.QueryListRecordsRequest{ @@ -151,10 +188,25 @@ func (suite *KeeperTestSuite) TestGrpcGetRecordLists() { sr.NoError(err) recAttr := helpers.UnMarshalMapFromJSONBytes(bz) for _, attr := range test.req.GetAttributes() { - if attr.Key[:4] == "x500" { - sr.Equal(keeper.GetAttributeValue(attr.Value), recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) + av := keeper.GetAttributeValue(attr.Value) + if nil != av && nil != recAttr[attr.Key] && + reflect.Slice == reflect.TypeOf(recAttr[attr.Key]).Kind() && + reflect.Slice != reflect.TypeOf(av).Kind() { + found := false + allValues := recAttr[attr.Key].([]interface{}) + for i := range allValues { + if av == allValues[i] { + fmt.Printf("Found %s in %s", allValues[i], recAttr[attr.Key]) + found = true + } + } + sr.Equal(true, found, fmt.Sprintf("Unable to find %s in %s", av, recAttr[attr.Key])) } else { - sr.Equal(keeper.GetAttributeValue(attr.Value), recAttr[attr.Key]) + if attr.Key[:4] == "x500" { + sr.Equal(av, recAttr["x500"].(map[string]interface{})[attr.Key[4:]]) + } else { + sr.Equal(av, recAttr[attr.Key]) + } } } } diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index e252e0ec..72231073 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "reflect" "sort" "time" @@ -349,9 +350,20 @@ func (k Keeper) ProcessAttributes(ctx sdk.Context, record types.RecordType) erro { // #nosec G705 for key := range record.Attributes { - indexKey := GetAttributesIndexKey(key, record.Attributes[key]) - if err := k.SetAttributeMapping(ctx, indexKey, record.ID); err != nil { - return err + attr := record.Attributes[key] + if reflect.Slice == reflect.TypeOf(attr).Kind() { + av := attr.([]interface{}) + for i := range av { + indexKey := GetAttributesIndexKey(key, av[i]) + if err := k.SetAttributeMapping(ctx, indexKey, record.ID); err != nil { + return err + } + } + } else { + indexKey := GetAttributesIndexKey(key, attr) + if err := k.SetAttributeMapping(ctx, indexKey, record.ID); err != nil { + return err + } } } }