views:

259

answers:

2

References:

Still pretty new to mongo db but I'm trying to update part of an existing document inside a collection... unfortunately, the link above doesn't have an update example.

Essentially, i just want to be able to:

  1. Add new fields to a document
  2. Update existing fields of a document to a new value

Here's my code (Grails + Groovy + Java + MongoDB + the java driver):

def shape = mongo.shapes.findOne(new BasicDBObject("data", "http://www.foo.com")); // get the document
mongo.shapes.update(new BasicDBObject("_id", shape._id), new BasicDBObject("isProcessed", 0));  // add a new "isProcessed" field set to 0
mongo.shapes.update(new BasicDBObject("_id", shape._id), new BasicDBObject("data", "http://www.bar.com"));

This pretty much clobbers the entire object... I might try just modifying the original shape object and then running the update on that. But until then, does anyone have experience updating just individual fields (rather than the entire document)?

EDIT:

I just tried it and was able to successfully update by sending the entire object across with new and/or updated fields and that works. I wonder if the driver is smart enough to only update the smallest subset of changes or if it's just blindly updating the entire thing? (In the case below, is it just updating the foo field across the wire or the entire shape document?)

Code:

def shape = mongo.shapes.findOne(); // get the first shape to use as a base
shape.removeField("_id");  // remove the id field
shape.put("foo","bar");  // add a new field "foo"
mongo.shapes.insert(shape);  // insert the new shape
def shape2 = mongo.shapes.findOne(new BasicDBObject("foo", "bar"));  // get the newly inserted shape (and more importantly, it's id)
shape2.put("foo", "bat");  // update the "foo" field to a new value
mongo.shapes.update(new BasicDBObject("_id", shape2._id), shape2);  // update the existing document in mongo
+2  A: 

Found an example here, which seems to show the usage for the update call. So for your example, I believe something like this should work?

// Find an object
def shape2 = mongo.shapes.findOne( new BasicDBObject( 'foo', 'bar' ) )
// And update the foo field from 'bar' to 'bat'
mongo.shapes.update( shape2, new BasicDBObject( '$set', new BasicDBObject( 'foo', 'bat' ) ) )

Edit

You might be able to use a category to construct your BasicDBObjects in a more groovy way...

Something like this might do it:

class BasicDBObjectMapBuilder {
  static String toDbObj( String s ) { s }
  static BasicDBObject toDbObj( Map m ) {
    m.inject( null ) { r, it -> new BasicDBObject( it.key, it.value.toDbObj() ) }
  }
}

use( BasicDBObjectMapBuilder ) {
  def shape2 = mongo.shapes.findOne( new BasicDBObject( 'foo', 'bar' ) )
  // And update the foo field from 'bar' to 'bat'
  mongo.shapes.update( shape2, [ '$set':[ 'foo', 'bat' ] ].toDbObj() )
}

I haven't tested this though...

Edit 2

Actually, BasicDBObject is a Map, so you should be able to do:

  mongo.shapes.update( shape2, [ '$set':[ 'foo', 'bat' ] ] as BasicDBObject )

without needing the builder

tim_yates
MY EYES! THEY BURN! Thank you so much for this, that makes total sense now... the code that they're making us write for this is SO ugly though! :)
longda
hahaha...yeah... I've put a rough attempt a a category to build the DBObjects up from a map in as an edit...not tested it tho...
tim_yates
Yeah, Java makes working with JSON ugly. The Java driver should provide ways to pass in JSON strings (but that is also hindered by lack of multi-line strings and alternate quote characters in Java), and also some builder methods with meaningful names (so you do not have to type $set).
Thilo
+2  A: 

I wonder if the driver is smart enough to only update the smallest subset of changes or if it's just blindly updating the entire thing?

No, if you use the "normal" update method, the whole object will be sent over the wire. I suspect that the database server itself will be clever enough to only update the necessary indexes (and not the ones that did not change), if possible (i.e. the object could be updated in place and did not have to be moved because it grew too much)

What you can do is use the "atomic update modifier" functions. The Java documentation is a bit light on them, but since the driver just transmits the JSON, the stuff from the non-Java tutorials should work, for example:

shapes.update((DBObject)JSON.parse(    "{ 'foo' : 'bar'}"),  
    (DBObject) JSON.parse(          "{ '$set' : { 'foo': 'bat'}}")   );
Thilo
Thanks! I think I like this code a bit better as it's slightly clearer than the standard syntax... I'll try it out.
longda
I tried this and could not get it to parse. It didn't like the single quotes, and then the second JSON.parse cannot be cast to a DBObject (it's a HashMap).
Gandalf
@Gandalf: Sorry about that, untested code. But it should be very similar to the above.
Thilo