cursorPage method

Future<MssqlCursorPage<TRow>> cursorPage({
  1. int size = 25,
  2. MssqlCursor? after,
  3. MssqlCursor? before,
  4. MssqlReadConsistency consistency = MssqlReadConsistency.committed,
})

A keyset page. after and before are mutually exclusive.

Composite ordering becomes nested AND/OR, not a tuple comparison. Reverse (before) results are returned in the caller's order. The cursor is an object; its keys are bound as parameters.

Implementation

Future<MssqlCursorPage<TRow>> cursorPage({
  int size = 25,
  MssqlCursor? after,
  MssqlCursor? before,
  MssqlReadConsistency consistency = MssqlReadConsistency.committed,
}) async {
  if (after != null && before != null) {
    throw ArgumentError(
      'cursorPage cannot take after and before together. Ask for one '
      'direction per call.',
    );
  }
  if (size <= 0) {
    throw ArgumentError.value(size, 'size', 'Must be positive.');
  }
  final ordered = ensureStableOrder();
  final dialect = await _resolvedDialect();
  final orders = ordered.state.orderBy;
  MssqlKeyset.requireNamedColumns(orders, 'cursorPage()');
  final orderSig = MssqlKeyset.orderSignature(orders, dialect);
  final filterSig = MssqlKeyset.filterSignature(
    binding: binding.erase(),
    state: ordered.state,
    dialect: dialect,
  );
  if (after != null) {
    MssqlKeyset.check(
      after,
      orderSignature: orderSig,
      filterSignature: filterSig,
      keyCount: orders.length,
    );
  }
  if (before != null) {
    MssqlKeyset.check(
      before,
      orderSignature: orderSig,
      filterSignature: filterSig,
      keyCount: orders.length,
    );
  }
  final walking = before != null ? MssqlKeyset.reverseOrders(orders) : orders;
  final cursor = after ?? before;
  final scopes = ordered.context.scopeCompiler(ordered.state.scope);
  final loader = ordered._loaderFor(session, dialect);
  return mssqlRunConsistent(
    session: session,
    consistency: consistency,
    body: (s) async {
      var query = ordered._select(orderBy: walking, scopes: scopes);
      if (cursor != null) {
        query = query.where(MssqlKeyset.after(walking, cursor.keys));
      }
      final fetched = await query
          .top(size + 1)
          .get(
            s,
            dialect: dialect,
            timeout: state.options.timeout,
            cancellationToken: state.options.cancellationToken,
            options: state.options,
          );
      final hasExtra = fetched.length > size;
      final kept = hasExtra ? fetched.sublist(0, size) : fetched;
      var mapped = kept.map(binding.fromRow).toList();
      if (before != null) {
        mapped = mapped.reversed.toList();
      }
      final parents = await ordered._attachOn(
        s,
        mapped,
        dialect: dialect,
        loader: loader.withSession(s),
      );
      if (kept.isEmpty) {
        return MssqlCursorPage<TRow>(rows: parents, hasMore: false);
      }
      final firstRaw = before != null ? kept.last : kept.first;
      final lastRaw = before != null ? kept.first : kept.last;
      MssqlCursor make(MssqlRow row) => MssqlKeyset.encode(
        orderSignature: orderSig,
        filterSignature: filterSig,
        keys: MssqlKeyset.keysFromRow(row, orders),
      );
      return MssqlCursorPage<TRow>(
        rows: parents,
        hasMore: hasExtra,
        next: before != null || hasExtra ? make(lastRaw) : null,
        previous: after != null || (before != null && hasExtra)
            ? make(firstRaw)
            : null,
      );
    },
  );
}