Skip to content

DOCSP-15179-DOCS-9187 upsert with unique index refactor #5198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions source/includes/extracts-parameter-upsert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,34 @@ content: |

- Updates a single document that matches the ``{{queryOrFilter}}``.

To avoid multiple upserts, ensure that the ``{{queryOrFilter}}`` fields
are :ref:`uniquely indexed <index-type-unique>`.
{{usageWithMulti}}

To avoid multiple :term:`upserts <upsert>`, ensure that the
``{{queryOrFilter}}`` field(s) are :ref:`uniquely indexed
<index-type-unique>`. {{uniqueIndexExample}}

Defaults to ``false``, which does *not* insert a new document when no
match is found.

Defaults to ``false``.
replacement:
conjunction: ''
returnNewDocument: ''
usageWithMulti: ''
uniqueIndexExample: ''
---
ref: findAndModify-behavior-command
source:
file: extracts-parameter-upsert.yaml
ref: _update-single-upsert-behavior
replacement:
upsertMethod: ":dbcommand:`findAndModify`"
queryOrFilter: query
uniqueIndexExample: "See :ref:`upsert-and-unique-index-dbcommand` for
an example."
conjunction: "Used in conjunction with the ``update`` field.


"
---
ref: findAndModify-behavior-method
source:
Expand All @@ -23,11 +44,39 @@ source:
replacement:
upsertMethod: ":method:`~db.collection.findAndModify()`"
queryOrFilter: query
uniqueIndexExample: "See :ref:`upsert-and-unique-index` for an
example."
conjunction: "Used in conjunction with the ``update`` field.


"
---
ref: update-upsert-behavior-command
source:
file: extracts-parameter-upsert.yaml
ref: _update-single-upsert-behavior
replacement:
upsertMethod: ":dbcommand:`update`"
queryOrFilter: query
usageWithMulti: "If both ``upsert`` and ``multi`` are true and no
documents match the query, the update operation inserts only a
single document."
uniqueIndexExample: "See :ref:`update-command-behaviors-unique-index`
for an example."
---
ref: update-upsert-behavior-method
source:
file: extracts-parameter-upsert.yaml
ref: _update-single-upsert-behavior
replacement:
upsertMethod: ":method:`~db.collection.update()`"
queryOrFilter: query
usageWithMulti: "If both ``upsert`` and ``multi`` are true and no
documents match the query, the update operation inserts only a
single document."
uniqueIndexExample: "See :ref:`update-with-unique-indexes` for an
example."
---
ref: findOneAndUpdate-behavior-method
source:
file: extracts-parameter-upsert.yaml
Expand Down
129 changes: 129 additions & 0 deletions source/includes/extracts-upsert-unique-index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
ref: _upsert-unique-index-base
content: |

When using the {{upsert}} option with the {{command}}
{{commandOrMethod}}, **and not** using a :ref:`unique index
<index-type-unique>` on the query field(s), multiple
instances of {{aOrAn}} {{command}} operation with similar query
field(s) could result in duplicate documents being inserted in
certain circumstances.

Consider an example where no document with the name ``Andy`` exists
and multiple clients issue the following command at roughly the same
time:

{{codeExample}}

If all {{command}} operations finish the query phase
before any client successfully inserts data, **and** there is no
:ref:`unique index <index-type-unique>` on the ``name`` field, each
{{command}} operation may result in an insert, creating multiple
documents with ``name: Andy``.

To ensure that only one such document is created, and the other
{{command}} operations update this new document instead, create a
:ref:`unique index <index-type-unique>` on the ``name`` field. This
guarantees that only one document with ``name: Andy`` is permitted
in the collection.

With this unique index in place, the multiple {{command}} operations
now exhibit the following behavior:

- Exactly one {{command}} operation will successfully insert a new
document.

- All other {{command}} operations will update the newly-inserted
document, incrementing the ``score`` value.

---
ref: upsert-unique-index-findAndModify-command
source:
file: extracts-upsert-unique-index.yaml
ref: _upsert-unique-index-base
replacement:
command: ":dbcommand:`findAndModify`"
commandOrMethod: "command"
aOrAn: "a"
upsert: "``upsert: true``"
codeExample: |

.. code-block:: javascript

db.runCommand(
{
findAndModify: "people",
query: { name: "Andy" },
update: { $inc: { score: 1 } },
upsert: true
}
)

---
ref: upsert-unique-index-findAndModify-method
source:
file: extracts-upsert-unique-index.yaml
ref: _upsert-unique-index-base
replacement:
command: ":method:`~db.collection.findOneAndUpdate()`"
commandOrMethod: "method"
aOrAn: "a"
upsert: "``upsert: true``"
codeExample: |

.. code-block:: javascript

db.people.findAndModify(
{
query: { name: "Andy" },
update: { $inc: { score: 1 } },
upsert: true
}
)

---
ref: upsert-unique-index-update-command
source:
file: extracts-upsert-unique-index.yaml
ref: _upsert-unique-index-base
replacement:
command: ":dbcommand:`update`"
commandOrMethod: "command"
aOrAn: "an"
upsert: ":ref:`upsert: true <update-command-upsert>`"
codeExample: |

.. code-block:: javascript

db.runCommand(
{
update: "people",
updates: [
{ q: { name: "Andy" }, u: { $inc: { score: 1 } }, multi: true, upsert: true }
]
}
)

---
ref: upsert-unique-index-update-method
source:
file: extracts-upsert-unique-index.yaml
ref: _upsert-unique-index-base
replacement:
command: ":method:`~db.collection.update()`"
commandOrMethod: "method"
aOrAn: "a"
upsert: ":ref:`upsert: true <update-upsert>`"
codeExample: |

.. code-block:: javascript

db.people.update(
{ name: "Andy" },
{ $inc: { score: 1 } },
{
upsert: true,
multi: true
}
)

...
47 changes: 5 additions & 42 deletions source/reference/command/findAndModify.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Definition

- boolean

- .. include:: /includes/extracts/findAndModify-behavior-method.rst
- .. include:: /includes/extracts/findAndModify-behavior-command.rst



Expand Down Expand Up @@ -357,49 +357,12 @@ following:
Behavior
--------

Upsert and Unique Index
~~~~~~~~~~~~~~~~~~~~~~~
.. _upsert-and-unique-index-dbcommand:

When the :dbcommand:`findAndModify` command includes the ``upsert:
true`` option **and** the query field(s) is not uniquely indexed, the
command could insert a document multiple times in certain circumstances.
Upsert with Unique Index
~~~~~~~~~~~~~~~~~~~~~~~~

Consider an example where no document with the name ``Andy`` exists and
multiple clients issue the following command:

.. code-block:: javascript

db.runCommand(
{
findAndModify: "people",
query: { name: "Andy" },
sort: { rating: 1 },
update: { $inc: { score: 1 } },
upsert: true
}
)

If all the commands finish the ``query`` phase before any command
starts the ``modify`` phase, **and** there is no unique index on the
``name`` field, the commands may each perform an upsert, creating
multiple duplicate documents.

To prevent the creation of multiple duplicate documents,
create a :ref:`unique index <index-type-unique>` on
the ``name`` field. With the unique index in place, then the multiple
:dbcommand:`findAndModify` commands will exhibit one of the
following behaviors:

- Exactly one :dbcommand:`findAndModify` successfully inserts a
new document.

- Zero or more :dbcommand:`findAndModify` commands update the
newly inserted document.

- Zero or more :dbcommand:`findAndModify` commands fail when
they attempt to insert a duplicate. If the command fails due to
a unique index constraint violation, you can retry the command.
Absent a delete of the document, the retry should not fail.
.. include:: /includes/extracts/upsert-unique-index-findAndModify-command.rst

.. _cmd-findAndModify-sharded-collection:

Expand Down
11 changes: 8 additions & 3 deletions source/reference/command/update.txt
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,7 @@ Each document contains the following fields:

- .. _update-command-upsert:

Optional. If ``true``, perform an insert if no documents match the query. If
both ``upsert`` and ``multi`` are true and no documents match the
query, the update operation inserts only a single document.
.. include:: /includes/extracts/update-upsert-behavior-command.rst

* - ``multi``

Expand Down Expand Up @@ -386,6 +384,13 @@ For example:

For examples, see :ref:`update-command-example-agg`.

.. _update-command-behaviors-unique-index:

Upsert with Unique Index
~~~~~~~~~~~~~~~~~~~~~~~~

.. include:: /includes/extracts/upsert-unique-index-update-command.rst

Limits
~~~~~~

Expand Down
43 changes: 3 additions & 40 deletions source/reference/method/db.collection.findAndModify.txt
Original file line number Diff line number Diff line change
Expand Up @@ -288,47 +288,10 @@ For more information on projection, see also:

.. _upsert-and-unique-index:

Upsert and Unique Index
~~~~~~~~~~~~~~~~~~~~~~~
Upsert with Unique Index
~~~~~~~~~~~~~~~~~~~~~~~~

When :method:`~db.collection.findAndModify()` includes the ``upsert:
true`` option **and** the query field(s) is not uniquely indexed, the
method could insert a document multiple times in certain circumstances.

In the following example, no document with the name ``Andy`` exists,
and multiple clients issue the following command:

.. code-block:: javascript

db.people.findAndModify({
query: { name: "Andy" },
sort: { rating: 1 },
update: { $inc: { score: 1 } },
upsert: true
})

Then, if these clients' :method:`~db.collection.findAndModify()`
methods finish the ``query`` phase before any command starts the
``modify`` phase, **and** there is no unique index on the ``name``
field, the commands may all perform an upsert, creating
multiple duplicate documents.

To prevent the creation of multiple duplicate documents with the same
name, create a :ref:`unique index <index-type-unique>` on the ``name``
field. With this unique index in place, the multiple methods will
exhibit one of the following behaviors:

- Exactly one :method:`~db.collection.findAndModify()`
successfully inserts a new document.

- Zero or more :method:`~db.collection.findAndModify()` methods
update the newly inserted document.

- Zero or more :method:`~db.collection.findAndModify()` methods fail
when they attempt to insert documents with the same name. If the
method fails due to the unique index constraint violation on the
``name`` field, you can retry the method. Absent a delete of the
document, the retry should not fail.
.. include:: /includes/extracts/upsert-unique-index-findAndModify-method.rst

.. _method-findAndModify-sharded-collection:

Expand Down
Loading