|
The following tutorial series consists of three articles which will teach you various aspects about Hyperledger Fabric chaincode development ranging from CRUD operations, data protection, and chaincode testing.
An overview of the series:
- Article 1: Basic chaincode development and storing private data in collections
- Article 2: Advanced chaincode queries and the CouchDB GUI
- Article 3: A tutorial towards testing your chaincode with MockStub
Requirements
- 4GB of RAM (more is preferred)
- Docker, Docker-Compose, Code editor (e.g. Visual Studio Code), Git
- NodeJS version 8.9+ (Preferred is 8.9.4 – Tip: change your version with a version manager like ‘n’)
- Basic JavaScript knowledge
Objectives
- Learn about CouchDB and its query language
- Learn the Mango query language.
- Create advanced chaincode queries, test them via the CouchDB GUI and implement them in your chaincode.
Introduction
In the first article, we learned to create basic chaincode functions that are capable of performing CRUD operations onto the ledger using the open source boilerplate provided by TheLedger.
In this second tutorial, we will focus on creating advanced chaincode queries, also referred to as rich queries, which enables us to create more complex queries instead of just looking for a specific key.
Get Started
Make sure you have a copy of the code which can be found on Github michielmulders/hyperledger-fabric-blockgeeks. It’s recommended to use git clone https://github.com/michielmulders/hyperledger-fabric-blockgeeks.git to create a local clone of the repository on your machine and checkout the second part of the tutorial with git checkout tutorial-2
Next, start the network with ./scripts/startFabric.sh , if you are new to this tutorial, make sure to check out the ‘Boilerplate setup’ section in the first article to get up and running.
Once your Hyperledger Fabric network has started, open up this link (http://localhost:5984/_utils/#/_all_dbs) in your browser which should display the CouchDB GUI.
About CouchDB
Actually, Hyperledger Fabric supports both LevelDB as CouchDB to serve as state database, holding the latest state of each object. LevelDB is the default key-value state database embedded in every peer. CouchDB is an optional alternative external state database. Like the LevelDB key-value store, CouchDB can store any binary data that is modeled in chaincode. However, CouchDB is a better choice as it supports JSON document storage which enables rich queries against the chaincode data whereas LevelDB only supports queries against keys.
According to the Hyperledger Fabric documentation,
“CouchDB runs as a separate database process alongside the peer, therefore there are additional considerations in terms of setup, management, operations, and especially security.”
So, LevelDB offers less functionality, however, it’s properly configured whereas CouchDB needs some extra configuration like providing a database admin user and password or providing a CouchDB address for the peer to know where to look for its current state.
The boilerplate we are using contains a configuration with CouchDB included. Let’s create our first rich query.
CouchDB GUI
When you have opened the GUI you can find at http://localhost:5984/_utils/#/_all_dbs, you should see the following interface.
Let’s open the mychannel_fabcar (channel name combined with chaincode name) database and explore the car objects inside it.
Next, open the ‘Run a Query with Mango’ link in order to execute our first query. Let’s start with a simple query to find all Car objects. We can accomplish this by looking for the docType ‘car’.
Mango Query Operators
1. Fields Operator
The ‘fields’ operator allows you to return only specific fields. Let’s take the query from the previous example and only return the color of each car object.
{ "selector": { "docType": "car" }, "fields": [ "color" ] }
2. In Operator
The $in operator allows you to search for specific values within a field. It enables you to pass an array of possible values you want to match. Let’s take the previous query and only return purple and violet cars.
{ "selector": { "docType": "car", "color": { "$in": [ "violet", "purple" ] } }, "fields": [ "color" ] }
The query will return three car objects, one of color ‘purple’ and two of color ‘violet’.
3. Limit and Skip Operator
Ok, let’s say for example we want to skip the first result and limit the result to two cars so we only have the two violet cars of the previous example. The limit operator lets you limit the amount of returned objects whereas the skip operator is capable of moving the selector cursor.
<div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%">{ <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"selector"</span>: { <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"docType"</span>: <span style="background-color: #fff0f0">"car"</span>, <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"color"</span>: { <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"$in"</span>: [ <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"violet"</span>, <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"purple"</span> <span style="color: #FF0000; background-color: #FFAAAA"> </span>] <span style="color: #FF0000; background-color: #FFAAAA"> </span>} <span style="color: #FF0000; background-color: #FFAAAA"> </span>}, <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"fields"</span>: [ <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"color"</span> <span style="color: #FF0000; background-color: #FFAAAA"> </span>], <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"limit"</span>: <span style="color: #0000DD; font-weight: bold">2</span>, <span style="color: #FF0000; background-color: #FFAAAA"> </span><span style="background-color: #fff0f0">"skip"</span>: <span style="color: #0000DD; font-weight: bold">1</span> } </pre></div>
4. Regex Operator
As the title suggests, it’s possible to pass regex as a Mango query. Small side note, it’s not recommended to pass complex regex expressions (like nested expressions) as they require too much computing power.
For this example, we only want to match car models that contain at least one letter. This should eliminate CAR5 from the result as the model is ‘205’. Let’s take a look.
{ "selector": { "model": { "$regex": "[A-Z]+" } } }
5. Query Subdocuments
It’s even possible to query for subdocuments. Imagine a modified car object where the owner field contains an object that consists of user properties.
{ “model”, “docType”, …, “owner”: { “name”, “age”, “address” } }
Mango is pretty flexible regarding subdocuments as shown below – the query looks for users that are 18 years old.
{ "selector": { "owner.age": 18 } }
6. Other Operators
Many other operators do exist:
- “$gt”: Field must be greater than value X.
- “$lt”: Field must be lower than value X.
- “$eq”: Field must be equal to value X.
- “$or”: Value must be in array of search values.
- “$not”: Field may not match value X.
The full implementation of query operators can be found here.
Chaincode Rich Queries
Now, we have learned to write more advanced queries, let’s use this new knowledge in our chaincode to retrieve data. We will be using the getQueryResultAsList function from the stubHelper that accepts a selector and returns the required data.
Again, let’s query for all car objects in our database using a rich query. The getQueryResultAsList function accepts an object that contains the query.
async queryAllCars(stubHelper: StubHelper, args: string[]): Promise<any> { return await stubHelper.getQueryResultAsList({ selector: { docType: 'car' } }); }
Let’s restart the network using ./scripts/startFabric.sh and execute the docker exec command that runs the queryAllCars function in our chaincode.
docker exec cli peer chaincode query -C mychannel -n fabcar -c ‘{“function”:”queryAllCars”, “Args”: [“”]}’
Docker will return us an array full of car objects. As you can see, it’s not that difficult to create rich queries in chaincode.
What did we learn?
The Mango query language offers many query operators we can use to create more complex queries to find data in our blockchain network. It’s even possible to use regex to filter for data, however, limit the use to only basic regex queries due to performance reasons. Besides that, it’s not difficult to implement these rich queries into chaincode as the StubHelper does all the hard work.
Code Cheatsheet
- A Mango query starts with a selector that targets a field(s) on which we call one or more operators.
selector: { field: { “$operator”: ... } }
- The stubHelper.getQueryResultAsList function sends the query to our Hyperledger Fabric network and returns a result array.
Further reads
- CouchDB documentation Hyperledger Fabric.
- Introduction to Rich Queries for CouchDB
- Full query language implementation
I am attempting to do the mango query but I am getting back this error:
“message”:”transaction returned with failure: TypeError: stubHelper.getQueryResultAsList is not a function”
I am using the IBM Blockchain 2 beta, which utilizes Fabric 1.4 and CouchDB.
Any idea why I am getting this error and how to fix it?