I have a realtime database structure like below.
Users have some friends. For example, I am Joe and I'd like to fetch users who have friends as Joe.
In this example, Dave and Harry have a friend called Joe, so my query should fetch Dave and Harry but shouldn't Micheal.
Is there a way to do this without fetching data in a loop?
{
"Users": {
"Joe": {
"Friends": ["Dave","Harry","Micheal"]
},
"Dave": {
"Friends": ["Joe","Jack","Brent"]
},
"Harry": {
"Friends": ["Jack","Joe"]
},
"Micheal": {
"Friends": ["Ken","Jack","Brent"]
}
}
}
Firebase Realtime database cannot perform a query across array members, to see if it contains a specific value in any position or to add/remove a record. Firebase documentation recommends against using arrays. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
As usual with NoSQL databases: if you can't perform the use-case you want with your current data structure, you can typically change/expand your data structure to allow the use-case. The best practice is to use maps instead of arrays. A possible database structure could look like this:
"Users": {
"Joe": {
"Friends"
"Dave": true,
"Harry": true,
"Micheal": true
},
"Dave": {
"Friends"
"Joe": true,
"Jack":true,
"Brent": true
},
"Harry": {
"Friends"
"Jack": true,
"Joe": true
},
"Micheal": {
"Friends"
"Ken":true,
"Jack": true,
"Brent": true
}
}
You can also take a look, here, for more informations.
Related
My use case is to fetch all posts which is not already commented by current user.
I want to achieve this with single query but my current approach is not working.
DB structure:
posts: {
post1:{
content: "Lorem Ipsum"
comments: {
uid1: true,
uid2: true
}
}
}
My query looks like this:
val postsRef = FirebaseDatabase.getInstance().reference.child("posts")
.orderByChild("comments/{uid}")
.equalTo(null)
Above query an index, but my current index is one dynamic value which firebase doesn't allow.
"posts" : {
"$postId": {
"comments": {
".indexOn" : ".value"
}
}
}
I'm stuck here, it would be helpful if anyone guide me to achieve this.
Thanks
Firebase database queries can only check for the presence of a certain value, not for the absence of it.
So you can either add each user ID to each post and then remove them when they comment, or (more likely) you will have to implement the logic in your application without a database query: just load all posts and remove the ones where you then find that this user already commented.
Today, I received an email which states,
[Firebase] Your Realtime Database 'CC-default-rtdb' has insecure rules, any user can read your entire database, any user can write to your entire database
My server runs every day to store values in the Realtime Database. When I started this Firebase project, I used the basic read and write rules.
{
"rules": {
".read": true,
".write": true
}
}
If I change the rules now, does it affect my external Node.JS server from storing values into the Realtime DB?
I even access this DB from a mobile app, so will it affect the mobile app from reading data if the rules are changed?
How can I secure these Realtime DB rules (from public to private) without interrupting the access for the external server and mobile app? Importantly, my external server access must not get disrupted.
If you are accessing your database through authentication(login) you can set your nodes to auth!=null so that any unauthorized user cannot access them.
You need to follow the following steps:
Set read to true for all nodes you need to make publicly available
Set read/write to auth!=null for all nodes you want to make available to any authorized user
Set custom validation rules for any node that needs special access (e.g.: A user can only write to his/her data)
As per the screenshot, if anyone gets a hold of your project id, they can modify and steal your entire database.
You need to set rules in a way that properly matches your use case. As far as I understand:
Allow the external server account access to the whole DB
Allow the mobile app conditional access (by user perhaps?)
Example
Take this database for instance:
{
"top_level": {
"users": [
{
"username": "X",
"userId": "0"
},
{
"username": "Y",
"userId": "1"
}
],
"public_data": {
"news": [
{
"title": "X",
"body": "Y"
}
]
}
}
}
I want to set:
Node "news" to be publicly available to read (no auth)
Node "users" to be only available to other logged-in users to read(auth)
Children of node "users" to only be writable for the user whose id matches the value userId in the node
The rule, in this case, would be:
{
"rules": {
".read": false,
".write": false,
"top_level": {
"users": {
".read": "auth!=null",
"$user_id": {
".write": "auth!=null && data.child('userId').val()===auth.uid"
}
},
"news" : {
".read":true
}
}
}
}
Note, rules set as true in the top-level override inner rules.
Comment if you need clarification.
The structure of the table is:
chats
--> randomId
-->--> participants
-->-->--> 0: 'name1'
-->-->--> 1: 'name2'
-->--> chatItems
etc
What I am trying to do is query the chats table to find all the chats that hold a participant by a passed in username string.
Here is what I have so far:
subscribeChats(username: string) {
return this.af.database.list('chats', {
query: {
orderByChild: 'participants',
equalTo: username, // How to check if participants contain username
}
});
}
Your current data structure is great to look up the participants of a specific chat. It is however not a very good structure for looking up the inverse: the chats that a user participates in.
A few problems here:
you're storing a set as an array
you can only index on fixed paths
Set vs array
A chat can have multiple participants, so you modelled this as an array. But this actually is not the ideal data structure. Likely each participant can only be in the chat once. But by using an array, I could have:
participants: ["puf", "puf"]
That is clearly not what you have in mind, but the data structure allows it. You can try to secure this in code and security rules, but it would be easier if you start with a data structure that implicitly matches your model better.
My rule of thumb: if you find yourself writing array.contains(), you should be using a set.
A set is a structure where each child can be present at most once, so it naturally protects against duplicates. In Firebase you'd model a set as:
participants: {
"puf": true
}
The true here is really just a dummy value: the important thing is that we've moved the name to the key. Now if I'd try to join this chat again, it would be a noop:
participants: {
"puf": true
}
And when you'd join:
participants: {
"john": true,
"puf": true
}
This is the most direct representation of your requirement: a collection that can only contain each participant once.
You can only index known properties
With the above structure, you could query for chats that you are in with:
ref.child("chats").orderByChild("participants/john").equalTo(true)
The problem is that this requires you to define an index on `participants/john":
{
"rules": {
"chats": {
"$chatid": {
"participants": {
".indexOn": ["john", "puf"]
}
}
}
}
}
This will work and perform great. But now each time someone new joins the chat app, you'll need to add another index. That's clearly not a scaleable model. We'll need to change our data structure to allow the query you want.
Invert the index - pull categories up, flattening the tree
Second rule of thumb: model your data to reflect what you show in your app.
Since you are looking to show a list of chat rooms for a user, store the chat rooms for each user:
userChatrooms: {
john: {
chatRoom1: true,
chatRoom2: true
},
puf: {
chatRoom1: true,
chatRoom3: true
}
}
Now you can simply determine your list of chat rooms with:
ref.child("userChatrooms").child("john")
And then loop over the keys to get each room.
You'll like have two relevant lists in your app:
the list of chat rooms for a specific user
the list of participants in a specific chat room
In that case you'll also have both lists in the database.
chatroomUsers
chatroom1
user1: true
user2: true
chatroom2
user1: true
user3: true
userChatrooms
user1:
chatroom1: true
chatroom2: true
user2:
chatroom1: true
user2:
chatroom2: true
I've pulled both lists to the top-level of the tree, since Firebase recommends against nesting data.
Having both lists is completely normal in NoSQL solutions. In the example above we'd refer to userChatrooms as the inverted index of chatroomsUsers.
Cloud Firestore
This is one of the cases where Cloud Firestore has better support for this type of query. Its array-contains operator allows filter documents that have a certain value in an array, while arrayRemove allows you to treat an array as a set. For more on this, see Better Arrays in Cloud Firestore.
The structure of the table is:
chats
--> randomId
-->--> participants
-->-->--> 0: 'name1'
-->-->--> 1: 'name2'
-->--> chatItems
etc
What I am trying to do is query the chats table to find all the chats that hold a participant by a passed in username string.
Here is what I have so far:
subscribeChats(username: string) {
return this.af.database.list('chats', {
query: {
orderByChild: 'participants',
equalTo: username, // How to check if participants contain username
}
});
}
Your current data structure is great to look up the participants of a specific chat. It is however not a very good structure for looking up the inverse: the chats that a user participates in.
A few problems here:
you're storing a set as an array
you can only index on fixed paths
Set vs array
A chat can have multiple participants, so you modelled this as an array. But this actually is not the ideal data structure. Likely each participant can only be in the chat once. But by using an array, I could have:
participants: ["puf", "puf"]
That is clearly not what you have in mind, but the data structure allows it. You can try to secure this in code and security rules, but it would be easier if you start with a data structure that implicitly matches your model better.
My rule of thumb: if you find yourself writing array.contains(), you should be using a set.
A set is a structure where each child can be present at most once, so it naturally protects against duplicates. In Firebase you'd model a set as:
participants: {
"puf": true
}
The true here is really just a dummy value: the important thing is that we've moved the name to the key. Now if I'd try to join this chat again, it would be a noop:
participants: {
"puf": true
}
And when you'd join:
participants: {
"john": true,
"puf": true
}
This is the most direct representation of your requirement: a collection that can only contain each participant once.
You can only index known properties
With the above structure, you could query for chats that you are in with:
ref.child("chats").orderByChild("participants/john").equalTo(true)
The problem is that this requires you to define an index on `participants/john":
{
"rules": {
"chats": {
"$chatid": {
"participants": {
".indexOn": ["john", "puf"]
}
}
}
}
}
This will work and perform great. But now each time someone new joins the chat app, you'll need to add another index. That's clearly not a scaleable model. We'll need to change our data structure to allow the query you want.
Invert the index - pull categories up, flattening the tree
Second rule of thumb: model your data to reflect what you show in your app.
Since you are looking to show a list of chat rooms for a user, store the chat rooms for each user:
userChatrooms: {
john: {
chatRoom1: true,
chatRoom2: true
},
puf: {
chatRoom1: true,
chatRoom3: true
}
}
Now you can simply determine your list of chat rooms with:
ref.child("userChatrooms").child("john")
And then loop over the keys to get each room.
You'll like have two relevant lists in your app:
the list of chat rooms for a specific user
the list of participants in a specific chat room
In that case you'll also have both lists in the database.
chatroomUsers
chatroom1
user1: true
user2: true
chatroom2
user1: true
user3: true
userChatrooms
user1:
chatroom1: true
chatroom2: true
user2:
chatroom1: true
user2:
chatroom2: true
I've pulled both lists to the top-level of the tree, since Firebase recommends against nesting data.
Having both lists is completely normal in NoSQL solutions. In the example above we'd refer to userChatrooms as the inverted index of chatroomsUsers.
Cloud Firestore
This is one of the cases where Cloud Firestore has better support for this type of query. Its array-contains operator allows filter documents that have a certain value in an array, while arrayRemove allows you to treat an array as a set. For more on this, see Better Arrays in Cloud Firestore.
While firebase recommends use of indexes to keep the data structure flat, I am running into an issue here.
Consider this users entity (from Firebase docs sample)
{
"users": {
"mchen": {
"name": "Mary Chen",
// index Mary's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"alpha": true,
"charlie": true
"delta" : true
...
"10000th entry":true
}
},
...
}
Now I can read a user from the database like this.
List<User> users = new Select().from(User.class).limit(5).execute();
But, my question is if the "groups" key has say 10,000 entries what would happen? Will it fetch the whole list? Wouldn't it crash the app, due to lack of memory.
In general, if I am trying to fetch a high level node (which has nested nodes), what part of it will be fetched?