Prevent rounding errors when using $inc in mongodb on US currency? - mongodb

I am calling update from node to add money to a user's account. Starting from 0, these two sequential updates:
83.94 and then 546.13 result in 630.0699999999999.
This is the same result as if you do this in JS
83.94+566.13
This is the mongo call:
xxx.update({_id: xId},
{
$inc: {money: dollars}
});
Is there any way to prevent this and get reasonable behavior? (besides converting to pennies instead of dollars).
Do I really have to "round to 2" every time I read this field?

Thanks #ryan. Just what I needed.
https://docs.mongodb.com/manual/tutorial/model-monetary-data/
https://docs.mongodb.com/manual/core/shell-types/#shell-type-decimal

Related

MongoDB: Bulk changing all field types in python

I have a ton of documents (around 10 million) and I need to change their field type. The usual forEach function (just looping through every value) seems to take forever and is clearly not viable in the timeframe I have (it basically took all night for one out of four updates)
I've heard that bulkwrites may be able to do it but I'm getting mixed messages. I saw a confusing answer on this site, for example, says that there's no written function to do it (you would have to do some workaround), others say that it can be done with updates in Python, using pymongo.
I was wondering if there was a quicker way to mass changes of field type (string->double, string -> int) using python? I can also work from the console but I find even less solutions there.
Thanks
You can try using aggregation query in the mongo shell
Something like
db.your_collection.aggregate([
{
$addFields: {
field1: {
$convert: {
input: "$field1",
to: "string"
}
}
}
},
{ $out: "your_collection" }
])
More info here https://docs.mongodb.com/manual/reference/operator/aggregation/convert/

How does resource.data.size() work in firestore rules (what is being counted)?

TLDR: What is request.resource.data.size() counting in the firestore rules when writing, say, some booleans and a nested Object to a document? Not sure what the docs mean by "entries in the map" (https://firebase.google.com/docs/reference/rules/rules.firestore.Resource#data, https://firebase.google.com/docs/reference/rules/rules.Map) and my assumptions appear to be wrong when testing in the rules simulator (similar problem with request.resource.data.keys().size()).
Longer version: Running into a problem in Firestore rules where not being able to update data as expected (despite similar tests working in the rules simulator). Have narrowed down the problem to point where can see that it is a rule checking for request.resource.data.size() equaling a certain number.
An example of the data being passed to the firestore update function looks like
Object {
"parentObj": Object {
"nestedObj": Object {
"key1": Timestamp {
"nanoseconds": 998000000,
"seconds": 1536498767,
},
},
},
"otherKey": true,
}
where the timestamp is generated via firebase.firestore.Timestamp.now().
This appears to work fine in the rules simulator, but not for the actual data when doing
let obj = {}
obj.otherKey = true
// since want to set object key name dynamically as nestedObj value,
// see https://stackoverflow.com/a/47296152/8236733
obj.parentObj = {} // needed for adding nested dynamic keys
obj.parentObj[nestedObj] = {
key1: fb.firestore.Timestamp.now()
}
firebase.firestore.collection('mycollection')
.doc('mydoc')
.update(obj)
Among some other rules, I use the rule request.resource.data.size() == 2 and this appears to be the rules that causes a permission denied error (since commenting out this rules get things working again). Would think that since the object is being passed with 2 (top-level) keys, then request.resource.data.size()=2, but this is apparently not the case (nor is it the number of keys total in the passed object) (similar problem with request.resource.data.keys().size()). So there's a long example to a short question. Would be very helpful if someone could clarify for me what is going wrong here.
From my last communications with firebase support around a month ago - there were issues with request.resource.data.size() and timestamp based security rules for queries.
I was also told that request.resource.data.size() is the size of the document AFTER a successful write. So if you're writing 2 additional keys to a document with 4 keys, that value you should be checking against is 6, not 2.
Having said all that - I am still having problems with request.resource.data.size() and any alternatives such as request.resource.size() which seems to be used in this documentation
https://firebase.google.com/docs/firestore/solutions/role-based-access
I also have some places in my security rules where it seems to work. I personally don't know why that is though.
Been struggling with that for a few hours and I see now that the doc on Firebase is clear: "the request.resource variable contains the future state of the document". So with ALL the fields, not only the ones being sent.
https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation.
But there is actually another way to ONLY count the number of fields being sent with request.writeFields.size(). The property writeFields is a table with all the incoming fields.
Beware: writeFields is deprecated and may stop working anytime, but I have not found any replacement.
EDIT: writeFields apparently does not work in the simulator anymore...

Sorting algorithm is activated only after refresh

My code sorts the my collection, but If I make an admin to change someones rank, the change only gets activated after a refresh. Am very confused as to why, and how to fix this, any help would be awesome!
Meteor.publish("sortingAlgo", function(){
return og.find({},{ sort: {rank: 1,scores: 1}});
});
Make sure you're also sorting on the client after you subscribe. In publications, sort is mostly useful when used in conjunction with limit.
You publication code is right.
You need to sort the value on client side as well. Because whenever you add a new doc its update the publication. But on the client side your value is append in you mini mongo.To fix this you need to sort the data on client side before using this.
//Subscribe your publication.
Template.name.helpers({
'data: function() {
return og.find({},{ sort: {rank: 1,scores: 1}});
}
})

Flask-PyMongo collMod

I'm trying to update a TTL collection with the PyMongo. Trying to run this I get 'failed no such cmd: index'
client.db.command({'collMod': url,
'index': {'keyPattern': {'dateCreated':1},
'expireAfterSeconds': 3600}})
anyone shine some light on what I'm doing wrong?
I believe that this would work assuming that url contains the name of the collection with the index you are modifying:
client.db.command('collMod', url,
index={'keyPattern': {'dateCreated':1},
'expireAfterSeconds': 3600}})
For anyone else looking for a solution to this I managed with the following:
client.db.command('collMod', 'notifications',
index={'keyPattern': {'expr': 1},
'background': True,
'expireAfterSeconds': 604800})
Which results in the following output:
{u'expireAfterSeconds_old': 3888000,
u'expireAfterSeconds_new': 604800, u'ok': 1.0}
Well I've just decided to switch my index around to instead set the time I want the document to expire at in the database instead of saying how long I want the document to live by using
db.test.ensure_index("expireOn", expireAfterSeconds=0)
Now I can set the field to a future time that I want the entry to expire instead of having to change the index. Seems strange that as of now there is no way to update the index programatically using python.

Mongodb $strcasecmp. Strange behaviour when the field content has dollar signs

I'm triying to compare two strings on MongoDB Aggregation Framework. This is the query I'm using:
db.people.aggregate({
$project:{
name:1,
balance:1,
compareBalance:{$strcasecmp:["$balance","$2,500.00"]}
}
});
My problem is that each "$balance" field has a dollar sign at the begining of the string, and the results returned by the query seem to be incorrect. For example:
{
"_id" : ObjectId("5257e2e7834a87e7ea509665"),
"balance" : "$1,416.00",
"name" : "Houston Monroe",
"compareBalance" : 1
}
As you can see the results, the field comparision is 1, but it should be -1 because $2,500.00 is higher than $1,416.00. In fact, all comparisions has a value of 1.
There is a workaround by using $substr to remove the dollar sign at the beginning of all fields, but I want to know who is doing this wrong, MongoDB or me.
Thanks in advance.
It sounds like you are trying to use the "balance" field as a numeric, for example might want to compare $10 to $100.
The best way to do this is to store the actual value, and add the formatting, the $ the , etc when displaying to the user.
So, you would have - balance: 2500
Slightly unrelated...
Not sure if you are doing much calculation on the value, but using binary floating point numbers for currency is a bad idea (can't accurately represent all numbers), so, it's often better to store an integer with the cents (or if high precision is required, an integer for hundredths of cents)
This could give: balanceCents: 250000 or balanceFourDec: 25000000
Then you can use $gt $lt and arithmetic
The $ is used as a field reference operator. So, the aggregation pipeline is trying to do a comparison between a field called "$balance" and "$2,500.00":
{
"balance": "$5,000.00",
"2,500.00": undefined
}
Of course, that's not what you are looking for.
You shouldn't start with the $ in the data. Also, unless you've got fixed length strings, sorting and comparisons isn't going to work the way you would expect if you're trying to store numbers as strings. If you're just doing this as an example, I'd suggest you use the actual math operators for numbers, and leave $strcasecmp to actual strings.
you can use the { $literal: < value > } pipeline operator to ignore the cash sign.
https://docs.mongodb.com/manual/reference/operator/aggregation/literal/