Hyperledger Fabric & couchdb, fantastic queries and where to find them

Hyperledger Fabric (HLF) uses a key value database to store its state. This object store holds binary data which can be queried using its key. By default fabric uses a LevelDB store which is included in the peer process.

When using simple structs in your chaincode, you most likely just need your data to be queried by their keys. But if you have more complex data, getting specific data based on fields is impossible. But there’s a solution (for some occasions)!

CouchDB… Relax

CouchDB is another type of key value store that can be easily plugged into Fabric. Both LevelDB & CouchDB can store binary data and can be interacted with using the chaincode. But CouchDB also adds rich querying to the mix, this of course if you are storing json documents. The “first network” sample project also contains a configuration with CouchDB included. The configuration for this can be found on here on the HLF wiki.

CouchDB feels like a key value store, with the querying ability of MongoDB. While the HLF team has improved their documentation since 1.0-alpha, I couldn’t seem to find much examples on how this rich querying works. After some searching, the only example I found was a simple query in the marbles chaincode on the HLF GitHub itself, hence this post.

So, let’s get down to those fantastic queries

Let’s use the marbles project (with some minor additions to make it more complex) as an example for our queries. All queries are just jsons which are parsed to be a string, this can be done by using this online tool. The whole CouchDB query documentation can be found here.

A full implementation with chaincode will be included at the end.

type marble struct { 
Name string `json:"name"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
}
type marbleStore struct { 
ObjectType string `json:"docType"`
Storename string `json:"storename"`
Ownername string `json:"ownername"`
Owner owner `json:"owner"`
Employees int `json:"employees"`
Marbles []marble `json:"marbles"`
}
type owner struct { 
Name string `json:"name"`
}

Simple queries

For example:

  • Searching for the marble store owned by tom
{
"selector": {
"ownername": "tom"
}
}
  • Querying subdocuments works similarly
{
"selector": {
"owner.lastname": "tom"
}
}
  • Searching for marbles with size greater than 0

Just like MongoDB, you can use $gt, $lt,$eq … the full list can be found here.

{
"selector": {
"employees": {
"$gt": 1
}
}
}

Advanced queries

For example:

  • Searching for specific marble colors in stores
{
"selector": {
"marbles": {
"$elemMatch": {
"color": "red"
}
}
}
}
  • Searching for an array of marble colors in stores
{
"selector": {
"marbles": {
"$elemMatch": {
"color": {
"$in": ["red", "green"]
}
}
}
}
}

Advanced utilities

Besides the “selector” property, couchDB has a few other neat properties to aid in your query.

{
"selector": {
"ownername": "tom"
},
"limit": 5,
"skip": 1,
"sort": [{"owner": "desc"}, {"storename": "desc" }],
"fields": ["storename","marbles"]
}

limit: Like any other querying language, you are able to limit the amount of records to return.

skip: You’ll be able to leave out the first X records.

sort: You are able to sort every individual field in “asc” or “desc” order.

fields: You can filter out unnecessary or forbidden fields.

So it all comes together

func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string)([] byte, error) {
fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)
resultsIterator, err: = stub.GetQueryResult(queryString)
defer resultsIterator.Close()
if err != nil {
return nil, err
}
// buffer is a JSON array containing QueryRecords
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("]")
fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
return buffer.Bytes(), nil
}

This function comes straight out of the marbles example. When you pass it your query above, make sure your escape (useful tool for this) it into a string so it looks like this:

"{\"selector\":{\"owner\":\"tom\"}}"

The GetQueryResult function will return an iterator. If it has any items, it will return a struct that looks something like this. The Key and Value are obvious and the Namespace is the channel it got queried from.

type queryResponse struct { 
Key string
Value string
Namespace string
}

So in the end, this function will produce bytes of a json like this.

[{
"Key": "marblestore1",
"Record": {
"owner": "tom",
..., // the ... represent the other properties
"employees": 2
}
]

Conclusion

These rich queries are really useful if you need more complex queries. But unlike GetStateByRange the GetQueryResult query doesn’t get re-executed during the validation phase. This means that there is no guarantee the result set is stable between chaincode execution and commit time for rich queries, and therefore rich queries are not appropriate for use in update transactions, unless your application can guarantee the result set is stable between chaincode execution time and commit time, or can handle potential changes in subsequent transactions. This is something to think about.

Important note for those writing tests for their chaincode: The MockStub used for testing does not implement the GetQueryResult method. So you’ll have to work around this, or else your tests will fail.

MockStub.go GetQueryResult function