diff --git a/snuba/query/conditions.py b/snuba/query/conditions.py index adb5f1d5ec..7301ac6f79 100644 --- a/snuba/query/conditions.py +++ b/snuba/query/conditions.py @@ -283,13 +283,16 @@ def combine_and_conditions(conditions: Sequence[Expression]) -> Expression: def _combine_conditions(conditions: Sequence[Expression], function: str) -> Expression: - """ - Combine multiple independent conditions in a single function - representing an AND or an OR. - This is the inverse of get_first_level_conditions. - """ + flag = False + if flag: + return _combine_conditions_new(conditions, function) + else: + return _combine_conditions_old(conditions, function) - # TODO: Make BooleanFunctions an enum for stricter typing. + +def _combine_conditions_new( + conditions: Sequence[Expression], function: str +) -> Expression: assert function in (BooleanFunctions.AND, BooleanFunctions.OR) assert len(conditions) > 0 if len(conditions) == 1: @@ -304,7 +307,27 @@ def _combine_conditions(conditions: Sequence[Expression], function: str) -> Expr for i in range(start, len(conditions) - 1, 2): new_conds.append(binary_condition(function, conditions[i], conditions[i + 1])) - return _combine_conditions(new_conds, function) + return _combine_conditions_new(new_conds, function) + + +def _combine_conditions_old( + conditions: Sequence[Expression], function: str +) -> Expression: + """ + Combine multiple independent conditions in a single function + representing an AND or an OR. + This is the inverse of get_first_level_conditions. + """ + + # TODO: Make BooleanFunctions an enum for stricter typing. + assert function in (BooleanFunctions.AND, BooleanFunctions.OR) + assert len(conditions) > 0 + if len(conditions) == 1: + return conditions[0] + + return binary_condition( + function, conditions[0], _combine_conditions_old(conditions[1:], function) + ) CONDITION_MATCH = Or( diff --git a/tests/query/parser/test_mql_query.py b/tests/query/parser/test_mql_query.py index 1cb7ebabf7..231b023527 100644 --- a/tests/query/parser/test_mql_query.py +++ b/tests/query/parser/test_mql_query.py @@ -2804,3 +2804,36 @@ def test_recursion_error_query() -> None: "offset": None, } res, _ = parse_mql_query(error_mql, context, get_dataset("generic_metrics")) + + +def test_recursion_error_query_tuple() -> None: + conds = " OR ".join( + [f'(release:"backend@{e}" AND project_id:1)' for e in range(1000)] + ) + error_mql = ( + "(sum(d:transactions/duration@millisecond) by (release, project_id) * 1000000.0){(" + + conds + + ")}" + ) + context = { + "start": "2024-04-08T05:48:00+00:00", + "end": "2024-04-08T06:49:00+00:00", + "rollup": { + "granularity": 60, + "interval": 60, + "orderby": None, + "with_totals": None, + }, + "scope": { + "org_ids": [1], + "project_ids": [1], + "use_case_id": "'transactions'", + }, + "indexer_mappings": { + "d:transactions/duration@millisecond": 9223372036854775909, + "transaction": 9223372036854776020, + }, + "limit": 10000, + "offset": None, + } + res, _ = parse_mql_query(error_mql, context, get_dataset("generic_metrics"))