I want to know when composite queries will become callable from update calls. Is this feature planned at all, isn’t it?
BTW, what makes it so difficult to program that it isn’t implemented yet?
It became insanely inconvenient to manage both update (for use from other canisters) and composite query (for use from the frontend) variants of many functions, especially because some of these functions are in libraries.
Short answer: composite queries are difficult because they need to keep the state changes while the query calls are pending and then throw them away when the calls complete. They have properties of both updates and queries. A proper implementation would require creating a multiverse of canisters (where each canister can have multiple copies for in-flight composite query calls).
Currently, DFINITY is not working on implementing compositing query in update context. This may change in future.
I don’t quite understand you, why it is difficult to throw changes in state away.
Anyway, we can introduce syntax like:
func f<composite>() {...} that would produce both a composite query function and a separate update function (they would need to have different names, like adding *Composite to the composite function of *Update to the update function).
I guess, this think is not difficult to implement for Motoko compiler authors. For Rust, it even can be implemented at library level by the user (but I use Motoko).
I don’t quite understand you, why it is difficult to throw changes in state away.
The difficult part is not throwing away the state, but having multiple copies of the same canister, which requires rewriting core parts of the replica: state management and execution environment.
func f<composite>() {...} that would produce both a composite query function and a separate update function
This syntactic sugar is quite dangerous. It defines a end-point that behaves like a query in one case and as an update in another case. The developer would need to be careful to not introduce subtle bugs due to the state changes unexpectedly committed in updates. For example, if the developer only tests the query version and the some canister calls the update version which triggers behavior that the developer didn’t expect (by committing the state changes that would be thrown away in the query case).
I guess that works fine in your case, but in general it might be not a good idea to add such construct to the language.
If such a syntactic sugar would solve your problem, could you implement it manually?
public query func foo_query() {
// call foo_impl()
}
public query func foo_update() {
// call foo_impl()
}
Zooming out a bit: did you consider having a single-canister architecture to avoid such hurdles? Since composite queries work only in canister on the same subnet, what benefits are you gettting from having multiple canisters?
The difficult part is not throwing away the state, but having multiple copies of the same canister, which requires rewriting core parts of the replica: state management and execution environment.
I don’t understand why just not to write changed state. Query calls change only stack right? Why not just move the stack pointer back after query?
Oh, it seems I grasped the problem: While Motoko query code cannot change state due to features of Motoko language, we can’t rely on that when using an arbitrary WASM code. Is this the problem? But I don’t see why it’s difficult to copy a canister memory during query call execution (till the next await), while locking the canister not to do any other changes, and after this copy the memory back. Whatever I think about this, it looks easy for me. I am intrigued to know why this is really difficult.
If such a syntactic sugar would solve your problem, could you implement it manually?
public query func foo_query() {
// call foo_impl()
}
public query func foo_update() {
// call foo_impl()
}
I got an idea: This can be implemented without code duplication by making foo_impl a template argument to foo.
First my attempt to do single-canister failed because of too long WASM to upload to one canister. Now I already have written a multi-canister code and therefore it is not going to change.