Understanding fauna auto-generation from schema

Basic authentication.
This may be related to another issue, but is a simpler instance (and may therefore help resolve that issue).

My schema:

type User {
  active: Boolean!
  username: String! @unique
  description: String
  email: String @unique
  mobile: String @unique
  ownerOf: [Ranking!]! @relation(name:"ownerranking")
  memberOf: [Player!]! @relation(name: "playerinranking")
}

type Player {
  active: Boolean!
  rank: Int!
  ladder: Ranking!  @relation # add the relation Directive
  playerInfo: User! @relation(name: "playerinranking")
  challenger: Player @relation (name: "playerchallenger")
}

type Ranking {
  active: Boolean!
  rankingname: String!
  rankingdesc: String
  player: [Player] @relation # add the relation
  owner: User! @relation(name:"ownerranking")
}

type Mutation {
  
  createNewRanking(active: Boolean!, rankingname : String!, rankingdesc : String, rankingownerid : String!): Ranking! @resolver(name: "create_new_ranking")
  createNewUser(active: Boolean!, username : String!, password : String!, description: String, email: String, mobile: String): loginResult! @resolver(name: "create_new_user")
  loginUser(username: String!, password: String!): loginResult! @resolver(name: "login_user")
  updateResult(challengeresult: ChallengeResult! playerrank: Int! opponentrank: Int!): Result! @resolver(name: "update_result")
}

enum ChallengeResult {
  Won
  Lost 
  Abandoned
}


type loginResult @embedded
{
  token : String
  logginUser : User
}

type Result @embedded {
  challengeresult: ChallengeResult!
  playerrank: Int!
  opponentrank: Int!
  message: String!
}

I have a query in PG:

{allPlayers{_id}}

which gives me:

"errors": [
{
"message": "Cannot query field 'allPlayers' on type 'Query'. Did you mean 'allPlayerUIDs', 'allPlayerRanks' or 'allUsers'? (line 2, column 3):\n allPlayers {\n ^",

allPlayers function definition:

name: "all_players",
    body: Query(
        Lambda(
            [],
            Select(
                "data",
                Map(Paginate(Match(Index("allPlayers"))), Lambda("x", Get(Var("x"))))
            )
        )
    ),

Function definitions for allUsers and allRankings are similar.
Why do I get no error (and the ids are displayed) for the following queries in PG:

{allUsers{_id}}
{allRankings{_id}}

but not for

{allPlayers{_id}}?

Thanks ā€¦

Fauna does not generate ā€œallā€ queries by default. You have to add them. And really, the way you can most easily add them is the same way you would quickly add any indexed query.

Letā€™s set aside the ā€œallā€ case for the moment. You can create an indexed query for Users by active for example.

type Query {
  # silly name so you know it is my own, not a fauna default one
  # note that `active` is spelled the same as the "term" that is desired.
  myUserByActiveStatusQuery(active: Boolean!): [User]
}

This automatically creates an index on User with term ['data', 'active']. If the spelling was off, it would still be created. If I did myUserByActiveStatusQuery(activeStatus: Boolean!): [User] by mistake, then the index would be created for term ['data', 'activeStatus'], regardless if it actually matched anything in the GraphQL schema.

The above is just shorthand for using the @index directive!

NOTE: the docs are bad at making it clear this exists, IMO. Examples leverage the short hand, but the page for the directive does not say it exists.

type Query {
  myUserByActiveStatusQuery(active: Boolean!): [User] @index(name: "myUserByActiveStatusQuery")
}

If you provide more than one argument to a query, it will generate an index with multiple terms.

type Query {
  playerByActiveAndRank(active: Boolean!, rank: Int!): [Player]
  #  remember shorthand for 
  playerByActiveAndRank(active: Boolean!, rank: Int!): [Player] @index(name: "playerByActiveAndRank")
}

So what happens when there are no arguments in the queries schema? You get an index with no terms! and thatā€™s really just an ā€œallā€ documents index. Doing nothing but this to your schema will give you an index that will result in all Players being returned. AND it will be a paginated query. You can replicate with an UDF, but then you need to add Pagination logic yourself if it is desired.

type Query {
  allPlayers: [Player]
}

You can do this multiple times with different names, and realize that there is nothing special about the name, and they will all return the same thing.

type Query {
  allPlayers: [Player]
  allPlayers2: [Player]
  allOfThePlayersForSure: [Player]
  aQueryWithoutTheWordPlayerButReturnsAllPlayers: [Player]
  ohWaitAMinute: [Player]
}

To finally answer your specific question

You did not share your Query definition. But:

This very specifically means that there is no field on type 'Query' that matches allPlayers. You need to add one.

also

I would advise a couple of things.

  1. consider using @index, or the short hand described, for the ā€œallā€ queries. Itā€™s WAY simpler than trying to define your own resolver.

  2. Note that creating a new ā€œallā€ query is unfortunately a bit redundant. This is because there is the built in index that is accessible through the Documents function. So now you are maintaining multiple indexes even if you are only using one. To leverage Documentsfunction, you can use @resolver, but if you want the full functionality of a paginated query, then you need to mark paginated true.

type Query {
  allPlayers: [Player] @resolver(name: "all_players", paginated: true)
}

use Documents and pagination in your UDF

{
  name: "all_players",
  body: Query(Lambda(["size", "after", "before"],
    Let(
      {
        match: Documents(Collection("Player")),
        page: If(
          Equals(Var("before"), null),
          If(
            Equals(Var("after"), null),
              Paginate(Var("match"), { size: Var("size") }),
              Paginate(Var("match"), { size: Var("size"), after: Var("after") })
          ),
          Paginate(Var("match"), { size: Var("size"), before: Var("before") }),
        )
      },
      Map(Var("page"), Lambda("ref", Get(Var("ref"))))
    )
  ))
}

This is taken straight out of docs.

2 Likes

Based on your advise I now just use:

type Query {
  allPlayers: [Player]
}

defined in the schema which, usefully, also gives me before and after cursors.

However, the allPlayers index only lists 1 (290121646658814469) of 2 Player docs in the collection:

{
  "ref": Ref(Collection("Player"), "290121646658814469"),
  "ts": 1616820015880000,
  "data": {
    "active": true,
    "rank": 1,
    "ladder": Ref(Collection("Ranking"), "290125481092579841"),
    "playerInfo": Ref(Collection("User"), "294007769929875981"),
    "challenger": Ref(Collection("Player"), "290125849145901569")
  }
}
{
  "ref": Ref(Collection("Player"), "290125849145901569"),
  "ts": 1616824266530000,
  "data": {
    "active": true,
    "rank": 2,
    "ladder": Ref(Collection("Ranking"), "290125505974239745"),
    "playerInfo": Ref(Collection("User"), "294007792006595080"),
    "challenger": Ref(Collection("Player"), "290121646658814469")
  }
}

All the referenced documents exist.
Why does:

{allPlayers{data{_id}}}

in PG only give me:

{
  "data": {
    "allPlayers": {
      "data": [
        {
          "_id": "290121646658814469"
        }
      ]
    }
  }
}

and not the _id of the second Player document as well?
Also, if I use pagination, I am not sure how to add a cursor term. This is my closest attempt:

{
  allPlayers (
    _size: 2
    _cursor: <?>
  ) {
    data {
      _id
      rank
      playerInfo{username}
    }
    after
    before
  }
}

which, of course, gives me:

{
  "errors": [
    {
      "message": "Invalid cursor."
    }
  ]
}

How do I obtain a valid cursor (or automatically generate appropriate ids on document creation for this purpose?)?
Thanks for all the background info on the UDF, it may well be useful to refer to in future and has made the documentation clearer for me.

You could yet have permissions issue that is blocking some of the data. Unless this is happening with the default admin key, too. Then I donā€™t know.

When you get an XPage type (eg PlayerPage) back from graphql you might get before and after fields. Use these values in subsequent queries for the cursor. You donā€™t have a cursor until you run the query first.

please refer to docs for how-to pagination.

2 Likes

I deleted the second Player document and created a new one with the same data. It now appears in the allPlayers index listing. Some kind of cacheing issue perhaps (?). Not an problem now anyway.

I am now receiving a cursor because I have created more documents making it relevant.

Youā€™ve helped me to understand how the auto-generated functionality works and this has been important for me to grasp especially in the context of npm packages that Iā€™m using to integrate fauna in my project (namely, fauna-gql-upload and elm-graphql). Knowing when and how to switch from ā€˜simpleā€™ GQL to FQL and UDFs has been challenging but necessary.

Thanks again for all the useful info and pointers ā€¦