Can Indexes use terms matching nested reference fields?

Hello,

I may have just missed it, but I could not find a clear answer to this in the documentation or searching the forums.

E.g., if I have these two collections defined in a GraphQL schema…

type Comment {
    author: Author!
    message: String!
}

type Author {
    firstName: String!
    lastName: String!
}

Can I create an Index for the Comment collection with terms that can filter by, say, the author field’s nested firstName field?

I’ve tried both of these approaches, but both return zero matches when I test.

// with extra "data" field
CreateIndex({
  name: "comments_by_firstname_a",
  source: Collection("Comment"),
  terms: [
    { field: ["data", "author", "data", "firstName"] }
  ]
})

// without extra "data" field
CreateIndex({
  name: "comments_by_firstname_b",
  source: Collection("Comment"),
  terms: [
    { field: ["data", "author", "firstName"] }
  ]
})

Is something like this possible, or will I need to call on multiple indexes and Join / Intersection the results together?

Thanks,
Ross

The short answer is no. But hopefully I can help explain some more.

Indexes work only on the data is contained within a document. Bindings are possible to compute, but those functions have to be pure and cannot read any other Documents.

To see what you can Index when using GraphQL, you must know how the relationships are stored. Check that out in the docs for GraphQL Replationships: GraphQL relationships | Fauna Documentation

In a one-to-many relationship, a reference is only stored in the many-side. Traversing the relationship from both directions is possible with indexes.

First, make sure you use the @relation directive.

type Comment {
    author: Author! @ relation
    message: String!
}

type Author {
    firstName: String!
    lastName: String!
    comments: [Comment] @relation
}

An Author document and a Comment document might look like this:

{
  ref: Ref(Collection("Author"), "5555555"),
  ts: 1631567921050000,
  data: {
    firstName: "Paul"
    lastName: "P"
  }
}
{
  ref: Ref(Collection("Comment"), "12345678"),
  ts: 1631567921050000,
  data: {
    author: Ref(Collection("Author"), "5555555"),
    message: "Hello!"
  }
}

You can see from the Documents that the Comment Documents contain the author Ref. But Refs do not have data fields, so you cannot index on them that way.

To address the specific case that you have: comments by authors with firstName = “Something”

The solution is similar whether you are using GraphQL or not, but it’s a bit easier to get nested results in a nice shape with GraphQL, so we can start there.

With GraphQL

You can add an @index Query field to the schema to create a filter by the Author’s firstName field. Remember, the name doesn’t matter: the arguments and the return type dictate how the index get’s defined.

type Query {
  authorsByFirstName(firstName: String!): [Author] @index
}

Technically, the @index part is redundant, since Query fields without any directives default to using @index underneath.

This will generate an Index as if you run this in the shell:

CreateIndex({
  name: "authorsByFirstName", // same as field name
  source: Collection("Author"), // same as result type
  terms: [  // terms match the arguments provided
    { field: ["data", "firstName"] },
  ]
  // note that there are no `values` specified.
})

Then you can query the Authors by first name and use a nested selection to fetch the related Comments

{
  authorsByFirstName(firstName: "Paul", _size: 10) {
    data {
      _id
      firstName
      lastName
      comments(_size: 10) {
        data {
          _id
          message
        }
      }
    }
  }
}

Without GraphQL

You can write a very similar query directly with FQL. Returning nested results with FQL is verbose, but is straightforward once you get the hang of it.

Using the @relation directives created an Index for us. We will need to use that directly with the FQL-only approach.

CreateIndex({
  name: "comment_author_by_author",
  source: Collection("Comment"),
  terms: [
    { field: ["data", "author"] }
  ]
})

This index let’s us traverse the relationship from an Author to all of its related Comment Documents, since the author ref is stored in the Comment Collection.

Let(
  {
    firstName: 'Paul',
    authorsPage: Paginate(
      Match(Index('authorsByFirstName'), Var('firstName')),
      { size: 10 }
    ),
  },
  {
    authorsByFirstName: Map(
      Var('authorsPage'),
      Lambda(
        'ref', // Author Ref
        {
          author: Get(Var('ref')),
          comments: Paginate(
            Match(Index('comment_author_by_author'), Var('ref')),
            { size: 10 }
          ),
        }
      )
    ),
  }
)

here, I’ve not really nested the comments within the author Documents, but just stuck them next to each other. I think this was just easier to demonstrate quickly. But you can use Merge and Select functions to nest Documents and even be selective about which fields you return, similar to GraphQL.

Ahh, this was very informative, thank you so much as always! You’re a life saver on these forums , I really appreciate the detailed breakdowns and support :sob: :pray:

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.