Firebase - Get all records which contains a key - firebase

I have a series of records in Firebase, like these:
{
records: {
n1: {
f1: 1,
f2: 0,
f3: 1
},
n2: {
f1: 0,
f4: 1,
f5: 0
}
}
}
I want, using an index, to get al records that contains the key f1 whatever its content might be (0 or 1)
There's a way to do this with Firebase?

Firebase Queries cannot test for the existence of a property, only for their value.
The easiest way to work around this limitation is to add a property with a value that signals the existence of the property you're looking for. Yeah... that is probably easier to understand with an example:
{
"records": {
"n1": {
"f1": 1,
"f2": 0,
"f3": 1,
"has_f1": true
},
"n2": {
"f1": 0,
"f4": 1,
"f5": 0,
"has_f1": true
},
"n3": {
"f7": 8,
"f8": 0
}
}
}
With this you can query:
ref.child('records')
.orderByChild('has_f1')
.equalTo(true)
.on('child_added', function(snapshot) {
console.log(snapshot.val());
})

Related

How to create AppSync DynamoDB resolver for object with relationships

I have a single DynamoDB table that has Games and Players. I currently have the following Lambda resolver that works for my AppSync getGame query. The question is, is it possible to write a DynamoDB resolver using the velocity templates that does the same so I can avoid the lambda invocation.
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
let result = null;
let params;
switch(event.field) {
case "getGame":
const id = event.arguments.id;
if (!id) {
throw new Error('Missing game id');
};
params = {
TableName: 'games',
KeyConditionExpression: 'pk = :pk AND sk = :sk',
ExpressionAttributeValues: {
':pk': 'game',
':sk': `meta_${id}`
}
};
const game = (await docClient.query(params).promise()).Items[0];
// get players
const gameKey = `game_${game.sk.split('_')[1]}_${game.sk.split('_')[2]}`;
params = {
TableName: 'games',
KeyConditionExpression: 'pk = :pk AND begins_with(sk, :sk)',
ExpressionAttributeValues: {
':pk': gameKey,
':sk': 'player_'
}
};
game.players = (await docClient.query(params).promise()).Items;
result = game;
break;
}
return result;
};
And the result looks like
{
"gsipk": "NEW_OPEN",
"sk": "meta_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"gsisk": "level_1_players_4",
"pk": "game",
"players": [
{
"gsipk": "player_3a7bb19c-0ccd-42df-a606-acd8b1f5e288",
"gsisk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"points": 0,
"num": 4,
"place": null,
"sk": "player_3a7bb19c-0ccd-42df-a606-acd8b1f5e288",
"pieces": [],
"wilds": 0,
"pk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"color": "gold",
"pows": 0
},
{
"gsipk": "player_96b772b1-4127-43da-b550-029d5c632675",
"gsisk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"points": 0,
"num": 2,
"place": null,
"sk": "player_96b772b1-4127-43da-b550-029d5c632675",
"pieces": [],
"wilds": 0,
"pk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"color": "blue",
"pows": 0
},
{
"gsipk": "player_9d30c675-930f-401b-ac5f-8db32bb2acb8",
"gsisk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"points": 0,
"num": 3,
"place": null,
"sk": "player_9d30c675-930f-401b-ac5f-8db32bb2acb8",
"pieces": [],
"wilds": 0,
"pk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"color": "green",
"pows": 0
},
{
"gsipk": "player_ab179ad1-a160-44f8-b438-0e93385b6c47",
"gsisk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"points": 0,
"num": 1,
"place": null,
"sk": "player_ab179ad1-a160-44f8-b438-0e93385b6c47",
"pieces": [],
"wilds": 0,
"pk": "game_1578241126110_35660fcc-3cde-4d30-9ebd-09abba1aedf7",
"color": "red",
"pows": 0
}
]
}
Okay, thanks to #cyberwombat's comment:
Unless you remap your data to be able to fetch all items in one request
I was able to figure this out. First, I had to refactor my table a bit. I changed the primary key (hash) to be game_<uuid> and then referenced the game details with a sort key (range) like meta_<timestamp> and players with a sort key like player_<uuid>.
Once I did this, I was able to use this resolver to Query and return the game details and all players with this request mapping template:
{
"version": "2017-02-28",
"operation": "Query",
"query" : {
"expression" : "pk = :pk",
"expressionValues" : {
":pk": { "S": "$ctx.arguments.pk" }
}
}
}
The above query returns 5 items (the 4 players and the game metadata). I then used a response mapping template like this:
#set($game = {})
#set($players = [])
#foreach($item in $ctx.result.items)
#if($util.matches('meta_\d*', $item.sk))
## main game object
#set($game = $item)
#else
## player
$util.qr($players.add($item))
#end
#end
$util.qr($game.put("players", $players))
$util.toJson($game)
Now I have a SINGLE query to DynamoDB and no lambda resolvers... beautiful.
Unless you remap your data to be able to fetch all items in one request you will need Pipeline resolvers.. In summary a pipeline is a number of resolvers in line wrapped in a before/after template.
In your case these before/after templates are not really used so the basic setup would be:
Before template (nothing is needed so an empty json is fine)
{}
After template (to pass the result from previous calls on)
$util.toJson($ctx.result)
Then you will have 2 DynamoDB resolvers. These are the same as other DynamoDB resolvers you may have previously done except that in the seecond one, in order to access the first DynamoDB resolvers result you will use $ctx.prev.result. So let's say you passed the game ID in the response of the first call as:
{
"game_id": "$ctx.result.get('theGameId')",
...
}
Then this can be accessed in second request template as $ctx.prev.result.game_id. You can also use the stash instead - $ctx.stash.put() and $ctx.prev.get(). The stash is useful if you need to do something in the BEFORE request (the very first one which we have blank for now) and pass that throughout the resolvers.

Calculate Matrix addresses with limited land access

While using HERE's Calculate Matrix my team have encountered routes that are causing matrix calculations errors while using balanced routing mode.
Especially routes that are from Sweden to Great Britan.
start: [
{
id: 'sw',
latitude: 57.72994,
longitude: 11.94854
}
]
destination: [
{
id: 'gb',
latitude: 55.9007,
longitude: -3.64154
}
]
Example of sent request(provide your own appId and appCode):
https://matrix.route.api.here.com/routing/7.2/calculatematrix.json?app_id=yourappid&app_code=yourappcode&mode=truck;balanced;traffic:disabled;tollroad:0&motorway:0&boatFerry:0&tunnel:0&dirtRoad:0&park:0&start0=geo!57.72994%2C11.94854&destination0=geo!55.9007%2C-3.64154&summaryAttributes=traveltime&costfactor&distance
Response:
{
response: {
metaInfo: {
timestamp: "2019-08-06T07:53:21Z",
mapVersion: "8.30.99.152",
moduleVersion: "7.2.201930-4693",
interfaceVersion: "2.6.66",
availableMapVersion: ["8.30.99.152"]
},
matrixEntry: [
{ startIndex: 0,
destinationIndex: 0,
status: "failed"
}
]
}
}
Example with calculate route with no problem:
https://route.api.here.com/routing/7.2/calculateroute.json?app_code=pxIXqdtgOSwQDXSDfjLQpw&app_id=cgZPrYfgRePXzXC3PbBp&jsonattributes=41&language=en-us&length=0&limitedweight=0&maneuverattributes=po,ti,pt,ac,di,fj,ix&metricsystem=metric&mode=balanced;truck;traffic:disabled&routeattributes=sh,gr&waypoint0=geo!stopOver!57.72994,11.94854&waypoint1=geo!stopOver!55.9007,-3.64154;;End&width=0
I would like to know what is the reason this is happening and what our team can improve to get results for such routes
I don't have a solution, but I am currently encountering the same problem. It seems to originate from a select few address combinations. (see full response below; some start0 --> destination1 fails but destination1 --> start0 does not fail). I don't recall having encountered this problem before, while some of the locations currently throwing an error have been in my program before. Could it be something temporary?
{
'response': {
'metaInfo': {
'timestamp': '2019-08-06T09:00:32Z',
'mapVersion': '8.30.99.152',
'moduleVersion': '7.2.201930-4693',
'interfaceVersion': '2.6.66',
'availableMapVersion': ['8.30.99.152']
},
'matrixEntry': [{
'startIndex': 0,
'destinationIndex': 0,
'summary': {
'distance': 50,
'travelTime': 6,
'costFactor': 6
}
}, {
'startIndex': 0,
'destinationIndex': 1,
'status': 'failed'
}, {
'startIndex': 1,
'destinationIndex': 0,
'summary': {
'distance': 2617,
'travelTime': 327,
'costFactor': 327
}
}, {
'startIndex': 1,
'destinationIndex': 1,
'summary': {
'distance': 48,
'travelTime': 6,
'costFactor': 6
}
}]
}
}

DocumentDB 'all/any' functionality

Imagine I have the data in Cosmos
[
{
"id": "FCEC01CD-A6E9-4DEA-8DD5-89711B5B05A1",
"sub": [
{
"id": 1,
"v": false
},
{
"id": 2,
"v": false
}
]
]
and I want to query for all id's that have all (sibbeling) 'sub' items having v=false
what query syntax would work?
(ARRAY_CONTAINS would not work, since that gives an 'any' result)
Thanks!
You need a user-defined function for this.
function arrayAllMatch(arr) {
for(i=0; i < arr.length; i++) {
if (arr[i].v === true) {
return false;
}
}
return true;
}
Then call within query (also include ARRAY_CONTAINS because it can use the index to reduce the number of calls to the UDF):
SELECT *
FROM c
WHERE ARRAY_CONTAINS(c.sub, {"v" : false }, true)
AND udf.arrayAllMatch(c.sub)

Selecting fields and limiting total results in MongoDB query (Meteor)

In my Meteor app I am trying to receive a limited number of users (on the server!). Of those users I only want to receive a few specified fields.
Why does this query not work as expected?
const users = Meteor.users.find(
{
'roles.__global_roles__': {$in: ['myRole']},
$and: query
},
{fields: {
username: 1,
firstName: 1,
lastName: 1,
update: 1
}},
{
limit: 2,
sort: {'update.any': -1}
}
).fetch();
With this query, limit and sort are ignored. Only the selected fields are received.
I then tried this:
const users = Meteor.users.find(
{
'roles.__global_roles__': {$in: ['myRole']},
$and: query
},
{
limit: 2,
sort: {'update.any': -1}
}
).fetch();
Now limit and sort works but obviously I get the whole user object with all fields.
What is the solution to combining both?
Thanks
find() has 2 parameters, you're providing 3 in your first example. try this:
const users = Meteor.users.find(
{
'roles.__global_roles__': {$in: ['myRole']},
$and: query
},
{fields: {
username: 1,
firstName: 1,
lastName: 1,
update: 1
},
limit: 2,
sort: {'update.any': -1}
}
).fetch();
iow, fields, limit, and sort are all part of the same JSON object as the 2nd argument.

Meteor - ViewModel link to collection

This part right here is where I'm struggling.
Template.Calculator.viewmodel({
a: 0,
b: 0,
c: 0,
total: function(){
return parseInt(this.a(), 10) + parseInt(this.b(), 10) + parseInt(this.c(), 10);
}
})
instead of having the variables equal 0, how can I have them pull from the collection the value that was saved when either first submitted or after an update occurred? So for example, let's say I saved a as 7 last time and b as 6, it would load the page with a: 6 b:7 c:0 and calculate. I am having the hardest time trying to pull that data from the collection.
Thanks for any help you can provide!
There are many ways of doing it.
If you know the data subscription is going to be ready by the time the template is created you can just do:
Template.Calculator.viewmodel(
function() {
var dv = DefaultValues.findOne();
return {
a: dv.a,
b: dv.b,
c: dv.c,
total: function () {
return this.a() + this.b() + this.c();
}
}
}
);
If you have many default values you can use the document when you create the view model:
Template.Calculator.viewmodel(
function() {
// Add all properties to the view model
return DefaultValues.findOne(); // or pluck the values
},
{
total: function () {
// We're assuming the default document will have properties a, b, and c.
return this.a() + this.b() + this.c();
}
}
);
If you don't know when the data is ready then you have the problem of maybe overwriting the user's values. It all depends on where you're subscribing to the data and when the subscription is ready. The following example subscribes when the template is created and updates the values as soon as the data is ready.
Template.Calculator.viewmodel({
a: 0,
b: 0,
c: 0,
total: function () {
return this.a() + this.b() + this.c();
},
onCreated: function (template) {
var that = this;
template.subscribe('defaultData', function(){
var dv = DefaultValues.findOne();
that.a(dv.a);
that.b(dv.b);
that.c(dv.c);
})
}
});
Again, it all depends on how you're getting the data so you might end up doing something different than these.
See Defining View Models for more information.

Resources