Skip to content

Commit bf9549b

Browse files
authored
Cursor pagination (#3)
* Cursor pagination * Create the separate file with the PageInfo type.
1 parent 4c87971 commit bf9549b

29 files changed

Lines changed: 5988 additions & 34 deletions

examples/cursor/README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Pagination queries generation example
2+
3+
This is an example based on the "simple" example. It shows how to generate pagination queries using the sqlc tool.
4+
5+
Main differences from the "simple" example:
6+
1. The `storage/query.sql` file contains the `ListAuthors` query without the `limit` and `offset` parameters.
7+
But it has the `-- paginated: offset` comment. It means that the `ListAuthors` query will be paginated by the `offset` parameter.
8+
In the future it will be possible to paginate queries by cursor. In this case the `-- paginated: cursor` comment should be used.
9+
```sql
10+
-- name: ListAuthors :many
11+
-- gql: Query
12+
-- paginated: offset
13+
SELECT * FROM authors
14+
ORDER BY name;
15+
```
16+
2. The `storage/sqlc.yaml` uses the https://github.com/debugger84/sqlc-gen-go plugin to generate the pagination queries instead of the original one.
17+
3. After running the generation the generated code contains the `ListAuthors` query with the `offset` and `limit` parameters. And the `AuthorPage` type returned from the query instead of `[]Author`.
18+
```graphql
19+
type AuthorPage @goModel(model: "cursor/storage.AuthorPage") {
20+
items: [Author!]!
21+
total: Int!
22+
hasNext: Boolean!
23+
}
24+
25+
input ListAuthorsInput @goModel(model: "cursor/storage.ListAuthorsParams") {
26+
limit: Int!
27+
offset: Int!
28+
}
29+
30+
extend type Query {
31+
listAuthors(request: ListAuthorsInput!): AuthorPage!
32+
}
33+
```
34+
## How to use
35+
1. Install sqlc (https://docs.sqlc.dev/en/latest/overview/install.html)
36+
2. Add new queries to the storage/query.sql file
37+
3. Mark all new queries with the comment:
38+
```sql
39+
-- gql: <extended_type>.<query_name>
40+
-- paginated: offset
41+
```
42+
For example:
43+
```sql
44+
-- gql: Query.getAllAuthors
45+
-- paginated: offset
46+
SELECT * FROM authors;
47+
```
48+
49+
3. Run the following commands to generate golang and graphql code:
50+
```bash
51+
cd examples/cursor
52+
sqlc generate -f storage/sqlc.yaml
53+
```
54+
4. The generated code will be in the storage and graphql folders
55+
5. Run the following command to generate the GraphQL resolvers by GraphQl schema:
56+
```bash
57+
go run github.com/99designs/gqlgen generate
58+
```
59+
It will generate the resolvers in the graph/resolver folder.
60+
61+
6. Find the changes in the graph/resolver folder.
62+
The changes will consist of the new queries with panics like this.
63+
```go
64+
func (r *queryResolver) GetAllAuthors(ctx context.Context, request storage.GetAllAuthorsParams) (storage.AuthorPage, error) {
65+
panic(fmt.Errorf("not implemented"))
66+
}
67+
```
68+
69+
7. Change the panic to the real implementation of the query. For example:
70+
```go
71+
func (r *queryResolver) GetAllAuthors(ctx context.Context, request storage.GetAllAuthorsParams) (storage.AuthorPage, error) {
72+
return r.Queries.GetAllAuthors(ctx, request)
73+
}
74+
```
75+
76+
8. Apply migrations to the database:
77+
```bash
78+
docker run --rm -it --network=host -v "$(pwd)/storage:/db" ghcr.io/amacneil/dbmate -u "postgres://postgres:foobar@localhost:5432/test?sslmode=disable" up
79+
```
80+
81+
9. Run the following command to start the server:
82+
```bash
83+
PORT=8081 PG_URI="postgres://postgres:foobar@127.0.0.1:5432/test?sslmode=disable" go run server.go
84+
```
85+
86+
10. Open the browser and go to the http://localhost:8081/. You will see the GraphQL playground.
87+
11. Try to add several authors to the database:
88+
```graphql
89+
mutation {
90+
createAuthor(request:{name:"John Doe", bio:"Unknown guy"}) {
91+
id
92+
name
93+
}
94+
}
95+
```
96+
12. Try to get all authors:
97+
```graphql
98+
{
99+
listAuthors(request:{limit:10, offset:0}){total, hasNext, items{id, name}}
100+
}
101+
102+
```
103+
and you will see the list of authors like this:
104+
```json
105+
{
106+
"data": {
107+
"listAuthors": {
108+
"total": 1,
109+
"hasNext": false,
110+
"items": [
111+
{
112+
"id": 1,
113+
"name": "John Doe"
114+
}
115+
]
116+
}
117+
}
118+
}
119+
```

examples/cursor/go.mod

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module cursor
2+
3+
go 1.22.2
4+
5+
require (
6+
github.com/99designs/gqlgen v0.17.49
7+
github.com/jackc/pgx/v5 v5.6.0
8+
github.com/vektah/gqlparser/v2 v2.5.16
9+
)
10+
11+
require (
12+
github.com/agnivade/levenshtein v1.1.1 // indirect
13+
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
14+
github.com/google/uuid v1.6.0 // indirect
15+
github.com/gorilla/websocket v1.5.0 // indirect
16+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
17+
github.com/jackc/pgpassfile v1.0.0 // indirect
18+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
19+
github.com/kr/text v0.1.0 // indirect
20+
github.com/mitchellh/mapstructure v1.5.0 // indirect
21+
github.com/rogpeppe/go-internal v1.12.0 // indirect
22+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
23+
github.com/sosodev/duration v1.3.1 // indirect
24+
github.com/urfave/cli/v2 v2.27.2 // indirect
25+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
26+
golang.org/x/crypto v0.20.0 // indirect
27+
golang.org/x/mod v0.18.0 // indirect
28+
golang.org/x/sync v0.7.0 // indirect
29+
golang.org/x/text v0.16.0 // indirect
30+
golang.org/x/tools v0.22.0 // indirect
31+
gopkg.in/yaml.v3 v3.0.1 // indirect
32+
)

examples/cursor/go.sum

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ=
2+
github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0=
3+
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
4+
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
5+
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
6+
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
7+
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
8+
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
9+
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
10+
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
11+
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
12+
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
13+
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
14+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
15+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
16+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
17+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
18+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
19+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
20+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
21+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
22+
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
23+
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
24+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
25+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
26+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
27+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
28+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
29+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
30+
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
31+
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
32+
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
33+
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
34+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
35+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
36+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
37+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
38+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
39+
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
40+
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
41+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
42+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
43+
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
44+
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
45+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
46+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
47+
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
48+
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
49+
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
50+
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
51+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
52+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
53+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
54+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
55+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
56+
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
57+
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
58+
github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
59+
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
60+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
61+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
62+
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
63+
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
64+
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
65+
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
66+
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
67+
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
68+
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
69+
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
70+
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
71+
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
72+
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
73+
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
74+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
75+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
76+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
77+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
78+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
79+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
80+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
81+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

examples/cursor/gqlgen.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
2+
schema:
3+
- graphql/*.graphql
4+
5+
# Where should the generated server code go?
6+
exec:
7+
filename: graph/gen/generated.go
8+
package: gen
9+
10+
# Uncomment to enable federation
11+
# federation:
12+
# filename: graph/federation.go
13+
# package: graph
14+
15+
# Where should any generated models go?
16+
model:
17+
filename: graph/model/models_gen.go
18+
package: model
19+
20+
# Where should the resolver implementations go?
21+
resolver:
22+
layout: follow-schema
23+
dir: graph/resolver
24+
package: resolver
25+
filename_template: "{name}.resolvers.go"
26+
# Optional: turn on to not generate template comments above resolvers
27+
# omit_template_comment: false
28+
29+
# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
30+
# struct_tag: json
31+
32+
# Optional: turn on to use []Thing instead of []*Thing
33+
omit_slice_element_pointers: true
34+
35+
# Optional: turn on to omit Is<Name>() methods to interface and unions
36+
# omit_interface_checks : true
37+
38+
# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
39+
# omit_complexity: false
40+
41+
# Optional: turn on to not generate any file notice comments in generated files
42+
# omit_gqlgen_file_notice: false
43+
44+
# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true.
45+
# omit_gqlgen_version_in_file_notice: false
46+
47+
# Optional: turn off to make struct-type struct fields not use pointers
48+
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
49+
# struct_fields_always_pointers: true
50+
51+
# Optional: turn off to make resolvers return values instead of pointers for structs
52+
resolvers_always_return_pointers: false
53+
54+
# Optional: turn on to return pointers instead of values in unmarshalInput
55+
# return_pointers_in_unmarshalinput: false
56+
57+
# Optional: wrap nullable input fields with Omittable
58+
# nullable_input_omittable: true
59+
60+
# Optional: set to speed up generation time by not performing a final validation pass.
61+
# skip_validation: true
62+
63+
# Optional: set to skip running `go mod tidy` when generating server code
64+
# skip_mod_tidy: true
65+
66+
# gqlgen will search for any type names in the schema in these go packages
67+
# if they match it will use them, otherwise it will generate them.
68+
autobind:
69+
# - "cursor/graph/model"
70+
71+
# This section declares type mapping between the GraphQL and go type systems
72+
#
73+
# The first line in each type will be used as defaults for resolver arguments and
74+
# modelgen, the others will be allowed when binding to fields. Configure them to
75+
# your liking
76+
models:
77+
ID:
78+
model:
79+
- github.com/99designs/gqlgen/graphql.ID
80+
- github.com/99designs/gqlgen/graphql.Int
81+
- github.com/99designs/gqlgen/graphql.Int64
82+
- github.com/99designs/gqlgen/graphql.Int32
83+
Int:
84+
model:
85+
- github.com/99designs/gqlgen/graphql.Int
86+
- github.com/99designs/gqlgen/graphql.Int64
87+
- github.com/99designs/gqlgen/graphql.Int32

0 commit comments

Comments
 (0)