Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-12263

Support $elemMatch with DBRefs that may contain additional properties

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 2.6.0-rc0
    • Affects Version/s: 2.5.4
    • Component/s: Querying
    • Labels:
      None
    • Fully Compatible
    • ALL

      tl;dr:

      • DBRefs are objects whose first two fields must be $ref and $id (in that order). An optional $db field, if present, must appear third. Other fields may follow (they can't have a $ prefix, of course).
      • $elemMatch should have consistent behavior, regardless of whether its argument or the value it is matching against look like DBRef objects.

      The current implementation simply treats an $elemMatch on a DBRef as an equality match, which doesn't seem correct. Consider the following, which uses the latest nightly:

      > db.runCommand('buildInfo').gitVersion
      8bad5a8633eeca3bcbb975aabf268db9921f2c57
      
      > db.people.insert({_id: 1})
      Insert WriteResult({ "ok" : 1, "n" : 1 })
      
      > db.people.insert({_id: 2, friends: [ {$ref: "people", $id: 1, $db: "test", _doctrine_class_name: "Documents\People" } ]})
      Insert WriteResult({ "ok" : 1, "n" : 1 })
      
      > db.people.insert({_id: 3, friends: [ {$ref: "people", $id: 2, $db: "test" } ]})
      Insert WriteResult({ "ok" : 1, "n" : 1 })
      

      First, I create three documents in the test.people collection. The middle document has an extra field in its DBRef object, which is something Doctrine ODM has done for years to discriminate references to super-classes. Based on the DBRef docs, they're only objects that required $ref and $id as the first two fields with an optional $db to follow. There was never a restriction on additional fields, even though the shell likes to hide them (as it does $db).

      > db.people.find()
      { "_id" : 1 }
      { "_id" : 2, "friends" : [ DBRef("people", 1) ] }
      { "_id" : 3, "friends" : [ DBRef("people", 2) ] }
      

      The example below demonstrates the shortcoming of treating $elemMatch as an equality comparison, and relying on _isDBRefDocument() for the detection:

      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $db: "test" }}})
      2014-01-06T11:45:33.768-0500 error: {
      	"$err" : "Can't canonicalize query: BadValue unknown operator: $ref",
      	"code" : 17287
      } at src/mongo/shell/query.js:131
      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $id: 1 }}})
      null
      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $id: 1, $db: "test" }}})
      null
      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $id: 1, $db: "test", _doctrine_class_name: "Documents\People" }}})
      2014-01-06T11:46:09.520-0500 error: {
      	"$err" : "Can't canonicalize query: BadValue unknown operator: $ref",
      	"code" : 17287
      } at src/mongo/shell/query.js:131
      

      In the first and last example, we get the original error because $elemMatch doesn't recognize its argument as a DBRef and apply the patch logic. But even with the patch, the equality comparison fails to match anything in the middle two examples, since the DBRef in the database does have an extra _doctrine_class_name field within.

      Repeating the test with the third example document, which doesn't include the extra Doctrine field:

      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $db: "test" }}})
      2014-01-06T11:46:37.305-0500 error: {
      	"$err" : "Can't canonicalize query: BadValue unknown operator: $ref",
      	"code" : 17287
      } at src/mongo/shell/query.js:131
      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $id: 2 }}})
      null
      > db.people.findOne({friends: { $elemMatch: { $ref: "people", $id: 2, $db: "test" }}})
      { "_id" : 3, "friends" : [ DBRef("people", 2) ] }
      

      We still have the same shortcomings, although the equality match in the last example does happen to work. I'm still not keen on that behavior because this is nothing like a real $elemMatch evaluation.

            Assignee:
            benety.goh@mongodb.com Benety Goh
            Reporter:
            jmikola@mongodb.com Jeremy Mikola
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: