Search code examples
rmongodbcrudrmongodbdatabase

Update multiple documents in MongoDB (rmongodb)


I stumbled upon some issues regarding the function handling updates (mongo.update()).

Disclaimer

As I'm still in the middle of getting familiar with both MongoDB and the rmongodb package, the illustrated issues might all very well be due to my lack of knowledge ;-)

Preparations

Ensuring that package is loaded

pkg <- "rmongodb"
lib <- file.path(R.home(), "library")
if (!suppressWarnings(require(pkg, lib.loc=lib, character.only=TRUE))) {
    install.packages(pkg, lib=lib)
    require(pkg, lib.loc=lib, character.only=TRUE)
}

Global variables

db      <- "__test"
ns.0    <- "user"
ns      <- paste(db, ns.0, sep=".")
con     <- mongo.create(db=db)

Creating the initial state of the DB

Ensure emtpy DB

mongo.remove(mongo=con, ns=ns, 
    criteria=mongo.bson.from.list(lst=list(namefirst="John")))

Insert document

fields      <- list(namefirst="John", namelast="Doe")
b           <- mongo.bson.from.list(lst=fields)
mongo.insert(mongo=con, ns=ns, b=b)
if (is.null(mongo.find.one(mong=con, ns=ns, query=b))) {
    stop("Something went wrong")
}

Update/upsert a document

criteria    <- list(namefirst="John", namelast="Smith")
fields      <- list(namefirst="John", namelast="Smith", age=30)
b           <- mongo.bson.from.list(lst=fields)
crit        <- mongo.bson.from.list(lst=criteria)
mongo.update(mongo=con, ns=ns, criteria=crit, objNew=b, 
    flags=mongo.update.upsert)
if (is.null(mongo.find.one(mong=con, ns=ns, query=b))) {
    stop("Something went wrong")
}

List available docs

criteria    <- list(namefirst="John")
crit        <- mongo.bson.from.list(lst=criteria)
cursor      <- mongo.find(mongo=con, ns=ns, query=crit)
out         <- NULL
while (mongo.cursor.next(cursor=cursor)) {
    bson <- mongo.cursor.value(cursor=cursor)    
    out <- c(out, list(bson))
}
> out
[[1]]
    _id : 7      50f484fe3c3b8b8e3daa72e0
    namefirst : 2    John
    namelast : 2     Doe

[[2]]
    _id : 7      50f484ff3c3b8b8e3daa72e1
    namefirst : 2    John
    namelast : 2     Smith
    age : 1      30.000000

At this point, there are two docs in the collection which is what we'd expect.

Issue #1: update with default flag value (0)

criteria    <- list(namefirst="John")
fields      <- list(age=99)
b           <- mongo.bson.from.list(lst=fields)
crit        <- mongo.bson.from.list(lst=criteria)
mongo.update(mongo=con, ns=ns, criteria=crit, objNew=b)
cursor      <- mongo.find(mongo=con, ns=ns, query=crit)
out         <- NULL
while (mongo.cursor.next(cursor=cursor)) {
    bson <- mongo.cursor.value(cursor=cursor)
    out <- c(out, list(mongo.bson.to.list(bson)))
}
if (out[[1]]$age != 99) {
    stop("Something went wrong")
}
> out
[[1]]
[[1]]$`_id`
{ $oid : "50f484ff3c3b8b8e3daa72e1" }

[[1]]$namefirst
[1] "John"

[[1]]$namelast
[1] "Smith"

[[1]]$age
[1] 30

After performing the update, there's only one document left.

Issue #2: update with "multi" flag value (2)

Start over from scratch as previous update seems to have corrupted the DB:

Ensure empty DB

mongo.remove(mongo=con, ns=ns, 
    criteria=mongo.bson.from.list(lst=list(namefirst="John")))

Insert document

fields      <- list(namefirst="John", namelast="Doe")
b           <- mongo.bson.from.list(lst=fields)
mongo.insert(mongo=con, ns=ns, b=b)
if (is.null(mongo.find.one(mong=con, ns=ns, query=b))) {
    stop("Something went wrong")
}

Update/upsert document

criteria    <- list(namefirst="John", namelast="Smith")
fields      <- list(namefirst="John", namelast="Smith", age=30)
b           <- mongo.bson.from.list(lst=fields)
crit        <- mongo.bson.from.list(lst=criteria)
mongo.update(mongo=con, ns=ns, criteria=crit, objNew=b, 
    flags=mongo.update.upsert)
if (is.null(mongo.find.one(mong=con, ns=ns, query=b))) {
    stop("Something went wrong")
}

Trying to update multiple documents. What I'm after here is to change the value for age to 99 in both collections

criteria    <- list(namefirst="John")
fields      <- list(age=99)
b           <- mongo.bson.from.list(lst=fields)
crit        <- mongo.bson.from.list(lst=criteria)
mongo.update(mongo=con, ns=ns, criteria=crit, objNew=b, 
    flags=mongo.update.multi)
cursor      <- mongo.find(mongo=con, ns=ns, query=crit)
out         <- NULL
while (mongo.cursor.next(cursor=cursor)) {
    bson <- mongo.cursor.value(cursor=cursor)
    out <- c(out, list(mongo.bson.to.list(bson)))
}

Investigating the current DB state

> out
[[1]]
[[1]]$`_id`
{ $oid : "50f485cd3c3b8b8e3daa72e2" }

[[1]]$namefirst
[1] "John"

[[1]]$namelast
[1] "Doe"


[[2]]
[[2]]$`_id`
{ $oid : "50f485ce3c3b8b8e3daa72e3" }

[[2]]$namefirst
[1] "John"

[[2]]$namelast
[1] "Smith"

[[2]]$age
[1] 30

> sapply(out, function(ii) ii$age)
[[1]]
NULL

[[2]]
[3] 30

I would expect both docs to have age = 99.


Solution

  • In both "potential bugs", you are updating the record to one that contains only the age field. Your records still exist, but they have no namefirst fields (or any other field besides age and _id) to match the later query. mongo.update() normally replaces the entire document, not selected fields. Your 2nd example could be coded like this:

    criteria    <- list(namefirst="John")
    fields      <- list(age=99)
    b           <- mongo.bson.from.list(lst=list('$set'=fields))
    crit        <- mongo.bson.from.list(lst=criteria)
    mongo.update(mongo=con, ns=ns, criteria=crit, objNew=b, 
        flags=mongo.update.multi)
    

    to set only desired fields. See http://docs.mongodb.org/manual/applications/update for more information.

    Btw, though your example is very clear, you may write this much more succinctly as:

    mongo.update(con, ns, list(namefirst="John"),
                          list('$set'=list(age=99)), mongo.update.multi)