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.576 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )