{"id":16485904,"url":"https://github.com/hanpama/pgcc","last_synced_at":"2025-02-28T22:42:24.149Z","repository":{"id":89902825,"uuid":"212713066","full_name":"hanpama/pgcc","owner":"hanpama","description":"Relay Cursor Connection implementation for Golang and PostgresQL https://facebook.github.io/relay/graphql/connections.htm","archived":false,"fork":false,"pushed_at":"2019-10-11T08:35:36.000Z","size":14,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-11T14:47:09.395Z","etag":null,"topics":["golang","graphql","postgresql","relay","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hanpama.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-04T01:18:32.000Z","updated_at":"2021-09-21T12:27:17.000Z","dependencies_parsed_at":"2023-04-16T17:30:59.462Z","dependency_job_id":null,"html_url":"https://github.com/hanpama/pgcc","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"1fc9b94ca9cee3988174f62cb102d4c63f854f29"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanpama%2Fpgcc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanpama%2Fpgcc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanpama%2Fpgcc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hanpama%2Fpgcc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hanpama","download_url":"https://codeload.github.com/hanpama/pgcc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241262369,"owners_count":19936046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","graphql","postgresql","relay","sql"],"created_at":"2024-10-11T13:27:41.263Z","updated_at":"2025-02-28T22:42:24.144Z","avatar_url":"https://github.com/hanpama.png","language":"Go","readme":"# PGCC\n\nRelay Cursor Connection implementation for Golang and PostgresQL\n\nA connection contains its paginated edges and pagination info.\n\n* A Cursor is a string which can uniquely identify the edge's position in the entire list.\n* Each edge has a node as its target, so the node ids are good to be cursors.\n\n\n```\ntown\n\nid(pk)    created\n-------   ----------\ntown-20   2019-09-06\ntown-19   2019-09-05\ntown-18   2019-09-04\ntown-17   2019-09-03\ntown-16   2019-09-02\ntown-15   2019-09-01\ntown-14   2019-08-31\ntown-13   2019-08-30\n...\n```\n\nWhen querying table `town` order by `created DESC`, You can use `id` as its cursor.\n\nLet's find the first 5 edges after `town-19`.\n\n```\ntown\n\nid        created\n-------   ----------\ntown-20   2019-09-06\ntown-19   2019-09-05 \u003c- pointed by `after`\ntown-18   2019-09-04 \u003c- 1\ntown-17   2019-09-03 \u003c- 2\ntown-16   2019-09-02 \u003c- 3\ntown-15   2019-09-01 \u003c- 4\ntown-14   2019-08-31 \u003c- 5\ntown-13   2019-08-30\n...\n```\n\nIts actual query is like:\n\n```sql\nSELECT id FROM town\nWHERE created \u003c (SELECT created FROM town WHERE id = 'town-19') -- maybe indexed?\nORDER BY created DESC\nLIMIT 5\n```\n\n\nHow about 'backward pagination' using `last` and `before`?\n\nWe are going to `ORDER` the rows `BY created ASC` but we should not reverse the order of edges in results.\n(https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-order)\n\nFor example, when we query last 3 edges before `town-4`:\n\n```\ntown\n\nid        created\n-------   ----------\ntown-1    2019-08-03\ntown-2    2019-08-04\ntown-3    2019-08-05\ntown-4    2019-08-06 \u003c- pointed by `before`\ntown-5    2019-08-07 \u003c- 1\ntown-6    2019-08-08 \u003c- 2\ntown-7    2019-08-09 \u003c- 3\ntown-8    2019-08-10\n...\n```\n\nThe query will be like:\n\n```sql\nWITH __backward_edges__ AS (\n  SELECT id FROM town\n  WHERE created \u003e (SELECT created FROM town WHERE id = 'town-4') -- still can use index\n  ORDER BY created ASC\n  LIMIT 3\n)\nSELECT * FROM __backward_edges__\nORDER BY created DESC\n```\n\nBecause `town-7` should appear before `town-6`, we reversed the `__backward_edges__` here.\n\n```go\n\nqb := pgcc.NewQueryBuilder(pgcc.Options{\n  TableName: \"town\",    // Table to query\n  Cursor:    \"id\",      // should be a key in the table\n  Select:    \"created\", // additional columns to select\n  SortKeys: []pgcc.SortKey{ // defines the sort orders for this connection\n    {Order: \"DESC\", Select: \"created\"},\n    {Order: \"ASC\", Select: \"id\"},\n  },\n})\n\ntype townEdge struct {\n  Cursor  string `json:\"cursor\"`\n  Created string `json:\"created\"`\n}\n\ntype townConnection struct {\n  pgcc.PageInfo\n  Edges []townEdge `json:\"edges\"`\n}\n\nq := qb.Paginate()\nq.SetFirst(2)\n\nvar src townConnection\nvar b []byte\nerr = tx.QueryRow(q.SQL(), q.Args()...).Scan(\u0026b)\nif err != nil {\n  // ...\n}\nerr = json.Unmarshal(b, \u0026src)\nif err != nil {\n  // ...\n}\n\n```\n\nIt generates a query like:\n\n```sql\nWITH __after__ AS (\n\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\tWHERE id = $2 AND TRUE  LIMIT 1\n), __before__ AS (\n\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\tWHERE id = $4 AND TRUE  LIMIT 1\n)\nSELECT\n  json_build_object(\n\t\t'edges', __edges__.result,\n\t\t'has_next_page', (SELECT (\n\tCASE\n\t\tWHEN $1 \u003e 0 then (\n\t\t\tWITH __limit_or_more__ AS (\n\t\t\t\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\t\t\t\tWHERE TRUE\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __after__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003c (select \"created_DESC\" FROM __after__) OR (created = (select \"created_DESC\" FROM __after__)AND id \u003e (select \"id_ASC\" FROM __after__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __before__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003e (select \"created_DESC\" FROM __before__) OR (created = (select \"created_DESC\" FROM __before__)AND id \u003c (select \"id_ASC\" FROM __before__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t\t LIMIT $1 + 1\n\t\t\t)\n\t\t\tSELECT count(*) \u003e $1 FROM __limit_or_more__\n\t\t)\n\t\tWHEN $4 IS NOT NULL then (\n\t\t\tWITH __zero_or_one__ AS (\n\t\t\t\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\t\t\t\tWHERE TRUE\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __before__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003c (select \"created_DESC\" FROM __before__) OR (created = (select \"created_DESC\" FROM __before__)AND id \u003e (select \"id_ASC\" FROM __before__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t LIMIT 1\n\t\t\t)\n\t\t\tSELECT COUNT(*) \u003e 0 FROM __zero_or_one__\n\t\t)\n\t\tELSE FALSE\n\tEND\n) AS result),\n\t\t'has_previous_page', (SELECT (\n\tCASE\n\t\tWHEN $3 \u003e 0 then (\n\t\t\tWITH __limit_or_more__ AS (\n\t\t\t\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\t\t\t\tWHERE TRUE\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __after__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003c (select \"created_DESC\" FROM __after__) OR (created = (select \"created_DESC\" FROM __after__)AND id \u003e (select \"id_ASC\" FROM __after__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __before__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003e (select \"created_DESC\" FROM __before__) OR (created = (select \"created_DESC\" FROM __before__)AND id \u003c (select \"id_ASC\" FROM __before__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t LIMIT $3 + 1\n\t\t\t)\n\t\t\tSELECT count(*) \u003e $3 FROM __limit_or_more__\n\t\t)\n\t\tWHEN $2 IS NOT NULL then (\n\t\t\tWITH __zero_or_one__ AS (\n\t\t\t\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\t\t\t\tWHERE TRUE\n\t\t\t\t\tAND CASE (SELECT TRUE FROM __after__)\n\t\t\t\t\t\tWHEN TRUE THEN (created \u003e (select \"created_DESC\" FROM __after__) OR (created = (select \"created_DESC\" FROM __after__)AND id \u003c (select \"id_ASC\" FROM __after__)))\n\t\t\t\t\t\tELSE TRUE\n\t\t\t\t\tEND\n\t\t\t\t LIMIT 1\n\t\t\t)\n\t\t\tSELECT COUNT(*) \u003e 0 FROM __zero_or_one__\n\t\t)\n\t\tELSE FALSE\n\tEND\n) AS result),\n\t\t'start_cursor', CAST(__edges__.result -\u003e\u003e 0 AS JSON) -\u003e\u003e 'cursor',\n\t\t'end_cursor', CAST(__edges__.result -\u003e\u003e json_array_length(__edges__.result)-1 AS JSON) -\u003e\u003e 'cursor'\n\t)\nFROM (WITH __forward_edges__ AS (\n\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\tWHERE NOT (($1 + 0) IS NULL AND ($3 + 0) IS NOT NULL)\n\t\tAND TRUE\n\t\tAND CASE (SELECT TRUE FROM __after__)\n\t\t\tWHEN TRUE THEN (created \u003c (select \"created_DESC\" FROM __after__) OR (created = (select \"created_DESC\" FROM __after__)AND id \u003e (select \"id_ASC\" FROM __after__)))\n\t\t\tELSE TRUE\n\t\tEND\n\t\tAND CASE (SELECT TRUE FROM __before__)\n\t\t\tWHEN TRUE THEN (created \u003e (select \"created_DESC\" FROM __before__) OR (created = (select \"created_DESC\" FROM __before__)AND id \u003c (select \"id_ASC\" FROM __before__)))\n\t\t\tELSE TRUE\n\t\tEND\n\n\tORDER BY\n\t\t\"created_DESC\" DESC,\n\t\t\"id_ASC\" ASC, id ASC\n\tLIMIT $1\n), __backward_edges__ AS (\n\tSELECT created AS \"created_DESC\", id AS \"id_ASC\", id AS cursor, created FROM town\n\tWHERE $1 + 0 IS NULL AND $3 \u003e 0\n\t\tAND TRUE\n\t\tAND CASE (SELECT TRUE FROM __after__)\n\t\t\tWHEN TRUE THEN (created \u003c (select \"created_DESC\" FROM __after__) OR (created = (select \"created_DESC\" FROM __after__)AND id \u003e (select \"id_ASC\" FROM __after__)))\n\t\t\tELSE TRUE\n\t\tEND\n\t\tAND CASE (SELECT TRUE FROM __before__)\n\t\t\tWHEN TRUE THEN (created \u003e (select \"created_DESC\" FROM __before__) OR (created = (select \"created_DESC\" FROM __before__)AND id \u003c (select \"id_ASC\" FROM __before__)))\n\t\t\tELSE TRUE\n\t\tEND\n\n\tORDER BY\n\t\t\"created_DESC\" ASC,\n\t\t\"id_ASC\" DESC, id DESC\n\tLIMIT $3\n)\nSELECT COALESCE(JSON_AGG(__edgerows__.result), '[]'::json) AS result FROM (\n\tSELECT ROW_TO_JSON(__rawedges__.*) AS result FROM (\n\t\t(SELECT __forward_edges__.* FROM __forward_edges__\n\t\tOFFSET (\n\t\t\tCASE ($1 \u003e 0 AND $3 \u003e 0)\n\t\t\t\tWHEN TRUE\n\t\t\t\tTHEN GREATEST(COALESCE(0 - $3 + (SELECT count(*) FROM __forward_edges__), 0), 0)\n\t\t\t\tELSE 0\n\t\t\tEND\n\t\t))\n\t\tUNION\n\t\tSELECT __backward_edges__.* FROM __backward_edges__\n\t\tORDER BY \"created_DESC\" DESC, \"id_ASC\" ASC, cursor ASC\n\t) as __rawedges__\n) __edgerows__) __edges__\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhanpama%2Fpgcc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhanpama%2Fpgcc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhanpama%2Fpgcc/lists"}