seekAfter method
Adds a lexicographic boundary after cursor and replaces query ordering.
Existing WHERE filters remain in effect. Cursor terms must use distinct columns from this table, with explicit NULL ordering for nullable columns.
Implementation
Query<R, F> seekAfter(List<CursorTerm> Function(F) cursor) {
final terms = List<CursorTerm>.unmodifiable(cursor(queryFields));
_validateCursor(terms.map((t) => t.order).toList());
SqlNode? predicate;
SqlNode? prefix;
for (final term in terms) {
final node = term.order.expression.expressionNode;
final last = term.assignedValue;
final isNull = UnaryNode('IS NULL', node, postfix: true);
SqlNode comparison;
if (last == null) {
comparison = term.order.nulls == NullOrder.first
? UnaryNode('IS NOT NULL', node, postfix: true)
: RawNode(const ['FALSE'], const []);
} else {
comparison = BinaryNode(
node,
term.order.descending ? '<' : '>',
ParameterNode(last, storageType: term.order.expression.codec.sqlType),
);
if (term.order.nulls == NullOrder.last) {
comparison = BinaryNode(comparison, 'OR', isNull);
}
}
final branch = prefix == null
? comparison
: BinaryNode(prefix, 'AND', comparison);
predicate = predicate == null
? branch
: BinaryNode(predicate, 'OR', branch);
final equal = last == null
? isNull
: BinaryNode(
node,
'=',
ParameterNode(
last,
storageType: term.order.expression.codec.sqlType,
),
);
prefix = prefix == null ? equal : BinaryNode(prefix, 'AND', equal);
}
final next = Expr<bool?>.internal(predicate!, Codecs.boolean.nullable());
return copyQuery(
queryState.copy(
predicate: queryState.predicate?.and(next) ?? next,
order: [for (final term in terms) term.order],
),
);
}