Skip to content

Commit 02a9df6

Browse files
authored
PYTHON-3227 Clustered Indexes for all Collections (#971)
1 parent f45f00b commit 02a9df6

File tree

6 files changed

+223
-34
lines changed

6 files changed

+223
-34
lines changed

pymongo/collection.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ def __init__(
117117
read_concern: Optional["ReadConcern"] = None,
118118
session: Optional["ClientSession"] = None,
119119
timeout: Optional[float] = None,
120-
encrypted_fields: Optional[Mapping[str, Any]] = None,
121120
**kwargs: Any,
122121
) -> None:
123122
"""Get / create a Mongo collection.
@@ -159,13 +158,11 @@ def __init__(
159158
- `session` (optional): a
160159
:class:`~pymongo.client_session.ClientSession` that is used with
161160
the create collection command
162-
- `encrypted_fields`: **(BETA)** Document that describes the encrypted fields for
163-
Queryable Encryption. If provided it will be passed to the create collection command.
164161
- `**kwargs` (optional): additional keyword arguments will
165162
be passed as options for the create collection command
166163
167164
.. versionchanged:: 4.2
168-
Added ``encrypted_fields`` parameter.
165+
Added the ``clusteredIndex`` and ``encryptedFields`` parameters.
169166
170167
.. versionchanged:: 4.0
171168
Removed the reindex, map_reduce, inline_map_reduce,
@@ -222,6 +219,7 @@ def __init__(
222219
self.__database: Database[_DocumentType] = database
223220
self.__name = name
224221
self.__full_name = "%s.%s" % (self.__database.name, self.__name)
222+
encrypted_fields = kwargs.pop("encryptedFields", None)
225223
if create or kwargs or collation:
226224
if encrypted_fields:
227225
common.validate_is_mapping("encrypted_fields", encrypted_fields)

pymongo/database.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@ def create_collection(
304304
read_concern: Optional["ReadConcern"] = None,
305305
session: Optional["ClientSession"] = None,
306306
timeout: Optional[float] = None,
307-
encrypted_fields: Optional[Mapping[str, Any]] = None,
308307
**kwargs: Any,
309308
) -> Collection[_DocumentType]:
310309
"""Create a new :class:`~pymongo.collection.Collection` in this
@@ -336,28 +335,6 @@ def create_collection(
336335
:class:`~pymongo.collation.Collation`.
337336
- `session` (optional): a
338337
:class:`~pymongo.client_session.ClientSession`.
339-
- `encrypted_fields`: **(BETA)** Document that describes the encrypted fields for
340-
Queryable Encryption. For example::
341-
342-
{
343-
"escCollection": "enxcol_.encryptedCollection.esc",
344-
"eccCollection": "enxcol_.encryptedCollection.ecc",
345-
"ecocCollection": "enxcol_.encryptedCollection.ecoc",
346-
"fields": [
347-
{
348-
"path": "firstName",
349-
"keyId": Binary.from_uuid(UUID('00000000-0000-0000-0000-000000000000')),
350-
"bsonType": "string",
351-
"queries": {"queryType": "equality"}
352-
},
353-
{
354-
"path": "ssn",
355-
"keyId": Binary.from_uuid(UUID('04104104-1041-0410-4104-104104104104')),
356-
"bsonType": "string"
357-
}
358-
]
359-
360-
} }
361338
- `**kwargs` (optional): additional keyword arguments will
362339
be passed as options for the `create collection command`_
363340
@@ -389,11 +366,42 @@ def create_collection(
389366
- ``pipeline`` (list): a list of aggregation pipeline stages
390367
- ``comment`` (str): a user-provided comment to attach to this command.
391368
This option is only supported on MongoDB >= 4.4.
369+
- ``encryptedFields`` (dict): **(BETA)** Document that describes the encrypted fields for
370+
Queryable Encryption. For example::
371+
372+
{
373+
"escCollection": "enxcol_.encryptedCollection.esc",
374+
"eccCollection": "enxcol_.encryptedCollection.ecc",
375+
"ecocCollection": "enxcol_.encryptedCollection.ecoc",
376+
"fields": [
377+
{
378+
"path": "firstName",
379+
"keyId": Binary.from_uuid(UUID('00000000-0000-0000-0000-000000000000')),
380+
"bsonType": "string",
381+
"queries": {"queryType": "equality"}
382+
},
383+
{
384+
"path": "ssn",
385+
"keyId": Binary.from_uuid(UUID('04104104-1041-0410-4104-104104104104')),
386+
"bsonType": "string"
387+
}
388+
]
389+
}
390+
- ``clusteredIndex`` (dict): Document that specifies the clustered index
391+
configuration. It must have the following form::
392+
393+
{
394+
// key pattern must be {_id: 1}
395+
key: <key pattern>, // required
396+
unique: <bool>, // required, must be ‘true’
397+
name: <string>, // optional, otherwise automatically generated
398+
v: <int>, // optional, must be ‘2’ if provided
399+
}
392400
- ``changeStreamPreAndPostImages`` (dict): a document with a boolean field ``enabled`` for
393401
enabling pre- and post-images.
394402
395403
.. versionchanged:: 4.2
396-
Added ``encrypted_fields`` parameter.
404+
Added the ``clusteredIndex`` and ``encryptedFields`` parameters.
397405
398406
.. versionchanged:: 3.11
399407
This method is now supported inside multi-document transactions
@@ -411,6 +419,7 @@ def create_collection(
411419
.. _create collection command:
412420
https://mongodb.com/docs/manual/reference/command/create
413421
"""
422+
encrypted_fields = kwargs.get("encryptedFields")
414423
if (
415424
not encrypted_fields
416425
and self.client.options.auto_encryption_opts
@@ -419,8 +428,14 @@ def create_collection(
419428
encrypted_fields = self.client.options.auto_encryption_opts._encrypted_fields_map.get(
420429
"%s.%s" % (self.name, name)
421430
)
431+
kwargs["encryptedFields"] = encrypted_fields
432+
422433
if encrypted_fields:
423-
common.validate_is_mapping("encrypted_fields", encrypted_fields)
434+
common.validate_is_mapping("encryptedFields", encrypted_fields)
435+
436+
clustered_index = kwargs.get("clusteredIndex")
437+
if clustered_index:
438+
common.validate_is_mapping("clusteredIndex", clustered_index)
424439

425440
with self.__client._tmp_session(session) as s:
426441
# Skip this check in a transaction where listCollections is not
@@ -439,7 +454,6 @@ def create_collection(
439454
read_concern,
440455
session=s,
441456
timeout=timeout,
442-
encrypted_fields=encrypted_fields,
443457
**kwargs,
444458
)
445459

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
{
2+
"description": "clustered-indexes",
3+
"schemaVersion": "1.4",
4+
"runOnRequirements": [
5+
{
6+
"minServerVersion": "5.3",
7+
"serverless": "forbid"
8+
}
9+
],
10+
"createEntities": [
11+
{
12+
"client": {
13+
"id": "client0"
14+
}
15+
},
16+
{
17+
"database": {
18+
"id": "database0",
19+
"client": "client0",
20+
"databaseName": "ts-tests"
21+
}
22+
},
23+
{
24+
"collection": {
25+
"id": "collection0",
26+
"database": "database0",
27+
"collectionName": "test"
28+
}
29+
}
30+
],
31+
"initialData": [
32+
{
33+
"collectionName": "test",
34+
"databaseName": "ts-tests",
35+
"documents": []
36+
}
37+
],
38+
"tests": [
39+
{
40+
"description": "createCollection with clusteredIndex",
41+
"operations": [
42+
{
43+
"name": "dropCollection",
44+
"object": "database0",
45+
"arguments": {
46+
"collection": "test"
47+
}
48+
},
49+
{
50+
"name": "createCollection",
51+
"object": "database0",
52+
"arguments": {
53+
"collection": "test",
54+
"clusteredIndex": {
55+
"key": {
56+
"_id": 1
57+
},
58+
"unique": true,
59+
"name": "test index"
60+
}
61+
}
62+
},
63+
{
64+
"name": "assertCollectionExists",
65+
"object": "testRunner",
66+
"arguments": {
67+
"databaseName": "ts-tests",
68+
"collectionName": "test"
69+
}
70+
}
71+
]
72+
},
73+
{
74+
"description": "listCollections includes clusteredIndex",
75+
"operations": [
76+
{
77+
"name": "dropCollection",
78+
"object": "database0",
79+
"arguments": {
80+
"collection": "test"
81+
}
82+
},
83+
{
84+
"name": "createCollection",
85+
"object": "database0",
86+
"arguments": {
87+
"collection": "test",
88+
"clusteredIndex": {
89+
"key": {
90+
"_id": 1
91+
},
92+
"unique": true,
93+
"name": "test index"
94+
}
95+
}
96+
},
97+
{
98+
"name": "listCollections",
99+
"object": "database0",
100+
"arguments": {
101+
"filter": {
102+
"name": {
103+
"$eq": "test"
104+
}
105+
}
106+
},
107+
"expectResult": [
108+
{
109+
"name": "test",
110+
"options": {
111+
"clusteredIndex": {
112+
"key": {
113+
"_id": 1
114+
},
115+
"unique": true,
116+
"name": "test index",
117+
"v": {
118+
"$$type": [
119+
"int",
120+
"long"
121+
]
122+
}
123+
}
124+
}
125+
}
126+
]
127+
}
128+
]
129+
},
130+
{
131+
"description": "listIndexes returns the index",
132+
"operations": [
133+
{
134+
"name": "dropCollection",
135+
"object": "database0",
136+
"arguments": {
137+
"collection": "test"
138+
}
139+
},
140+
{
141+
"name": "createCollection",
142+
"object": "database0",
143+
"arguments": {
144+
"collection": "test",
145+
"clusteredIndex": {
146+
"key": {
147+
"_id": 1
148+
},
149+
"unique": true,
150+
"name": "test index"
151+
}
152+
}
153+
},
154+
{
155+
"name": "listIndexes",
156+
"object": "collection0",
157+
"expectResult": [
158+
{
159+
"key": {
160+
"_id": 1
161+
},
162+
"name": "test index",
163+
"clustered": true,
164+
"unique": true,
165+
"v": {
166+
"$$type": [
167+
"int",
168+
"long"
169+
]
170+
}
171+
}
172+
]
173+
}
174+
]
175+
}
176+
]
177+
}

test/test_encryption.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,9 @@ def setup_scenario(self, scenario_def):
658658
kwargs["codec_options"] = OPTS
659659
if not data:
660660
kwargs["write_concern"] = wc
661-
db.create_collection(coll_name, **kwargs, encrypted_fields=encrypted_fields)
661+
if encrypted_fields:
662+
kwargs["encryptedFields"] = encrypted_fields
663+
db.create_collection(coll_name, **kwargs)
662664
coll = db[coll_name]
663665
if data:
664666
# Load data.

test/unified_format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ def _collectionOperation_count(self, target, *args, **kwargs):
996996
def _collectionOperation_listIndexes(self, target, *args, **kwargs):
997997
if "batch_size" in kwargs:
998998
self.skipTest("PyMongo does not support batch_size for list_indexes")
999-
return target.list_indexes(*args, **kwargs)
999+
return list(target.list_indexes(*args, **kwargs))
10001000

10011001
def _collectionOperation_listIndexNames(self, target, *args, **kwargs):
10021002
self.skipTest("PyMongo does not support list_index_names")

test/utils.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,8 +1002,6 @@ def parse_spec_options(opts):
10021002
if "maxCommitTimeMS" in opts:
10031003
opts["max_commit_time_ms"] = opts.pop("maxCommitTimeMS")
10041004

1005-
if "encryptedFields" in opts:
1006-
opts["encrypted_fields"] = opts.pop("encryptedFields")
10071005
if "hint" in opts:
10081006
hint = opts.pop("hint")
10091007
if not isinstance(hint, str):

0 commit comments

Comments
 (0)