GraphQL: Pagination

clsdk's GraphQL library supports most of the GraphQL Connection Model for paging through large data sets.

Example

This is a modification of example-graphql.cpp from Getting Started.

// Include support for the connection model (pagination)
#include <btb/graphql_connection.hpp>

#include <eosio/reflection2.hpp>
#include "example.hpp"

struct Animal
{
   example::animal obj;

   auto name() const { return obj.name; }
   auto type() const { return obj.type; }
   auto owner() const { return obj.owner; }
   auto purchasePrice() const { return obj.purchase_price; }
};
EOSIO_REFLECT2(Animal, name, type, owner, purchasePrice)

// Define the AnimalConnection and AnimalEdge GraphQL types
constexpr const char AnimalConnection_name[] = "AnimalConnection";
constexpr const char AnimalEdge_name[] = "AnimalEdge";
using AnimalConnection =
    btb::Connection<btb::ConnectionConfig<Animal, AnimalConnection_name, AnimalEdge_name>>;

struct Query
{
   eosio::name contract;

   // Searches for and pages through the animals in the database
   //
   // The gt, ge, lt, and le arguments support searching for animals with
   // names which are greater-than, greater-than-or-equal-to, less-than,
   // or less-than-or-equal-to the values provided. If more than 1 of these
   // are used, then the result is the intersection of these.
   //
   // If first is non-null, it limits the result to the first animals found
   // which meet the other criteria (gt, ge, lt, le, before, after).
   // If last is non-null, it limits the result to the last animals found.
   // Using first and last together is allowed, but is not recommended since
   // it has an unusual semantic, which matches the GraphQL spec.
   //
   // If before is non-null, then the result is limited to records before it.
   // If after is non-null, then the result is limited to records after it.
   // before and after are opaque cursor values.

   AnimalConnection animals(std::optional<eosio::name> gt,
                            std::optional<eosio::name> ge,
                            std::optional<eosio::name> lt,
                            std::optional<eosio::name> le,
                            std::optional<uint32_t> first,
                            std::optional<uint32_t> last,
                            std::optional<std::string> before,
                            std::optional<std::string> after) const
   {
      example::animal_table table{contract, contract.value};

      return btb::make_connection<AnimalConnection,  // The type of connection to use
                                  eosio::name        // The key type (animal name)
                                  >(
          gt, ge, lt, le, first, last, before, after,
          table,  // Either a table or a secondary index
          [](auto& obj) {
             // This is the key used for searching in the table or index
             // provided above
             return obj.name;
          },
          [&](auto& obj) {
             // Convert an object found in the table into a proxy
             return Animal{obj};
          },
          // Hook up the lower_bound and upper_bound functions. These
          // do the actual search.
          [](auto& table, auto key) { return table.lower_bound(key.value); },
          [](auto& table, auto key) { return table.upper_bound(key.value); });
   }
};
EOSIO_REFLECT2(Query,  //
               method(animals, "gt", "ge", "lt", "le", "first", "last", "before", "after"))

void example::example_contract::graphql(const std::string& query)
{
   Query root{get_self()};
   eosio::print(btb::gql_query(root, query, ""));
}

void example::example_contract::graphqlschema()
{
   eosio::print(btb::get_gql_schema<Query>());
}

Example Queries

Get all animals in the database

{
  animals {
    edges {
      node {
        name
      }
    }
  }
}

Get the first 5

{
  animals(first:5) {
    edges {
      node {
        name
      }
    }
  }
}

Get the last 5

{
  animals(last:5) {
    edges {
      node {
        name
      }
    }
  }
}

Get the first 5 starting with dog132

{
  animals(first: 5, ge: "dog132") {
    edges {
      node {
        name
      }
    }
  }
}

Pagination

{
  animals(first: 5) {
    pageInfo {
      hasPreviousPage
      hasNextPage
      startCursor
      endCursor
    }
    edges {
      node {
        name
      }
    }
  }
}

The result includes this in its output:

"pageInfo": {
  "hasPreviousPage": false,
  "hasNextPage": true,
  "startCursor": "0000000000B0AE39",
  "endCursor": "000000009010184D"
},

There are more results (hasNextPage is true) and we know where to resume (endCursor). To get the next 5:

{
  animals(first: 5, after: "000000009010184D") {
    pageInfo {
      hasPreviousPage
      hasNextPage
      startCursor
      endCursor
    }
    edges {
      node {
        name
      }
    }
  }
}