Andrew Serff
Andrew Serff

Reputation: 2127

How to use $setIsSubset (or other set operations) on child properties

I'm having what I feel is a very simple problem and I just missing something so I'm hoping someone can point out where I'm going wrong. I'm trying to use the $setIsSubset operation referencing a child property that contains a list of items, but it is telling me it is null. Basically the the query is wrong, but I don't see why. Here is the output from the Mongo shell:

$ mongo setTest
MongoDB shell version: 2.6.3
connecting to: setTest
> db.items.insert({"credentials":{"authorities":["AUTH1"]}});
WriteResult({ "nInserted" : 1 })
> db.items.find();
{ "_id" : ObjectId("53acd0e214c4ee1272550de4"), "credentials" : { "authorities" : [ "AUTH1" ] } }
> var userAuthorities = ["AUTH1","AUTH2","AUTH3"];
> db.items.aggregate([
...     { $redact:
...         {
...            $cond:
...               {
...                  if: { $setIsSubset: ["$credentials.authorities", userAuthorities ] },
...                  then: "$$DESCEND",
...                  else: "$$PRUNE"
...               }
...         }
...      }
... ]);
assert: command failed: {
    "errmsg" : "exception: both operands of $setIsSubset must be arrays. First argument is of type: EOO",
    "code" : 17310,
    "ok" : 0
} : aggregate failed
Error: command failed: {
    "errmsg" : "exception: both operands of $setIsSubset must be arrays. First argument is of type: EOO",
    "code" : 17310,
    "ok" : 0
} : aggregate failed
    at Error (<anonymous>)
    at doassert (src/mongo/shell/assert.js:11:14)
    at Function.assert.commandWorked (src/mongo/shell/assert.js:244:5)
    at DBCollection.aggregate (src/mongo/shell/collection.js:1149:12)
    at (shell):1:10
2014-06-26T20:04:54.465-0600 Error: command failed: {
    "errmsg" : "exception: both operands of $setIsSubset must be arrays. First argument is of type: EOO",
    "code" : 17310,
    "ok" : 0
} : aggregate failed at src/mongo/shell/assert.js:13
>

Can anyone see what I'm doing wrong?

Upvotes: 2

Views: 1842

Answers (1)

Neil Lunn
Neil Lunn

Reputation: 151092

Your problem here is not with the set operators themselves but with your understanding of $redact.

The $redact pipeline stage recursively traverses the document to test for the field values so the problem here is that the way you are referencing the array to match is not actually present at the "current depth" all of the time.

You have a couple of ways of approaching this. Either first manipulate to make sure that an array is present at all depths using $project:

var userAuthorities = ["AUTH1","AUTH2","AUTH3"];

db.items.aggregate([
    { "$project": {
        "credentials": 1,
        "authorities": { "$literal": [] }
    }},
    { "$redact": {
        "$cond": {
            "if": { "$setIsSubset": [ "$authorities", userAuthorities ] },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }                    
    }}
])

Or test for the presence of the field and replace with an empty array in-line via $ifNull:

db.items.aggregate([
    { "$redact": {
        "$cond": {
            "if": { 
                "$setIsSubset": [ 
                    { "$ifNull": [ "$authorities", { "$literal": [] } ]},
                    userAuthorities
                ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }                    
    }}
])

Or finally just always accept that you are using the $$ROOT comparison. But sort of defeats the purpose of $redact

db.items.aggregate([
    { "$redact": {
        "$cond": {
            "if": { 
                "$setIsSubset": [ 
                    "$$ROOT.credentials.authories"
                    userAuthorities
                ]
            },
            "then": "$$DESCEND",
            "else": "$$PRUNE"
        }                    
    }}
])

But basically putting it, the comparison with $redact is relative to the level of the document that is currently descended to. So your variable comparisons have to consider that as well.

Upvotes: 5

Related Questions