Optimize FindQuery.update() with partial writes - #843
Conversation
|
Thanks for opening, @lh0156! I'll take a look ASAP. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 256a3c8. Configure here.
| return { | ||
| key: ("1" if value else "0") if isinstance(value, bool) else value | ||
| for key, value in document.items() | ||
| } |
There was a problem hiding this comment.
Empty HSET mapping when updating nullable fields to None
Medium Severity
When all update values for a HashModel are None (e.g., clearing Optional fields), the None filter in _serialize_update_values produces an empty dict. This empty dict is then passed to pipeline.hset(key, mapping={}), which raises a DataError from redis-py because HSET requires at least one field-value pair. The old code called model.save() which wrote the entire model, so the HSET mapping always contained non-None fields from other columns. The partial-write approach loses that safety net.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 256a3c8. Configure here.


Summary
Implements #777 by updating matching records without materializing full Pydantic models.
What changed
FT.SEARCH ... NOCONTENTto collect matching keys page by page.HSETmappings forHashModel.JSON.SETcommands forJsonModel, including nested__paths.use_transactionflag and returns the number of updated records.Testing
pytest -q tests --ignore tests/test_benchmarks.py: 265 passed.pytest -q tests/test_hash_model.py tests/test_json_model.py: 147 passed.ruff checkandruff format --checkon changed source/tests.python -m compileall -q aredis_om tests.bandit -q -r aredis_om/model/model.py -s B608.Redis Stack was run locally via Docker Compose for the integration tests; benchmark tests were excluded from the full run.
Note
Medium Risk
Bulk updates now bypass per-instance save() and use direct HSET/JSON.SET, which changes behavior for edge cases (validators, side effects) but is covered by serialization parity and TTL tests; incorrect key-only search or encoding could corrupt many records at once.
Overview
FindQuery.update()no longer loads full models and callssave()per match. It validates and serializes update values once, paginatesFT.SEARCHwithNOCONTENTto collect keys only (ignoring.only()projections), then applies batched partial writes and returns the number of keys updated.For
HashModel, updates useHSETmappings with the same encoding rules assave()(timestamps, vectors, booleans, etc.), and positive per-field TTLs are re-applied viaHEXPIREafterHSET. ForJsonModel, updates use path-levelJSON.SET, including nested__paths (e.g.address__city→$.address.city).use_transactionstill controls pipeline transaction mode.FindQuery.dict()/copy()now retainknnstate. Docs note theintreturn value. New unit and integration tests cover validation-before-search, pagination, JSON paths, Pydantic v1 nested field resolution, and TTL preservation on bulk hash updates.Reviewed by Cursor Bugbot for commit 256a3c8. Bugbot is set up for automated code reviews on this repo. Configure here.