[SERVER-23313] Add a $replaceRoot stage which allows promoting a sub-document to the top level Created: 23/Mar/16  Updated: 03/May/17  Resolved: 29/Jul/16

Status: Closed
Project: Core Server
Component/s: Aggregation Framework
Affects Version/s: None
Fix Version/s: 3.3.11

Type: New Feature Priority: Major - P3
Reporter: Charlie Swanson Assignee: Carly Robison
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Depends
is depended on by CSHARP-1742 Add support for $replaceRoot aggregat... Closed
is depended on by JAVA-2287 Add builder for $replaceRoot aggregat... Closed
Documented
is documented by DOCS-8697 3.4 Rel Notes: Agg $replaceRoot oper... Closed
is documented by DOCS-8720 Document 3.4 $replaceRoot stage Closed
Duplicate
is duplicated by SERVER-24779 Add a $transform aggregation stage Closed
Related
related to DRIVERS-297 Aggregation Framework Support for 3.4 Closed
related to SERVER-25604 Improve readability/usefulness of $re... Closed
Backwards Compatibility: Fully Compatible
Sprint: Integration 18 (08/05/16)
Participants:

 Description   

Syntax

{$replaceRoot: {newRoot: <expression that evaluates to a document>}}

Examples

// =====  Example #1 - Promoting a subfield to the top level =====
>db.example.insert({_id: 0, a1: {b: 1}, a2: 2})
>db.example.aggregate([{$replaceRoot: {newRoot: “$a1”}}]);
{b: 1}
 
// =====  Example #2 - Promoting a nested field to the top level =====
>db.example.insert({_id: 0, a1: {b: {c1: 4, c2: 5}}, a2: 2})
>db.example.aggregate([{$replaceRoot: {newRoot: “$a1.b”}}])
{_id: 0, c1: 4, c2: 5}
 
// =====  Example #3 - Promoting a field that contains an _id =====
>db.example.insert({_id: 0, a1: {b: 1, _id: 7}, a2: 2})
>db.example.aggregate([{$replaceRoot: {newRoot: “$a1”}}]);
{b: 1, _id: 7}
 
// =====  Errors #1 - Invalid path syntax =====
>db.example.aggregate([{$replaceRoot: “$a” }])
Error: expected an object as specification for $replaceRoot stage
 
// =====  Error #2 - When the field doesn’t exist =====
>db.example.insert({_id: 0, a1: {b: 1}, a2: 2})
>db.example.aggregate([{$replaceRoot: {newRoot: “$c”}}])
Error: newRoot must resolve to an object
 
// =====  Error #3 - When the field is not an object =====
>db.example.insert({_id: 0, a1: {b: 1}, a2: 2 })
>db.example.aggregate([{$replaceRoot: {newRoot: “$a2”}}])
Error: newRoot must resolve to an object

More Details

  • We do not do anything special with the _id field.
    • The original _id (if it exists) will be lost. The user can include it by prepending a {$addFields: {“a._id”: “$_id”}} stage. Any _id field in the new root will not be moved to the front for display.

Original Description

In some cases, the documents of interest in the aggregation pipeline are nested inside an outer document. For example, if I had a schema like this:

{
  _id: "user_id",
  first: "first name",
  last: "last name",
  addresses: [
    {city: "New York", state: "NY", zip: "10036", address: "229 W 43rd"},
    {city: "Palo Alto", state: "CA", zip: "94301", address: "100 Forest Ave"}
    ...
  ]
}

Then I might have an aggregation pipeline which deals exclusively with the contents of 'addresses'. It's currently possible to do this, first by unwinding 'addresses', but this approach becomes annoying since all of the field names need to be prefixed with 'addresses.'. It would be useful to have some stage to promote the document inside 'addresses' to the top field. For example, something like this:

db.users.aggregate([
  {$unwind: "$addresses"},
  {$transform: {root: "$addresses"}}
])
 
{city: "New York", state: "NY", zip: "10036", address: "229 W 43rd"}
{city: "Palo Alto", state: "CA", zip: "94301", address: "100 Forest Ave"}

Such a stage could also support other transformations, like those possible in $project, or some that are not possible, such as adding a field to a document (SERVER-5781).



 Comments   
Comment by Asya Kamsky [ 07/Aug/16 ]

Also they might correct such errors via checking $type of path (in addition or instead of $ifNull) to fill in default values...

Comment by Asya Kamsky [ 07/Aug/16 ]

I don't think this error message is always correct/helpful:

"errmsg" : " 'newRoot' argument to $replaceRoot stage must evaluate to a valid Value, try ensuring that your field path(s) exist by prepending a $match: {<path>: $exists} aggregation stage.",

If the field (say "$a") is not an object we correctly say

try ensuring that it evaluates to an object by prepending a $match: {<path>: {$type: 'object'}}

Is there a reason not to always say that even if the field is missing? Also if path is missing or there but null they don't need to match, they might need to $ifNull inside newRoot, maybe to preserve document counts or fill in some default values.

Comment by Charlie Swanson [ 29/Jul/16 ]

Moving "Driver Changes" back to "Needed". It's my understanding that there are some drivers which provide shortcuts/helpers in their language(s) to make it easier to construct aggregation stages and expressions. They need to implement new helpers for new stages/expressions/accumulators.

Comment by Githook User [ 29/Jul/16 ]

Author:

{u'username': u'carlyrobison', u'name': u'Carly Robison', u'email': u'crobison@caltech.edu'}

Message: SERVER-23313 add replaceRoot aggregation stage

replaceRoot promotes a subdocument to the top level
Branch: master
https://github.com/mongodb/mongo/commit/dd60225291815c763d9f6a6b9eea077822bb4458

Comment by Asya Kamsky [ 01/Jul/16 ]

I think the issue isn't that it's annoying to deal with fields being prefixed with "addresses." but rather that there is no way to transform them to the top level except by knowing every single field at the top level and within subdocument.

Comment by Charlie Swanson [ 23/Mar/16 ]

To be determined. Probably lost by default, unless explicitly included via some syntax I haven't decided about yet. Maybe something like:

{$transform: {root: "$addresses", transformations: {_id: "$_id"}}}

Would include the _id, but not first or last.

Comment by Scott Hernandez (Inactive) [ 23/Mar/16 ]

What about the other fields in the original document, like "_id", "first", "last"? Are they lost or should they be included in the new documents?

Generated at Thu Feb 08 04:03:01 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.