Skip to content

Commit 81f439e

Browse files
author
Chris Elder
committed
FAB-2189 Scope rich queries to chaincode(QueryWrapper)
Note: This change depends on the following items: https://gerrit.hyperledger.org/r/5729 https://gerrit.hyperledger.org/r/5879 Motivation for this change: Need to inject chaincodeid filter on rich queries called from chaincode. - This change will add the QueryWrapper changes needed. - Add unit tests for query wrapper changes - Add unit tests for statedb query changes Change-Id: I18ccd28e5f3cb95141f2f2402af9d5d7bae30b1a Signed-off-by: Chris Elder <[email protected]>
1 parent 9da35a2 commit 81f439e

File tree

4 files changed

+341
-96
lines changed

4 files changed

+341
-96
lines changed

core/ledger/kvledger/txmgmt/statedb/commontests/test_common.go

+136-6
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,24 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
202202
jsonValue9 := "{\"asset_name\": \"marble9\",\"color\": \"green\",\"size\": 9,\"owner\": \"fred\"}"
203203
batch.Put("ns1", "key9", []byte(jsonValue9), version.NewHeight(1, 9))
204204
jsonValue10 := "{\"asset_name\": \"marble10\",\"color\": \"green\",\"size\": 10,\"owner\": \"mary\"}"
205-
batch.Put("ns1", "key10", []byte(jsonValue10), version.NewHeight(1, 12))
206-
savePoint := version.NewHeight(2, 12)
205+
batch.Put("ns1", "key10", []byte(jsonValue10), version.NewHeight(1, 10))
206+
207+
//add keys for a separate namespace
208+
batch.Put("ns2", "key1", []byte(jsonValue1), version.NewHeight(1, 11))
209+
batch.Put("ns2", "key2", []byte(jsonValue2), version.NewHeight(1, 12))
210+
batch.Put("ns2", "key3", []byte(jsonValue3), version.NewHeight(1, 13))
211+
batch.Put("ns2", "key4", []byte(jsonValue4), version.NewHeight(1, 14))
212+
batch.Put("ns2", "key5", []byte(jsonValue5), version.NewHeight(1, 15))
213+
batch.Put("ns2", "key6", []byte(jsonValue6), version.NewHeight(1, 16))
214+
batch.Put("ns2", "key7", []byte(jsonValue7), version.NewHeight(1, 17))
215+
batch.Put("ns2", "key8", []byte(jsonValue8), version.NewHeight(1, 18))
216+
batch.Put("ns2", "key9", []byte(jsonValue9), version.NewHeight(1, 19))
217+
batch.Put("ns2", "key10", []byte(jsonValue10), version.NewHeight(1, 20))
218+
219+
savePoint := version.NewHeight(2, 21)
207220
db.ApplyUpdates(batch, savePoint)
208221

209-
// query for owner=jerry
222+
// query for owner=jerry, use namespace "ns1"
210223
itr, err := db.ExecuteQuery("ns1", "{\"selector\":{\"owner\":\"jerry\"}}")
211224
testutil.AssertNoError(t, err, "")
212225

@@ -224,6 +237,33 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
224237
testutil.AssertNoError(t, err, "")
225238
testutil.AssertNil(t, queryResult2)
226239

240+
// query for owner=jerry, use namespace "ns2"
241+
itr, err = db.ExecuteQuery("ns2", "{\"selector\":{\"owner\":\"jerry\"}}")
242+
testutil.AssertNoError(t, err, "")
243+
244+
// verify one jerry result
245+
queryResult1, err = itr.Next()
246+
testutil.AssertNoError(t, err, "")
247+
testutil.AssertNotNil(t, queryResult1)
248+
versionedQueryRecord = queryResult1.(*statedb.VersionedQueryRecord)
249+
stringRecord = string(versionedQueryRecord.Record)
250+
bFoundRecord = strings.Contains(stringRecord, "jerry")
251+
testutil.AssertEquals(t, bFoundRecord, true)
252+
253+
// verify no more results
254+
queryResult2, err = itr.Next()
255+
testutil.AssertNoError(t, err, "")
256+
testutil.AssertNil(t, queryResult2)
257+
258+
// query for owner=jerry, use namespace "ns3"
259+
itr, err = db.ExecuteQuery("ns3", "{\"selector\":{\"owner\":\"jerry\"}}")
260+
testutil.AssertNoError(t, err, "")
261+
262+
// verify results - should be no records
263+
queryResult1, err = itr.Next()
264+
testutil.AssertNoError(t, err, "")
265+
testutil.AssertNil(t, queryResult1)
266+
227267
// query using bad query string
228268
itr, err = db.ExecuteQuery("ns1", "this is an invalid query string")
229269
testutil.AssertError(t, err, "Should have received an error for invalid query string")
@@ -237,7 +277,7 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
237277
testutil.AssertNoError(t, err, "")
238278
testutil.AssertNil(t, queryResult3)
239279

240-
// query with fields
280+
// query with fields, namespace "ns1"
241281
itr, err = db.ExecuteQuery("ns1", "{\"selector\":{\"owner\":\"jerry\"},\"fields\": [\"owner\", \"asset_name\", \"color\", \"size\"]}")
242282
testutil.AssertNoError(t, err, "")
243283

@@ -255,7 +295,34 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
255295
testutil.AssertNoError(t, err, "")
256296
testutil.AssertNil(t, queryResult2)
257297

258-
// query with complex selector
298+
// query with fields, namespace "ns2"
299+
itr, err = db.ExecuteQuery("ns2", "{\"selector\":{\"owner\":\"jerry\"},\"fields\": [\"owner\", \"asset_name\", \"color\", \"size\"]}")
300+
testutil.AssertNoError(t, err, "")
301+
302+
// verify one jerry result
303+
queryResult1, err = itr.Next()
304+
testutil.AssertNoError(t, err, "")
305+
testutil.AssertNotNil(t, queryResult1)
306+
versionedQueryRecord = queryResult1.(*statedb.VersionedQueryRecord)
307+
stringRecord = string(versionedQueryRecord.Record)
308+
bFoundRecord = strings.Contains(stringRecord, "jerry")
309+
testutil.AssertEquals(t, bFoundRecord, true)
310+
311+
// verify no more results
312+
queryResult2, err = itr.Next()
313+
testutil.AssertNoError(t, err, "")
314+
testutil.AssertNil(t, queryResult2)
315+
316+
// query with fields, namespace "ns3"
317+
itr, err = db.ExecuteQuery("ns3", "{\"selector\":{\"owner\":\"jerry\"},\"fields\": [\"owner\", \"asset_name\", \"color\", \"size\"]}")
318+
testutil.AssertNoError(t, err, "")
319+
320+
// verify no results
321+
queryResult1, err = itr.Next()
322+
testutil.AssertNoError(t, err, "")
323+
testutil.AssertNil(t, queryResult1)
324+
325+
// query with complex selector, namespace "ns1"
259326
itr, err = db.ExecuteQuery("ns1", "{\"selector\":{\"$and\":[{\"size\":{\"$gt\": 5}},{\"size\":{\"$lt\":8}},{\"$not\":{\"size\":6}}]}}")
260327
testutil.AssertNoError(t, err, "")
261328

@@ -273,7 +340,34 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
273340
testutil.AssertNoError(t, err, "")
274341
testutil.AssertNil(t, queryResult2)
275342

276-
// query with embedded implicit "AND" and explicit "OR"
343+
// query with complex selector, namespace "ns2"
344+
itr, err = db.ExecuteQuery("ns2", "{\"selector\":{\"$and\":[{\"size\":{\"$gt\": 5}},{\"size\":{\"$lt\":8}},{\"$not\":{\"size\":6}}]}}")
345+
testutil.AssertNoError(t, err, "")
346+
347+
// verify one fred result
348+
queryResult1, err = itr.Next()
349+
testutil.AssertNoError(t, err, "")
350+
testutil.AssertNotNil(t, queryResult1)
351+
versionedQueryRecord = queryResult1.(*statedb.VersionedQueryRecord)
352+
stringRecord = string(versionedQueryRecord.Record)
353+
bFoundRecord = strings.Contains(stringRecord, "fred")
354+
testutil.AssertEquals(t, bFoundRecord, true)
355+
356+
// verify no more results
357+
queryResult2, err = itr.Next()
358+
testutil.AssertNoError(t, err, "")
359+
testutil.AssertNil(t, queryResult2)
360+
361+
// query with complex selector, namespace "ns3"
362+
itr, err = db.ExecuteQuery("ns3", "{\"selector\":{\"$and\":[{\"size\":{\"$gt\": 5}},{\"size\":{\"$lt\":8}},{\"$not\":{\"size\":6}}]}}")
363+
testutil.AssertNoError(t, err, "")
364+
365+
// verify no more results
366+
queryResult1, err = itr.Next()
367+
testutil.AssertNoError(t, err, "")
368+
testutil.AssertNil(t, queryResult1)
369+
370+
// query with embedded implicit "AND" and explicit "OR", namespace "ns1"
277371
itr, err = db.ExecuteQuery("ns1", "{\"selector\":{\"color\":\"green\",\"$or\":[{\"owner\":\"fred\"},{\"owner\":\"mary\"}]}}")
278372
testutil.AssertNoError(t, err, "")
279373

@@ -300,4 +394,40 @@ func TestQuery(t *testing.T, dbProvider statedb.VersionedDBProvider) {
300394
testutil.AssertNoError(t, err, "")
301395
testutil.AssertNil(t, queryResult3)
302396

397+
// query with embedded implicit "AND" and explicit "OR", namespace "ns2"
398+
itr, err = db.ExecuteQuery("ns2", "{\"selector\":{\"color\":\"green\",\"$or\":[{\"owner\":\"fred\"},{\"owner\":\"mary\"}]}}")
399+
testutil.AssertNoError(t, err, "")
400+
401+
// verify one green result
402+
queryResult1, err = itr.Next()
403+
testutil.AssertNoError(t, err, "")
404+
testutil.AssertNotNil(t, queryResult1)
405+
versionedQueryRecord = queryResult1.(*statedb.VersionedQueryRecord)
406+
stringRecord = string(versionedQueryRecord.Record)
407+
bFoundRecord = strings.Contains(stringRecord, "green")
408+
testutil.AssertEquals(t, bFoundRecord, true)
409+
410+
// verify another green result
411+
queryResult2, err = itr.Next()
412+
testutil.AssertNoError(t, err, "")
413+
testutil.AssertNotNil(t, queryResult2)
414+
versionedQueryRecord = queryResult2.(*statedb.VersionedQueryRecord)
415+
stringRecord = string(versionedQueryRecord.Record)
416+
bFoundRecord = strings.Contains(stringRecord, "green")
417+
testutil.AssertEquals(t, bFoundRecord, true)
418+
419+
// verify no more results
420+
queryResult3, err = itr.Next()
421+
testutil.AssertNoError(t, err, "")
422+
testutil.AssertNil(t, queryResult3)
423+
424+
// query with embedded implicit "AND" and explicit "OR", namespace "ns3"
425+
itr, err = db.ExecuteQuery("ns3", "{\"selector\":{\"color\":\"green\",\"$or\":[{\"owner\":\"fred\"},{\"owner\":\"mary\"}]}}")
426+
testutil.AssertNoError(t, err, "")
427+
428+
// verify no results
429+
queryResult1, err = itr.Next()
430+
testutil.AssertNoError(t, err, "")
431+
testutil.AssertNil(t, queryResult1)
432+
303433
}

core/ledger/kvledger/txmgmt/statedb/statecouchdb/query_wrapper.go

+88-22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
117
package statecouchdb
218

319
import (
@@ -8,6 +24,7 @@ import (
824

925
var dataWrapper = "data"
1026
var jsonQueryFields = "fields"
27+
var jsonQuerySelector = "selector"
1128

1229
var validOperators = []string{"$and", "$or", "$not", "$nor", "$all", "$elemMatch",
1330
"$lt", "$lte", "$eq", "$ne", "$gte", "$gt", "$exits", "$type", "$in", "$nin",
@@ -20,6 +37,9 @@ All fields in the selector must have "data." prepended to the field names
2037
Fields listed in fields key will have "data." prepended
2138
Fields in the sort key will have "data." prepended
2239
40+
Also, the query will be scoped to the chaincodeid if the contextID is supplied
41+
In the example a contextID of "marble" is assumed.
42+
2343
Example:
2444
2545
Source Query:
@@ -28,50 +48,95 @@ Source Query:
2848
"sort": ["size", "color"], "limit": 10, "skip": 0}
2949
3050
Result Wrapped Query:
31-
{"selector":{"data.owner":{"$eq":"tom"}},
51+
{"selector":{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]},
3252
"fields": ["data.owner","data.asset_name","data.color","data.size","_id","version"],
3353
"sort":["data.size","data.color"],"limit":10,"skip":0}
3454
35-
3655
*/
37-
func ApplyQueryWrapper(namespace, queryString string) string {
38-
39-
//TODO - namespace is being added to support scoping queries to the correct chaincode context
40-
// A followup change will add the implementation for enabling the namespace filter
56+
func ApplyQueryWrapper(namespace, queryString string) (string, error) {
4157

4258
//create a generic map for the query json
4359
jsonQueryMap := make(map[string]interface{})
4460

4561
//unmarshal the selected json into the generic map
46-
json.Unmarshal([]byte(queryString), &jsonQueryMap)
62+
err := json.Unmarshal([]byte(queryString), &jsonQueryMap)
63+
if err != nil {
64+
return "", err
65+
}
4766

4867
//traverse through the json query and wrap any field names
4968
processAndWrapQuery(jsonQueryMap)
5069

51-
//process the query and add the version and fields if fields are specified
52-
for jsonKey, jsonValue := range jsonQueryMap {
53-
54-
//Add the "_id" and "version" fields, these are needed by default
55-
if jsonKey == jsonQueryFields {
56-
57-
//check to see if this is an interface map
58-
if reflect.TypeOf(jsonValue).String() == "[]interface {}" {
59-
60-
//Add the "_id" and "version" fields, these are needed by default
61-
//Overwrite the query fields if the "_id" field has been added
62-
jsonQueryMap[jsonQueryFields] = append(jsonValue.([]interface{}), "_id", "version")
63-
}
70+
//if "fields" are specified in the query, the add the "_id", "version" and "chaincodeid" fields
71+
if jsonValue, ok := jsonQueryMap[jsonQueryFields]; ok {
72+
//check to see if this is an interface map
73+
if reflect.TypeOf(jsonValue).String() == "[]interface {}" {
6474

75+
//Add the "_id" and "version" fields, these are needed by default
76+
//Overwrite the query fields if the "_id" field has been added
77+
jsonQueryMap[jsonQueryFields] = append(jsonValue.([]interface{}),
78+
"_id", "version", "chaincodeid")
6579
}
6680
}
6781

82+
//Check to see if the "selector" is specified in the query
83+
if jsonValue, ok := jsonQueryMap[jsonQuerySelector]; ok {
84+
//if the "selector" is found, then add the "$and" clause and the namespace filter
85+
setNamespaceInSelector(namespace, jsonValue, jsonQueryMap)
86+
} else {
87+
//if the "selector" is not found, then add a default namespace filter
88+
setDefaultNamespaceInSelector(namespace, jsonQueryMap)
89+
}
90+
6891
//Marshal the updated json query
6992
editedQuery, _ := json.Marshal(jsonQueryMap)
7093

7194
logger.Debugf("Rewritten query with data wrapper: %s", editedQuery)
7295

73-
return string(editedQuery)
96+
return string(editedQuery), nil
97+
98+
}
99+
100+
//setNamespaceInSelector adds an additional heirarchy in the "selector"
101+
//{"owner": {"$eq": "tom"}}
102+
//would be mapped as (assuming a namespace of "marble"):
103+
//{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]}
104+
func setNamespaceInSelector(namespace, jsonValue interface{},
105+
jsonQueryMap map[string]interface{}) {
106+
107+
//create a array to store the parts of the query
108+
var queryParts = make([]interface{}, 0)
109+
110+
//Add the namespace filter to filter on the chaincodeid
111+
namespaceFilter := make(map[string]interface{})
112+
namespaceFilter["chaincodeid"] = namespace
113+
114+
//Add the context filter and the existing selector value
115+
queryParts = append(queryParts, namespaceFilter, jsonValue)
116+
117+
//Create a new mapping for the new query stucture
118+
mappedSelector := make(map[string]interface{})
119+
120+
//Specify the "$and" operator for the parts of the query
121+
mappedSelector["$and"] = queryParts
122+
123+
//Set the new mapped selector to the query selector
124+
jsonQueryMap[jsonQuerySelector] = mappedSelector
125+
126+
}
127+
128+
//setDefaultNamespaceInSelector adds an default namespace filter in "selector"
129+
//If no selector is specified, the following is mapped to the "selector"
130+
//assuming a namespace of "marble"
131+
//{"chaincodeid":"marble"}
132+
func setDefaultNamespaceInSelector(namespace string, jsonQueryMap map[string]interface{}) {
133+
134+
//Add the context filter to filter on the chaincodeid
135+
namespaceFilter := make(map[string]interface{})
136+
namespaceFilter["chaincodeid"] = namespace
74137

138+
//Set the new mapped selector to the query selector
139+
jsonQueryMap[jsonQuerySelector] = namespaceFilter
75140
}
76141

77142
func processAndWrapQuery(jsonQueryMap map[string]interface{}) {
@@ -125,7 +190,8 @@ func processAndWrapQuery(jsonQueryMap map[string]interface{}) {
125190
}
126191
}
127192

128-
//processInterfaceMap processes an interface map and wraps field names or traverses the next level of the json query
193+
//processInterfaceMap processes an interface map and wraps field names or traverses
194+
//the next level of the json query
129195
func processInterfaceMap(jsonFragment map[string]interface{}) {
130196

131197
//iterate the the item in the map

0 commit comments

Comments
 (0)