Skip to content

Peek an extra row so hasNextPage avoids a second query - #5775

Open
drhops wants to merge 2 commits into
rmosolgo:masterfrom
drhops:relation-connection-peek-next-page
Open

drhops wants to merge 2 commits into
rmosolgo:masterfrom
drhops:relation-connection-peek-next-page

Conversation

@drhops

@drhops drhops commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #5773.

Problem

When a query selects both the page and pageInfo { hasNextPage }, RelationConnection runs two queries. One loads the page and the other checks for a row past it. The spec already notes this: "A better implementation would load first + 1 nodes and use that to set has_next_page."

Change

When hasNextPage is selected and the page is loaded anyway (nodes, edges, startCursor or endCursor), the connection loads first + 1 rows. It returns first of them and sets has_next_page from whether the extra row exists. That's one query instead of two.

This follows the lookahead approach suggested in #4908 :

  • ConnectionExtension adds extras [:lookahead], and Connection gets a lookahead accessor.
  • The lookahead is attached in ConnectionExtension#after_resolve and in Execution::Next's FieldResolveStep#finish_extensions, because Next doesn't call after_resolve. In both, the field's own arguments stay free of the lookahead.

What doesn't change

  • If hasNextPage isn't selected, the query fetches exactly first rows.
  • If only hasNextPage is selected, it keeps the existing separate check.
  • last and before, a relation already limited below first, and connections built without a lookahead all behave as before.

Cost

Every connection field now builds a Lookahead when it resolves (a few small objects), and :lookahead shows up in field.extras.

…ock engages

RelationConnection#async_dataloader? read `context[:dataloader]`, but
Multiplex stores the dataloader on the multiplex context, not on the
query context a connection holds, so the key is nil for schemas that
`use GraphQL::Dataloader::AsyncDataloader` and the locks added in rmosolgo#5708
and rmosolgo#5717 never engaged. Read it through Query::Context#dataloader
instead, which falls back to the multiplex dataloader, and build the
Fiber specs' connections from a real query context so they exercise the
same path execution does.
When a forward page (`first:`, no `last`/`before`) selects `hasNextPage`
and also loads the page (`nodes`, `edges`, `startCursor` or `endCursor`),
load `first + 1` rows and answer `hasNextPage` from whether the extra row
came back, instead of running a second LIMIT query. Pages without
`hasNextPage`, and `hasNextPage`-only queries, run exactly what they did
before.

ConnectionExtension now asks for the `:lookahead` extra and hands it to the
connection, under both the legacy interpreter and Execution::Next. The extra
is kept out of `connection.arguments`.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant