[SERVER-17159] $ Positional Operator Only Updating First Sub-Document Created: 03/Feb/15  Updated: 22/Jun/16  Resolved: 03/Feb/15

Status: Closed
Project: Core Server
Component/s: Write Ops
Affects Version/s: 2.6.7, 3.0.0-rc6
Fix Version/s: None

Type: Bug Priority: Critical - P2
Reporter: Durran Jordan Assignee: Unassigned
Resolution: Duplicate Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates SERVER-14662 Positional projection queries (and po... Closed
Operating System: ALL
Steps To Reproduce:

Consider the following document:

{
  "_id" : 1,
  "name" : "Aphex Twin",
  "albums" : [
    {
      "_id" : 1,
      "name" : "Selected Ambient Works 85–92",
      "tracks" : [
        { "_id" : 1, "name" : "Xtal" },
        { "_id" : 2, "name" : "Tha" },
        { "_id" : 3, "name" : "Pulsewidth" }
      ]
    }
  ]
}

I want to update the track with the name "Tha" to "**Tha" for demonstration purposes. Using update with $set, I tell Mongo to find the document with _id of 1, an album sub document _id of 1, an album tracks sub-document with _id of 2 and update it's name. I will show the behaviour from the console:

3.0.0-rc6 (Incorrectly updates the first track name and not the matching second):

MongoDB shell version: 3.0.0-rc6
connecting to: issue-test
> db.bands.findOne();
{
	"_id" : 1,
	"name" : "Aphex Twin",
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "Xtal"
				},
				{
					"_id" : 2,
					"name" : "Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	]
}
> db.bands.update({ "_id" : 1, "albums._id" : 1, "albums.0.tracks._id" : 2 }, { "$set" : { "albums.0.tracks.$.name" : "**Tha" }});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.bands.findOne();
{
	"_id" : 1,
	"name" : "Aphex Twin",
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "**Tha"
				},
				{
					"_id" : 2,
					"name" : "Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	]
}

2.6.7 (Incorrectly updates the first track name and not the matching second):

MongoDB shell version: 2.6.7
connecting to: issue-test
> db.bands.findOne();
{
	"_id" : 1,
	"name" : "Aphex Twin",
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "Xtal"
				},
				{
					"_id" : 2,
					"name" : "Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	]
}
> db.bands.update({ "_id" : 1, "albums._id" : 1, "albums.0.tracks._id" : 2 }, { "$set" : { "albums.0.tracks.$.name" : "**Tha" }});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.bands.findOne();
{
	"_id" : 1,
	"name" : "Aphex Twin",
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "**Tha"
				},
				{
					"_id" : 2,
					"name" : "Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	]
}

2.4.12 (Correctly updates the matching sub-document):

MongoDB shell version: 2.4.12
connecting to: issue-test
> db.bands.findOne();
{
	"_id" : 1,
	"name" : "Aphex Twin",
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "Xtal"
				},
				{
					"_id" : 2,
					"name" : "Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	]
}
> db.bands.update({ "_id" : 1, "albums._id" : 1, "albums.0.tracks._id" : 2 }, { "$set" : { "albums.0.tracks.$.name" : "**Tha" }});
> db.bands.findOne();
{
	"_id" : 1,
	"albums" : [
		{
			"_id" : 1,
			"name" : "Selected Ambient Works 8592",
			"tracks" : [
				{
					"_id" : 1,
					"name" : "Xtal"
				},
				{
					"_id" : 2,
					"name" : "**Tha"
				},
				{
					"_id" : 3,
					"name" : "Pulsewidth"
				}
			]
		}
	],
	"name" : "Aphex Twin"
}

Participants:

 Description   

In the 2.6.x series and higher (tested up to 3.0.0-rc6), a regression in the behaviour of the positional operator ($) has occurred causing it to only ever update the first sub-document, not the matching sub-document when nested more than one level deep. In essence, it is behaving as a "0" and not a "$" in the update syntax. In 2.4.12 this was working as expected, but not since. See the steps to reproduce for a detailed example.



 Comments   
Comment by Durran Jordan [ 03/Feb/15 ]

Sorry! I had been searching for a duplicate but was searching I guess for the wrong terms. The expectation here is in line with the 2.6 documentation for $ and satifies both bullet points. I can confirm that SERVER-14662 does cover this issue.

Comment by Ramon Fernandez Marina [ 03/Feb/15 ]

Hi durran.jordan, this looks like a dup of SERVER-14662 if I understand things correctly. The 2.6 documentation for $ says:

When used with update operations, e.g. db.collection.update() and db.collection.findAndModify(),

  • the positional $ operator acts as a placeholder for the first element that matches the query document, and
  • the array field must appear as part of the query document.

In 2.4 the value of the positional operator was non-deterministic; see this comment for an example.

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