PYTHON-3298 Add flag to create_collection to skip listCollections pre-check (#1006)
This commit is contained in:
parent
bbe364fea8
commit
484374eb3f
@ -13,6 +13,9 @@ PyMongo 4.2 brings a number of improvements including:
|
||||
changes may be made before the final release. See :ref:`automatic-queryable-client-side-encryption` for example usage.
|
||||
- Provisional (beta) support for :func:`pymongo.timeout` to apply a single timeout
|
||||
to an entire block of pymongo operations.
|
||||
- Added ``check_exists`` option to :meth:`~pymongo.database.Database.create_collection`
|
||||
that when True (the default) runs an additional ``listCollections`` command to verify that the
|
||||
collection does not exist already.
|
||||
|
||||
Bug fixes
|
||||
.........
|
||||
|
||||
@ -305,6 +305,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
|
||||
read_concern: Optional["ReadConcern"] = None,
|
||||
session: Optional["ClientSession"] = None,
|
||||
timeout: Optional[float] = None,
|
||||
check_exists: Optional[bool] = True,
|
||||
**kwargs: Any,
|
||||
) -> Collection[_DocumentType]:
|
||||
"""Create a new :class:`~pymongo.collection.Collection` in this
|
||||
@ -336,6 +337,8 @@ class Database(common.BaseObject, Generic[_DocumentType]):
|
||||
:class:`~pymongo.collation.Collation`.
|
||||
- `session` (optional): a
|
||||
:class:`~pymongo.client_session.ClientSession`.
|
||||
- ``check_exists`` (optional): if True (the default), send a listCollections command to
|
||||
check if the collection already exists before creation.
|
||||
- `**kwargs` (optional): additional keyword arguments will
|
||||
be passed as options for the `create collection command`_
|
||||
|
||||
@ -402,7 +405,7 @@ class Database(common.BaseObject, Generic[_DocumentType]):
|
||||
enabling pre- and post-images.
|
||||
|
||||
.. versionchanged:: 4.2
|
||||
Added the ``clusteredIndex`` and ``encryptedFields`` parameters.
|
||||
Added the ``check_exists``, ``clusteredIndex``, and ``encryptedFields`` parameters.
|
||||
|
||||
.. versionchanged:: 3.11
|
||||
This method is now supported inside multi-document transactions
|
||||
@ -441,8 +444,10 @@ class Database(common.BaseObject, Generic[_DocumentType]):
|
||||
with self.__client._tmp_session(session) as s:
|
||||
# Skip this check in a transaction where listCollections is not
|
||||
# supported.
|
||||
if (not s or not s.in_transaction) and name in self.list_collection_names(
|
||||
filter={"name": name}, session=s
|
||||
if (
|
||||
check_exists
|
||||
and (not s or not s.in_transaction)
|
||||
and name in self.list_collection_names(filter={"name": name}, session=s)
|
||||
):
|
||||
raise CollectionInvalid("collection %s already exists" % name)
|
||||
return Collection(
|
||||
|
||||
@ -220,6 +220,21 @@ class TestDatabase(IntegrationTest):
|
||||
self.assertIn("nameOnly", command)
|
||||
self.assertTrue(command["nameOnly"])
|
||||
|
||||
def test_check_exists(self):
|
||||
listener = OvertCommandListener()
|
||||
results = listener.results
|
||||
client = rs_or_single_client(event_listeners=[listener])
|
||||
self.addCleanup(client.close)
|
||||
db = client[self.db.name]
|
||||
db.drop_collection("unique")
|
||||
db.create_collection("unique", check_exists=True)
|
||||
self.assertIn("listCollections", listener.started_command_names())
|
||||
listener.reset()
|
||||
db.drop_collection("unique")
|
||||
db.create_collection("unique", check_exists=False)
|
||||
self.assertTrue(len(results["started"]) > 0)
|
||||
self.assertNotIn("listCollections", listener.started_command_names())
|
||||
|
||||
def test_list_collections(self):
|
||||
self.client.drop_database("pymongo_test")
|
||||
db = Database(self.client, "pymongo_test")
|
||||
|
||||
@ -283,7 +283,6 @@ class EventListenerUtil(CMAPListener, CommandListener):
|
||||
self._observe_sensitive_commands = False
|
||||
self._ignore_commands = _SENSITIVE_COMMANDS | set(ignore_commands)
|
||||
self._ignore_commands.add("configurefailpoint")
|
||||
self.ignore_list_collections = False
|
||||
self._event_mapping = collections.defaultdict(list)
|
||||
self.entity_map = entity_map
|
||||
if store_events:
|
||||
@ -314,10 +313,7 @@ class EventListenerUtil(CMAPListener, CommandListener):
|
||||
)
|
||||
|
||||
def _command_event(self, event):
|
||||
if not (
|
||||
event.command_name.lower() in self._ignore_commands
|
||||
or (self.ignore_list_collections and event.command_name == "listCollections")
|
||||
):
|
||||
if not event.command_name.lower() in self._ignore_commands:
|
||||
self.add_event(event)
|
||||
|
||||
def started(self, event):
|
||||
@ -1032,13 +1028,8 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
|
||||
|
||||
def _databaseOperation_createCollection(self, target, *args, **kwargs):
|
||||
# PYTHON-1936 Ignore the listCollections event from create_collection.
|
||||
for listener in target.client.options.event_listeners:
|
||||
if isinstance(listener, EventListenerUtil):
|
||||
listener.ignore_list_collections = True
|
||||
kwargs["check_exists"] = False
|
||||
ret = target.create_collection(*args, **kwargs)
|
||||
for listener in target.client.options.event_listeners:
|
||||
if isinstance(listener, EventListenerUtil):
|
||||
listener.ignore_list_collections = False
|
||||
return ret
|
||||
|
||||
def __entityOperation_aggregate(self, target, *args, **kwargs):
|
||||
|
||||
@ -202,23 +202,14 @@ class OvertCommandListener(EventListener):
|
||||
ignore_list_collections = False
|
||||
|
||||
def started(self, event):
|
||||
if self.ignore_list_collections and event.command_name.lower() == "listcollections":
|
||||
self.ignore_list_collections = False
|
||||
return
|
||||
if event.command_name.lower() not in _SENSITIVE_COMMANDS:
|
||||
super(OvertCommandListener, self).started(event)
|
||||
|
||||
def succeeded(self, event):
|
||||
if self.ignore_list_collections and event.command_name.lower() == "listcollections":
|
||||
self.ignore_list_collections = False
|
||||
return
|
||||
if event.command_name.lower() not in _SENSITIVE_COMMANDS:
|
||||
super(OvertCommandListener, self).succeeded(event)
|
||||
|
||||
def failed(self, event):
|
||||
if self.ignore_list_collections and event.command_name.lower() == "listcollections":
|
||||
self.ignore_list_collections = False
|
||||
return
|
||||
if event.command_name.lower() not in _SENSITIVE_COMMANDS:
|
||||
super(OvertCommandListener, self).failed(event)
|
||||
|
||||
@ -1114,6 +1105,7 @@ def prepare_spec_arguments(spec, arguments, opname, entity_map, with_txn_callbac
|
||||
elif opname == "create_collection":
|
||||
if arg_name == "collection":
|
||||
arguments["name"] = arguments.pop(arg_name)
|
||||
arguments["check_exists"] = False
|
||||
# Any other arguments to create_collection are passed through
|
||||
# **kwargs.
|
||||
elif opname == "create_index" and arg_name == "keys":
|
||||
|
||||
@ -307,15 +307,7 @@ class SpecRunner(IntegrationTest):
|
||||
args.update(arguments)
|
||||
arguments = args
|
||||
|
||||
try:
|
||||
if name == "create_collection" and (
|
||||
"encrypted" in operation["arguments"]["name"]
|
||||
or "plaintext" in operation["arguments"]["name"]
|
||||
):
|
||||
self.listener.ignore_list_collections = True
|
||||
result = cmd(**dict(arguments))
|
||||
finally:
|
||||
self.listener.ignore_list_collections = False
|
||||
result = cmd(**dict(arguments))
|
||||
# Cleanup open change stream cursors.
|
||||
if name == "watch":
|
||||
self.addCleanup(result.close)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user