successor
The next key in this ordering: the smallest key strictly greater than this one.
A zero byte appended, and it is exact rather than approximate. Under unsigned lexicographic comparison a shorter key that is a prefix of a longer one sorts first, so nothing can lie between k and k + 0x00. It is also total — keys have no maximum length, so there is no "last key" for this to fail on, which is what lets a range walk use it without a special case at the end.
This is how an exclusive lower bound is spelled. Every range in this API is inclusive at both ends, which is the right default for "delete July" and the wrong one for "carry on from where I stopped" — a resumable walk that restarted at the key it last handled would hand that key over twice. It is the one thing a drain loop needs that the inclusive bounds cannot say:
var watermark: Key? = null
while (true) {
val batch = db.scan(from = watermark, snapshot = view).use { … }
if (batch.isEmpty()) break
ship(batch)
watermark = batch.last().key.successor() // resume *after* it, never at it
}Cheap, and not free: the key is one byte longer than its predecessor, so a watermark carried through many rounds should be recomputed from the last key handled rather than by calling this on its own result.
It is not a prefix bound, and that mistake is silent. scan(p, p.successor()) is a range containing p alone — p + 0x00 is the smallest key above p, so nothing under p except p itself lies inside it — and a deleteRange spelled that way deletes at most one document, returns a count saying so, and reports success. Two separate callers in this repository reached for it that way; for "everything under p" use DocumentStore.scanPrefix and DocumentStore.deletePrefix, which take the prefix itself. startsWith carries the argument for why there is no arithmetic to offer instead.