From 1227f5544d413079e09acaf2cb7cc497794c8007 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Thu, 9 Nov 2017 11:07:18 -0800 Subject: [PATCH] PYTHON-1280 - Support maxTimeMS in index management methods --- doc/changelog.rst | 6 ++++ pymongo/collection.py | 70 ++++++++++++++++++++++++++++------------- test/test_collation.py | 2 +- test/test_collection.py | 29 +++++++++++++++++ test/test_cursor.py | 3 +- 5 files changed, 86 insertions(+), 24 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 5e75725f8..faa98bc8a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -23,6 +23,12 @@ Highlights include: :meth:`~pymongo.database.Database.list_collection_names`. - Support for mongodb+srv:// URIs. See :class:`~pymongo.mongo_client.MongoClient` for details. +- Index management helpers ( + :meth:`~pymongo.collection.Collection.create_index`, + :meth:`~pymongo.collection.Collection.create_indexes`, + :meth:`~pymongo.collection.Collection.drop_index`, + :meth:`~pymongo.collection.Collection.drop_indexes`, + :meth:`~pymongo.collection.Collection.reindex`) now support maxTimeMS. Deprecations: diff --git a/pymongo/collection.py b/pymongo/collection.py index c93aab567..77ef5bbce 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -1559,7 +1559,7 @@ class Collection(common.BaseObject): cmd.update(kwargs) return self._count(cmd, collation, session) - def create_indexes(self, indexes, session=None): + def create_indexes(self, indexes, session=None, **kwargs): """Create one or more indexes on this collection. >>> from pymongo import IndexModel, ASCENDING, DESCENDING @@ -1574,6 +1574,8 @@ class Collection(common.BaseObject): instances. - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + - `**kwargs` (optional): optional arguments to the createIndexes + command (like maxTimeMS) can be passed as keyword arguments. .. note:: `create_indexes` uses the `createIndexes`_ command introduced in MongoDB **2.6** and cannot be used with earlier @@ -1584,7 +1586,8 @@ class Collection(common.BaseObject): MongoDB >= 3.4. .. versionchanged:: 3.6 - Added ``session`` parameter. + Added ``session`` parameter. Added support for arbitrary keyword + arguments. .. versionchanged:: 3.4 Apply this collection's write concern automatically to this operation @@ -1595,17 +1598,24 @@ class Collection(common.BaseObject): """ common.validate_list('indexes', indexes) names = [] - def gen_indexes(): - for index in indexes: - if not isinstance(index, IndexModel): - raise TypeError("%r is not an instance of " - "pymongo.operations.IndexModel" % (index,)) - document = index.document - names.append(document["name"]) - yield document - cmd = SON([('createIndexes', self.name), - ('indexes', list(gen_indexes()))]) with self._socket_for_writes() as sock_info: + supports_collations = sock_info.max_wire_version >= 5 + def gen_indexes(): + for index in indexes: + if not isinstance(index, IndexModel): + raise TypeError( + "%r is not an instance of " + "pymongo.operations.IndexModel" % (index,)) + document = index.document + if "collation" in document and not supports_collations: + raise ConfigurationError( + "Must be connected to MongoDB " + "3.4+ to use collations.") + names.append(document["name"]) + yield document + cmd = SON([('createIndexes', self.name), + ('indexes', list(gen_indexes()))]) + cmd.update(kwargs) self._command( sock_info, cmd, read_preference=ReadPreference.PRIMARY, codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, @@ -1614,7 +1624,7 @@ class Collection(common.BaseObject): session=session) return names - def __create_index(self, keys, index_options, session): + def __create_index(self, keys, index_options, session, **kwargs): """Internal create index helper. :Parameters: @@ -1637,6 +1647,7 @@ class Collection(common.BaseObject): else: index['collation'] = collation cmd = SON([('createIndexes', self.name), ('indexes', [index])]) + cmd.update(kwargs) self._command( sock_info, cmd, read_preference=ReadPreference.PRIMARY, codec_options=_UNICODE_REPLACE_CODEC_OPTIONS, @@ -1721,7 +1732,8 @@ class Collection(common.BaseObject): arguments .. versionchanged:: 3.6 - Added ``session`` parameter. + Added ``session`` parameter. Added support for passing maxTimeMS + in kwargs. .. versionchanged:: 3.4 Apply this collection's write concern automatically to this operation when connected to MongoDB >= 3.4. Support the `collation` option. @@ -1736,7 +1748,10 @@ class Collection(common.BaseObject): """ keys = helpers._index_list(keys) name = kwargs.setdefault("name", helpers._gen_index_name(keys)) - self.__create_index(keys, kwargs, session) + cmd_options = {} + if "maxTimeMS" in kwargs: + cmd_options["maxTimeMS"] = kwargs.pop("maxTimeMS") + self.__create_index(keys, kwargs, session, **cmd_options) return name def ensure_index(self, key_or_list, cache_for=300, **kwargs): @@ -1775,7 +1790,7 @@ class Collection(common.BaseObject): return name return None - def drop_indexes(self, session=None): + def drop_indexes(self, session=None, **kwargs): """Drops all indexes on this collection. Can be used on non-existant collections or collections with no indexes. @@ -1784,13 +1799,16 @@ class Collection(common.BaseObject): :Parameters: - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + - `**kwargs` (optional): optional arguments to the createIndexes + command (like maxTimeMS) can be passed as keyword arguments. .. note:: The :attr:`~pymongo.collection.Collection.write_concern` of this collection is automatically applied to this operation when using MongoDB >= 3.4. .. versionchanged:: 3.6 - Added ``session`` parameter. + Added ``session`` parameter. Added support for arbitrary keyword + arguments. .. versionchanged:: 3.4 Apply this collection's write concern automatically to this operation @@ -1798,9 +1816,9 @@ class Collection(common.BaseObject): """ self.__database.client._purge_index(self.__database.name, self.__name) - self.drop_index("*", session=session) + self.drop_index("*", session=session, **kwargs) - def drop_index(self, index_or_name, session=None): + def drop_index(self, index_or_name, session=None, **kwargs): """Drops the specified index on this collection. Can be used on non-existant collections or collections with no @@ -1821,13 +1839,16 @@ class Collection(common.BaseObject): - `index_or_name`: index (or name of index) to drop - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + - `**kwargs` (optional): optional arguments to the createIndexes + command (like maxTimeMS) can be passed as keyword arguments. .. note:: The :attr:`~pymongo.collection.Collection.write_concern` of this collection is automatically applied to this operation when using MongoDB >= 3.4. .. versionchanged:: 3.6 - Added ``session`` parameter. + Added ``session`` parameter. Added support for arbitrary keyword + arguments. .. versionchanged:: 3.4 Apply this collection's write concern automatically to this operation @@ -1844,6 +1865,7 @@ class Collection(common.BaseObject): self.__database.client._purge_index( self.__database.name, self.__name, name) cmd = SON([("dropIndexes", self.__name), ("index", name)]) + cmd.update(kwargs) with self._socket_for_writes() as sock_info: self._command(sock_info, cmd, @@ -1853,19 +1875,22 @@ class Collection(common.BaseObject): parse_write_concern_error=True, session=session) - def reindex(self, session=None): + def reindex(self, session=None, **kwargs): """Rebuilds all indexes on this collection. :Parameters: - `session` (optional): a :class:`~pymongo.client_session.ClientSession`. + - `**kwargs` (optional): optional arguments to the reIndex + command (like maxTimeMS) can be passed as keyword arguments. .. warning:: reindex blocks all other operations (indexes are built in the foreground) and will be slow for large collections. .. versionchanged:: 3.6 - Added ``session`` parameter. + Added ``session`` parameter. Added support for arbitrary keyword + arguments. .. versionchanged:: 3.4 Apply this collection's write concern automatically to this operation @@ -1877,6 +1902,7 @@ class Collection(common.BaseObject): an error if we include the write concern. """ cmd = SON([("reIndex", self.__name)]) + cmd.update(kwargs) with self._socket_for_writes() as sock_info: return self._command( sock_info, cmd, read_preference=ReadPreference.PRIMARY, diff --git a/test/test_collation.py b/test/test_collation.py index 49f4d8e21..71133f3e5 100644 --- a/test/test_collation.py +++ b/test/test_collation.py @@ -351,7 +351,7 @@ class TestCollation(unittest.TestCase): bulk.execute() self.assertIsNone(self.db.test.find_one({'foo': 42})) - @client_context.require_version_min(3, 3, 11) + @raisesConfigurationErrorForOldMongoDB def test_indexes_same_keys_different_collations(self): self.db.test.drop() usa_collation = Collation('en_US') diff --git a/test/test_collection.py b/test/test_collection.py index a7fd360d8..fad990bc3 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -284,6 +284,35 @@ class TestCollection(IntegrationTest): with self.write_concern_collection() as coll: coll.drop_index('hello_1') + @client_context.require_no_mongos + @client_context.require_test_commands + def test_index_management_max_time_ms(self): + if (client_context.version[:2] == (3, 4) and + client_context.version[2] < 4): + raise unittest.SkipTest("SERVER-27711") + coll = self.db.test + self.client.admin.command("configureFailPoint", + "maxTimeAlwaysTimeOut", + mode="alwaysOn") + try: + self.assertRaises( + ExecutionTimeout, coll.create_index, "foo", maxTimeMS=1) + self.assertRaises( + ExecutionTimeout, + coll.create_indexes, + [IndexModel("foo")], + maxTimeMS=1) + self.assertRaises( + ExecutionTimeout, coll.drop_index, "foo", maxTimeMS=1) + self.assertRaises( + ExecutionTimeout, coll.drop_indexes, maxTimeMS=1) + self.assertRaises( + ExecutionTimeout, coll.reindex, maxTimeMS=1) + finally: + self.client.admin.command("configureFailPoint", + "maxTimeAlwaysTimeOut", + mode="off") + def test_reindex(self): db = self.db db.drop_collection("test") diff --git a/test/test_cursor.py b/test/test_cursor.py index 14a5fb717..be8028a30 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -169,7 +169,8 @@ class TestCursor(IntegrationTest): self.assertTrue(coll.find_one(max_time_ms=1000)) client = self.client - if "enableTestCommands=1" in client_context.cmd_line['argv']: + if (not client_context.is_mongos + and client_context.test_commands_enabled): # Cursor parses server timeout error in response to initial query. client.admin.command("configureFailPoint", "maxTimeAlwaysTimeOut",