A step of a batch may now name an operation that is already declared, instead of a collection.
Step gains operation, version and with; a step does
one or the other and never both, and writing both is refused when it is declared
(internal/store/compose.go:217).
The point is the ceiling. Store.ceiling (compose.go:99) reads the most rows
an operation may return straight out of its declaration: 1 for a get or a write, the declared
Limit for a scan or a rollup read, 1 for a count, and for a batch the sum of its steps'
ceilings. Five rules hold it together, of which the load-bearing two are N4 — a
term naming another step may only name one whose ceiling is 1 — and N5 —
a batch with a composed step declares a limit, and its steps' ceilings sum at or below it.
N4 is the rule that decides what this product is. Taking a value from a step that may hand back many rows means "run this once for each row that one returned": a for loop written in JSON, and a for loop is the first half of the expression language this store exists not to have. The ceiling would stop being a sum and become a product, and a product of three fifties is 125,000 rows behind three numbers none of which makes a reader look twice.
It is the answer to the standing objection — "no query language means K round trips for K
reads" — and answering it after the wire is public means adding fields to Step
after clients have shipped decoders for it. The ceiling rule is the same: a limit that is a sum today
and a product tomorrow is not a fix, it is a different product.
TestTheCeilingOfAComposedOperationAtAnyDepthIsTheLimitItDeclares
(internal/store/compose_test.go:438) builds a tower: level 0 is a scan with limit 50,
and every level above has two legs of the level below, declaring exactly the sum — 100, 200,
400. A reading where a leg runs once per row of another leg would put 50×50 = 2500 at level 1
alone and 2500×2500 at level 2. The numbers are what say which reading the code
implements.TestRedeclaringACalleeDoesNotMoveTheCeilingOfWhatAlreadyCallsIt
(compose_test.go:590) — a pinned version is what makes the ceiling a fact about a
declaration rather than about the store's state at call time.ErrDamaged
(compose.go:124): the records disagree with the rule that wrote them, which is damage,
not a user error.330d2f1 added a mutation case for the ceiling rule that nothing else caught —
the rule was measured against a mutant, not only against an example.408257b: a key taken from an earlier step is type-checked like an argument, at
declaration time, through keyTypeOf.TestAComposedOperationIsDeclaredOnARunningDaemonAndAnsweredInOneCall
(declare_live_test.go:438).internal/store/batch.go:145-150 rather than discovered —
ISS-5.action: batch whether or
not it writes, so it takes the write lock and loses what
SAPE-18 paid for — ISS-9.validateOperation
(internal/store/ops.go:486) resolves operation.Collection before it
branches on the action, so a batch that only calls other operations must name a real collection it
never touches — ISS-10.