feat: Add opt-in OffsetPageInfo metadata for paginated GraphQL queries - #2191
feat: Add opt-in OffsetPageInfo metadata for paginated GraphQL queries#2191velo wants to merge 14 commits into
Conversation
Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2191 +/- ##
============================================
+ Coverage 16.34% 16.70% +0.35%
- Complexity 1027 1061 +34
============================================
Files 618 620 +2
Lines 17910 18164 +254
Branches 2193 2241 +48
============================================
+ Hits 2928 3034 +106
- Misses 14683 14826 +143
- Partials 299 304 +5 ☔ View full report in Codecov by Harness. |
|
I like the idea to add this. Would be nice if we could also generate this GraphQL schema code. Couple of thoughts:
|
I think we would need a hint for that right?! Or do we want to always generate as paginated? |
…azily from the selection set Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
Yes, but not a hint - it would be a compiler config setting to generated paginated, default false (for now to be backwards compatible). |
Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
…raphQL schema Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
|
Neutral name |
…ecting it Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
…e when limit is unbounded Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
…wtime column Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
There was a problem hiding this comment.
These changes in this class are write-only AI code completely and i do not like it one single bit.
The newly introduced OffsetPageInfo reuses the LIMIT_AND_OFFSET code and it results in some weird mixed execution I do not want to debug 3months from now.
I suggested to introduce a new pagination type in my other comment. We should use that here and separate the executions, possibly reusing any common logic needed for LIMIT_AND_OFFSET, but we need clear abstraction layers and an execution flow that a human can debug without losing their sanity.
…dex paged rowtime columns Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
…nd document pagination Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
…lPlanRewriter` usage
What
Adds opt-in pagination metadata for user-authored GraphQL schemas. A query whose result type is a page wrapper — an object with a list field plus a
OffsetPageInfofield — returns its rows plus pagination metadata computed from a companionCOUNT(*)query.The standard
OffsetPageInfotype is auto-injected when referenced but not defined (and validated for an exact match when the user defines it):How
compiler.api.paginated-results(defaultfalse) generates<Type>Page {results, pagination}wrappers plus theOffsetPageInfotype for every multi-row query and relationship; with the default the inferred schema is unchanged.OffsetPageInfo. Only valid forMANYquery functions that declarelimit/offset.sqrl-planner):GraphqlSchemaWalkersees through the wrapper and validates/walks the element type against the table-function row type;GraphqlModelGeneratoremits two companion aggregate queries overFROM (<baseSql>) x:countSql(COUNT(*)only) andcountWithEventTimesSql(COUNT(*)+MIN/MAXover the rowtime column, absent when the result has no rowtime).OffsetPageInfoUtilinjects/validates the standard type.sqrl-server):SqlQuerygains nullablecountSql/countWithEventTimesSql(@JsonInclude(NON_NULL)→ oldvertx.jsonstill loads, existing snapshots unchanged).VertxQueryExecutionContextcomputes metadata lazily from the selection set: the aggregate query only runs whentotalRecords/totalPages(count variant) orfirstEventTime/lastEventTime(count+MIN/MAX variant) are selected, and runs in parallel with the data query. When onlyhasNextPage/nextOffsetare selected, no aggregate runs — the data query fetchesLIMIT+1rows and the extra row is trimmed. Selecting onlyresults(or only offset-derived fields likecurrentPage/hasPreviousPage) adds no extra work at all. Metadata math is a pure static method.Tests
PaginationMetadataTest— 7 unit cases for the metadata math (empty, first/middle/last/beyond-end pages,limit=0, event-time passthrough).UseCaseCompileTest/DAGWriterJsonTest— newclickstream/package-paginated.jsonusecase snapshots the generated paginated schema (wrappers,OffsetPageInfo, count SQL with bound parameters); all other usecase snapshots are byte-identical, proving the default is backwards compatible.PagedQueryIT— real-Postgres test with a recordingSqlClientproving the lazy execution: no aggregate query when onlyresultsor offset-derived fields are selected, LIMIT+1 (and trimming) forhasNextPagealone, plain-COUNT variant for totals, COUNT+MIN/MAX variant for event times.GraphQLValidationTest— 6 new schema cases: 2 positive (auto-injected type; user-defined type with non-conventional field names + a paged relationship) and 4 negative (wrong pagination type, missing limit/offset, paged subscription, unknown element field)..graphqls.Follow-up
Runtime execution (count query + wrapper assembly) is exercised by unit tests and consistent with the existing list path, but a full-pipeline usecase under
FullUseCaseIT(Postgres/Flink/Kafka containers) assertingOffsetPageInfopayloads across page boundaries is a recommended follow-up — it needs the heavy container harness.🤖 Generated with Claude Code