jeroen / mongolite

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

$elemMatch query not working on mongolite but working in pure Mongo #240

Closed GitHunter0 closed 2 years ago

GitHunter0 commented 2 years ago

Hi folks,

Please, conside this MWE:

library(dplyr)
library(mongolite)
library(jsonlite)

con <- mongolite::mongo(
    collection = 'collection',
    db = 'dbname',
    url = 'url'
  )

nested_data <- tibble(
  user = c("user1", "user2"),
  name = c("User One", "User Two"),
  settings = list(
    tibble(label=c("settings_1", "settings_2"),
           inputs=list(list("param1"=1),
                       list("param1"=2))),
    tibble(label=c("settings_A", "settings_B"),
           inputs=list(list("param1"=3),
                       list("param1"=4)))
  )
)   

  con$drop()
  con$insert(nested_data)
  con$find('{}') |> toJSON() |> prettify()

The code below returns Error: Invalid JSON object:

  con$find(
    '
      {"user": "user2"},
      {"name": 1,
       "settings":{"$elemMatch": {"label": "settings_B"}}
      }
    '
  )

While in Mongo Playground the same query returns this: image You can check in this link: https://mongoplayground.net/p/N44pluEV85L

Any idea why that syntax is not working in mongolite? I'm really needing to perform $elemMatch queries via mongolite but I cannot find a syntax that works.

Thank you

jeroen commented 2 years ago

The error that you get simply means that you are passing an invalid json string in find(). The query argument needs to be a single json object (you're passing 3 objects).

Maybe try something like this?

 con$find('{
  "user": "user2",
  "settings":{"$elemMatch": {"label": "settings_B"}}
 }')

Or otherwise try passing separate json strings to the different find() parameters, see the documentation for ?mongo.

jeroen commented 2 years ago

Ah I think I get it now, the second part of your query is actually a projection? Try passing them as separate json strings:

con$find('{"user": "user2"}', '{"name":1, "settings":{"$elemMatch": {"label": "settings_B"}}}')

Or alternatively to get one value:

iter <- con$iterate('{"user": "user2"}', '{"name":1, "settings":{"$elemMatch": {"label": "settings_B"}}}')
val <- iter$one()
print(val)
GitHunter0 commented 2 years ago

Thank you very much @jeroen , now the query is working with the specification below you provided:

con$find(query = '{"user": "user2"}', fields = '{"name":1, "settings":{"$elemMatch": {"label": "settings_B"}}}')

However, if I want to just return settings.inputs, how can I do that? The conventional way of adding "settings.inputs": true to fields will not work in this case.

jeroen commented 2 years ago

I'm not sure. You might have to use con$aggregate() to manually define an aggregation pipeline with multiple filter and projection steps.

GitHunter0 commented 2 years ago

Got it @jeroen , thank you for the feedback, mongolite is an important package in R ecosystem.