From c154c6b67ba9fd0f925fede1f524aeb5d91cb33b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 3 Apr 2024 06:17:51 -0500 Subject: [PATCH] PYTHON-4037 Avoid Appending Write/Read Concern in Atlas Search Index Helper Commands (#1570) --- doc/changelog.rst | 2 + pymongo/collection.py | 9 +- test/index_management/createSearchIndex.json | 72 +---- .../index_management/createSearchIndexes.json | 74 +---- .../searchIndexIgnoresReadWriteConcern.json | 252 ++++++++++++++++++ test/test_index_management.py | 29 ++ 6 files changed, 297 insertions(+), 141 deletions(-) create mode 100644 test/index_management/searchIndexIgnoresReadWriteConcern.json diff --git a/doc/changelog.rst b/doc/changelog.rst index 647a783c4..e4f928921 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -41,6 +41,8 @@ PyMongo 4.7 brings a number of improvements including: :attr:`pymongo.monitoring.ConnectionReadyEvent.duration` properties. - Added the ``type`` and ``kwargs`` arguments to :class:`~pymongo.operations.SearchIndexModel` to enable creating vector search indexes in MongoDB Atlas. +- Fixed a bug where ``read_concern`` and ``write_concern`` were improperly added to + :meth:`~pymongo.collection.Collection.list_search_indexes` queries. Unavoidable breaking changes diff --git a/pymongo/collection.py b/pymongo/collection.py index ceba72aff..da1a79966 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -72,6 +72,7 @@ from pymongo.operations import ( _IndexList, _Op, ) +from pymongo.read_concern import DEFAULT_READ_CONCERN, ReadConcern from pymongo.read_preferences import ReadPreference, _ServerMode from pymongo.results import ( BulkWriteResult, @@ -81,7 +82,7 @@ from pymongo.results import ( UpdateResult, ) from pymongo.typings import _CollationIn, _DocumentType, _DocumentTypeArg, _Pipeline -from pymongo.write_concern import WriteConcern, validate_boolean +from pymongo.write_concern import DEFAULT_WRITE_CONCERN, WriteConcern, validate_boolean T = TypeVar("T") @@ -119,7 +120,6 @@ if TYPE_CHECKING: from pymongo.collation import Collation from pymongo.database import Database from pymongo.pool import Connection - from pymongo.read_concern import ReadConcern from pymongo.server import Server @@ -2364,7 +2364,10 @@ class Collection(common.BaseObject, Generic[_DocumentType]): pipeline = [{"$listSearchIndexes": {"name": name}}] coll = self.with_options( - codec_options=DEFAULT_CODEC_OPTIONS, read_preference=ReadPreference.PRIMARY + codec_options=DEFAULT_CODEC_OPTIONS, + read_preference=ReadPreference.PRIMARY, + write_concern=DEFAULT_WRITE_CONCERN, + read_concern=DEFAULT_READ_CONCERN, ) cmd = _CollectionAggregationCommand( coll, diff --git a/test/index_management/createSearchIndex.json b/test/index_management/createSearchIndex.json index 31f4c3fdf..f9c4e44d3 100644 --- a/test/index_management/createSearchIndex.json +++ b/test/index_management/createSearchIndex.json @@ -50,8 +50,7 @@ "mappings": { "dynamic": true } - }, - "type": "search" + } } }, "expectError": { @@ -74,8 +73,7 @@ "mappings": { "dynamic": true } - }, - "type": "search" + } } ], "$db": "database0" @@ -99,8 +97,7 @@ "dynamic": true } }, - "name": "test index", - "type": "search" + "name": "test index" } }, "expectError": { @@ -124,68 +121,7 @@ "dynamic": true } }, - "name": "test index", - "type": "search" - } - ], - "$db": "database0" - } - } - } - ] - } - ] - }, - { - "description": "create a vector search index", - "operations": [ - { - "name": "createSearchIndex", - "object": "collection0", - "arguments": { - "model": { - "definition": { - "fields": [ - { - "type": "vector", - "path": "plot_embedding", - "numDimensions": 1536, - "similarity": "euclidean" - } - ] - }, - "name": "test index", - "type": "vectorSearch" - } - }, - "expectError": { - "isError": true, - "errorContains": "Atlas" - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "createSearchIndexes": "collection0", - "indexes": [ - { - "definition": { - "fields": [ - { - "type": "vector", - "path": "plot_embedding", - "numDimensions": 1536, - "similarity": "euclidean" - } - ] - }, - "name": "test index", - "type": "vectorSearch" + "name": "test index" } ], "$db": "database0" diff --git a/test/index_management/createSearchIndexes.json b/test/index_management/createSearchIndexes.json index be2d02cfc..3cf56ce12 100644 --- a/test/index_management/createSearchIndexes.json +++ b/test/index_management/createSearchIndexes.json @@ -83,8 +83,7 @@ "mappings": { "dynamic": true } - }, - "type": "search" + } } ] }, @@ -108,8 +107,7 @@ "mappings": { "dynamic": true } - }, - "type": "search" + } } ], "$db": "database0" @@ -134,8 +132,7 @@ "dynamic": true } }, - "name": "test index", - "type": "search" + "name": "test index" } ] }, @@ -160,70 +157,7 @@ "dynamic": true } }, - "name": "test index", - "type": "search" - } - ], - "$db": "database0" - } - } - } - ] - } - ] - }, - { - "description": "create a vector search index", - "operations": [ - { - "name": "createSearchIndexes", - "object": "collection0", - "arguments": { - "models": [ - { - "definition": { - "fields": [ - { - "type": "vector", - "path": "plot_embedding", - "numDimensions": 1536, - "similarity": "euclidean" - } - ] - }, - "name": "test index", - "type": "vectorSearch" - } - ] - }, - "expectError": { - "isError": true, - "errorContains": "Atlas" - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "createSearchIndexes": "collection0", - "indexes": [ - { - "definition": { - "fields": [ - { - "type": "vector", - "path": "plot_embedding", - "numDimensions": 1536, - "similarity": "euclidean" - } - ] - }, - "name": "test index", - "type": "vectorSearch" + "name": "test index" } ], "$db": "database0" diff --git a/test/index_management/searchIndexIgnoresReadWriteConcern.json b/test/index_management/searchIndexIgnoresReadWriteConcern.json new file mode 100644 index 000000000..edf71b7b7 --- /dev/null +++ b/test/index_management/searchIndexIgnoresReadWriteConcern.json @@ -0,0 +1,252 @@ +{ + "description": "search index operations ignore read and write concern", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "createSearchIndex ignores read and write concern", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + } + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + } + } + ], + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "createSearchIndexes ignores read and write concern", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [], + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "dropSearchIndex ignores read and write concern", + "operations": [ + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "dropSearchIndex": "collection0", + "name": "test index", + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "listSearchIndexes ignores read and write concern", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": {} + } + ], + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateSearchIndex ignores the read and write concern", + "operations": [ + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "updateSearchIndex": "collection0", + "name": "test index", + "definition": {}, + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/test/test_index_management.py b/test/test_index_management.py index c9a69aecc..c47cffa75 100644 --- a/test/test_index_management.py +++ b/test/test_index_management.py @@ -30,6 +30,8 @@ from test.utils import AllowListEventListener, EventListener from pymongo import MongoClient from pymongo.errors import OperationFailure from pymongo.operations import SearchIndexModel +from pymongo.read_concern import ReadConcern +from pymongo.write_concern import WriteConcern _TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "index_management") @@ -257,6 +259,33 @@ class TestSearchIndexProse(SearchIndexIntegrationBase): # Run a ``dropSearchIndex`` command and assert that no error is thrown. coll0.drop_search_index("foo") + def test_case_6(self): + """Driver can successfully create and list search indexes with non-default readConcern and writeConcern.""" + # Create a collection with the "create" command using a randomly generated name (referred to as ``coll0``). + coll0 = self.db[f"col{uuid.uuid4()}"] + coll0.insert_one({}) + + # Apply a write concern ``WriteConcern(w=1)`` and a read concern with ``ReadConcern(level="majority")`` to ``coll0``. + coll0 = coll0.with_options( + write_concern=WriteConcern(w="1"), read_concern=ReadConcern(level="majority") + ) + + # Create a new search index on ``coll0`` with the ``createSearchIndex`` helper. + name = "test-search-index-case6" + model = {"name": name, "definition": {"mappings": {"dynamic": False}}} + resp = coll0.create_search_index(model) + + # Assert that the command returns the name of the index: ``"test-search-index-case6"``. + self.assertEqual(resp, name) + + # Run ``coll0.listSearchIndexes()`` repeatedly every 5 seconds until the following condition is satisfied and store the value in a variable ``index``: + # - An index with the ``name`` of ``test-search-index-case6`` is present and the index has a field ``queryable`` with a value of ``true``. + index = self.wait_for_ready(coll0, name) + + # Assert that ``index`` has a property ``latestDefinition`` whose value is ``{ 'mappings': { 'dynamic': false } }`` + self.assertIn("latestDefinition", index) + self.assertEqual(index["latestDefinition"], model["definition"]) + def test_case_7(self): """Driver handles index types."""