client cursor - select by list of random keys - client side query batching

Author: milahuCreated Dec 26, 2019Updated May 8, 2026
Labels👻 Needs Champion

in SQL we can use the SQL IN operator

SELECT * FROM table
WHERE table.key IN UNNEST (@keylist)

where keylist is an array of keys like [1, 1000, 50, 10000] [source] [ the UNNEST operator converts from array to vertical vector ]

the keys are random, not adjacent, not predictable similar to a server-side pagination cursor, so we can say "client cursor"

use case in graphQL? parallel pagination. broad first search / breadth-first search. and: dealing with "read only" servers = we cannot change the server schema

workaround? using a list of N keys, we can repeat the same selector [sub-query] for N times and append the key index to all variable names

code sample in javascript with the compound key { repoOwner, repoName }

javascript
const url = 'https://api.github.com/graphql'

// basic authentication - only for testing
const username = 'your_github_username'
const password = 'your_github_password'

// query object
const qObj = {

  variables: {

    repoOwner_0: "graphql",
    repoName_0: "graphql-spec",

    repoOwner_1: "graphql",
    repoName_1: "graphql-js",
  },

  query: `query
    (
      $repoOwner_0: String!,
      $repoName_0: String!,

      $repoOwner_1: String!,
      $repoName_1: String!,
    ){

      r0: repository
      (
        owner: $repoOwner_0,
        name: $repoName_0,
      ){
        nameWithOwner
        pushedAt
        createdAt
        stargazers { totalCount }
      }

      r1: repository
      (
          owner: $repoOwner_1,
          name: $repoName_1,
      ){
        nameWithOwner
        pushedAt
        createdAt
        stargazers { totalCount }
      }

    }`
}

// send query
const headers = new Headers({
  'Authorization': 'Basic '+btoa(username+":"+password),
  'Content-Type': 'application/json',
  'Accept': 'application/json',
})
fetch(url, {
  method: 'POST',
  headers: headers,
  body: JSON.stringify(qObj),
})
.then(r => r.json())
.then(response => {
  // print response
  console.log('response = '+JSON.stringify(response, null, '  '))
})

result:

txt
response = {
  "data": {
    "r0": {
      "nameWithOwner": "graphql/graphql-spec",
      "pushedAt": "2019-12-19T01:02:54Z",
      "createdAt": "2015-07-01T01:26:56Z",
      "stargazers": {
        "totalCount": 12274
      }
    },
    "r1": {
      "nameWithOwner": "graphql/graphql-js",
      "pushedAt": "2019-12-26T02:31:06Z",
      "createdAt": "2015-06-30T12:16:50Z",
      "stargazers": {
        "totalCount": 15304
      }
    }
  }
}

this selector

      repository
      (
        owner: [$repoOwner_0, $repoOwner_1],
        name: [$repoName_0, $repoName_1],
      )

is not supported by the server:

List dimension mismatch on variable $var_name and argument name ([String!]! / String!)

the fields [repository, repositry] must be renamed to [r0, r1], otherwise i get

Field 'field_name' has an argument conflict

on stackoverflow.com i posted some more code, to generate such "concat queries" inspired by Batching queries with GraphQL & Python, by Julien Danjou

questions:

  1. am i re-inventing the wheel here? and just too blind to see a better solution?
  2. are there plans to implement such a feature? i could not even find a similar request

implementation sketch: add a @repeat directive / pseudo-object / block / container

javascript
const qObj = {

  variables: {

    repoOwner: ["graphql",      "graphql"   ],
    repoName:  ["graphql-spec", "graphql-js"],

    constVar: 1234,
  },

  query: `query
    (
      $repoOwner: String!,
      $repoName: String!,
      $constVar: Int!,
    ){

      # repeat this block for all keys
      # = for the longest array, fill shorter arrays
      
      @repeat {
        repository
        (
          owner: $repoOwner,
          name: $repoName,
        )
        {
          pushedAt
          stargazers { totalCount }

          sampleobject(arg: $constVar)
        }
      }

    }`
}