We recently introduced the $getField expression to provide an alternative way to extract fields from objects by their key name. See SERVER-30417.
Imagine you have a document where some field "foo" is missing. In this case, both the "$foo" field path expression and the corresponding $getField expression will return "missing":
MongoDB Enterprise > db.c.drop() true MongoDB Enterprise > db.c.insert({}) WriteResult({ "nInserted" : 1 }) MongoDB Enterprise > db.c.aggregate([{$project: {result: "$foo"}}]) { "_id" : ObjectId("60d10a22b614644d4c0f1a31") } MongoDB Enterprise > db.c.aggregate([{$project: {result: {$getField: "foo"}}}]) { "_id" : ObjectId("60d10a22b614644d4c0f1a31") }
You can see that because both expressions return "missing", no field named "result" appears in the resulting document.
Let's now consider a similar example where the user is attempting to extract a field "bar" from object "foo". They can do so either with the dotted field path expression "$foo.bar", or with a chain of nested $getField expressions. However, the field path expression returns missing whereas the nested $getField expressions return null:
MongoDB Enterprise > db.c.aggregate([{$project: {result: "$foo.bar"}}])
{ "_id" : ObjectId("60d10a22b614644d4c0f1a31") }
MongoDB Enterprise > db.c.aggregate([{$project: {result: {$getField: {field: "bar", input: {$getField: "foo"}}}}}])
{ "_id" : ObjectId("60d10a22b614644d4c0f1a31"), "result" : null }
The reason for this behavior is that MQL expressions generally return null when any of their inputs are either null, missing, or undefined. In the case of $getField, it will return null when the input argument is null, missing, or undefined. (The "field" argument, on the other hand, must always be a string literal, which is validated at parse time.) Furthermore, MQL expressions other than field path expressions generally do not return missing. However, $getField has a special case to return missing in order to ensure that it is analogous to a field path expression.
The problem here is that this analogous behavior breaks down for dotted field paths. That is, a missing dotted field path will return null rather than missing if rewritten as a chain of nested $getField expressions. For this reason, we should consider changing the behavior of $getField so that it returns missing rather than null if the value of the "input" expression evaluates to missing, null, or undefined.
There is a similar problem if a scalar exists along a dotted path:
MongoDB Enterprise > db.c.find()
{ "_id" : ObjectId("60d10cbcb614644d4c0f1a32"), "foo" : 1 }
MongoDB Enterprise > db.c.aggregate([{$project: {result: "$foo.bar"}}])
{ "_id" : ObjectId("60d10cbcb614644d4c0f1a32") }
MongoDB Enterprise > db.c.aggregate([{$project: {result: {$getField: {field: "bar", input: {$getField: "foo"}}}}}])
uncaught exception: Error: command failed: {
"ok" : 0,
"errmsg" : "PlanExecutor error during aggregation :: caused by :: $getField requires 'input' to evaluate to type Object, but got double",
"code" : 3041705,
"codeName" : "Location3041705"
} with original command request: {
"aggregate" : "c",
"pipeline" : [
{
"$project" : {
"result" : {
"$getField" : {
"field" : "bar",
"input" : {
"$getField" : "foo"
}
}
}
}
}
],
"cursor" : {
},
"lsid" : {
"id" : UUID("5de90c09-a31d-45f7-a1bf-53d307de419f")
}
} on connection: connection to 127.0.0.1:27017 : aggregate failed :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
doassert@src/mongo/shell/assert.js:18:14
_assertCommandWorked@src/mongo/shell/assert.js:731:17
assert.commandWorked@src/mongo/shell/assert.js:823:16
DB.prototype._runAggregate@src/mongo/shell/db.js:276:5
DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12
@(shell):1:1
The field path expression will return missing whereas the $getField version will throw an exception. This means that $getField would also have to return missing if "input" is any non-object type.
It's not obvious whether this suggested change is a good idea or not. It depends on whether we want $getField to act like all other MQL expressions, or if it should inherit the special behaviors of field path expressions.
Shout out to matthew.chiaravalloti for bringing this to our attention!
- design is described in
-
SERVER-94854 $getField accepts all input types and treats all non-object ones as null-ish
-
- Closed
-
- is related to
-
SERVER-30417 add expression to get value by keyname from object
-
- Closed
-
- related to
-
SERVER-58076 Exclude new language features from stable API for 1 quarter
-
- Closed
-
-
SERVER-95857 Eliminate $getField special case for null/undefined inputs
-
- Backlog
-