https://github.com/hanpama/pgcc
Relay Cursor Connection implementation for Golang and PostgresQL https://facebook.github.io/relay/graphql/connections.htm
https://github.com/hanpama/pgcc
golang graphql postgresql relay sql
Last synced: about 1 year ago
JSON representation
Relay Cursor Connection implementation for Golang and PostgresQL https://facebook.github.io/relay/graphql/connections.htm
- Host: GitHub
- URL: https://github.com/hanpama/pgcc
- Owner: hanpama
- Created: 2019-10-04T01:18:32.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2019-10-11T08:35:36.000Z (over 6 years ago)
- Last Synced: 2025-01-11T14:47:09.395Z (about 1 year ago)
- Topics: golang, graphql, postgresql, relay, sql
- Language: Go
- Homepage:
- Size: 13.7 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# PGCC
Relay Cursor Connection implementation for Golang and PostgresQL
A connection contains its paginated edges and pagination info.
* A Cursor is a string which can uniquely identify the edge's position in the entire list.
* Each edge has a node as its target, so the node ids are good to be cursors.
```
town
id(pk) created
------- ----------
town-20 2019-09-06
town-19 2019-09-05
town-18 2019-09-04
town-17 2019-09-03
town-16 2019-09-02
town-15 2019-09-01
town-14 2019-08-31
town-13 2019-08-30
...
```
When querying table `town` order by `created DESC`, You can use `id` as its cursor.
Let's find the first 5 edges after `town-19`.
```
town
id created
------- ----------
town-20 2019-09-06
town-19 2019-09-05 <- pointed by `after`
town-18 2019-09-04 <- 1
town-17 2019-09-03 <- 2
town-16 2019-09-02 <- 3
town-15 2019-09-01 <- 4
town-14 2019-08-31 <- 5
town-13 2019-08-30
...
```
Its actual query is like:
```sql
SELECT id FROM town
WHERE created < (SELECT created FROM town WHERE id = 'town-19') -- maybe indexed?
ORDER BY created DESC
LIMIT 5
```
How about 'backward pagination' using `last` and `before`?
We are going to `ORDER` the rows `BY created ASC` but we should not reverse the order of edges in results.
(https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-order)
For example, when we query last 3 edges before `town-4`:
```
town
id created
------- ----------
town-1 2019-08-03
town-2 2019-08-04
town-3 2019-08-05
town-4 2019-08-06 <- pointed by `before`
town-5 2019-08-07 <- 1
town-6 2019-08-08 <- 2
town-7 2019-08-09 <- 3
town-8 2019-08-10
...
```
The query will be like:
```sql
WITH __backward_edges__ AS (
SELECT id FROM town
WHERE created > (SELECT created FROM town WHERE id = 'town-4') -- still can use index
ORDER BY created ASC
LIMIT 3
)
SELECT * FROM __backward_edges__
ORDER BY created DESC
```
Because `town-7` should appear before `town-6`, we reversed the `__backward_edges__` here.
```go
qb := pgcc.NewQueryBuilder(pgcc.Options{
TableName: "town", // Table to query
Cursor: "id", // should be a key in the table
Select: "created", // additional columns to select
SortKeys: []pgcc.SortKey{ // defines the sort orders for this connection
{Order: "DESC", Select: "created"},
{Order: "ASC", Select: "id"},
},
})
type townEdge struct {
Cursor string `json:"cursor"`
Created string `json:"created"`
}
type townConnection struct {
pgcc.PageInfo
Edges []townEdge `json:"edges"`
}
q := qb.Paginate()
q.SetFirst(2)
var src townConnection
var b []byte
err = tx.QueryRow(q.SQL(), q.Args()...).Scan(&b)
if err != nil {
// ...
}
err = json.Unmarshal(b, &src)
if err != nil {
// ...
}
```
It generates a query like:
```sql
WITH __after__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE id = $2 AND TRUE LIMIT 1
), __before__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE id = $4 AND TRUE LIMIT 1
)
SELECT
json_build_object(
'edges', __edges__.result,
'has_next_page', (SELECT (
CASE
WHEN $1 > 0 then (
WITH __limit_or_more__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE TRUE
AND CASE (SELECT TRUE FROM __after__)
WHEN TRUE THEN (created < (select "created_DESC" FROM __after__) OR (created = (select "created_DESC" FROM __after__)AND id > (select "id_ASC" FROM __after__)))
ELSE TRUE
END
AND CASE (SELECT TRUE FROM __before__)
WHEN TRUE THEN (created > (select "created_DESC" FROM __before__) OR (created = (select "created_DESC" FROM __before__)AND id < (select "id_ASC" FROM __before__)))
ELSE TRUE
END
LIMIT $1 + 1
)
SELECT count(*) > $1 FROM __limit_or_more__
)
WHEN $4 IS NOT NULL then (
WITH __zero_or_one__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE TRUE
AND CASE (SELECT TRUE FROM __before__)
WHEN TRUE THEN (created < (select "created_DESC" FROM __before__) OR (created = (select "created_DESC" FROM __before__)AND id > (select "id_ASC" FROM __before__)))
ELSE TRUE
END
LIMIT 1
)
SELECT COUNT(*) > 0 FROM __zero_or_one__
)
ELSE FALSE
END
) AS result),
'has_previous_page', (SELECT (
CASE
WHEN $3 > 0 then (
WITH __limit_or_more__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE TRUE
AND CASE (SELECT TRUE FROM __after__)
WHEN TRUE THEN (created < (select "created_DESC" FROM __after__) OR (created = (select "created_DESC" FROM __after__)AND id > (select "id_ASC" FROM __after__)))
ELSE TRUE
END
AND CASE (SELECT TRUE FROM __before__)
WHEN TRUE THEN (created > (select "created_DESC" FROM __before__) OR (created = (select "created_DESC" FROM __before__)AND id < (select "id_ASC" FROM __before__)))
ELSE TRUE
END
LIMIT $3 + 1
)
SELECT count(*) > $3 FROM __limit_or_more__
)
WHEN $2 IS NOT NULL then (
WITH __zero_or_one__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE TRUE
AND CASE (SELECT TRUE FROM __after__)
WHEN TRUE THEN (created > (select "created_DESC" FROM __after__) OR (created = (select "created_DESC" FROM __after__)AND id < (select "id_ASC" FROM __after__)))
ELSE TRUE
END
LIMIT 1
)
SELECT COUNT(*) > 0 FROM __zero_or_one__
)
ELSE FALSE
END
) AS result),
'start_cursor', CAST(__edges__.result ->> 0 AS JSON) ->> 'cursor',
'end_cursor', CAST(__edges__.result ->> json_array_length(__edges__.result)-1 AS JSON) ->> 'cursor'
)
FROM (WITH __forward_edges__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE NOT (($1 + 0) IS NULL AND ($3 + 0) IS NOT NULL)
AND TRUE
AND CASE (SELECT TRUE FROM __after__)
WHEN TRUE THEN (created < (select "created_DESC" FROM __after__) OR (created = (select "created_DESC" FROM __after__)AND id > (select "id_ASC" FROM __after__)))
ELSE TRUE
END
AND CASE (SELECT TRUE FROM __before__)
WHEN TRUE THEN (created > (select "created_DESC" FROM __before__) OR (created = (select "created_DESC" FROM __before__)AND id < (select "id_ASC" FROM __before__)))
ELSE TRUE
END
ORDER BY
"created_DESC" DESC,
"id_ASC" ASC, id ASC
LIMIT $1
), __backward_edges__ AS (
SELECT created AS "created_DESC", id AS "id_ASC", id AS cursor, created FROM town
WHERE $1 + 0 IS NULL AND $3 > 0
AND TRUE
AND CASE (SELECT TRUE FROM __after__)
WHEN TRUE THEN (created < (select "created_DESC" FROM __after__) OR (created = (select "created_DESC" FROM __after__)AND id > (select "id_ASC" FROM __after__)))
ELSE TRUE
END
AND CASE (SELECT TRUE FROM __before__)
WHEN TRUE THEN (created > (select "created_DESC" FROM __before__) OR (created = (select "created_DESC" FROM __before__)AND id < (select "id_ASC" FROM __before__)))
ELSE TRUE
END
ORDER BY
"created_DESC" ASC,
"id_ASC" DESC, id DESC
LIMIT $3
)
SELECT COALESCE(JSON_AGG(__edgerows__.result), '[]'::json) AS result FROM (
SELECT ROW_TO_JSON(__rawedges__.*) AS result FROM (
(SELECT __forward_edges__.* FROM __forward_edges__
OFFSET (
CASE ($1 > 0 AND $3 > 0)
WHEN TRUE
THEN GREATEST(COALESCE(0 - $3 + (SELECT count(*) FROM __forward_edges__), 0), 0)
ELSE 0
END
))
UNION
SELECT __backward_edges__.* FROM __backward_edges__
ORDER BY "created_DESC" DESC, "id_ASC" ASC, cursor ASC
) as __rawedges__
) __edgerows__) __edges__
```