Found while testing #6179. Unrelated to it - the plan-time evaluation in SelectExecutionPlanner.isRidRange is where I was looking, but the divergence is upstream of that.
What happens
Against a document type with 40 records spread over 4 buckets:
SELECT count() as c FROM Doc WHERE @rid > #1:2 -- 37, correct
database.query("sql", "SELECT count() as c FROM Doc WHERE @rid > :p", "p", low) // 0
database.query("sql", "SELECT count() as c FROM Doc WHERE @rid > :p", "p", low.toString()) // 0
The literal form answers correctly. The parameter form returns zero rows, whether the parameter is bound to a RID or to its string spelling, and for values anywhere in the range - including one that should match nearly every record. No error is raised; the query simply returns nothing.
Comparison operators other than > are worth checking too; I only exercised >.
Why it matters
Silent wrong answers, and the shape is an ordinary one: paging through a type by RID with a bound cursor (WHERE @rid > :last ORDER BY @rid LIMIT n) is exactly how a client walks a large type without holding a result set open. Every page comes back empty and the walk looks finished after the first request.
Where to start
SelectExecutionPlanner.isRidRange / clusterMatchesRidRange evaluate the right-hand side at plan time to prune buckets by bucket id, taking Rid.toRecordId when the operand is a RID literal and execute((Result) null, context) otherwise. The literal path is the one that works. Worth establishing first whether the parameter form loses the rows in bucket pruning (an Identifiable that pruned to nothing) or later in the filter, since the fix is in a different place for each.
Found while testing #6179. Unrelated to it - the plan-time evaluation in
SelectExecutionPlanner.isRidRangeis where I was looking, but the divergence is upstream of that.What happens
Against a document type with 40 records spread over 4 buckets:
The literal form answers correctly. The parameter form returns zero rows, whether the parameter is bound to a
RIDor to its string spelling, and for values anywhere in the range - including one that should match nearly every record. No error is raised; the query simply returns nothing.Comparison operators other than
>are worth checking too; I only exercised>.Why it matters
Silent wrong answers, and the shape is an ordinary one: paging through a type by RID with a bound cursor (
WHERE @rid > :last ORDER BY @rid LIMIT n) is exactly how a client walks a large type without holding a result set open. Every page comes back empty and the walk looks finished after the first request.Where to start
SelectExecutionPlanner.isRidRange/clusterMatchesRidRangeevaluate the right-hand side at plan time to prune buckets by bucket id, takingRid.toRecordIdwhen the operand is a RID literal andexecute((Result) null, context)otherwise. The literal path is the one that works. Worth establishing first whether the parameter form loses the rows in bucket pruning (anIdentifiablethat pruned to nothing) or later in the filter, since the fix is in a different place for each.