PYTHON-2036 Expand CRUD API support for index hinting (also PYTHON-2015, PYTHON-2104, PYTHON-2134)

This commit is contained in:
Prashant Mital 2020-02-25 16:49:26 -08:00
parent a460725f6b
commit 1323ef15cb
No known key found for this signature in database
GPG Key ID: D5A4E9E5CFB4CBD7
10 changed files with 1325 additions and 101 deletions

View File

@ -156,6 +156,7 @@ class _Bulk(object):
self.bypass_doc_val = bypass_document_validation
self.uses_collation = False
self.uses_array_filters = False
self.uses_hint = False
self.is_retryable = True
self.retrying = False
self.started_retryable_write = False
@ -180,7 +181,7 @@ class _Bulk(object):
self.ops.append((_INSERT, document))
def add_update(self, selector, update, multi=False, upsert=False,
collation=None, array_filters=None):
collation=None, array_filters=None, hint=None):
"""Create an update document and add it to the list of ops.
"""
validate_ok_for_update(update)
@ -193,13 +194,16 @@ class _Bulk(object):
if array_filters is not None:
self.uses_array_filters = True
cmd['arrayFilters'] = array_filters
if hint is not None:
self.uses_hint = True
cmd['hint'] = hint
if multi:
# A bulk_write containing an update_many is not retryable.
self.is_retryable = False
self.ops.append((_UPDATE, cmd))
def add_replace(self, selector, replacement, upsert=False,
collation=None):
collation=None, hint=None):
"""Create a replace document and add it to the list of ops.
"""
validate_ok_for_replace(replacement)
@ -209,6 +213,9 @@ class _Bulk(object):
if collation is not None:
self.uses_collation = True
cmd['collation'] = collation
if hint is not None:
self.uses_hint = True
cmd['hint'] = hint
self.ops.append((_UPDATE, cmd))
def add_delete(self, selector, limit, collation=None):
@ -252,9 +259,13 @@ class _Bulk(object):
def _execute_command(self, generator, write_concern, session,
sock_info, op_id, retryable, full_result):
if sock_info.max_wire_version < 5 and self.uses_collation:
raise ConfigurationError(
'Must be connected to MongoDB 3.4+ to use a collation.')
if sock_info.max_wire_version < 5:
if self.uses_collation:
raise ConfigurationError(
'Must be connected to MongoDB 3.4+ to use a collation.')
if self.uses_hint:
raise ConfigurationError(
'Must be connected to MongoDB 3.4+ to use hint.')
if sock_info.max_wire_version < 6 and self.uses_array_filters:
raise ConfigurationError(
'Must be connected to MongoDB 3.6+ to use arrayFilters.')
@ -428,6 +439,9 @@ class _Bulk(object):
if self.uses_array_filters:
raise ConfigurationError(
'arrayFilters is unsupported for unacknowledged writes.')
if self.uses_hint:
raise ConfigurationError(
'hint is unsupported for unacknowledged writes.')
# Cannot have both unacknowledged writes and bypass document validation.
if self.bypass_doc_val and sock_info.max_wire_version >= 4:
raise OperationFailure("Cannot set bypass_document_validation with"

View File

@ -761,7 +761,7 @@ class Collection(common.BaseObject):
check_keys=True, multi=False, manipulate=False,
write_concern=None, op_id=None, ordered=True,
bypass_doc_val=False, collation=None, array_filters=None,
session=None, retryable_write=False):
hint=None, session=None, retryable_write=False):
"""Internal update / replace helper."""
common.validate_boolean("upsert", upsert)
if manipulate:
@ -791,6 +791,17 @@ class Collection(common.BaseObject):
'arrayFilters is unsupported for unacknowledged writes.')
else:
update_doc['arrayFilters'] = array_filters
if hint is not None:
if sock_info.max_wire_version < 5:
raise ConfigurationError(
'Must be connected to MongoDB 3.4+ to use hint.')
elif not acknowledged:
raise ConfigurationError(
'hint is unsupported for unacknowledged writes.')
if not isinstance(hint, string_type):
hint = helpers._index_document(hint)
update_doc['hint'] = hint
command = SON([('update', self.name),
('ordered', ordered),
('updates', [update_doc])])
@ -839,7 +850,7 @@ class Collection(common.BaseObject):
check_keys=True, multi=False, manipulate=False,
write_concern=None, op_id=None, ordered=True,
bypass_doc_val=False, collation=None, array_filters=None,
session=None):
hint=None, session=None):
"""Internal update / replace helper."""
def _update(session, sock_info, retryable_write):
return self._update(
@ -847,7 +858,7 @@ class Collection(common.BaseObject):
check_keys=check_keys, multi=multi, manipulate=manipulate,
write_concern=write_concern, op_id=op_id, ordered=ordered,
bypass_doc_val=bypass_doc_val, collation=collation,
array_filters=array_filters, session=session,
array_filters=array_filters, hint=hint, session=session,
retryable_write=retryable_write)
return self.__database.client._retryable_write(
@ -856,7 +867,7 @@ class Collection(common.BaseObject):
def replace_one(self, filter, replacement, upsert=False,
bypass_document_validation=False, collation=None,
session=None):
hint=None, session=None):
"""Replace a single document matching the filter.
>>> for doc in db.test.find({}):
@ -893,27 +904,30 @@ class Collection(common.BaseObject):
match the filter.
- `bypass_document_validation`: (optional) If ``True``, allows the
write to opt-out of document level validation. Default is
``False``.
``False``. This option is only supported on MongoDB 3.2 and above.
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only supported
on MongoDB 3.4 and above.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.6
Added ``session`` parameter.
.. versionchanged:: 3.4
Added the `collation` option.
.. versionchanged:: 3.2
Added bypass_document_validation support
Added bypass_document_validation support.
.. versionadded:: 3.0
"""
@ -926,12 +940,13 @@ class Collection(common.BaseObject):
filter, replacement, upsert,
write_concern=write_concern,
bypass_doc_val=bypass_document_validation,
collation=collation, session=session),
collation=collation, hint=hint, session=session),
write_concern.acknowledged)
def update_one(self, filter, update, upsert=False,
bypass_document_validation=False,
collation=None, array_filters=None, session=None):
collation=None, array_filters=None, hint=None,
session=None):
"""Update a single document matching the filter.
>>> for doc in db.test.find():
@ -959,32 +974,35 @@ class Collection(common.BaseObject):
match the filter.
- `bypass_document_validation`: (optional) If ``True``, allows the
write to opt-out of document level validation. Default is
``False``.
``False``. This option is only supported on MongoDB 3.2 and above.
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only supported
on MongoDB 3.4 and above.
- `array_filters` (optional): A list of filters specifying which
array elements an update should apply. Requires MongoDB 3.6+.
array elements an update should apply. This option is only
supported on MongoDB 3.6 and above.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.9
Added the ability to accept a pipeline as the `update`.
Added the ability to accept a pipeline as the ``update``.
.. versionchanged:: 3.6
Added the `array_filters` and ``session`` parameters.
Added the ``array_filters`` and ``session`` parameters.
.. versionchanged:: 3.4
Added the `collation` option.
Added the ``collation`` option.
.. versionchanged:: 3.2
Added bypass_document_validation support
Added ``bypass_document_validation`` support.
.. versionadded:: 3.0
"""
@ -999,12 +1017,12 @@ class Collection(common.BaseObject):
write_concern=write_concern,
bypass_doc_val=bypass_document_validation,
collation=collation, array_filters=array_filters,
session=session),
hint=hint, session=session),
write_concern.acknowledged)
def update_many(self, filter, update, upsert=False, array_filters=None,
bypass_document_validation=False, collation=None,
session=None):
hint=None, session=None):
"""Update one or more documents that match the filter.
>>> for doc in db.test.find():
@ -1032,32 +1050,35 @@ class Collection(common.BaseObject):
match the filter.
- `bypass_document_validation` (optional): If ``True``, allows the
write to opt-out of document level validation. Default is
``False``.
``False``. This option is only supported on MongoDB 3.2 and above.
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only supported
on MongoDB 3.4 and above.
- `array_filters` (optional): A list of filters specifying which
array elements an update should apply. Requires MongoDB 3.6+.
array elements an update should apply. This option is only
supported on MongoDB 3.6 and above.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
.. note:: `bypass_document_validation` requires server version
**>= 3.2**
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.9
Added the ability to accept a pipeline as the `update`.
.. versionchanged:: 3.6
Added ``array_filters`` and ``session`` parameters.
.. versionchanged:: 3.4
Added the `collation` option.
.. versionchanged:: 3.2
Added bypass_document_validation support
Added bypass_document_validation support.
.. versionadded:: 3.0
"""
@ -1072,7 +1093,7 @@ class Collection(common.BaseObject):
write_concern=write_concern,
bypass_doc_val=bypass_document_validation,
collation=collation, array_filters=array_filters,
session=session),
hint=hint, session=session),
write_concern.acknowledged)
def drop(self, session=None):
@ -2834,7 +2855,8 @@ class Collection(common.BaseObject):
def __find_and_modify(self, filter, projection, sort, upsert=None,
return_document=ReturnDocument.BEFORE,
array_filters=None, session=None, **kwargs):
array_filters=None, hint=None, session=None,
**kwargs):
"""Internal findAndModify helper."""
common.validate_is_mapping("filter", filter)
@ -2854,6 +2876,9 @@ class Collection(common.BaseObject):
if upsert is not None:
common.validate_boolean("upsert", upsert)
cmd["upsert"] = upsert
if hint is not None:
if not isinstance(hint, string_type):
hint = helpers._index_document(hint)
write_concern = self._write_concern_for_cmd(cmd, session)
@ -2868,6 +2893,11 @@ class Collection(common.BaseObject):
'arrayFilters is unsupported for unacknowledged '
'writes.')
cmd["arrayFilters"] = array_filters
if hint is not None:
if sock_info.max_wire_version < 8:
raise ConfigurationError(
'Must be connected to MongoDB 4.2+ to use hint.')
cmd['hint'] = hint
if (sock_info.max_wire_version >= 4 and
not write_concern.is_server_default):
cmd['writeConcern'] = write_concern.document
@ -2952,7 +2982,7 @@ class Collection(common.BaseObject):
def find_one_and_replace(self, filter, replacement,
projection=None, sort=None, upsert=False,
return_document=ReturnDocument.BEFORE,
session=None, **kwargs):
hint=None, session=None, **kwargs):
"""Finds a single document and replaces it, returning either the
original or the replaced document.
@ -2994,16 +3024,24 @@ class Collection(common.BaseObject):
if no document matches. If
:attr:`ReturnDocument.AFTER`, returns the replaced
or inserted document.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `**kwargs` (optional): additional command arguments can be passed
as keyword arguments (for example maxTimeMS can be used with
recent server versions).
.. versionchanged:: 3.11
Added the ``hint`` option.
.. versionchanged:: 3.6
Added ``session`` parameter.
.. versionchanged:: 3.4
Added the `collation` option.
Added the ``collation`` option.
.. versionchanged:: 3.2
Respects write concern.
@ -3019,12 +3057,13 @@ class Collection(common.BaseObject):
kwargs['update'] = replacement
return self.__find_and_modify(filter, projection,
sort, upsert, return_document,
session=session, **kwargs)
hint=hint, session=session, **kwargs)
def find_one_and_update(self, filter, update,
projection=None, sort=None, upsert=False,
return_document=ReturnDocument.BEFORE,
array_filters=None, session=None, **kwargs):
array_filters=None, hint=None, session=None,
**kwargs):
"""Finds a single document and updates it, returning either the
original or the updated document.
@ -3104,19 +3143,28 @@ class Collection(common.BaseObject):
:attr:`ReturnDocument.AFTER`, returns the updated
or inserted document.
- `array_filters` (optional): A list of filters specifying which
array elements an update should apply. Requires MongoDB 3.6+.
array elements an update should apply. This option is only
supported on MongoDB 3.6 and above.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `**kwargs` (optional): additional command arguments can be passed
as keyword arguments (for example maxTimeMS can be used with
recent server versions).
.. versionchanged:: 3.11
Added the ``hint`` option.
.. versionchanged:: 3.9
Added the ability to accept a pipeline as the `update`.
Added the ability to accept a pipeline as the ``update``.
.. versionchanged:: 3.6
Added the `array_filters` and `session` options.
Added the ``array_filters`` and ``session`` options.
.. versionchanged:: 3.4
Added the `collation` option.
Added the ``collation`` option.
.. versionchanged:: 3.2
Respects write concern.
@ -3133,7 +3181,8 @@ class Collection(common.BaseObject):
kwargs['update'] = update
return self.__find_and_modify(filter, projection,
sort, upsert, return_document,
array_filters, session=session, **kwargs)
array_filters, hint=hint,
session=session, **kwargs)
def save(self, to_save, manipulate=True, check_keys=True, **kwargs):
"""Save a document in this collection.

View File

@ -14,6 +14,9 @@
"""Operation class definitions."""
from bson.py3compat import string_type
from pymongo import helpers
from pymongo.common import validate_boolean, validate_is_mapping, validate_list
from pymongo.collation import validate_collation_or_none
from pymongo.helpers import _gen_index_name, _index_document, _index_list
@ -136,9 +139,10 @@ class DeleteMany(object):
class ReplaceOne(object):
"""Represents a replace_one operation."""
__slots__ = ("_filter", "_doc", "_upsert", "_collation")
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_hint")
def __init__(self, filter, replacement, upsert=False, collation=None):
def __init__(self, filter, replacement, upsert=False, collation=None,
hint=None):
"""Create a ReplaceOne instance.
For use with :meth:`~pymongo.collection.Collection.bulk_write`.
@ -151,65 +155,43 @@ class ReplaceOne(object):
- `collation` (optional): An instance of
:class:`~pymongo.collation.Collation`. This option is only
supported on MongoDB 3.4 and above.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
.. versionchanged:: 3.11
Added the ``hint`` option.
.. versionchanged:: 3.5
Added the `collation` option.
Added the ``collation`` option.
"""
if filter is not None:
validate_is_mapping("filter", filter)
if upsert is not None:
validate_boolean("upsert", upsert)
if hint is not None:
if not isinstance(hint, string_type):
hint = helpers._index_document(hint)
self._filter = filter
self._doc = replacement
self._upsert = upsert
self._collation = collation
self._hint = hint
def _add_to_bulk(self, bulkobj):
"""Add this operation to the _Bulk instance `bulkobj`."""
bulkobj.add_replace(self._filter, self._doc, self._upsert,
collation=self._collation)
def __eq__(self, other):
if type(other) == type(self):
return (
(other._filter, other._doc, other._upsert, other._collation) ==
(self._filter, self._doc, self._upsert, self._collation))
return NotImplemented
def __ne__(self, other):
return not self == other
def __repr__(self):
return "%s(%r, %r, %r, %r)" % (
self.__class__.__name__, self._filter, self._doc, self._upsert,
self._collation)
class _UpdateOp(object):
"""Private base class for update operations."""
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_array_filters")
def __init__(self, filter, doc, upsert, collation, array_filters):
if filter is not None:
validate_is_mapping("filter", filter)
if upsert is not None:
validate_boolean("upsert", upsert)
if array_filters is not None:
validate_list("array_filters", array_filters)
self._filter = filter
self._doc = doc
self._upsert = upsert
self._collation = collation
self._array_filters = array_filters
collation=self._collation, hint=self._hint)
def __eq__(self, other):
if type(other) == type(self):
return (
(other._filter, other._doc, other._upsert, other._collation,
other._array_filters) ==
(self._filter, self._doc, self._upsert, self._collation,
self._array_filters))
other._hint) == (self._filter, self._doc, self._upsert,
self._collation, other._hint))
return NotImplemented
def __ne__(self, other):
@ -218,7 +200,50 @@ class _UpdateOp(object):
def __repr__(self):
return "%s(%r, %r, %r, %r, %r)" % (
self.__class__.__name__, self._filter, self._doc, self._upsert,
self._collation, self._array_filters)
self._collation, self._hint)
class _UpdateOp(object):
"""Private base class for update operations."""
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_array_filters",
"_hint")
def __init__(self, filter, doc, upsert, collation, array_filters, hint):
if filter is not None:
validate_is_mapping("filter", filter)
if upsert is not None:
validate_boolean("upsert", upsert)
if array_filters is not None:
validate_list("array_filters", array_filters)
if hint is not None:
if not isinstance(hint, string_type):
hint = helpers._index_document(hint)
self._filter = filter
self._doc = doc
self._upsert = upsert
self._collation = collation
self._array_filters = array_filters
self._hint = hint
def __eq__(self, other):
if type(other) == type(self):
return (
(other._filter, other._doc, other._upsert, other._collation,
other._array_filters, other._hint) ==
(self._filter, self._doc, self._upsert, self._collation,
self._array_filters, self._hint))
return NotImplemented
def __ne__(self, other):
return not self == other
def __repr__(self):
return "%s(%r, %r, %r, %r, %r, %r)" % (
self.__class__.__name__, self._filter, self._doc, self._upsert,
self._collation, self._array_filters, self._hint)
class UpdateOne(_UpdateOp):
@ -227,7 +252,7 @@ class UpdateOne(_UpdateOp):
__slots__ = ()
def __init__(self, filter, update, upsert=False, collation=None,
array_filters=None):
array_filters=None, hint=None):
"""Represents an update_one operation.
For use with :meth:`~pymongo.collection.Collection.bulk_write`.
@ -242,7 +267,15 @@ class UpdateOne(_UpdateOp):
supported on MongoDB 3.4 and above.
- `array_filters` (optional): A list of filters specifying which
array elements an update should apply. Requires MongoDB 3.6+.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
.. versionchanged:: 3.11
Added the `hint` option.
.. versionchanged:: 3.9
Added the ability to accept a pipeline as the `update`.
.. versionchanged:: 3.6
@ -251,13 +284,14 @@ class UpdateOne(_UpdateOp):
Added the `collation` option.
"""
super(UpdateOne, self).__init__(filter, update, upsert, collation,
array_filters)
array_filters, hint)
def _add_to_bulk(self, bulkobj):
"""Add this operation to the _Bulk instance `bulkobj`."""
bulkobj.add_update(self._filter, self._doc, False, self._upsert,
collation=self._collation,
array_filters=self._array_filters)
array_filters=self._array_filters,
hint=self._hint)
class UpdateMany(_UpdateOp):
@ -266,7 +300,7 @@ class UpdateMany(_UpdateOp):
__slots__ = ()
def __init__(self, filter, update, upsert=False, collation=None,
array_filters=None):
array_filters=None, hint=None):
"""Create an UpdateMany instance.
For use with :meth:`~pymongo.collection.Collection.bulk_write`.
@ -281,7 +315,15 @@ class UpdateMany(_UpdateOp):
supported on MongoDB 3.4 and above.
- `array_filters` (optional): A list of filters specifying which
array elements an update should apply. Requires MongoDB 3.6+.
- `hint` (optional): An index to use to support the query
predicate specified either by its string name, or in the same
format as passed to
:meth:`~pymongo.collection.Collection.create_index` (e.g.
``[('field', ASCENDING)]``). This option is only supported on
MongoDB 4.2 and above.
.. versionchanged:: 3.11
Added the `hint` option.
.. versionchanged:: 3.9
Added the ability to accept a pipeline as the `update`.
.. versionchanged:: 3.6
@ -290,13 +332,14 @@ class UpdateMany(_UpdateOp):
Added the `collation` option.
"""
super(UpdateMany, self).__init__(filter, update, upsert, collation,
array_filters)
array_filters, hint)
def _add_to_bulk(self, bulkobj):
"""Add this operation to the _Bulk instance `bulkobj`."""
bulkobj.add_update(self._filter, self._doc, True, self._upsert,
collation=self._collation,
array_filters=self._array_filters)
array_filters=self._array_filters,
hint=self._hint)
class IndexModel(object):

View File

@ -0,0 +1,366 @@
{
"runOn": [
{
"minServerVersion": "4.2.0"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
}
],
"collection_name": "test_bulkwrite_update_hint",
"tests": [
{
"description": "BulkWrite updateOne with update hints",
"operations": [
{
"name": "bulkWrite",
"arguments": {
"requests": [
{
"name": "updateOne",
"arguments": {
"filter": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
}
},
{
"name": "updateOne",
"arguments": {
"filter": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
}
}
],
"options": {
"ordered": true
}
},
"result": {
"deletedCount": 0,
"insertedCount": 0,
"insertedIds": {},
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0,
"upsertedIds": {}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_bulkwrite_update_hint",
"updates": [
{
"q": {
"_id": 1
},
"u": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
},
{
"q": {
"_id": 1
},
"u": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
}
],
"ordered": true
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 13
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
}
]
}
}
},
{
"description": "BulkWrite updateMany with update hints",
"operations": [
{
"name": "bulkWrite",
"arguments": {
"requests": [
{
"name": "updateMany",
"arguments": {
"filter": {
"_id": {
"$lt": 3
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
}
},
{
"name": "updateMany",
"arguments": {
"filter": {
"_id": {
"$lt": 3
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
}
}
],
"options": {
"ordered": true
}
},
"result": {
"deletedCount": 0,
"insertedCount": 0,
"insertedIds": {},
"matchedCount": 4,
"modifiedCount": 4,
"upsertedCount": 0,
"upsertedIds": {}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_bulkwrite_update_hint",
"updates": [
{
"q": {
"_id": {
"$lt": 3
}
},
"u": {
"$inc": {
"x": 1
}
},
"multi": true,
"hint": "_id_"
},
{
"q": {
"_id": {
"$lt": 3
}
},
"u": {
"$inc": {
"x": 1
}
},
"multi": true,
"hint": {
"_id": 1
}
}
],
"ordered": true
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 13
},
{
"_id": 2,
"x": 24
},
{
"_id": 3,
"x": 33
},
{
"_id": 4,
"x": 44
}
]
}
}
},
{
"description": "BulkWrite replaceOne with update hints",
"operations": [
{
"name": "bulkWrite",
"arguments": {
"requests": [
{
"name": "replaceOne",
"arguments": {
"filter": {
"_id": 3
},
"replacement": {
"x": 333
},
"hint": "_id_"
}
},
{
"name": "replaceOne",
"arguments": {
"filter": {
"_id": 4
},
"replacement": {
"x": 444
},
"hint": {
"_id": 1
}
}
}
],
"options": {
"ordered": true
}
},
"result": {
"deletedCount": 0,
"insertedCount": 0,
"insertedIds": {},
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0,
"upsertedIds": {}
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_bulkwrite_update_hint",
"updates": [
{
"q": {
"_id": 3
},
"u": {
"x": 333
},
"hint": "_id_"
},
{
"q": {
"_id": 4
},
"u": {
"x": 444
},
"hint": {
"_id": 1
}
}
],
"ordered": true
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 333
},
{
"_id": 4,
"x": 444
}
]
}
}
}
]
}

View File

@ -0,0 +1,128 @@
{
"runOn": [
{
"minServerVersion": "4.3.1"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"collection_name": "findOneAndReplace_hint",
"tests": [
{
"description": "FindOneAndReplace with hint string",
"operations": [
{
"object": "collection",
"name": "findOneAndReplace",
"arguments": {
"filter": {
"_id": 1
},
"replacement": {
"x": 33
},
"hint": "_id_"
},
"result": {
"_id": 1,
"x": 11
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"findAndModify": "findOneAndReplace_hint",
"query": {
"_id": 1
},
"update": {
"x": 33
},
"hint": "_id_"
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 33
},
{
"_id": 2,
"x": 22
}
]
}
}
},
{
"description": "FindOneAndReplace with hint document",
"operations": [
{
"object": "collection",
"name": "findOneAndReplace",
"arguments": {
"filter": {
"_id": 1
},
"replacement": {
"x": 33
},
"hint": {
"_id": 1
}
},
"result": {
"_id": 1,
"x": 11
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"findAndModify": "findOneAndReplace_hint",
"query": {
"_id": 1
},
"update": {
"x": 33
},
"hint": {
"_id": 1
}
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 33
},
{
"_id": 2,
"x": 22
}
]
}
}
}
]
}

View File

@ -0,0 +1,136 @@
{
"runOn": [
{
"minServerVersion": "4.3.1"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"collection_name": "findOneAndUpdate_hint",
"tests": [
{
"description": "FindOneAndUpdate with hint string",
"operations": [
{
"object": "collection",
"name": "findOneAndUpdate",
"arguments": {
"filter": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
},
"result": {
"_id": 1,
"x": 11
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"findAndModify": "findOneAndUpdate_hint",
"query": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 12
},
{
"_id": 2,
"x": 22
}
]
}
}
},
{
"description": "FindOneAndUpdate with hint document",
"operations": [
{
"object": "collection",
"name": "findOneAndUpdate",
"arguments": {
"filter": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
},
"result": {
"_id": 1,
"x": 11
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"findAndModify": "findOneAndUpdate_hint",
"query": {
"_id": 1
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 12
},
{
"_id": 2,
"x": 22
}
]
}
}
}
]
}

View File

@ -0,0 +1,146 @@
{
"runOn": [
{
"minServerVersion": "4.2.0"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"collection_name": "test_replaceone_hint",
"tests": [
{
"description": "ReplaceOne with hint string",
"operations": [
{
"object": "collection",
"name": "replaceOne",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"replacement": {
"x": 111
},
"hint": "_id_"
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_replaceone_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"x": 111
},
"hint": "_id_"
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 111
}
]
}
}
},
{
"description": "ReplaceOne with hint document",
"operations": [
{
"object": "collection",
"name": "replaceOne",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"replacement": {
"x": 111
},
"hint": {
"_id": 1
}
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_replaceone_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"x": 111
},
"hint": {
"_id": 1
}
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 111
}
]
}
}
}
]
}

View File

@ -0,0 +1,168 @@
{
"runOn": [
{
"minServerVersion": "4.2.0"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
},
{
"_id": 3,
"x": 33
}
],
"collection_name": "test_updatemany_hint",
"tests": [
{
"description": "UpdateMany with hint string",
"operations": [
{
"object": "collection",
"name": "updateMany",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
},
"result": {
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_updatemany_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"$inc": {
"x": 1
}
},
"multi": true,
"hint": "_id_"
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 23
},
{
"_id": 3,
"x": 34
}
]
}
}
},
{
"description": "UpdateMany with hint document",
"operations": [
{
"object": "collection",
"name": "updateMany",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
},
"result": {
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_updatemany_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"$inc": {
"x": 1
}
},
"multi": true,
"hint": {
"_id": 1
}
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 23
},
{
"_id": 3,
"x": 34
}
]
}
}
}
]
}

View File

@ -0,0 +1,154 @@
{
"runOn": [
{
"minServerVersion": "4.2.0"
}
],
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 22
}
],
"collection_name": "test_updateone_hint",
"tests": [
{
"description": "UpdateOne with hint string",
"operations": [
{
"object": "collection",
"name": "updateOne",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_updateone_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"$inc": {
"x": 1
}
},
"hint": "_id_"
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 23
}
]
}
}
},
{
"description": "UpdateOne with hint document",
"operations": [
{
"object": "collection",
"name": "updateOne",
"arguments": {
"filter": {
"_id": {
"$gt": 1
}
},
"update": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
},
"result": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectations": [
{
"command_started_event": {
"command": {
"update": "test_updateone_hint",
"updates": [
{
"q": {
"_id": {
"$gt": 1
}
},
"u": {
"$inc": {
"x": 1
}
},
"hint": {
"_id": 1
}
}
]
}
}
}
],
"outcome": {
"collection": {
"data": [
{
"_id": 1,
"x": 11
},
{
"_id": 2,
"x": 23
}
]
}
}
}
]
}

View File

@ -22,12 +22,13 @@ from bson import decode, encode
from bson.binary import Binary, STANDARD
from bson.codec_options import CodecOptions
from bson.int64 import Int64
from bson.py3compat import iteritems, abc, text_type
from bson.py3compat import iteritems, abc, string_type, text_type
from bson.son import SON
from gridfs import GridFSBucket
from pymongo import (client_session,
helpers,
operations)
from pymongo.command_cursor import CommandCursor
from pymongo.cursor import Cursor
@ -204,6 +205,25 @@ class SpecRunner(IntegrationTest):
if 'maxCommitTimeMS' in opts:
opts['max_commit_time_ms'] = opts.pop('maxCommitTimeMS')
if 'hint' in opts:
hint = opts.pop('hint')
if not isinstance(hint, string_type):
hint = list(iteritems(hint))
opts['hint'] = hint
# Properly format 'hint' arguments for the Bulk API tests.
if 'requests' in opts:
reqs = opts.pop('requests')
for req in reqs:
args = req.pop('arguments')
if 'hint' in args:
hint = args.pop('hint')
if not isinstance(hint, string_type):
hint = list(iteritems(hint))
args['hint'] = hint
req['arguments'] = args
opts['requests'] = reqs
return dict(opts)
def run_operation(self, sessions, collection, operation):