PYTHON-2957 Support 'let' option for multiple CRUD commands (#804)

This commit is contained in:
Julius Park 2021-11-30 15:02:40 -08:00 committed by GitHub
parent e3d1d6f5b4
commit 046d789d9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1721 additions and 130 deletions

View File

@ -1,6 +1,22 @@
Changelog
=========
Changes in Version 4.1
----------------------
- :meth:`pymongo.collection.Collection.update_one`,
:meth:`pymongo.collection.Collection.update_many`,
:meth:`pymongo.collection.Collection.delete_one`,
:meth:`pymongo.collection.Collection.delete_many`,
:meth:`pymongo.collection.Collection.aggregate`,
:meth:`pymongo.collection.Collection.find_one_and_delete`,
:meth:`pymongo.collection.Collection.find_one_and_replace`,
:meth:`pymongo.collection.Collection.find_one_and_update`,
and :meth:`pymongo.collection.Collection.find` all support a new keyword
argument ``let`` which is a map of parameter names and values. Parameters
can then be accessed as variables in an aggregate expression context.
Changes in Version 4.0
----------------------

View File

@ -30,7 +30,7 @@ class _AggregationCommand(object):
:meth:`pymongo.database.Database.aggregate` instead.
"""
def __init__(self, target, cursor_class, pipeline, options,
explicit_session, user_fields=None, result_processor=None):
explicit_session, let=None, user_fields=None, result_processor=None):
if "explain" in options:
raise ConfigurationError("The explain option is not supported. "
"Use Database.command instead.")
@ -44,6 +44,9 @@ class _AggregationCommand(object):
self._performs_write = True
common.validate_is_mapping('options', options)
if let:
common.validate_is_mapping("let", let)
options["let"] = let
self._options = options
# This is the batchSize that will be used for setting the initial

View File

@ -593,7 +593,7 @@ class Collection(common.BaseObject):
check_keys=False, multi=False,
write_concern=None, op_id=None, ordered=True,
bypass_doc_val=False, collation=None, array_filters=None,
hint=None, session=None, retryable_write=False):
hint=None, session=None, retryable_write=False, let=None):
"""Internal update / replace helper."""
common.validate_boolean("upsert", upsert)
collation = validate_collation_or_none(collation)
@ -626,6 +626,9 @@ class Collection(common.BaseObject):
command = SON([('update', self.name),
('ordered', ordered),
('updates', [update_doc])])
if let:
common.validate_is_mapping("let", let)
command["let"] = let
if not write_concern.is_server_default:
command['writeConcern'] = write_concern.document
@ -663,7 +666,7 @@ class Collection(common.BaseObject):
check_keys=False, multi=False,
write_concern=None, op_id=None, ordered=True,
bypass_doc_val=False, collation=None, array_filters=None,
hint=None, session=None):
hint=None, session=None, let=None):
"""Internal update / replace helper."""
def _update(session, sock_info, retryable_write):
return self._update(
@ -672,7 +675,7 @@ class Collection(common.BaseObject):
write_concern=write_concern, op_id=op_id, ordered=ordered,
bypass_doc_val=bypass_doc_val, collation=collation,
array_filters=array_filters, hint=hint, session=session,
retryable_write=retryable_write)
retryable_write=retryable_write, let=let)
return self.__database.client._retryable_write(
(write_concern or self.write_concern).acknowledged and not multi,
@ -759,7 +762,7 @@ class Collection(common.BaseObject):
def update_one(self, filter, update, upsert=False,
bypass_document_validation=False,
collation=None, array_filters=None, hint=None,
session=None):
session=None, let=None):
"""Update a single document matching the filter.
>>> for doc in db.test.find():
@ -802,10 +805,16 @@ class Collection(common.BaseObject):
MongoDB 4.2 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.9
@ -830,12 +839,12 @@ class Collection(common.BaseObject):
write_concern=write_concern,
bypass_doc_val=bypass_document_validation,
collation=collation, array_filters=array_filters,
hint=hint, session=session),
hint=hint, session=session, let=let),
write_concern.acknowledged)
def update_many(self, filter, update, upsert=False, array_filters=None,
bypass_document_validation=False, collation=None,
hint=None, session=None):
hint=None, session=None, let=None):
"""Update one or more documents that match the filter.
>>> for doc in db.test.find():
@ -878,10 +887,16 @@ class Collection(common.BaseObject):
MongoDB 4.2 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
:Returns:
- An instance of :class:`~pymongo.results.UpdateResult`.
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.9
@ -906,7 +921,7 @@ class Collection(common.BaseObject):
write_concern=write_concern,
bypass_doc_val=bypass_document_validation,
collation=collation, array_filters=array_filters,
hint=hint, session=session),
hint=hint, session=session, let=let),
write_concern.acknowledged)
def drop(self, session=None):
@ -938,7 +953,8 @@ class Collection(common.BaseObject):
def _delete(
self, sock_info, criteria, multi,
write_concern=None, op_id=None, ordered=True,
collation=None, hint=None, session=None, retryable_write=False):
collation=None, hint=None, session=None, retryable_write=False,
let=None):
"""Internal delete helper."""
common.validate_is_mapping("filter", criteria)
write_concern = write_concern or self.write_concern
@ -965,6 +981,10 @@ class Collection(common.BaseObject):
if not write_concern.is_server_default:
command['writeConcern'] = write_concern.document
if let:
common.validate_is_document_type("let", let)
command["let"] = let
# Delete command.
result = sock_info.command(
self.__database.name,
@ -980,20 +1000,21 @@ class Collection(common.BaseObject):
def _delete_retryable(
self, criteria, multi,
write_concern=None, op_id=None, ordered=True,
collation=None, hint=None, session=None):
collation=None, hint=None, session=None, let=None):
"""Internal delete helper."""
def _delete(session, sock_info, retryable_write):
return self._delete(
sock_info, criteria, multi,
write_concern=write_concern, op_id=op_id, ordered=ordered,
collation=collation, hint=hint, session=session,
retryable_write=retryable_write)
retryable_write=retryable_write, let=let)
return self.__database.client._retryable_write(
(write_concern or self.write_concern).acknowledged and not multi,
_delete, session)
def delete_one(self, filter, collation=None, hint=None, session=None):
def delete_one(self, filter, collation=None, hint=None, session=None,
let=None):
"""Delete a single document matching the filter.
>>> db.test.count_documents({'x': 1})
@ -1017,10 +1038,16 @@ class Collection(common.BaseObject):
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.6
@ -1034,10 +1061,11 @@ class Collection(common.BaseObject):
self._delete_retryable(
filter, False,
write_concern=write_concern,
collation=collation, hint=hint, session=session),
collation=collation, hint=hint, session=session, let=let),
write_concern.acknowledged)
def delete_many(self, filter, collation=None, hint=None, session=None):
def delete_many(self, filter, collation=None, hint=None, session=None,
let=None):
"""Delete one or more documents matching the filter.
>>> db.test.count_documents({'x': 1})
@ -1061,10 +1089,16 @@ class Collection(common.BaseObject):
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
:Returns:
- An instance of :class:`~pymongo.results.DeleteResult`.
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.6
@ -1078,7 +1112,7 @@ class Collection(common.BaseObject):
self._delete_retryable(
filter, True,
write_concern=write_concern,
collation=collation, hint=hint, session=session),
collation=collation, hint=hint, session=session, let=let),
write_concern.acknowledged)
def find_one(self, filter=None, *args, **kwargs):
@ -1889,15 +1923,16 @@ class Collection(common.BaseObject):
return options
def _aggregate(self, aggregation_command, pipeline, cursor_class, session,
explicit_session, **kwargs):
explicit_session, let=None, **kwargs):
cmd = aggregation_command(
self, cursor_class, pipeline, kwargs, explicit_session,
self, cursor_class, pipeline, kwargs, explicit_session, let,
user_fields={'cursor': {'firstBatch': 1}})
return self.__database.client._retryable_read(
cmd.get_cursor, cmd.get_read_preference(session), session,
retryable=not cmd._performs_write)
def aggregate(self, pipeline, session=None, **kwargs):
def aggregate(self, pipeline, session=None, let=None, **kwargs):
"""Perform an aggregation using the aggregation framework on this
collection.
@ -1944,6 +1979,8 @@ class Collection(common.BaseObject):
A :class:`~pymongo.command_cursor.CommandCursor` over the result
set.
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 4.0
Removed the ``useCursor`` option.
.. versionchanged:: 3.9
@ -1973,6 +2010,7 @@ class Collection(common.BaseObject):
CommandCursor,
session=s,
explicit_session=session is not None,
let=let,
**kwargs)
def aggregate_raw_batches(self, pipeline, session=None, **kwargs):
@ -2232,7 +2270,7 @@ class Collection(common.BaseObject):
def __find_and_modify(self, filter, projection, sort, upsert=None,
return_document=ReturnDocument.BEFORE,
array_filters=None, hint=None, session=None,
**kwargs):
let=None, **kwargs):
"""Internal findAndModify helper."""
common.validate_is_mapping("filter", filter)
@ -2243,6 +2281,9 @@ class Collection(common.BaseObject):
cmd = SON([("findAndModify", self.__name),
("query", filter),
("new", return_document)])
if let:
common.validate_is_mapping("let", let)
cmd["let"] = let
cmd.update(kwargs)
if projection is not None:
cmd["fields"] = helpers._fields_list_to_dict(projection,
@ -2290,7 +2331,7 @@ class Collection(common.BaseObject):
def find_one_and_delete(self, filter,
projection=None, sort=None, hint=None,
session=None, **kwargs):
session=None, let=None, **kwargs):
"""Finds a single document and deletes it, returning the document.
>>> db.test.count_documents({'x': 1})
@ -2337,7 +2378,13 @@ class Collection(common.BaseObject):
- `**kwargs` (optional): additional command arguments can be passed
as keyword arguments (for example maxTimeMS can be used with
recent server versions).
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added ``hint`` parameter.
.. versionchanged:: 3.6
@ -2356,13 +2403,13 @@ class Collection(common.BaseObject):
.. versionadded:: 3.0
"""
kwargs['remove'] = True
return self.__find_and_modify(filter, projection, sort,
return self.__find_and_modify(filter, projection, sort, let=let,
hint=hint, session=session, **kwargs)
def find_one_and_replace(self, filter, replacement,
projection=None, sort=None, upsert=False,
return_document=ReturnDocument.BEFORE,
hint=None, session=None, **kwargs):
hint=None, session=None, let=None, **kwargs):
"""Finds a single document and replaces it, returning either the
original or the replaced document.
@ -2412,10 +2459,16 @@ class Collection(common.BaseObject):
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
- `**kwargs` (optional): additional command arguments can be passed
as keyword arguments (for example maxTimeMS can be used with
recent server versions).
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added the ``hint`` option.
.. versionchanged:: 3.6
@ -2436,14 +2489,14 @@ class Collection(common.BaseObject):
common.validate_ok_for_replace(replacement)
kwargs['update'] = replacement
return self.__find_and_modify(filter, projection,
sort, upsert, return_document,
sort, upsert, return_document, let=let,
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, hint=None, session=None,
**kwargs):
let=None, **kwargs):
"""Finds a single document and updates it, returning either the
original or the updated document.
@ -2533,10 +2586,16 @@ class Collection(common.BaseObject):
MongoDB 4.4 and above.
- `session` (optional): a
:class:`~pymongo.client_session.ClientSession`.
- `let` (optional): Map of parameter names and values. Values must be
constant or closed expressions that do not reference document
fields. Parameters can then be accessed as variables in an
aggregate expression context (e.g. "$$var").
- `**kwargs` (optional): additional command arguments can be passed
as keyword arguments (for example maxTimeMS can be used with
recent server versions).
.. versionchanged:: 4.1
Added ``let`` parameter.
.. versionchanged:: 3.11
Added the ``hint`` option.
.. versionchanged:: 3.9
@ -2561,7 +2620,7 @@ class Collection(common.BaseObject):
kwargs['update'] = update
return self.__find_and_modify(filter, projection,
sort, upsert, return_document,
array_filters, hint=hint,
array_filters, hint=hint, let=let,
session=session, **kwargs)
def __iter__(self):

View File

@ -24,7 +24,8 @@ from bson import RE_TYPE, _convert_raw_document_lists_to_streams
from bson.code import Code
from bson.son import SON
from pymongo import helpers
from pymongo.common import validate_boolean, validate_is_mapping
from pymongo.common import (validate_boolean, validate_is_mapping,
validate_is_document_type)
from pymongo.collation import validate_collation_or_none
from pymongo.errors import (ConnectionFailure,
InvalidOperation,
@ -140,7 +141,7 @@ class Cursor(object):
collation=None, hint=None, max_scan=None, max_time_ms=None,
max=None, min=None, return_key=None, show_record_id=None,
snapshot=None, comment=None, session=None,
allow_disk_use=None):
allow_disk_use=None, let=None):
"""Create a new cursor.
Should not be called directly by application developers - see
@ -197,6 +198,10 @@ class Cursor(object):
if projection is not None:
projection = helpers._fields_list_to_dict(projection, "projection")
if let:
validate_is_document_type("let", let)
self.__let = let
self.__spec = spec
self.__projection = projection
self.__skip = skip
@ -370,6 +375,8 @@ class Cursor(object):
operators["$explain"] = True
if self.__hint:
operators["$hint"] = self.__hint
if self.__let:
operators["let"] = self.__let
if self.__comment:
operators["$comment"] = self.__comment
if self.__max_scan:

View File

@ -56,109 +56,6 @@
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "aggregate",
"object": "collection0",
"arguments": {
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
}
},
{
"$project": {
"_id": 0,
"x": "$$x",
"y": "$$y",
"rand": "$$rand"
}
}
],
"let": {
"id": 1,
"x": "foo",
"y": {
"$literal": "bar"
},
"rand": {
"$rand": {}
}
}
},
"expectResult": [
{
"x": "foo",
"y": "bar",
"rand": {
"$$type": "double"
}
}
]
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"aggregate": "coll0",
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
}
},
{
"$project": {
"_id": 0,
"x": "$$x",
"y": "$$y",
"rand": "$$rand"
}
}
],
"let": {
"id": 1,
"x": "foo",
"y": {
"$literal": "bar"
},
"rand": {
"$rand": {}
}
}
}
}
}
]
}
]
},
{
"description": "Aggregate with let option and dollar-prefixed $literal value",
"runOnRequirements": [
{
"minServerVersion": "5.0",
"topologies": [
"single",
"replicaset"
]
}
],
"operations": [
{
"name": "aggregate",

View File

@ -0,0 +1,201 @@
{
"description": "deleteMany-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2,
"name": "name"
},
{
"_id": 3,
"name": "name"
}
]
}
],
"tests": [
{
"description": "deleteMany with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "deleteMany",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"let": {
"name": "name"
}
},
"expectResult": {
"deletedCount": 2
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"delete": "coll0",
"deletes": [
{
"q": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"limit": 0
}
],
"let": {
"name": "name"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
}
]
}
]
},
{
"description": "deleteMany with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "3.6.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "deleteMany",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"let": {
"name": "name"
}
},
"expectError": {
"errorContains": "'delete.let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"delete": "coll0",
"deletes": [
{
"q": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"limit": 0
}
],
"let": {
"name": "name"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2,
"name": "name"
},
{
"_id": 3,
"name": "name"
}
]
}
]
}
]
}

View File

@ -0,0 +1,191 @@
{
"description": "deleteOne-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "deleteOne with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "deleteOne",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
},
"expectResult": {
"deletedCount": 1
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"delete": "coll0",
"deletes": [
{
"q": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"limit": 1
}
],
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 2
}
]
}
]
},
{
"description": "deleteOne with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "3.6.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "deleteOne",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
},
"expectError": {
"errorContains": "'delete.let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"delete": "coll0",
"deletes": [
{
"q": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"limit": 1
}
],
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
]
}
]
}

View File

@ -0,0 +1,148 @@
{
"description": "find-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "Find with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "find",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
},
"expectResult": [
{
"_id": 1
}
]
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "coll0",
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
}
}
}
]
}
]
},
{
"description": "Find with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "3.6.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "find",
"object": "collection0",
"arguments": {
"filter": {
"_id": 1
},
"let": {
"x": 1
}
},
"expectError": {
"errorContains": "Unrecognized field 'let'",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"find": "coll0",
"filter": {
"_id": 1
},
"let": {
"x": 1
}
}
}
}
]
}
]
}
]
}

View File

@ -0,0 +1,180 @@
{
"description": "findOneAndDelete-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "findOneAndDelete with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "findOneAndDelete",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"remove": true,
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 2
}
]
}
]
},
{
"description": "findOneAndDelete with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "4.2.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "findOneAndDelete",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"let": {
"id": 1
}
},
"expectError": {
"errorContains": "field 'let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"remove": true,
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
]
}
]
}

View File

@ -0,0 +1,197 @@
{
"description": "findOneAndReplace-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "findOneAndReplace with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "findOneAndReplace",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"replacement": {
"x": "x"
},
"let": {
"id": 1
}
},
"expectResult": {
"_id": 1
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": {
"x": "x"
},
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1,
"x": "x"
},
{
"_id": 2
}
]
}
]
},
{
"description": "findOneAndReplace with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "4.2.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "findOneAndReplace",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"replacement": {
"x": "x"
},
"let": {
"id": 1
}
},
"expectError": {
"errorContains": "field 'let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": {
"x": "x"
},
"let": {
"id": 1
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
]
}
]
}

View File

@ -0,0 +1,217 @@
{
"description": "findOneAndUpdate-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "findOneAndUpdate with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "findOneAndUpdate",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"id": 1,
"x": "foo"
}
},
"expectResult": {
"_id": 1
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"id": 1,
"x": "foo"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1,
"x": "foo"
},
{
"_id": 2
}
]
}
]
},
{
"description": "findOneAndUpdate with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "4.2.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "findOneAndUpdate",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"id": 1,
"x": "foo"
}
},
"expectError": {
"errorContains": "field 'let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"findAndModify": "coll0",
"query": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"id": 1,
"x": "foo"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
]
}
]
}

View File

@ -0,0 +1,243 @@
{
"description": "updateMany-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2,
"name": "name"
},
{
"_id": 3,
"name": "name"
}
]
}
],
"tests": [
{
"description": "updateMany with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "updateMany",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"update": [
{
"$set": {
"x": "$$x",
"y": "$$y"
}
}
],
"let": {
"name": "name",
"x": "foo",
"y": {
"$literal": "bar"
}
}
},
"expectResult": {
"matchedCount": 2,
"modifiedCount": 2,
"upsertedCount": 0
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"update": "coll0",
"updates": [
{
"q": {
"$expr": {
"$eq": [
"$name",
"$$name"
]
}
},
"u": [
{
"$set": {
"x": "$$x",
"y": "$$y"
}
}
],
"multi": true
}
],
"let": {
"name": "name",
"x": "foo",
"y": {
"$literal": "bar"
}
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2,
"name": "name",
"x": "foo",
"y": "bar"
},
{
"_id": 3,
"name": "name",
"x": "foo",
"y": "bar"
}
]
}
]
},
{
"description": "updateMany with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "3.6.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "updateMany",
"object": "collection0",
"arguments": {
"filter": {
"_id": 1
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"x": "foo"
}
},
"expectError": {
"errorContains": "'update.let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"update": "coll0",
"updates": [
{
"q": {
"_id": 1
},
"u": [
{
"$set": {
"x": "$$x"
}
}
],
"multi": true
}
],
"let": {
"x": "foo"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2,
"name": "name"
},
{
"_id": 3,
"name": "name"
}
]
}
]
}
]
}

View File

@ -0,0 +1,215 @@
{
"description": "updateOne-let",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0",
"observeEvents": [
"commandStartedEvent"
]
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "crud-tests"
}
},
{
"collection": {
"id": "collection0",
"database": "database0",
"collectionName": "coll0"
}
}
],
"initialData": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
],
"tests": [
{
"description": "UpdateOne with let option",
"runOnRequirements": [
{
"minServerVersion": "5.0"
}
],
"operations": [
{
"name": "updateOne",
"object": "collection0",
"arguments": {
"filter": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"id": 1,
"x": "foo"
}
},
"expectResult": {
"matchedCount": 1,
"modifiedCount": 1,
"upsertedCount": 0
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"update": "coll0",
"updates": [
{
"q": {
"$expr": {
"$eq": [
"$_id",
"$$id"
]
}
},
"u": [
{
"$set": {
"x": "$$x"
}
}
]
}
],
"let": {
"id": 1,
"x": "foo"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1,
"x": "foo"
},
{
"_id": 2
}
]
}
]
},
{
"description": "UpdateOne with let option unsupported (server-side error)",
"runOnRequirements": [
{
"minServerVersion": "3.6.0",
"maxServerVersion": "4.4.99"
}
],
"operations": [
{
"name": "updateOne",
"object": "collection0",
"arguments": {
"filter": {
"_id": 1
},
"update": [
{
"$set": {
"x": "$$x"
}
}
],
"let": {
"x": "foo"
}
},
"expectError": {
"errorContains": "'update.let' is an unknown field",
"isClientError": false
}
}
],
"expectEvents": [
{
"client": "client0",
"events": [
{
"commandStartedEvent": {
"command": {
"update": "coll0",
"updates": [
{
"q": {
"_id": 1
},
"u": [
{
"$set": {
"x": "$$x"
}
}
]
}
],
"let": {
"x": "foo"
}
}
}
}
]
}
],
"outcome": [
{
"collectionName": "coll0",
"databaseName": "crud-tests",
"documents": [
{
"_id": 1
},
{
"_id": 2
}
]
}
]
}
]
}

View File

@ -2178,6 +2178,23 @@ class TestCollection(IntegrationTest):
with self.assertRaises(NotImplementedError):
bool(Collection(self.db, 'test'))
@client_context.require_version_min(5, 0, 0)
def test_helpers_with_let(self):
c = self.db.test
helpers = [(c.delete_many, ({}, {})), (c.delete_one, ({}, {})),
(c.find, ({})), (c.update_many, ({}, {'$inc': {'x': 3}})),
(c.update_one, ({}, {'$inc': {'x': 3}})),
(c.find_one_and_delete, ({}, {})),
(c.find_one_and_replace, ({}, {})),
(c.aggregate, ([], {}))]
for let in [10, "str"]:
for helper, args in helpers:
with self.assertRaisesRegex(TypeError,
"let must be an instance of dict"):
helper(*args, let=let)
for helper, args in helpers:
helper(*args, let={})
if __name__ == "__main__":
unittest.main()