Skip to content

Commit 46b7f7b

Browse files
authored
[MPT-18174] Fix any and all with multiple nested conditions for a collection (#214)
Any and all are treating nested conditions incorrectly. It wraps entire set of conditions, instead of properly using collection name: Collection Name is used because inferring possible collections in query values is complex and prone to inference errors. Simplest and safest way I can think of is using collection name. https://softwareone.atlassian.net/browse/MPT-18174 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> Closes [MPT-18174](https://softwareone.atlassian.net/browse/MPT-18174) - Fix nested condition handling for any() and all(): these methods now require an explicit collection_name and build nested expressions using that collection instead of wrapping the entire condition set. - Public API changed: RQLQuery.any(collection_name) and RQLQuery.all(collection_name) now require a collection/path argument (no-argument forms removed). - Added internal _nest helper to compose collection-scoped nested expressions (e.g., any(saleDetails,gt(orderQty,1))). - Multi-condition queries now format correctly for collections (e.g., all(saleDetails,and(gt(orderQty,1),lt(price,100)))). - Tests updated to validate explicit collection-name usage and multiple nested condition scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> [MPT-18174]: https://softwareone.atlassian.net/browse/MPT-18174?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2 parents 68c9e07 + eb284ec commit 46b7f7b

File tree

2 files changed

+49
-12
lines changed

2 files changed

+49
-12
lines changed

mpt_api_client/rql/query_builder.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -257,28 +257,39 @@ def __getattr__(self, name: str) -> Self:
257257
def __str__(self) -> str:
258258
return self._to_string(self)
259259

260-
def any(self) -> Self:
260+
def any(self, collection_name: str) -> Self:
261261
"""Any nested objects have to match the filter condition.
262262
263+
Args:
264+
collection_name: The name of the collection to which apply the `any` operator.
265+
263266
Returns:
264267
RQLQuery: RQLQuery with new condition
265268
266269
Examples:
267-
RQLQuery(saleDetails__orderQty__gt=11).any()
268-
will result: any(saleDetails,orderQty=11)
270+
RQLQuery(orderQty__gt=11).any("saleDetails")
271+
will result (single): any(saleDetails,orderQty=11)
272+
(RQLQuery(orderQty__gt=11) & RQLQuery(price__lt=100)).any("saleDetails")
273+
will result (multiple): any(and(saleDetails,gt(orderQty,11),lt(price,100)))
269274
"""
270-
return self.new(op=self.OP_ANY, children=[self])
275+
return self._nest(self.OP_ANY, collection_name)
271276

272-
def all(self) -> Self:
277+
def all(self, collection_name: str) -> Self:
273278
"""All nested objects have to match the filter condition.
274279
280+
Args:
281+
collection_name: The name of the collection to which apply the `all` operator.
282+
275283
Returns:
276284
RQLQuery: RQLQuery with new condition
277285
278-
Example:
279-
RQLQuery(saleDetails__orderQty__gt=1).all()
286+
Examples:
287+
RQLQuery(orderQty__gt=1).all("saleDetails")
288+
will result (single): all(saleDetails,gt(orderQty,1))
289+
(RQLQuery(orderQty__gt=11) & RQLQuery(price__lt=100)).all("saleDetails")
290+
will result (multiple): all(and(saleDetails,gt(orderQty,11),lt(price,100)))
280291
"""
281-
return self.new(op=self.OP_ALL, children=[self])
292+
return self._nest(self.OP_ALL, collection_name)
282293

283294
def n(self, name: str) -> Self: # noqa: WPS111
284295
"""Set the current field for this `RQLQuery` object.
@@ -522,3 +533,9 @@ def _append(self, query: "RQLQuery") -> "RQLQuery" | Self:
522533

523534
self.children.append(query)
524535
return self
536+
537+
def _nest(self, op: str, collection_name: str) -> Self:
538+
name = collection_name.replace("__", ".")
539+
collection = self._to_string(self) if self.children else self.expr or ""
540+
expr = f"{op}({name},{collection})"
541+
return self.new(expr=expr)

tests/unit/rql/query_builder/test_rql_all_any.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,36 @@
22

33

44
def test_all():
5-
query = RQLQuery(saleDetails__orderQty__gt=1).all()
5+
query = RQLQuery(orderQty__gt=1).all("saleDetails")
66

77
result = str(query)
88

9-
assert result == "all(gt(saleDetails.orderQty,1))"
9+
assert result == "all(saleDetails,gt(orderQty,1))"
1010

1111

1212
def test_any():
13-
query = RQLQuery(saleDetails__orderQty__gt=1).any()
13+
query = RQLQuery(orderQty__gt=1).any("saleDetails")
1414

1515
result = str(query)
1616

17-
assert result == "any(gt(saleDetails.orderQty,1))"
17+
assert result == "any(saleDetails,gt(orderQty,1))"
18+
19+
20+
def test_all_multiple_conditions():
21+
order_qty_query = RQLQuery(orderQty__gt=1)
22+
price_query = RQLQuery(price__lt=100)
23+
query = (order_qty_query & price_query).all("saleDetails")
24+
25+
result = str(query)
26+
27+
assert result == "all(saleDetails,and(gt(orderQty,1),lt(price,100)))"
28+
29+
30+
def test_any_multiple_conditions():
31+
order_qty_query = RQLQuery(orderQty__gt=1)
32+
price_query = RQLQuery(price__lt=100)
33+
query = (order_qty_query & price_query).any("saleDetails")
34+
35+
result = str(query)
36+
37+
assert result == "any(saleDetails,and(gt(orderQty,1),lt(price,100)))"

0 commit comments

Comments
 (0)