0

I'm having a weird issue where I'm querying my user table and depending on a whether the user doing the query has the field "group" filled it will either return all users in that group when querying on that field or only the active user when he is part of that group.

User with group

export type UserFilter = {
  uids?: string[];
  organizationId?: AppUser['organizationId'];
  teacher?: PartialUserRef;
  group?: PartialGroupRef;
};

getUsers(userFilter?: UserFilter) {
    return this.afs
      .collection<DbUser>(constants.dbCollections.users, (ref) => {
        let query = ref.orderBy('name');

        if (userFilter?.organizationId) {
          query = query.where(
            'organizationId',
            '==',
            userFilter.organizationId
          );
        }

        if (userFilter?.uids) {
          query = query.where('uid', 'in', userFilter.uids);
        } else {
          // Only one array-contains is allowed per query
          if (userFilter?.group) {
            query = query.where('groups', 'array-contains', userFilter.group);
          } else if (userFilter?.teacher) {
            query = query.where(
              'teachers',
              'array-contains',
              userFilter.teacher
            );
          }
        }

        return query;
      })
      .snapshotChanges()
      .pipe(
        map((dbUsers) => dbUsers.map((dbUser) => dbUser.payload.doc.data())),
        takeUntil(this.afAuth.authState.pipe(first((user) => !user)))
      );
  }

My payload for the user filter is exactly the same in both scenarios:

{
    "uids": null,
    "organizationId": "996",
    "teacher": null,
    "group": {
        "id": "dg8wMCf0mkyhsVoCdhBp",
        "name": "A"
    }
}

So when the group in the filter exists on the user, the query only returns that user instead of the full group.

My Firestore security rules show no denies:

match /users/{userId} {
  // Allow users to read their own user data.
  allow read: if userId() == userId;
}

match /users/{userId} {
  // Allow teachers to get user data if they belong to the same organization.
  // This disallows reads when users have no organization set.
  allow read: if isTeacher() && resource.data.organizationId == userOrganizationId(request.auth.uid);
}

edit: even when I allow all reads on the table the issue persists so this this is not likely the cause

Another idea is that it is related to indexes but I find that strange since the query payload does not change. This should be the relevant index though:

{
  "collectionGroup": "users",
  "queryScope": "COLLECTION",
  "fields": [
    {
      "fieldPath": "groups",
      "arrayConfig": "CONTAINS"
    },
    {
      "fieldPath": "organizationId",
      "order": "ASCENDING"
    },
    {
      "fieldPath": "name",
      "order": "ASCENDING"
    }
  ]
}

So how and why is the group field on the searching user affecting my results?

EDIT:
To clarify what the data looks like and what is returned

Correct result: the searching user is not part of group A. All users of group A are returned.

[
    {
        "roles": [
            3
        ],
        "teachers": [
            {
                "uid": "5gd3sVyBkaO4oOf4Q0tBoNDJnnH2",
                "name": "Jonathan"
            }
        ],
        "email": "Asa.Dorsey@dummy.com",
        "createdDate": "2021-10-13T11:31:03.821Z",
        "uid": "dummy5",
        "groups": [
            {
                "id": "dg8wMCf0mkyhsVoCdhBp",
                "name": "A"
            }
        ],
        "organizationId": "996",
        "name": "Asa Dorsey"
    },
    {
        "organizationId": "996",
        "roles": [
            3
        ],
        "teachers": [
            {
                "uid": "5gd3sVyBkaO4oOf4Q0tBoNDJnnH2",
                "name": "Jonathan"
            }
        ],
        "createdDate": "2021-10-13T11:31:03.820Z",
        "name": "Barnaby Allison",
        "email": "Barnaby.Allison@dummy.com",
        "groups": [
            {
                "id": "dg8wMCf0mkyhsVoCdhBp",
                "name": "A"
            }
        ],
        "uid": "dummy1"
    },
    ...
]

Incorrect result: the logged in user is a part of group A. Only the user himself is returned.

[
    {
        "createdDate": "2021-11-29T11:08:59Z",
        "uid": "thiuoKkwjd745hcCsAeakGgssYs1",
        "organizationId": "996",
        "name": "Jonathan (admin)",
        "firstName": "Jonathan",
        "groups": [
            {
                "name": "A",
                "id": "dg8wMCf0mkyhsVoCdhBp"
            }
        ],
        "emailVerified": true,
        "roles": [
            0,
            1
        ],
        "lastName": "(admin)",
    }
]

Note how "name" and "id" fields appear in different order here, this might be relevant. The groups are assigned with the same method so it should not be in different order...

export const getGroupRef = (group: Group): GroupReference => ({
  id: group.id,
  name: group.name,
});

...

return admin
    .firestore()
    .collection(constants.dbCollections.users)
    .doc(uid)
    .update({
      groups: firestore.FieldValue.arrayUnion(getGroupRef(group)) as any,
    } as Partial<DbUser>);
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Jonathan
  • 8,771
  • 4
  • 41
  • 78
  • I think I don't understand your question/scenario very clearly. Can you share with us the expected output for your query and what you are getting now so that I understand the difference and I can work on it to solve your issue at my best effort? Please understand you may be explaining your question nicely, but its not coming across very clear to me. Edit and include the entire user table, both the scenarios( separately showing the difference) – Priyashree Bhadra Jan 19 '22 at 10:51
  • I am user A, the user logged in running the query. Depending on whether user A is a member of group X the same query either correctly returns all users of group X or incorrectly only returns user A if he is a member of group X. Groups is an array field on the DB user containing group ID and group name. – Jonathan Jan 19 '22 at 11:35
  • My gut feeling suspects the indices but I can't figure it out. – Jonathan Jan 19 '22 at 11:58
  • I can't help but notice "name" and "id" are in different order for the searching user than the other results. In the Firebase console they appear in the same order though. – Jonathan Jan 19 '22 at 12:07
  • Your "groups" & "teacher" are arrays of objects but array-contains is for arrays only containing "simple" or primitive data (e.g. string, number...). Also I am not sure if you can have multiple array-contains in if-else block because as per this [doc](https://firebase.google.com/docs/firestore/query-data/queries#compound_queries) you can include at most one array-contains in a query. – Priyashree Bhadra Jan 21 '22 at 12:13
  • Also, I feel query variable constructs query but does not execute it. You are just returning the query using `return query`. query is a Query object. To execute the query, and get the results from the server, you call get() on the query, which then returns a Promise. Once the promise results, you can handle the results in the QuerySnapshot object. Like mentioned in this [thread](https://stackoverflow.com/a/59843835/15803365) – Priyashree Bhadra Jan 21 '22 at 12:17
  • 1
    @PriyashreeBhadra the query is valid and only uses one array-contains, it gets results using `snapshotChanges`. This all works as expected. The thing with array-contains using an object is indeed the finicky part. It works with objects but supposedly they have to be absolutely identical. Now the weird thing is that the query is identical in both cases but the results depend on an extra record in the dataset (a user with a group defined, possibly varying in some invisible way). – Jonathan Jan 21 '22 at 13:43
  • I found this [stackoverflow thread](https://stackoverflow.com/a/67962717/15803365), go through the answer and the comments, I am really finding new facts now I guess which is not documented in the documentation. I am not vouching for its working but seems like a good one. – Priyashree Bhadra Jan 21 '22 at 14:25

0 Answers0