jeroen / mongolite

Fast and Simple MongoDB Client for R
https://jeroen.github.io/mongolite/
286 stars 65 forks source link

Error: Invalid key 'xxx' update only works with $ operators #118

Closed maxto closed 6 years ago

maxto commented 6 years ago

Mongo collection update doesn't work without $ operators after the last mongolite release

conn <- mongolite::mongo(collection = "test","db" = "test",url = "mongodb://user:pw!!@127.0.0.1:27017",verbose = T)
conn$insert('{"num":1234,"name":"Max"}')
{
    "_id" : ObjectId("5a461d0d007bf330bc007365"),
    "num" : 1234,
    "name" : "Max"
}
conn$update(query = '{"num":1234}',update = '{"name":"Jim"}',upsert = TRUE,multiple = FALSE)
Error: Invalid key 'name': update only works with $ operators

conn$update(query = '{"num":1234}',update = '{"$set":{"name":"Jim"}}',upsert = TRUE,multiple = FALSE)
{
    "_id" : ObjectId("5a461d0d007bf330bc007365"),
    "num" : 1234,
    "name" : "Jim"
}

conn$update(query = '{"num":1235}',update = '{"$set":{"name":"Tony"}}',upsert = TRUE,multiple = FALSE)
{
    "_id" : ObjectId("5a46239fae8420de18aab262"),
    "num" : 1235,
    "name" : "Tony"
}

So, we lost the replace method for the whole object with 1 query and we need to work always in atomic way, but that's not always the case

conn$update(query = '{"num":1235}',update = '{"$set":{"surname":"Tod"}}',upsert = TRUE,multiple = FALSE)
{
    "_id" : ObjectId("5a46239fae8420de18aab262"),
    "num" : 1235,
    "name" : "Tony",
    "surname" : "Tod"
}

Instead with the previous release

conn$update(query = '{"num":1235}',update = '{"surname":"Tod"}',upsert = TRUE,multiple = FALSE)
{
    "_id" : ObjectId("5a46239fae8420de18aab262"),
    "num" : 1235,
    "surname" : "Tod"
}
jeroen commented 6 years ago

The update api was switched in this commit from the deprecated mongoc_collection_update() to the new mongoc_collection_update_one() in order to support optional array filters.

It could be a bug in mongoc_collection_update_one in the c driver, or perhaps this is a feature? In this SO topic it is stated that: You can only modify multiple documents using the $ modifier operators. You probably had something like this:

> db.coll.update({ }, { a: 'b' }, false, true);
Which would normally replace the first object in the collection with { a: 'b' } if multi was false. You wouldn't want to replace all the objects in your collection with the same document!

Use the $set operator instead:

> db.coll.update({ }, { '$set': { a: 'b' } }, false, true);
This will set the a property of every document (creating it as necessary) to 'b'.
maxto commented 6 years ago

Thank for your answer @jeroen . I saw the c driver, but it's not a bug, in the official mongodb doc there are 4 methods

db.collection.update() (probably deprecated as you stated)
db.collection.updateOne(<filter>, <update>, <options>) (update one doc)
db.collection.updateMany(<filter>, <update>, <options>) (update multi)
db.collection.replaceOne(<filter>, <replacement>, <options>) (replace one doc)

So we miss a method in mongolite, the last one (replaceOne) to replace a whole document. Is it right or you've been working on it? Thks

jeroen commented 6 years ago

That is correct I haven't implemented replace yet. However you are trying to do an update rather than a replacement , correct?

maxto commented 6 years ago

Yes I do. I build a package as wrapper of mongolite to do many scheduled tasks with update method, everthing's broken on 22 dec, but i've already modified the main code following the new api methods. I only need to know if you want to maintain up the old update method, in order to change my package functions with new atomic update_one and replace_one (for the last one i'll do a temporary trick,waiting for your implementation) thks

jeroen commented 6 years ago

I have added a new replace method. Could you help me test this?

devtools::install_github("jeroen/mongolite")

Here is some example code:

con <- mongolite::mongo()
con$drop()
con$insert(c(
  '{"num":1234,"name":"Max"}',
  '{"num":1235,"name":"Jerry"}'
))

# Should error
con$update(query = '{"num":1234}', update = '{"name":"Jim"}')

# Replace record
con$replace(query = '{"num":1234}', update = '{"name":"Jim"}')

# Replace with upsert
con$replace(query = '{"num":9999}',update = '{"name":"Peter"}', upsert = TRUE)
con$replace(query = '{"num":1235}',update = '{"name":"Jerry"}', upsert = TRUE)
maxto commented 6 years ago

Just tried the tests but it asked for $ operator, so basically the same functionality as update.

{
    "_id" : ObjectId("5a4ab9789550831b20004aa5"),
    "num" : 1234,
    "name" : "Max"
}
{
    "_id" : ObjectId("5a4ab9789550831b20004aa6"),
    "num" : 1235,
    "name" : "Jerry"
}
con$update(query = '{"num":1234}', update = '{"name":"Jim"}')
Error: Invalid key 'name': update only works with $ operators

con$replace(query = '{"num":1234}', update = '{"name":"Jim"}')
Error: Invalid key 'name': update only works with $ operators

con$replace(query = '{"num":1234}', update = '{"$set":{"name":"Jim"}}')
List of 2
 $ modifiedCount: int 1
 $ matchedCount : int 1
{
    "_id" : ObjectId("5a4ab9789550831b20004aa5"),
    "num" : 1234,
    "name" : "Jim"
}

con$replace(query = '{"num":9999}',update = '{"name":"Peter"}', upsert = TRUE)
Error: Invalid key 'name': update only works with $ operators

con$replace(query = '{"num":9999}',update = '{"$set":{"name":"Peter"}}', upsert = TRUE)
List of 3
 $ modifiedCount: int 0
 $ matchedCount : int 0
 $ upsertedId   : chr "5a4abb6f35f29ef10f063ba9"
{
    "_id" : ObjectId("5a4abb6f35f29ef10f063ba9"),
    "num" : 9999,
    "name" : "Peter"
}

con$replace(query = '{"num":1235}',update = '{"name":"Jerry"}', upsert = TRUE)
Error: Invalid key 'name': update only works with $ operators

con$replace(query = '{"num":1235}',update = '{"$set":{"name":"Jerry"}}', upsert = TRUE)
List of 2
 $ modifiedCount: int 0
 $ matchedCount : int 1

I also tried

con$replace(query = '{"num":1235}', update = '{"$set":{"height":190,"age":30}}')
{
    "_id" : ObjectId("5a4ab9789550831b20004aa6"),
    "num" : 1235,
    "name" : "Jerry",
    "height" : 190,
    "age" : 30
}

Note: with mongodb 3.4.9 the replace method crashed Rstudio. With mongodb 3.6.1 and mongolite 1.2 the update method crashed, too. I guess there are some conflicts in C driver api , so it needs to be careful about server and api versions.

jeroen commented 6 years ago

That's very strange, it should never crash. I have tested this on MongoDB 3.2, 3.4 and 3.6 and it all seems OK.

Perhaps your installation got corrupted. Can you try reinstalling the dev version not of mongolite, making sure mongolite is not loaded while you install it? This is especially important on Windows.

Also which version of r and rstudio do you use? Make sure you are up to date.

maxto commented 6 years ago
R version 3.4.3 (2017-11-30)
Rstudio: Version 1.1.383
MongoDB: 3.6.1
Mongolite: v1.4.9

yes i installed with mongolite-stable unloaded, i tried with a clean mongolite dev installation and c mongodb downgraded to 3.4.1 and it'fine, i guess a Windows (tried on win7 and win10) issue managing mongolite installations from github (no issues from CRAN) compiling with RbuildTools

Now it's fine, the replace method works, but i cannot replicate the error anymore, Windows alway makes me happy with these stuff :) thank you

con$replace(query = '{"num":1234}', update = '{"name":"Jim"}')
List of 2
 $ modifiedCount: int 1
 $ matchedCount : int 1
{
    "_id" : ObjectId("5a4b3e4d007bf321c8000712"),
    "name" : "Jim"
}