-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Unknown
-
Affects Version/s: 9.1.1
-
Component/s: Persistence
-
None
-
None
-
Fully Compatible
-
Ruby Drivers
-
Not Needed
-
-
None
-
None
-
None
-
None
-
None
-
None
Description
Mongoid 9.1.1 and the current master branch can send conflicting update paths when one save changes an embeds_many array and one of its embedded documents. MongoDB rejects the update with Mongo::Error::OperationFailure code 40 (ConflictingUpdateOperators), so none of the changes are applied.
Mongoid can produce this update through its public association APIs. For example, building another child and changing an existing child's HABTM foreign keys in the same save can put $push children and $addToSet children.0.grade_ids in one update.
Reproduction
class Grade include Mongoid::Document end class Child include Mongoid::Document has_and_belongs_to_many :grades, inverse_of: nil embedded_in :parent end class Parent include Mongoid::Document embeds_many :children end grades = Array.new(2) { Grade.create! } parent = Parent.create!(children: [ Child.new(grade_ids: [grades[0].id]), Child.new(grade_ids: [grades[0].id]) ]) parent.reload child = parent.children.first parent.children.build child.grade_ids = child.grade_ids + [grades[1].id] parent.save!
The save raises Mongo::Error::OperationFailure code 40 because $push children conflicts with $addToSet children.0.grade_ids.
Other affected cases combine $pull children with $pullAll or $addToSet under a surviving child, use pulls at different nested array depths, or combine $push children with a safe $unset under an existing child. Assigning an empty embeds_many array to a surviving child while a preceding sibling is removed also produces an $unset plus $pull conflict on the same array root.
Expected behaviour
Mongoid should apply operations in separate writes when it can do so without changing which embedded document or array position they target. Unsupported $unset plus $pull combinations should continue to fail without changing stored data, rather than silently modifying a different child.
Actual behaviour
The conflicting paths are sent in one MongoDB update. MongoDB rejects the update with code 40, and none of its changes are applied.
Root cause
Mongoid::Atomic::Modifiers detects some conflicts by root field, but $addToSet, $pullAll, and $pull are not consistently checked against $push and other pulls. The order of $unset and $pull on the same array root also cannot always be chosen safely.
Related issue
The stale embedded array path is tracked in MONGOID-5364. That path correction matters when evaluating conflicts involving positional child updates.
Environment
Mongoid 9.1.1 and the current master branch.
- is related to
-
MONGOID-6019 Conflicting embedded array updates are silently written to the wrong embedded document after MONGOID-6013
-
- Needs Triage
-
-
MONGOID-6020 Foreign key array changes are written to the following embedded document after touch and the removal of a preceding sibling
-
- Needs Triage
-