Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get nested documents in FaunaDB?

Tags:

faunadb

To get a single document in Fauna we do:

q.Get(q.Ref(q.Collection('posts'), '192903209792046592'))

And we get something like:

{
  data: {
    title: 'Lorem ipsum',
    authorId: '892943607792046595'
  }
}

Is it possible to instead get the post and the author in the same query?

Something like this:

{
  data: {
    title: 'Lorem ipsum',
    author: {
      name: 'John Doe'
    }
  }
}
like image 751
Pier Avatar asked Apr 08 '20 15:04

Pier


2 Answers

Since this is a common question I'm going to expand it and give you all info that you should need to get started. I recently wrote an example that will soon also exemplify this in detail.

I'm going to construct the query step by step to be as education as possible Let's say.. we write a twitter like application and want to retrieve tweets. The first thing we would do is just get a list of tweets.

First of all.. Getting references

Paginate(Documents(Collection('fweets')))

which returns a list of references

... or index values

Paginate(Documents(Index('some_index')))

which returns all values that you selected when you created the index, something like: [[value1, value2], ...]

This will return a Page of references, or essentially a list of references.

Getting the actual documents of a list with Get

Then you do what you did in your question, you Get the reference by mapping over it with Map (and Map will be your workhorse to answer your question as we go further)

Map(
   Paginate(Documents(Collection('fweets'))),
   Lambda('ref', Var('ref'))
)

Transforming these documents to get other data (your specific question)

You can use the exact same technique as we did to get the references, map over the document. Only now we would do the Get on the references that point to other collections. Imagine I have an Author in each of my Tweets, let us get that author. We are going to user Let to structure our query and go step by step Let's first restructure our query with Let

Map(
  Paginate(Documents(Collection('fweets'))),
  // and in this function, the magic will happen, for now we just return the tweet.
  Lambda('f',
    Let({
        fweet: Get(Var('f'))
      },
      Var('fweet')
    )
  )
)

We'll now add one line to get the authors.

Map(
  Paginate(Documents(Collection('fweets'))),
  // and in this function, the magic will happen
  Lambda('f',
    Let({
        fweet: Get(Var('f')),
        author: Get(Select(['data', 'author'], Var('fweet'))), // we get the author reference
      },
      // And now we return a nested doc
      {
        fweet: Var('fweet'),
        author: Var('author')
      }
    )
  )
)

This would return:

[{
   "fweet": {
      < your tweet data > 
    },
    "author": {
      < your author data >
    }
}, ... ]

And now that we have this structure adding something extra is easy. Imagine we also have an 'asset' tweet linked to the tweet for which we store the ref in tweets

Map(
  Paginate(Documents(Collection('fweets'))),
  Lambda('f',
    Let({
        fweet: Get(Var('f')),
        author: Get(Select(['data', 'author'], Var('fweet'))), 
        asset: Get(Select(['data', 'asset'], Var('fweet')))
      },
      // And now we return a nested doc
      {
        fweet: Var('fweet'),
        author: Var('author'),
        asset: Var('asset'),
      }
    )
  )
)

Of course.. what if the thing we want to fetch is not a stored reference but we want to join on a property? So imagine we have multiple comments on a tweet that we want to fetch? That is where indexes come in!

Map(
  Paginate(Documents(Collection('fweets'))),
  Lambda('f',
    Let({
        fweet: Get(Var('f')),
        author: Get(Select(['data', 'author'], Var('fweet'))), 
        asset: Get(Select(['data', 'asset'], Var('fweet'))), 
        comments: Map(
           Paginate(Match(Index('comments_by_fweet_ordered'), Var('f'))),
           Lambda(
              // my index has two values, I only need the comment reference but adding the ts makes them appear in order!
              ['ts', 'commentref'], 
              Get(Var('commentref'))
           )
        )
      },
      // And now we return a nested doc
      {
        fweet: Var('fweet'),
        author: Var('author'),
        asset: Var('asset'),
        comments: Var('comments')
      }
    )
  )
)

and just like that.. you can add complexity gradually and do really complex queries. The query in my app continues like that to get things such as tweet statistics or even the original tweet if its a retweet There is actually little you can't do in FQL :)

like image 136
Brecht De Rooms Avatar answered Oct 22 '22 09:10

Brecht De Rooms


So first, it's best to store refs in documents and not ids.

When storing the example post in the question it should be something like this (with the JS driver):

{
  title: 'Lorem ipsum',
  authorRef: q.Ref(q.Collection("authors"), "1234556456858234")
}

Then to get the post with the nested author you need to create custom objects with Let.

It would be something like this (again with the JS driver):

q.Let(
  {
    postDoc: q.Get(q.Ref(Collection('posts'), '192903209792046592')),
    authorRef: q.Select(['data', 'authorRef'], q.Var('postDoc')),
    authorDoc: q.Get(q.Var('authorRef')),
  },
  {
    title: q.Select(['data', 'title'], q.Var('postDoc')),
    author: {
      name: q.Select(['data', 'name'], q.Var('authorDoc')), 
    }
  }
)

Which would return:

{
  title: 'Lorem ipsum',
  author: {
    name: 'John Doe'
  }
}

Another option could be to simply return the two full documents side-by-side:

q.Let(
  {
    postDoc: q.Get(q.Ref(Collection('posts'), '192903209792046592')),
    authorRef: q.Select(['data', 'authorRef'], q.Var('postDoc'))
  },
  {
    postDoc: q.Var('postDoc'),
    authorDoc: q.Get(q.Var('authorRef')),
  }
)
like image 22
Pier Avatar answered Oct 22 '22 11:10

Pier