Skip to content

Commit

Permalink
FAB-11503 upd. marbles cc with pagination APIs
Browse files Browse the repository at this point in the history
This CR makes marbles chaincode to use the following two
new pagination chaincode APIs
(1) GetStateByRangeWithPagination
(2) GetQueryResultsWithPagination

The usage of GetStateByPartialCompositeKeyWithPagination() for
pagination would be similar to that of GetStateByRangeWithPagination().

Change-Id: Ifb8e61aaa5a46f7c3f5a51eb71ebed89a1582eac
Signed-off-by: senthil <cendhu@gmail.com>
  • Loading branch information
cendhu authored and Chris Elder committed Sep 7, 2018
1 parent 0ee45ac commit 190982e
Showing 1 changed file with 169 additions and 49 deletions.
218 changes: 169 additions & 49 deletions examples/chaincode/go/marbles02/marbles_chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
return t.getHistoryForMarble(stub, args)
} else if function == "getMarblesByRange" { //get marbles based on range query
return t.getMarblesByRange(stub, args)
} else if function == "getMarblesByRangeWithPagination" {
return t.getMarblesByRangeWithPagination(stub, args)
} else if function == "queryMarblesWithPagination" {
return t.queryMarblesWithPagination(stub, args)
}

fmt.Println("invoke did not find func: " + function) //error
Expand Down Expand Up @@ -344,31 +348,10 @@ func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args
}

// ===========================================================================================
// getMarblesByRange performs a range query based on the start and end keys provided.

// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// constructQueryResponseFromIterator constructs a JSON array containing query results from
// a given result iterator
// ===========================================================================================
func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {

if len(args) < 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}

startKey := args[0]
endKey := args[1]

resultsIterator, err := stub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()

func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) {
// buffer is a JSON array containing QueryResults
var buffer bytes.Buffer
buffer.WriteString("[")
Expand All @@ -377,7 +360,7 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
Expand All @@ -396,6 +379,58 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar
}
buffer.WriteString("]")

return &buffer, nil
}

// ===========================================================================================
// addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination
// info, to the constructed query results
// ===========================================================================================
func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer {

buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":")
buffer.WriteString("\"")
buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount))
buffer.WriteString("\"")
buffer.WriteString(", \"Bookmark\":")
buffer.WriteString("\"")
buffer.WriteString(responseMetadata.Bookmark)
buffer.WriteString("\"}}]")

return buffer
}

// ===========================================================================================
// getMarblesByRange performs a range query based on the start and end keys provided.

// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// ===========================================================================================
func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response {

if len(args) < 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}

startKey := args[0]
endKey := args[1]

resultsIterator, err := stub.GetStateByRange(startKey, endKey)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()

buffer, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return shim.Error(err.Error())
}

fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String())

return shim.Success(buffer.Bytes())
Expand Down Expand Up @@ -537,34 +572,119 @@ func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString
}
defer resultsIterator.Close()

// buffer is a JSON array containing QueryRecords
var buffer bytes.Buffer
buffer.WriteString("[")
buffer, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}

bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, err
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"Key\":")
buffer.WriteString("\"")
buffer.WriteString(queryResponse.Key)
buffer.WriteString("\"")
fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())

buffer.WriteString(", \"Record\":")
// Record is a JSON object, so we write as-is
buffer.WriteString(string(queryResponse.Value))
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
return buffer.Bytes(), nil
}

// ====== Pagination =========================================================================
// TODO: Explain what is pagination and where it can be used
// =========================================================================================

// ====== Example: Pagination with Range Query ===============================================
// getMarblesByRangeWithPagination performs a range query based on the start & end key,
// page size and a bookmark.

// The number of fetched records would be equal to or lesser than the page size.
// Read-only function results are not typically submitted to ordering. If the read-only
// results are submitted to ordering, or if the query is used in an update transaction
// and submitted to ordering, then the committing peers will re-execute to guarantee that
// result sets are stable between endorsement time and commit time. The transaction is
// invalidated by the committing peers if the result set has changed between endorsement
// time and commit time.
// Therefore, range queries are a safe option for performing update transactions based on query results.
// ===========================================================================================
func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response {

if len(args) < 4 {
return shim.Error("Incorrect number of arguments. Expecting 4")
}
buffer.WriteString("]")

fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
startKey := args[0]
endKey := args[1]
//return type of ParseInt is int64
pageSize, err := strconv.ParseInt(args[2], 10, 32)
if err != nil {
return shim.Error(err.Error())
}
bookmark := args[3]

resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()

buffer, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return shim.Error(err.Error())
}

bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata)

fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String())

return shim.Success(buffer.Bytes())
}

// ===== Example: Pagination with Ad hoc Rich Query ========================================================
// queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query
// for marbles. Query string matching state database syntax is passed in and executed as is.
// The number of fetched records would be equal to or lesser than the specified page size.
// Supports ad hoc queries that can be defined at runtime by the client.
// If this is not desired, follow the queryMarblesForOwner example for parameterized queries.
// Only available on state databases that support rich query (e.g. CouchDB)
// =========================================================================================
func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response {

// 0
// "queryString"
if len(args) < 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}

queryString := args[0]
//return type of ParseInt is int64
pageSize, err := strconv.ParseInt(args[1], 10, 32)
if err != nil {
return shim.Error(err.Error())
}
bookmark := args[2]

queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(queryResults)
}

// =========================================================================================
// getQueryResultForQueryStringWithPagination executes the passed in query string with
// pagination info. Result set is built and returned as a byte array containing the JSON results.
// =========================================================================================
func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) {

fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)

resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark)
if err != nil {
return nil, err
}
defer resultsIterator.Close()

buffer, err := constructQueryResponseFromIterator(resultsIterator)
if err != nil {
return nil, err
}

bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata)

fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String())

return buffer.Bytes(), nil
}
Expand Down

0 comments on commit 190982e

Please sign in to comment.