Skip to content

Commit

Permalink
Merge pull request #2769 from idoshr/array_filters
Browse files Browse the repository at this point in the history
support for array_filters
  • Loading branch information
bagerard authored Jan 8, 2024
2 parents a49b094 + 70636a2 commit dda51bd
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Development
===========
- (Fill this out as you fix issues and develop your features).
- Fix for uuidRepresentation not read when provided in URI #2741
- Add option to user array_filters https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered/ #2769
- Fix combination of __raw__ and mongoengine syntax #2773
- Add tests against MongoDB 6.0 and MongoDB 7.0 in the pipeline
- Fix validate() not being called when inheritance is used in EmbeddedDocument and validate is overriden #2784
Expand Down
17 changes: 17 additions & 0 deletions docs/guide/querying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,23 @@ and provide the pipeline as a list

.. versionadded:: 0.23.2

Update with Array Operator
--------------------------------
It is possible to update specific value in array by use array_filters (arrayFilters) operator.
This is done by using ``__raw__`` keyword argument to the update method and provide the arrayFilters as a list.

`Update with Array Operator <https://www.mongodb.com/docs/manual/reference/operator/update/positional-filtered->`_
::

# assuming an initial 'tags' field == ['test1', 'test2', 'test3']
Page.objects().update(__raw__={'$set': {"tags.$[element]": 'test11111'}},
array_filters=[{"element": {'$eq': 'test2'}}],

# updated 'tags' field == ['test1', 'test11111', 'test3']

)


Sorting/Ordering results
========================
It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`.
Expand Down
10 changes: 8 additions & 2 deletions mongoengine/queryset/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ def update(
write_concern=None,
read_concern=None,
full_result=False,
array_filters=None,
**update,
):
"""Perform an atomic update on the fields matched by the query.
Expand All @@ -546,6 +547,7 @@ def update(
:param read_concern: Override the read concern for the operation
:param full_result: Return the associated ``pymongo.UpdateResult`` rather than just the number
updated items
:param array_filters: A list of filters specifying which array elements an update should apply.
:param update: Django-style update keyword arguments
:returns the number of updated documents (unless ``full_result`` is True)
Expand All @@ -560,7 +562,9 @@ def update(

queryset = self.clone()
query = queryset._query
if "__raw__" in update and isinstance(update["__raw__"], list):
if "__raw__" in update and isinstance(
update["__raw__"], list
): # Case of Update with Aggregation Pipeline
update = [
transform.update(queryset._document, **{"__raw__": u})
for u in update["__raw__"]
Expand All @@ -581,7 +585,9 @@ def update(
update_func = collection.update_one
if multi:
update_func = collection.update_many
result = update_func(query, update, upsert=upsert)
result = update_func(
query, update, upsert=upsert, array_filters=array_filters
)
if full_result:
return result
elif result.raw_result:
Expand Down
59 changes: 59 additions & 0 deletions tests/queryset/test_queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,65 @@ class Blog(Document):

Blog.drop_collection()

def test_update_array_filters(self):
"""Ensure that updating by array_filters works."""

class Comment(EmbeddedDocument):
comment_tags = ListField(StringField())

class Blog(Document):
tags = ListField(StringField())
comments = EmbeddedDocumentField(Comment)

Blog.drop_collection()

# update one
Blog.objects.create(tags=["test1", "test2", "test3"])

Blog.objects().update_one(
__raw__={"$set": {"tags.$[element]": "test11111"}},
array_filters=[{"element": {"$eq": "test2"}}],
)
testc_blogs = Blog.objects(tags="test11111")

assert testc_blogs.count() == 1

Blog.drop_collection()

# update one inner list
comments = Comment(comment_tags=["test1", "test2", "test3"])
Blog.objects.create(comments=comments)

Blog.objects().update_one(
__raw__={"$set": {"comments.comment_tags.$[element]": "test11111"}},
array_filters=[{"element": {"$eq": "test2"}}],
)
testc_blogs = Blog.objects(comments__comment_tags="test11111")

assert testc_blogs.count() == 1

# update many
Blog.drop_collection()

Blog.objects.create(tags=["test1", "test2", "test3", "test_all"])
Blog.objects.create(tags=["test4", "test5", "test6", "test_all"])

Blog.objects().update(
__raw__={"$set": {"tags.$[element]": "test11111"}},
array_filters=[{"element": {"$eq": "test2"}}],
)
testc_blogs = Blog.objects(tags="test11111")

assert testc_blogs.count() == 1

Blog.objects().update(
__raw__={"$set": {"tags.$[element]": "test_all1234577"}},
array_filters=[{"element": {"$eq": "test_all"}}],
)
testc_blogs = Blog.objects(tags="test_all1234577")

assert testc_blogs.count() == 2

def test_update_using_positional_operator(self):
"""Ensure that the list fields can be updated using the positional
operator."""
Expand Down

0 comments on commit dda51bd

Please sign in to comment.