Skip to content

Commit

Permalink
FAB-2263 GetQueryResult should support index name
Browse files Browse the repository at this point in the history
Motivation for this change:
In CouchDB _find API, use_index parameter can specify which index
to utilize for the query. Change the query wrapper to accept the
"use-index" parameter.

This change can be accomplished using existing JSON query formatting.

-  Change query wrapper to allow "use_index" correctly

-  Changes have been made to the marbles02 example to demonstrate
queries using specific indexes.

-  Change Vagrantfile to add a "couchdb" alias to the vagrant
host file.  This will allow REST API calls to be consistent
between vagrant and docker images.

Change-Id: Ie54750ccc5ccf8aa01192168b60354f0984c5676
Signed-off-by: Chris Elder <[email protected]>
  • Loading branch information
Chris Elder committed Feb 25, 2017
1 parent 4eec836 commit 167b14a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 10 deletions.
24 changes: 14 additions & 10 deletions core/ledger/kvledger/txmgmt/statedb/statecouchdb/query_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import (
"reflect"
)

var dataWrapper = "data"
var jsonQueryFields = "fields"
var jsonQuerySelector = "selector"
const dataWrapper = "data"
const jsonQueryFields = "fields"
const jsonQuerySelector = "selector"
const jsonQueryUseIndex = "use_index"

var validOperators = []string{"$and", "$or", "$not", "$nor", "$all", "$elemMatch",
"$lt", "$lte", "$eq", "$ne", "$gte", "$gt", "$exits", "$type", "$in", "$nin",
Expand All @@ -37,7 +38,8 @@ 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
Also, the query will be scoped to the chaincodeid if the contextID is supplied
Also, the query will be scoped to the chaincodeid
In the example a contextID of "marble" is assumed.
Example:
Expand Down Expand Up @@ -67,13 +69,12 @@ func ApplyQueryWrapper(namespace, queryString string) (string, error) {
//traverse through the json query and wrap any field names
processAndWrapQuery(jsonQueryMap)

//if "fields" are specified in the query, the add the "_id", "version" and "chaincodeid" fields
//if "fields" are specified in the query, then add the "_id", "version" and "chaincodeid" 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" and "version" fields, these are needed by default
//Overwrite the query fields if the "_id" field has been added
//Add the "_id", "version" and "chaincodeid" fields, these are needed by default
jsonQueryMap[jsonQueryFields] = append(jsonValue.([]interface{}),
"_id", "version", "chaincodeid")
}
Expand Down Expand Up @@ -142,7 +143,7 @@ func setDefaultNamespaceInSelector(namespace string, jsonQueryMap map[string]int
func processAndWrapQuery(jsonQueryMap map[string]interface{}) {

//iterate through the JSON query
for _, jsonValue := range jsonQueryMap {
for jsonKey, jsonValue := range jsonQueryMap {

//create a case for the data types found in the JSON query
switch jsonValueType := jsonValue.(type) {
Expand All @@ -165,8 +166,11 @@ func processAndWrapQuery(jsonQueryMap map[string]interface{}) {

case string:

//This is a simple string, so wrap the field and replace in the array
jsonValueType[itemKey] = fmt.Sprintf("%v.%v", dataWrapper, itemValue)
//if this is not "use_index", the wrap the string
if jsonKey != jsonQueryUseIndex {
//This is a simple string, so wrap the field and replace in the array
jsonValueType[itemKey] = fmt.Sprintf("%v.%v", dataWrapper, itemValue)
}

case []interface{}:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,33 @@ func TestQueryNoSelector(t *testing.T) {
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))

//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, "\"use_index\":\"_design/testDoc\""), 1)

}

//TestQueryWithUseDesignDocAndIndexName tests query with index design doc and index name specified
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))

//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, "\"use_index\":[\"_design/testDoc\",\"testIndexName\"]"), 1)

}
2 changes: 2 additions & 0 deletions devenv/Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ LOCALDEV = "/local-dev"
$script = <<SCRIPT
set -x
echo "127.0.0.1 couchdb" | tee -a /etc/hosts
export DOCKER_STORAGE_BACKEND="#{ENV['DOCKER_STORAGE_BACKEND']}"
cd #{SRCMOUNT}/devenv
Expand Down
37 changes: 37 additions & 0 deletions examples/chaincode/go/marbles02/marbles_chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,48 @@ under the License.

// ==== Query marbles ====
// peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}'
// peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}'

// Rich Query (Only supported if CouchDB is used as state database):
// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}'
// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}'

//The following examples demonstrate creating indexes on CouchDB
//Example hostname:port configurations
//
//Docker or vagrant environments:
// http://couchdb:5984/
//
//Inside couchdb docker container
// http://127.0.0.1:5984/

// Index for chaincodeid, 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"}
//
// 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
//

// Index for chaincodeid, 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"}
//
// 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

// 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\"]}"]}'

// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database):
// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}'

package main

import (
Expand Down

0 comments on commit 167b14a

Please sign in to comment.