10

When running my Android app using a Firebase Realtime Database I get the following warning:

Using an unspecified index. Consider adding ".indexOn" ... to your security and Firebase Database rules for better performance

I totally understand the warning. But I don't know how to make it better. I really want to query only on indexed fields!

This is my DB:

{      
  "groupUsers" : {
    "g1" : {
      "u1" : "admin"
    },
    "g2" : {
      "u1" : "admin",
      "u2" : "readonly"
    }
  },
  "groups" : {    
    "g1" : {
      "areas" : {
        "a1" : {
          "groupId" : "g1",
          "name" : "My Group"
        }
      },
      "interests" : {
        "i1" : {
          "groupId" : "g1",
          "name" : "My Interest"
        }
      },
      "points" : {
        "p1" : {
          "address" : "First Street",
          "areaId" : "a1",
          "groupId" : "g1",
          "latitude" : -25,
          "longitude" : -55,
          "name" : "Harry"
        }
      },
      "properties" : {
        "name" : "My Group Name"
      },
      "waypoints" : {
        "w1" : {
          "areaId" : "a1",
          "groupId" : "g1"
        }
      }
    }
  }
  "users" : {
    "u1" : {
      "email" : "some@domain.com",
      "firstName" : "Peter",
      "lastName" : "Smith"
    },
    "u2" : {
      "email" : "other@email.com",
      "firstName" : "John",
      "lastName" : "Wayne"
    }
  }
}

These are my security rules:

{
  "rules": {       
    "groups": {          
      "$groupId": {
        ".read":  "root.child('groupUsers').child($groupId).child(auth.uid).exists()",
        ".write": "! root.child('groupUsers').child($groupId).exists() || root.child('groupUsers').child($groupId).child(auth.uid).val() === 'admin'",
        "$child": {
          ".write": "root.child('groupUsers').child($groupId).child(auth.uid).exists() && root.child('groupUsers').child($groupId).child(auth.uid).val() !== 'readonly' && ($child === 'points' || $child === 'visits')"
        }
      },

      "areas": {        
        ".indexOn": ["groupId", "name"]
      },
        "waypoints": {
        ".indexOn": ["groupId", "areaId", "sequenceNumber"]
      },
      "interests": {
        ".indexOn": ["groupId", "rank", "name"]
      },
      "points": {        
        ".indexOn": ["groupId", "areaId", "name"]
      },
      "visits": {
        ".indexOn": ["groupId", "pointId", "interestId", "userId"] 
      }
    },
    "users": {
      ".read": "auth != null",
      "$userId": {        
        ".write": "auth != null && $userId === auth.uid && newData.val() != null",
        ".indexOn": ["email", "firstName", "lastName"]
      }
    },
    "groupUsers": {
      ".read": "auth != null",
      "$groupId": {
        ".write": "auth != null && (root.child('groupUsers').child($groupId).child(auth.uid).val() === 'admin' || !root.child('groupUsers').child($groupId).exists())"
      }
    }
  }
}

The problem is the groupUser structure. It has group keys as property names. I do not have a field to index on since I do not have a constant property name. How to change the structure to make it possible that all fields are indexed and that all my rules still work?

juergen d
  • 201,996
  • 37
  • 293
  • 362

2 Answers2

1

I am not an expert in Firebase but it seems to me like there is an issue with your security rules. Specifically in the groups section you are referencing a field named "name" as index, this field however is not a direct field of the group object. 'name' is a field of the properties object so your groups configuration should had been :

.indexOn": ["properties/name"]

OR

.indexOn": "properties/name"

As you can see Firebase has reserved the '/' character to navigate inside the nested fields of your objects to use as indexes.

If by any chance the properties field is accessed by default and this is an implicit characteristic of FireBase, please skip this part of my response.

I think that if we are to denote the relation-object(user/group) as the source of the warning, we need to at least optimize the rest of the object indexes correctly and then maybe we can redesign the relational table a little bit to incorporate internal, indexable fields.

Optimization of the Entity-Relation structure:

As for the Entity-Relation table groupUser, one suggestion could be that you incorporate it in the Group table. Add a groupUsers field inside the group. This field would consist of an array and every array entry is an User object. The only problem with this approach is that you need to somehow specify the foreign key relation between the users and these entries (like we do in relational tables) in order to take advantage of cascade operations. Example change:

    "groups" : {    
        "g1" : {
          "areas" : {
            "a1" : {
              "groupId" : "g1",
              "name" : "My Group"
            }
          },
          "groupUsers" : [
             {"userID" : "u1"},
             {"userID" : "u2"}],...
}

or using a non-array solution:

    "groups" : {    
        "g1" : {
          "areas" : {
            "a1" : {
              "groupId" : "g1",
              "name" : "My Group"
            }
          },
          "groupUsers" : {
              "r1" : {"userID" : "u1"},
              "r2" : {"userID" : "u2"}
           },...
}
  • You are right, there is no sense in indexing on `name` since it is in `properties`. But that does not have anything to do with my problem. Thanks anyway. I removed that unsignificant part from my rules file. – juergen d Jan 10 '18 at 10:29
  • I wouldn't say that it makes no sense but just that currently you were referencing a non-existent field (group/name instead of group/properties/name). I will update my response in hope that it will further assist you with the entity-relation issue. – Aristotelis Margaris Jan 10 '18 at 16:00
1

Tried getting your question, I got your point that you don't have key name to set index, As per documentation here we can use ".value" here to add index on values,

Below might work for you, added .value at user role level in groupUsers, as this seems to be only string value(user role) where you can set index

"groupUsers": {
  ".read": "auth != null",
  "$groupId": {
    ".write": "auth != null && (root.child('groupUsers').child($groupId).child(auth.uid).val() === 'admin' || !root.child('groupUsers').child($groupId).exists())"
    ".indexOn": ".value"
  }
}
Akhil
  • 6,667
  • 4
  • 31
  • 61
  • Good idea but this only works if the values are scalars. But in my case they are key-value pairs. – juergen d Jan 15 '18 at 16:42
  • Under groupId you have only "admin" and "readonly" as values, these are not key values pair. – Akhil Jan 16 '18 at 01:11
  • No, I have `"userid" : "admin"` which is a key-value pair. And I tried it - it does not work. – juergen d Jan 16 '18 at 09:29
  • Ok, What I was saying was **groupId** has key-value pairs, but **".value"** works only on values, it will ignore keys( u1,u2..etc in your example). It is same as example on https://firebase.google.com/docs/database/security/indexing-data, **"dino sports scores"** where dino names are different but index is on value – Akhil Jan 16 '18 at 09:42
  • Yes, I saw that example but sadly it is not the same. If you look closely you will see that it does ignore keys, but my structure is `key:key:value` and not `key:value`. – juergen d Jan 16 '18 at 09:47