Behaviour of the project stage operator in project

2019-08-04 18:57发布

My question is closely related to this, but not similar.

I have a sample document in my collection:

db.t.insert({"a":1,"b":2});

My intent is to project a field named combined of type array with the values of both a and b together.([1,2]).

I simply try to aggregate with a $project stage:

db.t.aggregate([
{$project:{"combined":[]}}
])

MongoDB throws an error: disallowed field type Array in object expression.

Which means a field cannot be projected as a array.

But when i use a $cond operator to project an array, the field gets projected.

db.t.aggregate([
{$project:{"combined":{$cond:[{$eq:[1,1]},["$a","$b"],"$a"]}}}
])

I get the o/p: {"combined" : [ "$a", "$b" ] }.

If you notice the output, the value of a and b are treated as if they were literals and not a field paths.

Can anyone please explain to me this behavior?, When i make the condition to fail,

db.t.aggregate([
{$project:{"combined":{$cond:[{$eq:[1,2]},["$a","$b"],"$a"]}}}
])

I get the expected output where $a is treated as a field path, since $a is not enclosed as an array element.

1条回答
闹够了就滚
2楼-- · 2019-08-04 19:41

I've run into this before too and it's annoying, but it's actually working as documented for literal ["$a", "$b"]; the first error about disallowed field type is...not as clear why it complains. You have to follow the description of the grammar of the $project stage spread out in the documentation, however. I'll try to do that here. Starting at $project,

The $project stage has the following prototype form:

{ $project: { <specifications> } }

and specifications can be one of the following:

1. <field> : <1 or true or 0 or false>
2. <field> : <expression>

What's an expression? From aggregation expressions,

Expressions can include field paths and system variables, literals, expression objects, and operator expressions.

What are each of those things? A field path/system variable should be familiar: it's a string literal prefixed with $ or $$. An expression object has the form

{ <field1>: <expression1>, ... }

while an operator expression has one of the forms

{ <operator>: [ <argument1>, <argument2> ... ] }

{ <operator>: <argument> }

for some enumerated list of values for <operator>.

What's an <argument>? The documentation isn't clear on it, but from my experience I think it's any expression, subject to the syntax rules of the given operator (examine the operator expression "cond" : ... in the question).

Arrays fit in only as containers for argument lists and as literals. Literals are literals - their content is not evaluated for field paths or system variables, which is why the array literal argument in the $cond ends up with the value [ "$a", "$b" ]. The expressions in the argument array are evaluated.

The first error about Array being a disallowed value type is a bit odd to me, since an array literal is a valid expression, so according to the documentation it can be a value in an object expression. I don't see any ambiguity in parsing it as part of an object expression, either. It looks like it's just a rule they made to make the parsing easier? You can "dodge" it using $literal to put in a constant array value:

db.collection.project([{ "$project" : { "combined" : { "$literal" : [1, 2] } } }])

I hope this helps explain why things work this way. I was surprised the first time I tried to do something like [ "$a", "$b" ] and it didn't work as I expected. It'd be nice if there were a feature to pack field paths into an array, at least. I've found uses for it when $grouping on ordered pairs of values, as well.

There's a JIRA ticket, SERVER-8141, requesting an $array operator to help with cases like this.

查看更多
登录 后发表回答