I'm having trouble setting up a search query within my Firebase database. I put the rules all the way of my search, but I always get the index error.
Below is my structure:
My Firebase database structure:
My query:
https://zoome-production-users.firebaseio.com/country.json?orderBy="fullname"&equalTo="Vitor Darela"
Erro:
{
"error": "Index not defined, add \".indexOn\": \"fullname\", for path \"/country\", to the rules"
}
In your REST call, you run a query on /country in your database. This query inspects each direct child node of /country to see if it matches your filter. In your JSON data /country/AD does not have a property fullname, so the query will not return anything. Even if you were to add the index that the error message tells you about, it will not return anything.
The query you are trying to do is not possible with your current data structure. You will need to modify your data structure to allow it. It seems that you're trying to to find the countries that have a user with a certain name. To allow that query, add an additional data structure that holds precisely that:
"countries_by_fullname": {
"Vitor Darela": {
"AD": true
}
}
With this additional structure you can find the list of countries by simply reading /countries_by_fullname/Vitor Darela.
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value
Related
This is my firebase realtime database structure
I want to retrieve all questions with QID = "PQ1" . What i tried is given below . Its result is null .
database.child("Question").child("QID").equalTo("PQ1").limitToFirst(200).get().addOnSuccessListener {
Log.i("12345", "Got value ${it.value}")
}
My references :
Firebase - Search a child by value
Firebase search by child value
Search firebase by Child of child value
Your call to database.child("Question").child("QID") looks for a child node at path /Question/QID in your database, which doesn't exist and thus explains why you get an empty snapshot.
To query the Firebase Realtime Database needs two steps:
You order all child nodes on a property, on their key, or on their value by calling on of the orderBy... methods.
You then filter the ordered child nodes by calling one or more of the equalTo, startAt, startAfter, endAt, endBefore, limitToFirst and/or limitToLast methods.
While you're doing #2, you're not ordering the nodes first by calling orderBy....
The correct code:
database.child("Question").orderByChild("QID").equalTo("PQ1").limitToFirst(200).get().addOnSuccessListener {
Log.i("12345", "Got value ${it.value}")
}
Also don't forget that executing a query will potentially have multiple results, so the snapshot in it.value contains a list of those results and you will need to iterate over its children. Even if there is only a single result, the snapshot will contain a list of one result.
In a Firestore security rule, I'm trying to check if the user attempting to create/delete a document in one collection is also the owner of that object, as marked in another collection. Basically, my data looks something like this:
In the users collection, each user has a document like this:
{
name: 'john',
userItems: [
{
id: 'random-id',
...
},
...
],
...
}
In the items collection (which I am writing the rule for), all of the items from all of the users of the platform are there, and have Firestore IDs which correspond to the id keys in the elements of the items list of their owners. So if john created an item with the id random-id, his user document would look like the above, and there would be a new document in the items collection with the Firestore ID of random-id.
What I am trying to achieve is to create a security rule wherein a document in the items collection can only be updated if the user document of the currently authed user, which I can access with get(/databases/$(database)/documents/users/$(request.auth.uid)), has an element in their userItems list which has the id key equal to request.resource.id. If this were normal JS, I'd probably do something like:
match /items/{item} {
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid))
.data
.userItems
.some(userItem =>
userItem.id === request.resource.id
)
}
However, the Firestore List interface is very lacklustre, and doesn't support fancy operations like .some, or even basic/manual looping (as far as I'm aware). I've been trying to come up with some clever way around this, but even .joining the list and .matching the resulting string with some fancy RegExp wouldn't work, since I'm pretty sure that maps would parse as '[object Object]' instead of stringifying properly.
Is there any way to do this using standard Firestore rules, without reconfiguring my DB structure?
What you're trying to do isn't possible with security rules. You will need to either change that way you represent your data (which I recommend, as a list is probably not the best representation), or add more data to satisfy your requirements.
If the the random-id is unique in the list, you should consider using a map instead of a list to represent it, so that you can do simple lookups on the Map that becomes available in rules. If your userItems field was a map indexed by that ID, you could instead say:
allow write: if get(...).data.userItems.keys().hasAny([request.resource.id]);
If for some reason you can't change the field, you will need to duplicate the IDs into a new list field and check it like this:
allow write: if get(...).data.userItemIds.hasAny([request.resource.id]);
I have a very large database of users that I need to paginate through. The structure is as follows
users > -userId > logs > -logId > {type, timestamp, ...}
Not all users have logs so according to the data order docs if I orderBy "logs" these users should appear first (https://firebase.google.com/docs/database/web/lists-of-data#data-order)
When using orderByChild(), data that contains the specified child key
is ordered as follows:
Children with a null value for the specified child key come first.
As a result I am trying to use the following code to retrieve the last 5 users with logs but am running out of memory on the cloud platform (2GB). Should I be adding an index to assist with this query or is my syntax here incorrect?
admin.database().ref("users").orderByChild("logs").limitToLast(5).once("value", (snapshot) => {
Is there a better way to fetch a limited group of users who have logs? Any feedback would be appreciated
Should I be adding an index to assist with this query or is my syntax here incorrect?
The answer to that is always yes. Without an index in your security rules, the server will send all data at the location to the client, which then orders and filters it.
So for your query, your rules will need to contain an index on logs under the users node.
I'm attempting to setup security rules that allow access to a collection, based on the value of a document field in a subcollection.
This works as expected when retrieving an individual document by id, which is a get operation. However, when querying main_collection (a list operation), this fails with a "permission denied" error. Since there is only a single document in the collection, this is not a case where I don't have permission to some of the documents being queried, such as on this question.
My database structure looks like the following. It contains the collection being listed (main_collection), which has a single document (some_doc), which has a single subcollection (sub_collection), which has a single document (another_doc).
/main_collection/some_doc/sub_collection/another_doc
another_doc has one string field someFieldValue.
For this example, my query is of the entire collection, which is the single document. In my actual application it only queries the documents it expects to have access to, but the end result here is the same because I cannot filter against a document's subcollection from the client library.
firestore.collection('main_collection').get()
These are my security rules.
service cloud.firestore {
match /databases/{database}/documents {
match /main_collection/{mainColDoc} {
// This operation works
allow get: if subCollectionDocumentHasField('someFieldValue');
// This operation fails with permission denied
allow list: if subCollectionDocumentHasField('someFieldValue');
// This checks for the existence of a field on the subcollection's document
function subCollectionDocumentHasField(fieldName) {
return get(/databases/$(database)/documents/main_collection/$(mainColDoc)/sub_collection/another_doc).data.keys().hasAny([fieldName]);
//return get(/databases/$(database)/documents/main_collection/some_doc/sub_collection/another_doc).data.keys().hasAny([fieldName]);
}
}
}
}
The subCollectionDocumentHasField function checks for the existence of the someFieldValue field on the document another_doc. In this function, if I replace the $(mainColDoc) variable with the hard-coded document id some_doc, the list operation is successful. Since the $(database) path variable can be used in this context, I would expect that others could be as well.
Is this a bug or expected behavior?
This is actually the expected behavior, you can't use Firebase's rules to filter the results of your query.
A typical scenario would be to have collection of messages, where each message refers to its creator.
You can't simply add a rule where reading is only allowed on messages for which creator is the authenticated user, to filter automatically the messages of the current authenticated user.
The only way to go is to query with filter on the client side (or through a Cloud function).
The documentation is very clear about this :
When writing queries to retrieve documents, keep in mind that security rules are not filters—queries are all or nothing. To save you time and resources, Cloud Firestore evaluates a query against its potential result set instead of the actual field values for all of your documents. If a query could potentially return documents that the client does not have permission to read, the entire request fails.
From Firebase's documentation
I opened a ticket with Google and confirmed effectively what #José inferred from usage, which is that a security rule "is only checked once per query".
For clarification, while a security rule on a list operation will typically not query the contents of a document (to avoid potenitally-poor performance), there is at least one condition when it will query the contents of a document. This is when the security rule is guaranteed to return only one document. When this guarantee is met, the single document's contents will be queried because high performance can be maintained; the same as on a get operation.
So, in the linked example in my question where the list operation's rule is referencing a parent document, this guarantee is met and the parent document's contents will get queried.
Also, in my example where the list operation's rule is referencing a hard-coded document id, this guarantee is met and the hard-coded document's contents will get queried.
For the sake of stating it explicitly, for a list operation, in any case where Firestore cannot guarantee that its rule will only query a single document, access will be automatically denied, by design.
To reiterate what the other answers say, but stated in a slightly different way: The query must be consistent with the security rules, before any query documents are looked at, or it will fail with permission denied.
For example, if all of the documents in a sub-collection happen to match the security rule (e.g., your create and list rules both require the owner field is "X"), the query still must match the security rules (e.g., the query must also filter on owner is "X") or it will fail with a permission denied error, independent of the actual content of the sub-collection.
Currently I can't seem to find a way to delete an object from firebase list via REST api. For example I am trying to remove this from the list:
https://mrdapper.firebaseio.com/v0/users/41/favs.json?orderBy=%22id%22&equalTo=107657061
Posting a DELETE request doesn't work with query parameter.
You can't delete with a query, (although that would be awesome). But you can use the results to send a DELETE request.
Do a GET:
GET https://mrdapper.firebaseio.com/v0/users/41/favs.json?orderBy=%22id%22&equalTo=107657061
This will return the object and you can send a DELETE request for each item it returns.
DELETE https://mrdapper.firebaseio.com/v0/users/41/favs/<returned-id>.json
Now, you may not like sending one delete request per object. But, with your data structure this is necessary.
If you'd like to easily query and delete items, you could try this data structure:
/users/$user_id
/userFavs/$user_id/$fav_id
Store the favs in it's own location under the root. This will allow you to retrieve user data without always getting the favs.
For userFavs if you key off userid and the favid you can easily query and delete.
{
"userFavs": {
"41": {
"107657061": {
"note_count": 43633
}
}
}
}
Now you can easily get all of the the user's favorites by specifying the user's id. If you need to delete by an id, that is also a key. So you can now DELETE without querying.
DELETE https://mrdapper.firebaseio.com/v0/userFavs/41/107657061.json