Hyperledger Fabric chain code development – rich query

Hyperledger Fabric chain code development – rich query

Common chain codes can be simply added and searched using PutState and GetState, and DelState can be added if it is not good enough. Because the bottom layer of the fabric is a KV database, these three interfaces can meet the basic needs of adding, deleting, modifying and checking. However, these are all single data operations. If you want to perform more powerful data operations, only using this is not enough. At the beginning of all data operations during query, I found these two functions that support rich query through official documents. (Rich query is only supported in CouchDB)

Source code

func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error)
func (s *ChaincodeStub) GetQueryResultWithPagination(query string, pageSize int32,
    bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)

The first is a simple rich query function, and the second is a paged query function that supports rich query

// GetQueryResult documentation can be found in interfaces.go
func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {<!-- -->
    // Access public data by setting the collection to empty string
    collection := ""
    // ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
    iterator, _, err := s. handleGetQueryResult(collection, query, nil)
?
    return iterator, err
}
// GetQueryResultWithPagination ...
func (s *ChaincodeStub) GetQueryResultWithPagination(query string, pageSize int32,
    bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {<!-- -->
    // Access public data by setting the collection to empty string
    collection := ""
?
    metadata, err := createQueryMetadata(pageSize, bookmark)
    if err != nil {<!-- -->
        return nil, nil, err
    }
    return s.handleGetQueryResult(collection, query, metadata)
}

The above is their source code. In fact, it can be found that they all call the handleGetQueryResult function, and the knowledge of pagination query functions is one more step to create metadata and package the range. Then it can be compared with the GetStateByRange(startKey, endKey string) function.

bookmark is a bookmark, the content is an unordered character array, if it is empty, it will re-query, if it is not empty, it will start to query from the position it represents, and you can get a page every time you query bookmark, that is to say, the query on the next page needs to bring the bookmark of the previous page.

func (s *ChaincodeStub) handleGetQueryResult(collection, query string,
    metadata []byte) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {<!-- -->
 //Get query results, this step uses metadata for pagination
    response, err := s.handler.handleGetQueryResult(collection, query, metadata, s.ChannelID, s.TxID)
    if err != nil {<!-- -->
        return nil, nil, err
    }
 ? ?//Create an iterator
    iterator := s.createStateQueryIterator(response)
    // repackage
    responseMetadata, err := createQueryResponseMetadata(response.Metadata)
    if err != nil {<!-- -->
        return nil, nil, err
    }
?
    return iterator, responseMetadata, nil
}

To use handleGetQueryResult together with these two rich query functions, there is also a GetPrivateDataQueryResult function. This function seems to query private data, but I don’t know what the private data is for the time being, so I won’t mention it if I press it. These four functions are all in shim/stub.go.

Index and Data Structure

Rich query is currently only supported by couchDB. This is a function that is more dependent on the database, so one issue must be considered: indexing. Refer to the Chinese documentation of Fabric to build an index.

Pay special attention to two points:

1. The index is based on demand, if too many indexes will affect the query rate

2. The directory of the index is fixed, and the chaincode is in the same directory as META-INF/statedb/couchdb/indexes

A complete index is as follows:

{<!-- -->
 ?"index":{<!-- -->
 ? ? ?//The name of the field to be queried, generally including docType, the explanation of docType is later
 ? ? ?"fields":["docType","owner"] // Names of the fields to be queried
  },
 ? // (optional) The name of the design file that will be indexed. If a file contains an index, it can not be used, and the official recommendation is also a single index
 ?"ddoc":"indexOwnerDoc",
 ?"name":"indexOwner",
 ?"type":"json"
}

I also saw a standard way of writing a data structure in the official document

type marble struct {<!-- -->
 ? ?//docType is used to distinguish various types of objects in the state database
 ObjectType string `json:"docType"`
 ? ?//The following normal data structure
 ? ?Name ? ? ? string `json:"name"`
 Color?string `json:"color"`
 Size?int`json:"size"`
 ? ?Owner ? ? ?string `json:"owner"`
}

docType is used to identify the data type of this document. There may also be other documents in the chaincode database at the same time. Documents in the database are queryable for these attribute values.

Common functions

The following are the commonly used rich query functions I summarized based on the examples given in the document, mainly using getQueryResultForQueryString and getQueryResultForQueryStringWithPagination, the latter two functions are used for paging rich query functions. It can be written and called in smart contracts.

================================================= ==============================================
// getQueryResultForQueryString executes the passed in query string.
// The result set is built and returned as a byte array containing the JSON result.
==================================================== ==========================================
func getQueryResultForQueryString(APIstub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {<!-- -->
?
    fmt.Printf("- getQueryResultForQueryString queryString:\\
%s\\
", queryString)
?
    resultsIterator, err := APIstub. GetQueryResult(queryString)
    if err != nil {<!-- -->
        return nil, err
    }
    defer resultsIterator. Close()
?
    buffer, err := constructQueryResponseFromIterator(resultsIterator)
    if err != nil {<!-- -->
        return nil, err
    }
?
    fmt.Printf("- getQueryResultForQueryString queryResult:\\
%s\\
", buffer.String())
?
    return buffer.Bytes(), nil
}
?
==================================================== ==========================================
// getQueryResultForQueryStringWithPagination executes the incoming query string, which includes
// Pagination information. The result set is built and returned as a byte array containing the JSON results.
==================================================== ==========================================
func getQueryResultForQueryStringWithPagination(APIstub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) {<!-- -->
?
    fmt.Printf("- getQueryResultForQueryStringWithPagination queryString:\\
%s\\
", queryString)
?
    resultsIterator, responseMetadata, err := APIstub.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("- getQueryResultForQueryStringWithPagination queryResult:\\
%s\\
", bufferWithPaginationInfo.String())
?
    return buffer.Bytes(), nil
}
================================================= ==============================================
// constructQueryResponseFromIterator constructs a JSON array containing query results from the given results iterator.
// A given result iterator ============================================ ==================================================
func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) {<!-- -->
// buffer is a JSON array containing QueryResults
var buffer bytes. Buffer
buffer. WriteString("[")

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(""")

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

return & buffer, nil
}

==================================================== ===========================================
// addPaginationMetadataToQueryResults will contain paginated QueryResponseMetadata
// information, added 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
}

The following is an example of a contract function I wrote that calls the paging function (I call the smart contract function that can be called in the SDK a contract function)

func (s *SmartContract) queryCourseWithPagination(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {<!-- -->
fmt.Println("func queryCourseWithPagination start")
fmt.Printf("args:%v\\
", args)
if len(args) != 3 {<!-- -->
return shim.Error("arguments num not enough," + strconv.Itoa(len(args)))
}
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]
    //Before getting the parameters, call the pagination rich query function in this step, after querying the result, it will be converted into a byte stream and returned
queryResults, err := getQueryResultForQueryStringWithPagination(APIstub, queryString, int32(pageSize), bookmark)
if err != nil {<!-- -->
return shim. Error(err. Error())
}
return shim.Success(queryResults)
}

chaincode commonly used package shim link