Skip to content

Commit

Permalink
[FAB-7752] rm chaincodeid frm couchdb doc envelope
Browse files Browse the repository at this point in the history
Now that we create one database per chaincode, the chaincodeID that gets
implicitly added to the json document, to queries, and to indexes is not
required and might cause issues for user. Hence, this CR removes
the implicit addition of chaincodeID to docs, queries, and indexes.

Change-Id: Ie8ac9240fc709e0870ec25cddd49e3c2fc4ba6b2
Signed-off-by: senthil <[email protected]>
  • Loading branch information
cendhu authored and Chris Elder committed Jan 19, 2018
1 parent ea3bf3c commit dbfa920
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 112 deletions.
63 changes: 5 additions & 58 deletions core/ledger/kvledger/txmgmt/statedb/statecouchdb/query_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ All fields in the selector must have "data." prepended to the field names
Fields listed in fields key will have "data." prepended
Fields in the sort key will have "data." prepended
- The query will be scoped to the chaincodeid
- limit be added to the query and is based on config
- skip is defaulted to 0 and is currently not used, this is for future paging implementation
Expand All @@ -56,12 +54,12 @@ Source Query:
"sort": ["size", "color"]}
Result Wrapped Query:
{"selector":{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]},
{"selector":{"data.owner":{"$eq":"tom"}},
"fields": ["data.owner","data.asset_name","data.color","data.size","_id","version"],
"sort":["data.size","data.color"],"limit":10,"skip":0}
*/
func ApplyQueryWrapper(namespace, queryString string, queryLimit, querySkip int) (string, error) {
func ApplyQueryWrapper(queryString string, queryLimit, querySkip int) (string, error) {

//create a generic map for the query json
jsonQueryMap := make(map[string]interface{})
Expand All @@ -77,26 +75,17 @@ func ApplyQueryWrapper(namespace, queryString string, queryLimit, querySkip int)
//traverse through the json query and wrap any field names
processAndWrapQuery(jsonQueryMap)

//if "fields" are specified in the query, then add the "_id", "version" and "chaincodeid" fields
//if "fields" are specified in the query, then add the "_id", and "version"fields
if jsonValue, ok := jsonQueryMap[jsonQueryFields]; ok {
//check to see if this is an interface map
if reflect.TypeOf(jsonValue).String() == "[]interface {}" {

//Add the "_id", "version" and "chaincodeid" fields, these are needed by default
//Add the "_id", and "version" fields, these are needed by default
jsonQueryMap[jsonQueryFields] = append(jsonValue.([]interface{}),
"_id", "version", "chaincodeid")
"_id", "version")
}
}

//Check to see if the "selector" is specified in the query
if jsonValue, ok := jsonQueryMap[jsonQuerySelector]; ok {
//if the "selector" is found, then add the "$and" clause and the namespace filter
setNamespaceInSelector(namespace, jsonValue, jsonQueryMap)
} else {
//if the "selector" is not found, then add a default namespace filter
setDefaultNamespaceInSelector(namespace, jsonQueryMap)
}

//Add limit
jsonQueryMap[jsonQueryLimit] = queryLimit

Expand All @@ -112,48 +101,6 @@ func ApplyQueryWrapper(namespace, queryString string, queryLimit, querySkip int)

}

//setNamespaceInSelector adds an additional hierarchy in the "selector"
//{"owner": {"$eq": "tom"}}
//would be mapped as (assuming a namespace of "marble"):
//{"$and":[{"chaincodeid":"marble"},{"data.owner":{"$eq":"tom"}}]}
func setNamespaceInSelector(namespace, jsonValue interface{},
jsonQueryMap map[string]interface{}) {

//create a array to store the parts of the query
var queryParts = make([]interface{}, 0)

//Add the namespace filter to filter on the chaincodeid
namespaceFilter := make(map[string]interface{})
namespaceFilter["chaincodeid"] = namespace

//Add the context filter and the existing selector value
queryParts = append(queryParts, namespaceFilter, jsonValue)

//Create a new mapping for the new query structure
mappedSelector := make(map[string]interface{})

//Specify the "$and" operator for the parts of the query
mappedSelector["$and"] = queryParts

//Set the new mapped selector to the query selector
jsonQueryMap[jsonQuerySelector] = mappedSelector

}

//setDefaultNamespaceInSelector adds an default namespace filter in "selector"
//If no selector is specified, the following is mapped to the "selector"
//assuming a namespace of "marble"
//{"chaincodeid":"marble"}
func setDefaultNamespaceInSelector(namespace string, jsonQueryMap map[string]interface{}) {

//Add the context filter to filter on the chaincodeid
namespaceFilter := make(map[string]interface{})
namespaceFilter["chaincodeid"] = namespace

//Set the new mapped selector to the query selector
jsonQueryMap[jsonQuerySelector] = namespaceFilter
}

func processAndWrapQuery(jsonQueryMap map[string]interface{}) {

//iterate through the JSON query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestSimpleQuery(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner":{"$eq":"jerry"}},"limit": 10,"skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -49,7 +49,7 @@ func TestQueryWithOperator(t *testing.T) {

rawQuery := []byte(`{"selector":{"$or":[{"owner":{"$eq":"jerry"}},{"owner": {"$eq": "frank"}}]},"limit": 10,"skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -72,7 +72,7 @@ func TestQueryWithImplicitOperatorAndExplicitOperator(t *testing.T) {

rawQuery := []byte(`{"selector":{"color":"green","$or":[{"owner":"fred"},{"owner":"mary"}]}}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -97,7 +97,7 @@ func TestQueryWithFields(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner": {"$eq": "tom"}},"fields": ["owner", "asset_name", "color", "size"], "limit": 10, "skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -124,7 +124,7 @@ func TestQueryWithSortFields(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner": {"$eq": "tom"}},"fields": ["owner", "asset_name", "color", "size"], "sort": ["size", "color"], "limit": 10, "skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -151,7 +151,7 @@ func TestQueryWithSortObjects(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner": {"$eq": "tom"}},"fields": ["owner", "asset_name", "color", "size"], "sort": [{"size": "desc"}, {"color": "desc"}], "limit": 10, "skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand Down Expand Up @@ -191,13 +191,13 @@ func TestQueryLeadingOperator(t *testing.T) {
}
}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")

//$and operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 2)
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 1)

//$gte operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$gte\""), 1)
Expand All @@ -221,13 +221,13 @@ func TestQueryLeadingAndEmbeddedOperator(t *testing.T) {
]
}}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")

//$and operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 2)
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 1)

//$gte operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$gte\""), 1)
Expand Down Expand Up @@ -266,13 +266,13 @@ func TestQueryEmbeddedOperatorAndArrayOfObjects(t *testing.T) {
}
}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")

//$and operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 2)
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$and\""), 1)

//$gte operator should be unchanged
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"$gte\""), 1)
Expand All @@ -297,7 +297,7 @@ func TestQueryEmbeddedOperatorAndArrayOfValues(t *testing.T) {
}
}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -322,27 +322,12 @@ func TestQueryEmbeddedOperatorAndArrayOfValues(t *testing.T) {

}

//TestQueryNoSelector with no selector specified
func TestQueryNoSelector(t *testing.T) {

rawQuery := []byte(`{"fields": ["owner", "asset_name", "color", "size"]}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")

//check to make sure the default selector is added
testutil.AssertEquals(t, strings.Count(wrappedQuery, "\"selector\":{\"chaincodeid\":\"ns1\"}"), 1)

}

//TestQueryWithUseDesignDoc tests query with index design doc specified
func TestQueryWithUseDesignDoc(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner":{"$eq":"jerry"}},"use_index":"_design/testDoc","limit": 10,"skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -357,7 +342,7 @@ func TestQueryWithUseDesignDocAndIndexName(t *testing.T) {

rawQuery := []byte(`{"selector":{"owner":{"$eq":"jerry"}},"use_index":["_design/testDoc","testIndexName"],"limit": 10,"skip": 0}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand All @@ -372,7 +357,7 @@ func TestQueryWithLargeInteger(t *testing.T) {

rawQuery := []byte(`{"selector":{"$and":[{"size":{"$eq": 1000007}}]}}`)

wrappedQuery, err := ApplyQueryWrapper("ns1", string(rawQuery), 10000, 0)
wrappedQuery, err := ApplyQueryWrapper(string(rawQuery), 10000, 0)

//Make sure the query did not throw an exception
testutil.AssertNoError(t, err, "Unexpected error thrown when for query JSON")
Expand Down
19 changes: 5 additions & 14 deletions core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,7 @@ func (vdb *VersionedDB) ExecuteQuery(namespace, query string) (statedb.ResultsIt
// Get the querylimit from core.yaml
queryLimit := ledgerconfig.GetQueryLimit()

// TODO: Remove namespace from wrapper query
queryString, err := ApplyQueryWrapper(namespace, query, queryLimit, 0)
queryString, err := ApplyQueryWrapper(query, queryLimit, 0)
if err != nil {
logger.Debugf("Error calling ApplyQueryWrapper(): %s\n", err.Error())
return nil, err
Expand Down Expand Up @@ -565,15 +564,13 @@ func (vdb *VersionedDB) processUpdateBatch(updateBatch *statedb.UpdateBatch, mis
if isDelete {
// this is a deleted record. Set the _deleted property to true
//couchDoc.JSONValue = createCouchdbDocJSON(string(compositeKey), revision, nil, ns, vv.Version, true)
//TODO: Remove ns/chaincodeID from json doc
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, nil, ns, vv.Version, true)
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, nil, vv.Version, true)

} else {

if couchdb.IsJSON(string(vv.Value)) {
// Handle as json
//TODO: Remove ns from json doc
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, vv.Value, ns, vv.Version, false)
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, vv.Value, vv.Version, false)

} else { // if value is not json, handle as a couchdb attachment

Expand All @@ -584,8 +581,7 @@ func (vdb *VersionedDB) processUpdateBatch(updateBatch *statedb.UpdateBatch, mis
attachments := append([]*couchdb.AttachmentInfo{}, attachment)

couchDoc.Attachments = attachments
//TODO: Remove ns from json doc
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, nil, ns, vv.Version, false)
couchDoc.JSONValue = createCouchdbDocJSON(key, revision, nil, vv.Version, false)

}
}
Expand Down Expand Up @@ -835,11 +831,10 @@ func (vdb *VersionedDB) ClearCachedVersions() {
// _id - couchdb document ID, need for all couchdb batch operations
// _rev - couchdb document revision, needed for updating or deleting existing documents
// _deleted - flag using in batch operations for deleting a couchdb document
// chaincodeID - chain code ID, added to header, used to scope couchdb queries
// version - version, added to header, used for state validation
// data wrapper - JSON from the chaincode goes here
// The return value is the CouchDoc.JSONValue with the header fields populated
func createCouchdbDocJSON(id, revision string, value []byte, chaincodeID string, version *version.Height, deleted bool) []byte {
func createCouchdbDocJSON(id, revision string, value []byte, version *version.Height, deleted bool) []byte {

// create a version mapping
jsonMap := map[string]interface{}{"version": fmt.Sprintf("%v:%v", version.BlockNum, version.TxNum)}
Expand All @@ -858,10 +853,6 @@ func createCouchdbDocJSON(id, revision string, value []byte, chaincodeID string,

} else {

// TODO: Remove chaincodeID from header
// add the chaincodeID
jsonMap["chaincodeid"] = chaincodeID

// Add the wrapped data if the value is not null
if value != nil {

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"index":{"fields":["chaincodeid","data.owner"]},"name":"indexOwner","type":"json"}
{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}
14 changes: 6 additions & 8 deletions examples/chaincode/go/marbles02/marbles_chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,26 +45,24 @@ under the License.
//Inside couchdb docker container
// http://127.0.0.1:5984/

// Index for chaincodeid, docType, owner.
// Index for docType, owner.
// Note that docType and owner fields must be prefixed with the "data" wrapper
// chaincodeid must be added for all queries
//
// Definition for use with Fauxton interface
// {"index":{"fields":["chaincodeid","data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}
// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}
//
// example curl definition for use with command line
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"chaincodeid\",\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1/_index
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1/_index
//

// Index for chaincodeid, docType, owner, size (descending order).
// Index for docType, owner, size (descending order).
// Note that docType, owner and size fields must be prefixed with the "data" wrapper
// chaincodeid must be added for all queries
//
// Definition for use with Fauxton interface
// {"index":{"fields":[{"data.size":"desc"},{"chaincodeid":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"}
// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"}
//
// example curl definition for use with command line
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"chaincodeid\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1/_index
// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1/_index

// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}'
Expand Down

0 comments on commit dbfa920

Please sign in to comment.