Design firebase DB for filtering - firebase

I use Firebase and AngularFire4 in a web application. The model is relatively simple, but I am having troubles in finding the right way to design it.
I have two collections: places and countries like the following:
Places
"places" : {
"-Kjx7NhHnyZNIZbxvzx4" : {
"name" : "Dereck beer",
"type" : "Bar"
"location" : {
"cityName" : "Kagoshima",
"country" : {
"code" : "JP",
"id" : 110,
"name" : "Japan"
},
"streetName" : "892-0842 Higashisen"
},
"modifiedOn": 121211321321
}
//... More data here...
}
Countries
"countries" : {
"110" : {
"code" : "JP",
"id" : 110,
"name" : "Japan"
}
}
At the moment in the web application I display the list of places and I can navigate to the detail view of each place. I can also display the countries where the places belong to (I create a new entry in countries collection, if not existing already, when a new place is created/updated).
However I would like to filter the places by some properties, like by type for instance. I tried with the following query, from AngularFire docs, but it does not work:
constructor(private db: AngularFireDatabase) {
var query = {
orderByChild: 'modifiedOn',
equalTo: {
value: 'Bar',
key: 'type' }
};
this.db.list("/places", query).subscribe( data => {
//All places are returned, and not only the one of type = "Bar"
});
}
If possible, I would like to avoid this approach and to replicate excessively my collections just for being able to query them.

it should be
var query = {
orderByChild: 'type',
equalTo: 'Bar'
};
orderByChild and equalTo is a pair in firebase, it's different from sql query

Related

Meteor: Can filter a collection by _id but I can't filter a collection using other fields

I'm fairly new to meteor and I'm still trying to find my way around with filtering collections. Here is my problem, I have a collection defined as follows;
parent_id: {
label: 'Parent ID',
type: String,
},
ar_session_id: {
label: 'Session ID',
type: String,
},
I have inserted some documents and here is one;
{
"_id" : "oQdtbBtKXHzdxWvzn",
"parent_id" : "dJkbDBXut5WzwkaFN",
"ar_session_id" : "dJkbDBXut5WzwkaFNuz77MFgcuGyvgokip",
"question" : "Do you have blah blah...?",
"answer" : "no",
"createdAt" : 1564563509127
}
I am able to filter using parent_id but I can't filter using ar_session_id
var parent_id = "dJkbDBXut5WzwkaFN";
var ar_session_id = "dJkbDBXut5WzwkaFNuz77MFgcuGyvgokip";
qry1 = AssessmentResponse.find({parent_id: parent_id}).fetch();
qry2 = AssessmentResponse.find({ar_session_id: ar_session_id}).fetch();
qry2 returns an empty set. What is it that I am missing?
The only reason I could think of would be if you are not publishing ar_session_id in the client.
For instance if you had something like this:
Meteor.publish("AssessmentResponse", function () {
return AssessmentResponse.find({}, { fields: { ar_session_id: 0 } });
});
Otherwise there is no reason the filtering would be empty, assuming you don't have any typo.

How to keep FULL_TRANSITIVE compatibility while adding new types to nested map in avro schema?

I have an existing avro schema that contains a field with a nested map of map of a record type (let's call it RecordA for now). I'm wondering if it's possible to add a new record type, RecordB, to this nested map of maps while maintaining FULL_TRANSIENT compatibility?
My thinking was that as long as the inner maps gets defaulted to an empty map it still adheres to the schema so it's backwards/forward compatible.
I've tried to redefine the type map<map<RecordA>> maps to map<map<union{RecordA, RecordB}>> maps in an .avdl file, but the schema registry is telling me this is not compatible.
I've also tried to default each map individually to an empty map ({ }) in a generated .avsc file, but schema registry says that's incompatible as well.
I do want to acknowledge that I know map<map<..>> is a bad practice, but what's been done has been done.
Registered Schema (original) .avdl:
record Outer {
map<map<RecordA>> maps;
}
record RecordA {
string value;
string updateTime;
}
Attempt with .avdl:
record Outer {
map<map<union{RecordA, RecordB}>> maps = {};
}
record RecordA {
string value;
string updateTime;
}
record RecordB {
union{null, array<string>} values = null;
union{null, string} updateTime = null;
}
Attempt with .avsc:
{
"name" : "maps",
"type" : {
"type" : "map",
"values" : {
"type" : "map",
"values" : [ {
"type" : "record",
"name" : "RecordA",
"fields" : [ {
"name" : "value",
"type" : "string"
}, {
"name" : "updateTime",
"type" : "string"
} ],
"default": { }
}, {
"type" : "record",
"name" : "RecordB",
"fields" : [ {
"name" : "value",
"type" : [ "null", "string" ],
"default" : null
}, {
"name" : "values",
"type" : [ "null", "string" ],
"default" : null
}, {
"name" : "updateTime",
"type" : [ "null", "string" ],
"default" : null
} ],
"default": { }
} ]
}
},
"default" : { }
}
The end goal is to have a map of maps to a record who has a field that can either be a string or array<string>. The original schema was registered to a schema-registry where the field has type string with no union {} with null or a default, so I believe the map needs to be map to a union of types with either version of the field.
Each try has returned the following from the schema-registry compatibility API
{
"is_compatible": false
}
Any insight would be very much appreciated!

Firebase database transactional search and update

I have a collection in firebase real time database that is a pool of codes that can be used once per 'store'. I need to search for an unused code, then mark it reserved by a store in an atomic fashion. The problem is I can't figure out how to do a transactional search and update in firebase, and the unused code is being 'used' multiple times until it gets updated.
const getUnusedCode = (storeID) => {
const codeRef = rtdb.ref('codes');
return codeRef
.orderByChild(storeID)
.equalTo(null)
.limitToFirst(1)
.once('child_added')
.then(snap => {
//setting the map {[storeID]:true} reserves the code
return snap.ref.update({ [storeID]: true }).then(() => {
return snap.key;
});
});
};
Edit: Here is the structure of the 'codes' collection:
{
"-LQl9FFD39PAeN5DnrGE" : {
"code" : 689343821901,
"i" : 0,
"5s6EgdItKW7pBIawgulg":true,
"ZK0lFbDnXcWJ6Gblg0tV":true,
"uKbwxPbZu2fJlsn998vm":true
},
"-LQl9FOxT4eq6EbwrwOx" : {
"code" : 689343821918,
"i" : 1,
"5s6EgdItKW7pBIawgulg":true
},
"-LQl9FPaUV33fvkiFtv-" : {
"code" : 689343821925,
"i" : 2
},
"-LQl9FQEwKKO9T0z4LIP" : {
"code" : 689343821932,
"i" : 3,
"ZK0lFbDnXcWJ6Gblg0tV":true
},
"-LQl9FQsEVSNZyhgdHmI" : {
"code" : 689343821949,
"i" : 4,
"5s6EgdItKW7pBIawgulg":true,
"uKbwxPbZu2fJlsn998vm":true
}
}
In this data, "5s6EgdItKW7pBIawgulg" is a store id, and true means this code has been used for this store
When new items are being imported, this function may get called hundres of times a minute, and is returning duplicates since it's not an atomic search-then-update. Is this possible in Firebase?
From what I understand you have a structure like this
codes: {
"code1": {
storeid: "store1"
},
"code2": {
storeid: "store2"
}
}
And you're trying to transactionally update it per store.
If this is the only update you're trying to do, I'd highly recommend inverting your data structure:
codes: {
"store1": "code1",
"store2": "code2"
}
On this structure the transaction for a store is quite simple, since the path is known:
var storeRef = firebase.database().ref("codes").child("store1");
storeRef.transation(function(current) {
if (current) {
// remove the code from the database
return null;
}
else {
// abort the transaction, since the code no longer exists
return undefined;
}
});
If you can't change the data structure, I'd probably user your current code to find the DatabaseReference to the code, and then use a transaction within the callback to update:
codeRef
.orderByChild(storeID)
.equalTo(null)
.limitToFirst(1)
.once('child_added')
.then(snap => {
//setting the map {[storeID]:true} reserves the code
return snap.ref.transaction(function(current) {
if (!current || current[storeId]) {
// the node no longer exists, or it already was claimed for this store
return undefined; // abort the transaction
}
else {
current[storeId] = true;
return current;
}
})
});

Firebase Realtime Database - Rules - Does two objects have at least one common child?

I am trying to check, if two objects have at least one common child. In the following example I want to be able to be control, if people can read org.money.value.
The right to read is determined by comparing the children of org.keys and users.{auth.uid}.keys. If there is a common key, reading would be allowed.
Database JSON:
{
"org" : {
"keys" : {
"red" : {
"value" : "..."
},
"blue" : {
"value" : "..."
}
},
"money" : {
"value" : "..."
}
},
"users" : {
"John" : { // in reality John == auth.uid of a user
"keys" : {
"red" : {
"value" : "..."
}
}
},
"Alice" : { // in reality Alice == auth.uid of a user
"keys" : {
"green" : {
"value" : "..."
}
}
}
}
}
Rules:
"rules:"{
"org" : {
"money" : {
// can read if "org.keys" and "users.auth.uid.keys"
// have at least one common child name.
// With the above data reading would be allowed for John,
// but not for Alice.
".read" : what to write here?
}
}
}
Is it possible to make this work?
By the way, the organization does not know the auth.uid of users.
I can't think of any way that you could do this determination in the JSON rules with your current database structure. I would suggest altering your structure to allow for this type of read determination. Here's a potential solution I came up with, which will require more filtering on client side:
When you create a new user key, loop through the org keys to see if it is already contained there. If so, add a BOOL to the user object, perhaps "canReadMoney" and set it to true. Then, your rule for money would look something like this:
"rules:"{
"org" : {
"money" : {
".read" : "root.child('users').child(auth.uid).child('canReadMoney').val==true"
}
}
}
another solution could be storing endpoint in the database like this
usersShareOrg
{
"John": { "Org" : true }
"Alice": {"Org": false}
}
and these values would be calculated and stored every time you added new user or org.keys entity.

Fetching denormalized data with firebase, angularFire

I'm trying to fetch all flowers data which belongs to a certain user, in this case simplelogin:69.
I'm starting with fetching all flower keys from the user, like this:
/users/simplelogin:69/flowers/
var ref = new Firebase("https://URL.firebaseio.com/users/"+uid+"/flowers");
var sync = $firebase(ref);
Now im stuck figuring out a clean way to fetch all the flower data by looping thrue every flower key from simplelogin:69 without looping thrue EVERY key in /flowers/ (in example below i only have three flower keys but in production i might have 10k).
I tried FirebaseIndex and firebase-util, but can't get it to work properly. Do anyone have any tips or anything? I've read previous posts here on stack but most seems out of date or not really suited for what im going for. Would really appriciate anything that can be solved with AngularFire.
Kind regards,
Elias
{
"flowers" : {
"-JiU57sFAfQwYtIq-LCl" : {
"image" : "test",
"name" : "test",
"type" : "Roses",
"uid" : "simplelogin:69"
},
"-JiU9-3ajlnFLpyUmBvL" : {
"image" : "dasdasd",
"name" : "sadasdas",
"type" : "Roses",
"uid" : "simplelogin:69"
},
"-JiUF-mioK3jQCYy6ZiG" : {
"image" : "ss",
"name" : "ss",
"type" : "Lilies",
"uid" : "simplelogin:69"
}
},
"users" : {
"simplelogin:69" : {
"flowers" : {
"-JiU57sFAfQwYtIq-LCl" : true,
"-JiU9-3ajlnFLpyUmBvL" : true,
"-JiUF-mioK3jQCYy6ZiG" : true
}
},
"simplelogin:70" : {
},
"simplelogin:71" : {
}
}
}
Got it to work now, thanks to #Kato 's answer on thread:
Firebase data normalized. How should I fetch a collection based on this structure? (tried it before creating this thread but didnt get it to work, so made som small changes and now it works).
Posting the solution for anyone stubling upon the same situation:
$scope.flowers = {};
var flowerRef = new Firebase('https://URL.firebaseio.com/flowers/');
var keyRef = new Firebase('https://URL.firebaseio.com/users/'+checkAuth.auth.uid+'/flowers');
keyRef.on('child_added', function(snap) {
var flowerId = snap.key();
flowerRef.child(flowerId).on('value', function(snap) {
$timeout(function() {
if( snap.val() === null ) {
delete $scope.flowers[flowerId];
}
else {
$scope.flowers[flowerId] = snap.val();
}
});
});
});
keyRef.on('child_removed', function(snap) {
var flowerId = snap.key();
$timeout(function(snap) {
delete $scope.flowers[flowerId];
});
});
This really is a tough issue with Firebase. If you implement a custom factory object for the user's flower list, you could dynamically request new flower data as the list changes.

Resources