Querying Ledger Data by Range
Currently, SimpleContract implements only three basic operations: put, get, and del. However, it is often useful to collect a number of results following some rules or conditions.
First, let’s add a new operation named getByRange (from, to) to our SimpleContract. It will retrieve all the keys that are in range between from (inclusively) and to (exclusively) and return associated key-value pairs in any human-readable format, e.g. JSON.
To implement this operation in SimpleContract, we will use the getStateByRange function provided by the ChaincodeStub interface.
getStateByRange(startKey: string, endKey: string): Promise<StateQueryIterator>
getStateByRange returns a range iterator over a set of keys in the ledger. The iterator can be used to iterate over all keys between startKey (inclusive) and endKey (exclusive). The keys are returned in lexical order. getStateByRange allows startKey and endKey to be empty strings, which imply unbounded range queries on a start or an end.
Many functions in ChaincodeStub interface return an iterator that implements the same interface - StateQueryIterator. Let’s look at the definition for this type in detail.
To implement the StateQueryIterator interface, a type must have the following methods:
close(): Promise<void> closes an iterator to free up resources when the iterator is no longer used.
next(): Promise<NextResult<KV>> returns the currently pointed element of type NextResult<KV> and moves the iterator to the next position.
Basically, StateQueryIterator interface describes a type that follows a forward iterator design pattern, which is well-known and straightforward. We are going to utilize it to iterate over the specified range.
To better understand the underlying structure of StateQueryIterator, let’s review the NextResult type. NextResult<T> is a wrapper type encapsulating the actual return value of type T and a boolean flag signaling reaching the end of an iterated range. Type T in our case is substituted with KV, which represents a simple key-value pair. A complete NextResult<KV> object looks as follows.
// NextResult<KV>
{
value: { // KV
namespace: string;
key: string;
value: UInt8Array;
};
done: boolean;
}
Now, we know everything to implement the getByRange operation. Let’s add the new function to SimpleContract.
async getByRange(ctx, keyFrom, keyTo) {
const iterator = await ctx.stub.getStateByRange(keyFrom, keyTo);
let results = [];
let result = await iterator.next();
while (!result.done) {
results.push({
key: result.value.key,
value: result.value.value.toString()
});
result = await iterator.next();
}
await iterator.close();
return JSON.stringify(results);
}
The implementation above is a bit tricky. The result.value field doesn’t represent the value of a key-value pair stored in the ledger, it’s actually a value field of NextResult<KV> object. This leads to an inexplicit result.value.value construction, which is hard to interpret without Hyperledger Fabric API knowledge. The other peculiarity worth mentioning is the necessity of closing the iterator before the end of function execution to avoid memory leakage.
However, we can avoid these inconveniences by utilizing the for await...of Javascript statement. Since the return value of getStateByRange can be treated as an AsyncIterable<KV> object, we can simplify our implementation as follows.
async getByRange(ctx, keyFrom, keyTo) {
const iteratorPromise = ctx.stub.getStateByRange(keyFrom, keyTo);
let results = [];
for await (const res of iteratorPromise) {
results.push({
key: res.key,
value: res.value.toString()
});
}
return JSON.stringify(results);
}
Last updated
Was this helpful?