seekAfter method

Query<R, F> seekAfter(
  1. List<CursorTerm> cursor(
    1. F
    )
)

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],
    ),
  );
}