← 1.0.0 scope SAPE-22

A count must declare how far it walks

Status
Done
Version
1.0.0
Component
server, store
Commits
dde3995

Description

ActionCount was the one action that could be declared without declaring what it costs. The limit was asked for on a scan, on a rollup read and on a composed batch; a count slipped between them. That made "every declaration in this store says what it costs" a sentence with one exception in it, and an exception reachable by writing "action": "count" and leaving one field out.

validateOperation's case ActionScan, ActionCount branch (internal/store/ops.go:683) now refuses Limit <= 0 for both, with a different sentence for each: a scan declares how many rows it may return, a count declares how far it walks. The walk is the whole cost of a count — a count over forty million documents returns the number 40000000, and the caller finds out how big the collection was by waiting.

A Limit < 0 refusal used to sit further down, after scanAcross, and it was the only thing catching a negative limit on a count. The new rule covers every non-positive limit for both actions, so that branch became unreachable and was removed — an unreachable refusal is a rule a reader believes is doing work.

Why it was in 1.0.0

It is a breaking change to a declaration that was previously accepted: an existing count declaration with no limit stops being declarable. After a tag that is a migration somebody has to run; before it, it is a rule nobody has written against yet. It is listed under Breaking in the CHANGELOG for that reason.

The second reason is SAPE-20. compose.go's ceiling() has asked a count for a limit since composed operations landed, so a count without one was already unusable as a leg — the two rules had to agree before either shipped.

How it was verified

  1. Over the wire: TestACountDeclaredOverTheWireMustSayHowFarItWalks (internal/server/count_limit_test.go:27), which also pins that this fires through the Declare frame and not only through sapedb apply.
  2. Through apply: the shared table in TestDeclaringOverTheWireRefusesExactlyWhatApplyRefuses (internal/cli/declare_apply_test.go:44) — one table, both paths, so the two cannot drift apart.
  3. count_limit_test.go:73 records what the old Limit < 0 branch used to catch, so removing it is a measured removal rather than a deletion nobody checked.
  4. The desktop workbench declares counts through the same rule: 89db51a in sape-desktop ("a count declares its walk here too").
  5. CHANGELOG prints the refusal verbatim: sapedb/store: the declaration does not make sense: a count must declare how far it walks…

What it did not do