DBIO-GraphQL

 view release on metacpan or  search on metacpan

docs/adr/0005-stateless-base64-pk-cursors.md  view on Meta::CPAN


Cursors are **stateless, self-describing, base64-encoded primary keys**, with
no server-side state and no extra dependencies beyond core `MIME::Base64`
(`GraphQL.pm`, `_encode_cursor` / `_decode_cursor`).

- A cursor is `base64( val1:val2:... )` of the **last returned row's primary
  key** (all PK columns, in order — composite PKs supported). Colons and
  percent signs inside values are percent-escaped (`%` → `%25`, `:` → `%3A`)
  so the `:` separator is unambiguous; `_decode_cursor` reverses this.
- `after` applies a **`pk > value` seek**: for a single-column PK,
  `{ $pk => { '>' => $decoded } }`; for a composite PK, a per-column
  `{ $pk[i] => { '>' => $decoded[i] } }` condition. The query then fetches
  `first + 1` rows to compute `hasNextPage`, and emits `nextCursor` from the
  last kept row's PK.
- **Cursor wins over `page`.** When both `cursor` and `page` are supplied,
  cursor pagination is taken and offset pagination is ignored
  (`_apply_pagination` checks `$args->{cursor}` first; the precedence is
  documented in the helper's comment).
- Ordering defaults to ascending PK (`_apply_pagination` orders by
  `primary_columns` when no `orderBy` is given), which is what makes the
  `pk > value` seek correspond to "the next page."

lib/DBIO/GraphQL.pm  view on Meta::CPAN

  else {
    # Default: stable order by PK
    $rs = $rs->search(undef, { order_by => [ map { { -asc => $_ } } @pk_cols ] });
  }

  if (my $cursor_args = $args->{cursor}) {
    my $first = $cursor_args->{first} // 10;
    my $after = $cursor_args->{after};

    if ($after) {
      my @decoded = _decode_cursor($after);
      if (@pk_cols == 1) {
        $rs = $rs->search({ $pk_cols[0] => { '>' => $decoded[0] } });
      }
      else {
        my %after_cond = map { $pk_cols[$_] => { '>' => $decoded[$_] } }
                             0 .. $#pk_cols;
        $rs = $rs->search(\%after_cond);
      }
    }

    my @rows    = $rs->search(undef, { rows => $first + 1 })->all;
    my $has_next = @rows > $first;
    @rows = @rows[0 .. $first - 1] if $has_next;

    my $next_cursor;



( run in 0.526 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )