Proposal: Composite Queries

@claudio and I have been discussing recursive composite query calls. We need your input to decide what is the most natural (least surprising) semantics for developers.

Please take a look at this example, where we have two canisters: A and B. Each canister has a global counter set to 0 and a composite query method inc_counter() that increments the counter and returns its new value. Additionally, canister B has two composite query methods: call_a() and call_b().

What would you expect the queries to return when invoked by a user (e.g. using dfx).

Canister A:

counter: int32 = 0; // <= Global variable

#[composite_query]
fn inc_counter() -> int32 {
	counter += 1;
    return counter;
}

Canister B:

counter: int32 = 0; // <= Global variable

#[composite_query]
fn inc_counter() -> int32 {
	counter += 1;
    return counter;
}

#[composite_query]
fn call_a() -> int32 {
    counter += 1;
    // Canister B calls canister A twice.
    let a1 = call("Canister A", "inc_counter").await;
    let a2 = call("Canister A", "inc_counter").await;
    return counter + a1 + a2;
}

#[composite_query]
fn call_b() -> int32 {
    counter += 1;
    // Canister B calls canister B (self) twice.
    let b1 = call("Canister B", "inc_counter").await;
    let b2 = call("Canister B", "inc_counter").await;
    return counter + b1 + b2;
}

Option 1

Composite queries are fully isolated from each other (do not see each other’s changes):

  • A.inc_counter() returns 1.
  • B.inc_counter() returns 1.
  • B.call_a() returns 3 (=1+1+1).
  • B.call_b() returns 3 (=1+1+1).

Option 2

Composite queries see each other’s changes if they are in the same call graph:

  • A.inc_counter() returns 1.
  • B.inc_counter() returns 1.
  • B.call_a() returns 4 (=1+1+2). That is: counter=1, b1=1, b2=2.
  • B.call_b() returns 8 (=3+2+3). That is: counter=3, a1=2, a2=3.

Option 3

Composite queries are isolated except when they are recursive:

  • A.inc_counter() returns 1.
  • B.inc_counter() returns 1.
  • B.call_a() returns 3 (=1+1+1).
  • B.call_b() returns 8 (=3+2+3).

Option 4

Composite queries are isolated and recursion is not allowed:

  • A.inc_counter() returns 1.
  • B.inc_counter() returns 1.
  • B.call_a() returns 3 (=1+1+1).
  • B.call_b() returns Error: recursive calls are not allowed.

Tagging everyone how commented in this thread: @skilesare, @Manu, @paulyoung, @jzxchiang, @lastmjs, @Seb, @nomeata.

1 Like