PYTHON-4037 Avoid Appending Write/Read Concern in Atlas Search Index Helper Commands (#1570)

This commit is contained in:
Steven Silvester 2024-04-03 06:17:51 -05:00 committed by GitHub
parent 44e47304ff
commit c154c6b67b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 297 additions and 141 deletions

View File

@ -41,6 +41,8 @@ PyMongo 4.7 brings a number of improvements including:
:attr:`pymongo.monitoring.ConnectionReadyEvent.duration` properties. :attr:`pymongo.monitoring.ConnectionReadyEvent.duration` properties.
- Added the ``type`` and ``kwargs`` arguments to :class:`~pymongo.operations.SearchIndexModel` to enable - Added the ``type`` and ``kwargs`` arguments to :class:`~pymongo.operations.SearchIndexModel` to enable
creating vector search indexes in MongoDB Atlas. 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 Unavoidable breaking changes

View File

@ -72,6 +72,7 @@ from pymongo.operations import (
_IndexList, _IndexList,
_Op, _Op,
) )
from pymongo.read_concern import DEFAULT_READ_CONCERN, ReadConcern
from pymongo.read_preferences import ReadPreference, _ServerMode from pymongo.read_preferences import ReadPreference, _ServerMode
from pymongo.results import ( from pymongo.results import (
BulkWriteResult, BulkWriteResult,
@ -81,7 +82,7 @@ from pymongo.results import (
UpdateResult, UpdateResult,
) )
from pymongo.typings import _CollationIn, _DocumentType, _DocumentTypeArg, _Pipeline 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") T = TypeVar("T")
@ -119,7 +120,6 @@ if TYPE_CHECKING:
from pymongo.collation import Collation from pymongo.collation import Collation
from pymongo.database import Database from pymongo.database import Database
from pymongo.pool import Connection from pymongo.pool import Connection
from pymongo.read_concern import ReadConcern
from pymongo.server import Server from pymongo.server import Server
@ -2364,7 +2364,10 @@ class Collection(common.BaseObject, Generic[_DocumentType]):
pipeline = [{"$listSearchIndexes": {"name": name}}] pipeline = [{"$listSearchIndexes": {"name": name}}]
coll = self.with_options( 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( cmd = _CollectionAggregationCommand(
coll, coll,

View File

@ -50,8 +50,7 @@
"mappings": { "mappings": {
"dynamic": true "dynamic": true
} }
}, }
"type": "search"
} }
}, },
"expectError": { "expectError": {
@ -74,8 +73,7 @@
"mappings": { "mappings": {
"dynamic": true "dynamic": true
} }
}, }
"type": "search"
} }
], ],
"$db": "database0" "$db": "database0"
@ -99,8 +97,7 @@
"dynamic": true "dynamic": true
} }
}, },
"name": "test index", "name": "test index"
"type": "search"
} }
}, },
"expectError": { "expectError": {
@ -124,68 +121,7 @@
"dynamic": true "dynamic": true
} }
}, },
"name": "test index", "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"
} }
], ],
"$db": "database0" "$db": "database0"

View File

@ -83,8 +83,7 @@
"mappings": { "mappings": {
"dynamic": true "dynamic": true
} }
}, }
"type": "search"
} }
] ]
}, },
@ -108,8 +107,7 @@
"mappings": { "mappings": {
"dynamic": true "dynamic": true
} }
}, }
"type": "search"
} }
], ],
"$db": "database0" "$db": "database0"
@ -134,8 +132,7 @@
"dynamic": true "dynamic": true
} }
}, },
"name": "test index", "name": "test index"
"type": "search"
} }
] ]
}, },
@ -160,70 +157,7 @@
"dynamic": true "dynamic": true
} }
}, },
"name": "test index", "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"
} }
], ],
"$db": "database0" "$db": "database0"

View File

@ -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
}
}
}
}
]
}
]
}
]
}

View File

@ -30,6 +30,8 @@ from test.utils import AllowListEventListener, EventListener
from pymongo import MongoClient from pymongo import MongoClient
from pymongo.errors import OperationFailure from pymongo.errors import OperationFailure
from pymongo.operations import SearchIndexModel 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") _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. # Run a ``dropSearchIndex`` command and assert that no error is thrown.
coll0.drop_search_index("foo") 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): def test_case_7(self):
"""Driver handles index types.""" """Driver handles index types."""