Skip to content

Commit 9364abe

Browse files
aherlihybehackett
authored andcommitted
PYTHON-990 - Support partial indexes
1 parent b852810 commit 9364abe

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

pymongo/collection.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,8 @@ def create_index(self, keys, **kwargs):
12391239
collection. MongoDB will automatically delete documents from
12401240
this collection after <int> seconds. The indexed field must
12411241
be a UTC datetime or the data will not expire.
1242+
- `partialFilterExpression`: A document that specifies a filter for
1243+
a partial index.
12421244
12431245
See the MongoDB documentation for a full list of supported options by
12441246
server version.
@@ -1248,6 +1250,7 @@ def create_index(self, keys, **kwargs):
12481250
using the option will fail if a duplicate value is detected.
12491251
12501252
.. note:: `expireAfterSeconds` requires server version **>= 2.2**
1253+
.. note:: `partialFilterIndexes` requires server version **>= 3.2**
12511254
12521255
:Parameters:
12531256
- `keys`: a single key or a list of (key, direction)
@@ -1256,6 +1259,8 @@ def create_index(self, keys, **kwargs):
12561259
options (see the above list) should be passed as keyword
12571260
arguments
12581261
1262+
.. versionchanged:: 3.2
1263+
Added partialFilterExpression to support partial indexes.
12591264
.. versionchanged:: 3.0
12601265
Renamed `key_or_list` to `keys`. Removed the `cache_for` option.
12611266
:meth:`create_index` no longer caches index names. Removed support

pymongo/operations.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,23 @@ def __init__(self, keys, **kwargs):
222222
collection. MongoDB will automatically delete documents from
223223
this collection after <int> seconds. The indexed field must
224224
be a UTC datetime or the data will not expire.
225+
- `partialFilterExpression`: A document that specifies a filter for
226+
a partial index.
225227
226228
See the MongoDB documentation for a full list of supported options by
227229
server version.
228230
231+
.. note:: `partialFilterIndexes` requires server version **>= 3.2**
232+
229233
:Parameters:
230234
- `keys`: a single key or a list of (key, direction)
231235
pairs specifying the index to create
232236
- `**kwargs` (optional): any additional index creation
233237
options (see the above list) should be passed as keyword
234238
arguments
239+
240+
.. versionchanged:: 3.2
241+
Added partialFilterExpression to support partial indexes.
235242
"""
236243
keys = _index_list(keys)
237244
if "name" not in kwargs:

test/test_collection.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,59 @@ def test_create():
481481
# Index wasn't created, only the default index on _id
482482
self.assertEqual(1, len(db.test.index_information()))
483483

484+
@client_context.require_version_min(3, 1, 9, -1)
485+
def test_index_filter(self):
486+
db = self.db
487+
db.drop_collection("test")
488+
489+
# Test bad filter spec on create.
490+
self.assertRaises(OperationFailure, db.test.create_index, "x",
491+
partialFilterExpression=5)
492+
self.assertRaises(OperationFailure, db.test.create_index, "x",
493+
partialFilterExpression={"x": {"$asdasd": 3}})
494+
self.assertRaises(OperationFailure, db.test.create_index, "x",
495+
partialFilterExpression={"$and": 5})
496+
self.assertRaises(OperationFailure, db.test.create_index, "x",
497+
partialFilterExpression={
498+
"$and": [{"$and": [{"x": {"$lt": 2}},
499+
{"x": {"$gt": 0}}]},
500+
{"x": {"$exists": True}}]})
501+
502+
self.assertEqual("x_1", db.test.create_index(
503+
[('x', ASCENDING)], partialFilterExpression={"a": {"$lte": 1.5}}))
504+
db.test.insert_one({"x": 5, "a": 2})
505+
db.test.insert_one({"x": 6, "a": 1})
506+
507+
# Operations that use the partial index.
508+
explain = db.test.find(
509+
{"x": 6, "a": 1}).explain()['queryPlanner']['winningPlan']
510+
self.assertEqual("x_1", explain.get('inputStage', {}).get('indexName'))
511+
self.assertTrue(explain.get('inputStage', {}).get('isPartial'))
512+
explain = db.test.find(
513+
{"x": {"$gt": 1}, "a": 1}).explain()['queryPlanner']['winningPlan']
514+
self.assertEqual("x_1", explain.get('inputStage', {}).get('indexName'))
515+
self.assertTrue(explain.get('inputStage', {}).get('isPartial'))
516+
explain = db.test.find(
517+
{"x": 6,
518+
"a": {"$lte": 1}}).explain()['queryPlanner']['winningPlan']
519+
self.assertEqual("x_1", explain.get('inputStage', {}).get('indexName'))
520+
self.assertTrue(explain.get('inputStage', {}).get('isPartial'))
521+
522+
# Operations that do not use the partial index.
523+
explain = db.test.find(
524+
{"x": 6,
525+
"a": {"$lte": 1.6}}).explain()['queryPlanner']['winningPlan']
526+
self.assertEqual("COLLSCAN", explain.get('stage'))
527+
explain = db.test.find(
528+
{"x": 6}).explain()['queryPlanner']['winningPlan']
529+
self.assertEqual("COLLSCAN", explain.get('stage'))
530+
531+
# Test drop_indexes.
532+
db.test.drop_index("x_1")
533+
explain = db.test.find(
534+
{"x": 6, "a": 1}).explain()['queryPlanner']['winningPlan']
535+
self.assertEqual("COLLSCAN", explain.get('stage'))
536+
484537
def test_field_selection(self):
485538
db = self.db
486539
db.drop_collection("test")

0 commit comments

Comments
 (0)