Query Methods
TL;DR
-
Created with the
query
function - Read-only
- Executed on a single node
- No consensus
- Latency on the order of ~100 milliseconds
- 5 billion Wasm instruction limit
- 4 GiB heap limit
- ~32k queries per second per canister
The most basic way to expose your canister's
functionality publicly is through a query
method. Here's an example of a simple query
method named getString
:
import { Canister, query, text } from 'azle/experimental';
export default Canister({
getString: query([], text, () => {
return 'This is a query method!';
})
});
Query methods are defined inside of a call to
Canister
using the
query
function.
The first parameter to query
is an
array of CandidType
objects that
will be used to decode the Candid bytes of the
arguments sent from the client when calling your
query method.
The second parameter to query
is a
CandidType
object used to encode
the return value of your function to Candid
bytes to then be sent back to the client.
The third parameter to query
is the
function that receives the decoded arguments,
performs some computation, and then returns a
value to be encoded. The TypeScript signature of
this function (parameter and return types) will
be inferred from the
CandidType
arguments in the first
and second parameters to query
.
getString
can be called from the
outside world through the IC's HTTP API. You'll
usually invoke this API from the
dfx command line
,
dfx web UI
, or an agent.
From the dfx command line
you can
call it like this:
dfx canister call my_canister getString
Query methods are read-only. They do not persist any state changes. Take a look at the following example:
import { Canister, query, text, Void } from 'azle/experimental';
let db: {
[key: string]: string;
} = {};
export default Canister({
set: query([text, text], Void, (key, value) => {
db[key] = value;
})
});
Calling set
will perform the
operation of setting the
key
property on the
db
object to value
,
but after the call finishes that change will be
discarded.
This is because query methods are executed on a single node machine and do not go through consensus. This results in lower latencies, perhaps on the order of 100 milliseconds.
There is a limit to how much computation can be done in a single call to a query method. The current query call limit is 5 billion Wasm instructions. Here's an example of a query method that runs the risk of reaching the limit:
import { Canister, nat32, query, text } from 'azle/experimental';
export default Canister({
pyramid: query([nat32], text, (levels) => {
return new Array(levels).fill(0).reduce((acc, _, index) => {
const asterisks = new Array(index + 1).fill('*').join('');
return `${acc}${asterisks}\n`;
}, '');
})
});
From the dfx command line
you can
call pyramid
like this:
dfx canister call my_canister pyramid '(1_000)'
With an argument of 1_000
,
pyramid
will fail with an error
...exceeded the instruction limit for
single message execution
.
Keep in mind that each query method invocation has up to 4 GiB of heap available.
In terms of query scalability, an individual canister likely has an upper bound of ~36k queries per second.