Query filter using two fields in a `LinkingObjects`

Based on the example below, Is it possible to query Profile based on two fields within a ``LinkingObjects, such as friendSent`? I have a profile type and a friend request type:

class Profile: Object {
    // ...

    @Persisted(originProperty: "sender")    var friendSent: LinkingObjects<FriendRequest>
    @Persisted(originProperty: "recipient") var friendRcvd: LinkingObjects<FriendRequest>
}

class FriendRequest: Object {
    @Persisted var sender:    Profile!
    @Persisted var recipient: Profile!
    @Persisted var status: RequestStatusEnum = .active
}
`

I'd then like to make a query for a `@ObservedResults(Profile.self) var users`

profile = Profile(ā€¦) // some profile
let friends_of_profile = users.where({
($0.friendRcvd.sender == profile && $0.friendRcvd.status == .active)
&& ($0.friendSent.recipient == profile && $0.friendSent.status == .active)
});


This nearly works, however, the individual clauses for `friendSent` and `friendRcvd` don't seem to be applied to only the same document. So I'm seeing the clauses for  `$0.friendRcvd` evaluate to `true` if there exists a document where  `.sender == profile` and one where `.status == .active`, but they aren't necessarily the same one.

Is it possible to write a query that requires those two filters to be applied to the same document?

Thanks!

I think the answer is ā€˜Yesā€™ but the formatting in the question make is challenging to read. Only the code should be code formatted - the rest of the text should not be.

Also, thereā€™s a ``users` object in the question but we donā€™t know what that is.

The query in the question has a bunch of &&, meaning all of those statement must be true for the object to be returned, and, they all most be true on a single object. So the parens () are extraneous.

This code is puzzling

let friends_of_profile = users.where({ ($0.friendRcvd.sender

as $0 would be a user object, not a profile object. So the user object would have to have a Profile property to then reach the .sender property - like this

class User: Object {
   @Persisted var friendRcvd: Profile

so is friendRcvd a property of User, Profile or something else?

Also LinkingObjects can contain one or more objects so naming the property as plural may help clarify the code. So instead of

friendSent: LinkingObjects<FriendRequest>

it would be better

friendsSent: LinkingObjects<FriendRequest>

I think the question needs editing and updating for clarity.

Sorry for the formatting problems- I didnā€™t get a chance to preview it during the posting process. It also looks like I canā€™t edit my post, so hereā€™s it again:

Based on the example below, Is it possible to query Profile based on two fields within a LinkingObjects , such as friendSent? I have a profile type and a friend request type:

class Profile: Object {
    // ...

    @Persisted(originProperty: "sender")    var friendSent: LinkingObjects<FriendRequest>
    @Persisted(originProperty: "recipient") var friendRcvd: LinkingObjects<FriendRequest>
}

class FriendRequest: Object {
    @Persisted var sender:    Profile!
    @Persisted var recipient: Profile!
    @Persisted var status: RequestStatusEnum = .active
}

Iā€™d then like to make a query for a @ObservedResults(Profile.self) var users

profile = Profile(ā€¦) // some profile
let friends_of_profile = users.where({
    ($0.friendRcvd.sender == profile && $0.friendRcvd.status == .active)
    && ($0.friendSent.recipient == profile && $0.friendSent.status == .active)
});

This nearly works, however, the individual clauses for friendSent and friendRcvd donā€™t seem to be applied to only the same document. So Iā€™m seeing the clauses for $0.friendRcvd evaluate to true if there exists a document where .sender == profile and one where .status == .active, but they arenā€™t necessarily the same one.

Is it possible to write a query that requires those two filters to be applied to the same document?

Thank you!

1 Like

Again, the query does not match the objects presented in the question

let friends_of_profile = users.where

Is querying the users collection - and each User class object would need to have a .friendRcvd and a .friendSent property. But there is no User object in the question.

More importantly the Profile Object LinkingObjects property are not ONE object - they are one or more objects.

Lastly, do you have a use case where the friendRcvd.sender and friendSent.recipient would be the same profile? That seems odd.

Hi Jay,

I specified the definition of users in my question - there is no users collection:

Apologies for the confusing naming. I also understand regarding the singular/plural naming of .friendRcvd as well.

So the query is looking for friends: profile A is a friend of profile B if A and B have both sent active FriendRequests to each other. Therefore, my query:

queries for profiles (within the full users set) where they have received active friend requests from profile and have sent active friend requests to profile.

However, my question proposed in my post still stands:

Thanks,
Luc

One issue may be an assumption of what this resolves to. Let me present an example

let a, b, c, d = 0

if ( a == 0 && b == 0) && ( c == 0 && d == 0) {
    //do something if above is true
}

is equivalent to

if  a == 0 && b == 0 && c == 0 && d == 0 {
    //do something if above is true - note no () as it all must be true or if fails in either case.
}

The other thing when youā€™re doing a query like that, the query is an ANY query. In other words, return profiles that have ANY linked profile containing profile and ANY linked profile where status is active. I have still not fully wrapped my brain around the query though but I think you might run into circular references. Going to give this more thought and see if I can provide a better solution/answer.

This sort of query behavior matches my testing. Is there a way to evaluate the two parts of a clause like

on strictly the same FriendRequest document? That way this clause could successfully query for specifically active friend requests from profile .

Thanks,
Luc

I donā€™t believe thereā€™s a way with the current schema. The issue is thereā€™s too many back and forward references within the database to return more singular data. So the big picture is that itā€™s returning results where the collection has ANY profiles that match and ANY status == .active.

One approach is to break out the invites into their own collection instead of trying to do it all in one. Itā€™s somewhat denormalizing the data but will provide more granular access to specific data. Something like

InviteClass: Object {
   @Persisted var whoWasInvited: User!
   @Persisted var invitedByWho: User!
   @Persisted var inviteCompletedDate: Date? // nil would mean the invite was not completed
}

I see- In this case, you could check just inviteCompletedDate != nil, which doesnā€™t suffer from the same ANY problem with a query.

Is there a way to write my original intended query using a standard MongoDB query, instead of the Realm interface? Iā€™m wondering if this is a limitation of the interface or MongoDB itself.

Maybe? But the issue is how the schema is constructed.

When doing a query on a top level object about something in a LinkingObjects List, itā€™s asking Realm/Mongo to return top level objects that have ANY x matching y in their List property.

If you want to query for more granular data, then you need to query on that data - like in my above example to then get which objects match exactly.

Is it possible to do two subqueries on FriendRequest and then a union between them?

Possible? Yes. The correct solution? Maybe.

One issue is the size of the dataset. Realm objects are lazily loaded - meaning working with thousands (tens-of) requires very little memory as objects are only loaded when they are being worked with.

Creating a Union would require the objects to be loaded - possibly into an array - and that defeats the lazy-loadingness aspect. If the dataset is small, then yes, itā€™s possible.

If the data set is large or will scale, then no, not a good idea as all of those objects could overwhelm the devices memory.

Also, subqueries are somewhat difficult to manage. I often find that during development it sounds like a great idea but more often than not, I end up ditching the subquery to implement something more straightforward- denormalizing the data per above pretty much gets around all of those issues.

That being said, I am not totally clear on what the results of the query should be. For example, I donā€™t understand in what use case the Recipient and Sender would be the same profile. If you look at the InviteClass above, it solves those issues. Hereā€™s a sample query

let results = realm.objects(InviteClass.self).where { 
   ($0.whoWasInvited == profileA && $0.invitedByWho == profileB)
   ||
   ($0.whoWasInvited == profileB && $0.invitedByWho == profileA)
}

That query should return exactly two matches of the Invite Class. For example suppose profileA is ā€œJayā€ and profileB is ā€œCindyā€

If 
  Jay was invited by Cindy
  OR 
  Cindy was invited by Jay

would be two matches. If there were no invites or if it was one or the other the results.count would be 0 or 1.

Would it be possible to run two queries, one for finding ā€œactiveā€ friend requests to Person A and then ā€œactiveā€ friend requests from Person A. If these could be combined as one query, by union-ing two subqueries, then I could use .count ontop of that. That way this action could be performed directly on the FriendRequest data, instead of on the linking objects of Profile.

Again, ā€œpossibleā€ leaves a lot of possibilities so yes, itā€™s possible.

The issue is the schema setup - if you want to run a query for specific data in the FriendsRequest collection, run it against that collection, not a LinkingObjects property of another collection. If itā€™s run against linking objects, it returns the top level model in the other collection where ANY in the linking objects is a match.

Here is your current model (from the initial question)

So something like

let results = realm.objects(FriendRequest.self).where { ($0.sender == personA || $0.recipient == personA) && $0.status == .active)

would return all active friend requests that were either initiated or received by personA.