Hi,
what you are trying to do is already quite advanced so let’s break it down. A small advice when writing filters, the easiest way to write a Filter is often to start with a Map and then convert it to a filter. With the map, you can see what each step of your query returns so you can more easily construct it incrementally.
First things first, with the partial schema, FaunaDB should have generated something like that:
Collections:
Indexes
(I did add the allUsers index) to the schema myself as a query since the partial schema you provided will not give you that and I wanted to use the same index as you used in your example.
Then, try to think as if you are thinking to do this in a regular programming language such as JavaScript.
We have a location ‘some place’ and users have multiple locations. We want to find all users that have this location. In a regular programming language we would (or could):
- Get all users
- Apply a filter over the users
- In that filter, get all locations
- loop over the locations to check whether one of those locations matches the search term.
Before we can start, let’s get users. You already have an index generated by your schema I saw called allUsers.
Paginate(Match(Index('allUsers')))
That index only returns references since the values of that index only had ‘ref’ in values.
Therefore, we need to get the user itself. As mentioned, we’ll start with a Map.
Map(
Paginate(Match(Index('allUsers'))),
Lambda(
['userref'],
Get(Var('userref'))
)
)
This will get us all complete user documents with their data.
Let’s restructure that though and introduce Let which will help us to structure the query.
Map(
Paginate(Match(Index('allUsers'))),
Lambda(
['userref'],
Let(
{
user: Get(Var('userref'))
},
Var('user')
)
)
)
Let let’s us assign variables (in this case ‘user’) and each of these variables can be used in the next statement. So let’s continue building (we’ll zoom in on the Let in the code sample), we can get the locations by using another generated index (location_users_by_user). We select the ‘data’ immediately here to make it easier to work with later on. This is where it helps a lot to run a partial query like that and realize what the structure of the data is that each part returns.
...
Let(
{
user: Get(Var('userref')),
locationrefs: Select(['data'], Paginate(Match(Index('location_users_by_user'), Var('userref')))),
},
Var('locationrefs')
)
...
Then we can continue and go from location references to the actual documents (we’ve done that before on the user, using Get). Another tip, you can always return multiple values from that let in an object to see how the data in each step of that let looks like, which we’ll do here:
...
Let(
{
user: Get(Var('userref')),
locationrefs: Select(['data'], Paginate(Match(Index('location_users_by_user'), Var('userref')))),
locations: Map(Var('locationrefs'), Lambda(['lr'], Select(['data'], Get(Var('lr')))))
},
{ user: Var('user'), locationrefs: Var('locationrefs'), locations: Var('locations') }
)
...
If you run the complete query then you could see that this works so far and see how the resulting structure of each of the objects returned like:
{
data: [
{
user: {
ref: Ref(Collection("User"), "268060044733448710"),
ts: 1591900829930000,
data: {
fullName: "kim possible"
}
},
locationrefs: [Ref(Collection("Location"), "268060044738691590")],
locations: [
{
locationName: "some place"
}
]
},
{
user: {
ref: Ref(Collection("User"), "268062175773327877"),
ts: 1591902862260000,
data: {
fullName: "kim asdasd"
}
},
locationrefs: [Ref(Collection("Location"), "268062175777522181")],
locations: [
{
locationName: "some asdasda"
}
]
},
{
user: {
ref: Ref(Collection("User"), "268062258149458439"),
ts: 1591902940825000,
data: {
fullName: "kim asdasd"
}
},
locationrefs: [
Ref(Collection("Location"), "268062258156798471"),
Ref(Collection("Location"), "268062258165187079")
],
locations: [
{
locationName: "some asdasda"
},
{
locationName: "some asdasda"
}
]
},
Final thing we have to do is actually decide whether a location was found. We can use Any for that. Any takes an array of boolean and returns true if any is true. That means we first Map the locations to a boolean on whether they match or not, call Any and we are done. The whole Map query will look like:
Map(
Paginate(Match(Index('allUsers'))),
Lambda(
['userref'],
Let(
{
user: Get(Var('userref')),
locationrefs: Select(['data'], Paginate(Match(Index('location_users_by_user'), Var('userref')))),
locations: Map(Var('locationrefs'), Lambda(['lr'], Select(['data'], Get(Var('lr'))))),
locationsFound: Any(Map(
Var('locations'),
Lambda(['l'], ContainsStr(Select(['locationName'], Var('l')), 'some place'))
))
},
Var('locationsFound')
)
)
)
Now that we are done we transform it to a filter. Note that we moved the Paginate. Filter, in contrary to Map can be applied on the result of Match.
Paginate(Filter(
Match(Index('allUsers')),
Lambda(
['userref'],
Let(
{
user: Get(Var('userref')),
locationrefs: Select(['data'], Paginate(Match(Index('location_users_by_user'), Var('userref')))),
locations: Map(Var('locationrefs'), Lambda(['lr'], Select(['data'], Get(Var('lr'))))),
locationsFound: Any(Map(
Var('locations'),
Lambda(['l'], ContainsStr(Select(['locationName'], Var('l')), 'some place'))
))
},
Var('locationsFound')
)
)
))
Which returns me what I need, the references of the users who have a specific location.
{
data: [
Ref(Collection("User"), "268060044733448710"),
Ref(Collection("User"), "268062263133340167")
]
}
If you need the user documents, you’ll map over these and call Get, but you’ve already seen how to do that above