Skip to content

Commit ed6bf65

Browse files
committed
Adding update_match
1 parent 29f6ead commit ed6bf65

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

arangoasync/collection.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,79 @@ def response_handler(resp: Response) -> Cursor:
11641164

11651165
return await self._executor.execute(request, response_handler)
11661166

1167+
async def update_match(
1168+
self,
1169+
filters: Json,
1170+
body: T,
1171+
limit: Optional[int | str] = None,
1172+
keep_none: Optional[bool] = None,
1173+
wait_for_sync: Optional[bool] = None,
1174+
merge_objects: Optional[bool] = None,
1175+
allow_dirty_read: Optional[bool] = None,
1176+
) -> Result[int]:
1177+
"""Update matching documents.
1178+
1179+
Args:
1180+
filters (dict | None): Query filters.
1181+
body (dict): Full or partial document body with the updates.
1182+
limit (int | str | None): Maximum number of documents to return.
1183+
keep_none (bool | None): If set to `True`, fields with value `None` are
1184+
retained in the document. Otherwise, they are removed completely.
1185+
wait_for_sync (bool | None): Wait until operation has been synced to disk.
1186+
merge_objects (bool | None): If set to `True`, sub-dictionaries are merged
1187+
instead of the new one overwriting the old one.
1188+
allow_dirty_read (bool | None): Allow reads from followers in a cluster.
1189+
1190+
Returns:
1191+
int: Number of documents updated.
1192+
1193+
Raises:
1194+
DocumentUpdateError: If update fails.
1195+
"""
1196+
if not self._is_none_or_dict(filters):
1197+
raise ValueError("filters parameter must be a dict")
1198+
if not (self._is_none_or_int(limit) or limit == "null"):
1199+
raise ValueError("limit parameter must be a non-negative int")
1200+
1201+
# If the waitForSync parameter is not specified or set to false,
1202+
# then the collection’s default waitForSync behavior is applied.
1203+
sync = f", waitForSync: {wait_for_sync}" if wait_for_sync is not None else ""
1204+
query = f"""
1205+
FOR doc IN @@collection
1206+
{self._build_filter_conditions(filters)}
1207+
{f"LIMIT {limit}" if limit is not None else ""}
1208+
UPDATE doc WITH @body IN @@collection
1209+
OPTIONS {{ keepNull: @keep_none, mergeObjects: @merge {sync} }}
1210+
""" # noqa: E201 E202
1211+
bind_vars = {
1212+
"@collection": self.name,
1213+
"body": body,
1214+
"keep_none": keep_none,
1215+
"merge": merge_objects,
1216+
}
1217+
data = {"query": query, "bindVars": bind_vars}
1218+
headers: RequestHeaders = {}
1219+
if allow_dirty_read is not None:
1220+
if allow_dirty_read is True:
1221+
headers["x-arango-allow-dirty-read"] = "true"
1222+
else:
1223+
headers["x-arango-allow-dirty-read"] = "false"
1224+
1225+
request = Request(
1226+
method=Method.POST,
1227+
endpoint="/_api/cursor",
1228+
data=self.serializer.dumps(data),
1229+
headers=headers,
1230+
)
1231+
1232+
def response_handler(resp: Response) -> int:
1233+
if resp.is_success:
1234+
result = self.deserializer.loads(resp.raw_body)
1235+
return cast(int, result["extra"]["stats"]["writesExecuted"])
1236+
raise DocumentUpdateError(resp, request)
1237+
1238+
return await self._executor.execute(request, response_handler)
1239+
11671240
async def insert_many(
11681241
self,
11691242
documents: Sequence[T],

tests/test_document.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,36 @@ async def test_document_delete_many(doc_col, bad_col, docs):
452452
assert len(result) == len(docs)
453453
for res in result:
454454
assert "error" in res
455+
456+
457+
@pytest.mark.asyncio
458+
async def test_document_update_match(doc_col, bad_col, docs):
459+
# Check errors first
460+
with pytest.raises(DocumentUpdateError):
461+
await bad_col.update_match({}, {})
462+
with pytest.raises(ValueError):
463+
await doc_col.update_match({}, {}, limit=-1)
464+
with pytest.raises(ValueError):
465+
await doc_col.update_match("abcd", {})
466+
467+
# Insert all documents
468+
await doc_col.insert_many(docs)
469+
470+
# Update value for all documents
471+
count = await doc_col.update_match({}, {"val": 42})
472+
async for doc in await doc_col.find():
473+
assert doc["val"] == 42
474+
assert count == len(docs)
475+
476+
# Update documents partially
477+
count = await doc_col.update_match({"text": "foo"}, {"val": 24})
478+
async for doc in await doc_col.find():
479+
if doc["text"] == "foo":
480+
assert doc["val"] == 24
481+
assert count == sum([1 for doc in docs if doc["text"] == "foo"])
482+
483+
# No matching documents
484+
count = await doc_col.update_match({"text": "no_matching"}, {"val": -1})
485+
async for doc in await doc_col.find():
486+
assert doc["val"] != -1
487+
assert count == 0

0 commit comments

Comments
 (0)