GraphQL: Linking Objects

Proxy objects can link together to form graphs.

Example

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

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

struct User;

// GraphQL proxy for example::animal
struct Animal
{
   eosio::name contract;
   example::animal obj;

   auto name() const { return obj.name; }
   auto type() const { return obj.type; }
   auto purchasePrice() const { return obj.purchase_price; }

   // Link to a proxy which represents owner
   User owner() const;
};
EOSIO_REFLECT2(Animal, name, type, purchasePrice, owner)

// GraphQL proxy which represents a user. This proxy may exist even
// if there are no database records for that user.
struct User
{
   eosio::name contract;
   eosio::name name;

   // User's remaining balance, if any
   std::optional<eosio::asset> balance() const
   {
      example::balance_table table{contract, contract.value};
      auto it = table.find(name.value);
      if (it != table.end())
         return it->balance;
      else
         return std::nullopt;
   }

   // Link to proxy objects which represent animals owned by user
   std::vector<Animal> animals() const
   {
      std::vector<Animal> result;
      example::animal_table table{contract, contract.value};

      // This is an inefficent approach and will time out if there are
      // too many animals in the table. We could add a secondary index,
      // but that would consume RAM. The blocks-to-browser system
      // supports secondary indexes which don't consume on-chain RAM.
      for (auto& animal : table)
         if (animal.owner == name)
            result.push_back(Animal{contract, animal});

      return result;
   }
};
EOSIO_REFLECT2(User, name, balance, animals)

User Animal::owner() const
{
   return {contract, obj.owner};
}

struct Query
{
   eosio::name contract;

   User user(eosio::name name) const { return {contract, name}; }

   std::optional<Animal> animal(eosio::name name) const
   {
      example::animal_table table{contract, contract.value};
      auto it = table.find(name.value);
      if (it != table.end())
         return Animal{contract, *it};
      else
         return std::nullopt;
   }
};
EOSIO_REFLECT2(Query, contract, method(user, "name"), method(animal, "name"))

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 information about Alice's and Joe's balances and animals. Joe has never interacted with the contract.

{
  alice: user(name: "alice") {
    name
    balance
    animals {
      name
      type
      purchasePrice
    }
  }
  joe: user(name: "joe") {
    name
    balance
    animals {
      name
      type
      purchasePrice
    }
  }
}

Result:

{
  "data": {
    "alice": {
      "name": "alice",
      "balance": "90.0000 EOS",
      "animals": [
        {
          "name": "barf",
          "type": "dog",
          "purchasePrice": "110.0000 EOS"
        },
        {
          "name": "fido",
          "type": "dog",
          "purchasePrice": "100.0000 EOS"
        }
      ]
    },
    "joe": {
      "name": "joe",
      "balance": null,
      "animals": []
    }
  }
}
{
  animal(name: "fido") {
    name
    type
    purchasePrice
    owner {
      name
      balance
    }
  }
}

Result:

{
  "data": {
    "animal": {
      "name": "fido",
      "type": "dog",
      "purchasePrice": "100.0000 EOS",
      "owner": {
        "name": "alice",
        "balance": "90.0000 EOS"
      }
    }
  }
}

All animals owned by the person who owns fido

{
  animal(name: "fido") {
    owner {
      name
      animals {
        name
      }
    }
  }
}

Result:

{
  "data": {
    "animal": {
      "owner": {
        "name": "alice",
        "animals": [
          {
            "name": "barf"
          },
          {
            "name": "fido"
          }
        ]
      }
    }
  }
}