PHP MongoDB, update document without erasing the rest - mongodb

I'm trying to update a mongo document, using PHP and the update() function. However, when I do this, it replaces the WHOLE document with the value I wanted to update. How can I fix this?
The code I have written up to now: http://twaddlr.com/view/73
(Scroll down to the "update" function. It's a database wrapper I'm writting for my site)

The key is to use $set in the update, e.g. instead of this (sorry using JavaScript syntax here, not sure about the exact PHP driver syntax):
db.my_collection.update({hello: "world"}, {foo: "bar"})
you do
db.my_collection.update({hello: "world"}, {$set: {foo: "bar"}})
If you use $set, only the properties you specify will be updated, the whole document will not be replaced.
You can read more about this in the documentation, here: http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations
Edit: looking at your code, this is exactly what you do in the addRow method. Just do the same thing in update.

Related

How do I create an array for an updateOne() call in mongoc (C libarary for Mongodb)?

I am completely mystified (and supremely frustrated). How do I create this call using the mongoc library?
I have the following doc structure in the collection
{_id: myOID,
subsriptions: {
newProducts: true,
newBlogPosts: true,
pressReleases: true,
}
}
I want to remove one of the subscriptions, for example, the user no longer wants to receive press releases from me.
This works in the mongo shell. Now I need to do it in C code
updateOne({_id: myOID}, [{'$unset': 'subscriptions.pressReleases'}], {})
Note how the update parameter in the Mongo shell is an anonymous array. I need to do that for the bson passed in as the update parameter in the mongoc_collection_update_one() API call.
The C code for updateOne is
mongo_status = mongoc_collection_update_one (mongo_collection,
mongo_query,
mongo_update,
NULL, /* No Opts to pass in */
NULL, /* no reply wanted */
&mongo_error);
Also note that in the aggregate() API, this is done with
{"pipeline" : [{'$unset': 'elists.lunch' }] }
Neither the updateOne() shell function nor the mongoc_collection_update_one() API call accept that, they want just the array.
How do I create the bson to use as the second parameter for mongoc_collection_update_one() API call?
Joe's answer works and I am able to accomplish what I need to do.
The $unset update operator takes an object, just like $set.
updateOne({_id: myOID},{'$unset':{'subscriptions.pressReleases': true}})
OR perhaps even better
updateOne({_id: myOID},{'$unset':{'subscriptions.pressReleases': {'$exists': true}}})
which will remove the subscription flag no matter what the value is for that field.
Doing it this way does not require an anonymous array (which I still don't know how to create).

call custom python function on every document in a collection Mongo DB

I want to call a custom python function on some existing attribute of every document in the entire collection and store the result as a new key-value pair in that (same) document. May I know if there's any way to do that (since each call is independent of others) ?
I noticed cursor.forEach but can't it be done just using python efficiently ?
A simple example would be to split the string in text and store the no. of words as a new attribute.
def split_count(text):
# some complex preprocessing...
return len(text.split())
# Need something like this...
db.collection.update_many({}, {'$set': {"split": split_count('$text') }}, upsert=True)
But it seems like setting a new attribute in a document based on the value of another attribute in the same document is not possible this way yet. This post is old but the issues seem to be still open.
I found a way to call any custom python function on a collection using parallel_scan in PyMongo.
def process_text(cursor):
for row in cursor.batch_size(200):
# Any complex preprocessing here...
split_text = row['text'].split()
db.collection.update_one({'_id': row['_id']},
{'$set': {'split_text': split_text,
'num_words': len(split_text) }},
upsert=True)
def preprocess(num_threads=4):
# Get up to max 'num_threads' cursors.
cursors = db.collection.parallel_scan(num_threads)
threads = [threading.Thread(target=process_text, args=(cursor,)) for cursor in cursors]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
This is not really faster than cursor.forEach (but not that slow either), but it helps me execute any arbitrarily complex python code and save the results from within Python itself.
Also if I have an array of ints in one of the attributes, doing cursor.forEach converts them to floats which I don't want. So I preferred this way.
But I would be glad to know if there're any better ways than this :)
It is quite unlikely that it will ever be efficient to do this kind of thing in python. This is because the document would have to make a round trip and go through the python function on the client machine.
In your example code, you are passing the result of a function to a mongodb update query, which won't work. You can't run any python code inside mongodb queries on the db server.
As the answer to you linked question suggests, this type of action has to be performed in the mongo shell. e.g:
db.collection.find().snapshot().forEach(
function (elem) {
splitLength = elem.text.split(" ").length
db.collection.update(
{
_id: elem._id
},
{
$set: {
split: splitLength
}
}
);
}
);

mongodb - i cannot update with $pushAll and simple assignment at the same time

the following fails:
db.test.update({_id:102},{$pushAll:{our_days:["sat","thurs","frid"]}, country:"XYZ"}, {upsert:true})
error message: "Invalid modifier specified: country"
The correct way seems to be:
db.test.update({_id:102},{$pushAll:{our_days:["sat","thurs","frid"]}, $set:{country:"XYZ"}}, {upsert:true})
So is it the case that I cannot mix modifiers like "$pushAll" with simple assignments like field:value, in the same update document? Instead I have to use the $set modifier for simple assignments?
Is there anything in the docs that describes this behaviour?
This happens because db.test.update({_id : 1}, {country : 1}) will just change the whole document to country = 1 and thus removing everything else.
So most probably mongo being smart tells you: You want to update specific element and at the same time to remove everything (and that element as well) to substitute it with country = 1. Most probably this is not what you want. So I would rather rise an error.
Regarding the documentation - I think that the best way is to reread mongodb update.

Wrong distance in geonear method with Doctrine MongoDB ODM

I'm using DoctrineMongoDBBundle with Symfony2 and I've a problem with geocoordinates. This works fine but when the longitude is for example like that: 0.635467 the code doesn't work. I have more geocoordinates and only fails when it begins with 0. and the distance field is NULL.
This is my code:
$locations = $dm->createQueryBuilder('MyBundle:Location')
->field('id')->in($arrayIds)
->field('geocoordinates')
->geoNear($geocodes['lat'],$geocodes['lon'])
->getQuery()->execute()->toArray();
I'm following this link: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/geospatial-queries.html but with the geonear method.
The geoNear() query builder method is not intended to be used on a field. near() is the builder method that would follow a field() focus. You can see what both of these builder methods do in Builder.php within the doctrine/mongodb project. Note that geoNear() changes the query type (similar to what update() does). The query type is then checked in Query.php (follow the switch statement) and determines how we issue the query on the collection. Some are actual query operations, but things like map/reduce and geoNear are commands.
See if the following code works:
$dm->createQueryBuilder('MyBundle:Location')
->geoNear($geocodes['lat'],$geocodes['lon'])
->field('id')->in($arrayIds)
->getQuery()->execute()->toArray();
If not, please debug the values that Query.php passes to the Collection::near() method. Alternatively, you can debug the entire query array generated by the builder by using the Query::getQuery() method.

Mongoid, find object by searching by part of the Id?

I want to be able to search for my objects by searching for the last 4 characters of the id. How can I do that?
Book.where(_id: params[:q])
Where the param would be something like a3f4, and in this case the actual id for the object that I want to be found would be:
bc313c1f5053b66121a8a3f4
Notice the last for characters are what we searched for. How can I search for just "part" of my objects id? instead of having my user search manually by typing in the entire id?
I found in MongoDB's help docs, that I can provide a regex:
db.x.find({someId : {$regex : "123\\[456\\]"}}) // use "\\" to escape
Is there a way for me to search using the regular mongo ruby driver and not using Mongoid?
Usually, in Mongoid you can search with a regexp like you normally would with a string in your call to where() ie:
Book.where(:title => /^Alice/) # returns all books with titles starting with 'Alice'
However this doesn't work in your case, because the _id field is not stored as a string, but as an ObjectID. However, you could add (and index) a field on your models which could provide this functionality for you, which you can populate in an after_create callback.
<shameless_plug>
Alternatively, if you're just looking for a shorter solution to the default Mongoid IDs, I could suggest something like mongoid_token which makes it pretty easy to add shorter tokens/ids to your Mongoid documents.
</shameless_plug>