def test_grouping_simple(self): result = parse_search_query( '(user.email:[email protected] OR user.email:[email protected])') assert result == [ SearchBoolean(left_term=self.term1, operator="OR", right_term=self.term2) ] result = parse_search_query( '(user.email:[email protected] OR user.email:[email protected]) AND user.email:[email protected]' ) assert result == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='OR', right_term=self.term2), operator='AND', right_term=self.term3) ] result = parse_search_query( 'user.email:[email protected] AND (user.email:[email protected] OR user.email:[email protected])' ) assert result == [ SearchBoolean(left_term=self.term1, operator='AND', right_term=SearchBoolean(left_term=self.term2, operator='OR', right_term=self.term3)) ]
def test_simple(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected]' ) == [SearchBoolean(left_term=self.term1, operator="OR", right_term=self.term2)] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected]' ) == [SearchBoolean(left_term=self.term1, operator="AND", right_term=self.term2)]
def test_order_of_operations(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='OR', right_term=SearchBoolean(left_term=self.term2, operator='AND', right_term=self.term3)) ] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean( left_term=self.term1, operator='AND', right_term=self.term2, ), operator='OR', right_term=self.term3) ]
def test_nested_grouping(self): result = parse_search_query( '(user.email:[email protected] OR (user.email:[email protected] OR user.email:[email protected]))' ) assert result == [ SearchBoolean(left_term=self.term1, operator="OR", right_term=SearchBoolean(left_term=self.term2, operator="OR", right_term=self.term3)) ] result = parse_search_query( '(user.email:[email protected] OR (user.email:[email protected] OR (user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected])))' ) assert result == [ SearchBoolean(left_term=self.term1, operator="OR", right_term=SearchBoolean( left_term=self.term2, operator="OR", right_term=SearchBoolean( left_term=SearchBoolean( left_term=self.term3, operator="AND", right_term=self.term4), operator="OR", right_term=self.term5, ))) ]
def test_multiple_statements(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] OR user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='OR', right_term=SearchBoolean(left_term=self.term2, operator='OR', right_term=self.term3)) ] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='AND', right_term=SearchBoolean(left_term=self.term2, operator='AND', right_term=self.term3)) ] # longer even number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=self.term4)) ] # longer odd number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5))) ] # absurdly long assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5)), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=self.term4, operator='AND', right_term=self.term5))))) ]
def get_filter(query=None, params=None): """ Returns an eventstore filter given the search text provided by the user and URL params """ # NOTE: this function assumes project permissions check already happened parsed_terms = [] if query is not None: try: parsed_terms = parse_search_query(query, allow_boolean=True, params=params) except ParseError as e: raise InvalidSearchQuery( f"Parse error: {e.expr.name} (column {e.column():d})") kwargs = { "start": None, "end": None, "conditions": [], "having": [], "user_id": None, "organization_id": None, "project_ids": [], "group_ids": [], "condition_aggregates": [], "aliases": params.get("aliases", {}) if params is not None else {}, } projects_to_filter = [] if any( isinstance(term, ParenExpression) or SearchBoolean.is_operator(term) for term in parsed_terms): ( condition, having, found_projects_to_filter, group_ids, ) = convert_search_boolean_to_snuba_query(parsed_terms, params) if condition: and_conditions = flatten_condition_tree(condition, SNUBA_AND) for func in and_conditions: kwargs["conditions"].append( convert_function_to_condition(func)) if having: kwargs["condition_aggregates"] = [ term.key.name for term in parsed_terms if isinstance(term, AggregateFilter) ] and_having = flatten_condition_tree(having, SNUBA_AND) for func in and_having: kwargs["having"].append(convert_function_to_condition(func)) if found_projects_to_filter: projects_to_filter = list(set(found_projects_to_filter)) if group_ids is not None: kwargs["group_ids"].extend(list(set(group_ids))) else: projects_to_filter = set() for term in parsed_terms: if isinstance(term, SearchFilter): conditions, found_projects_to_filter, group_ids = format_search_filter( term, params) if len(conditions) > 0: kwargs["conditions"].extend(conditions) if found_projects_to_filter: projects_to_filter.update(found_projects_to_filter) if group_ids is not None: kwargs["group_ids"].extend(group_ids) elif isinstance(term, AggregateFilter): converted_filter = convert_aggregate_filter_to_snuba_query( term, params) kwargs["condition_aggregates"].append(term.key.name) if converted_filter: kwargs["having"].append(converted_filter) projects_to_filter = list(projects_to_filter) # Keys included as url params take precedent if same key is included in search # They are also considered safe and to have had access rules applied unlike conditions # from the query string. if params: for key in ("start", "end"): kwargs[key] = params.get(key, None) # OrganizationEndpoint.get_filter() uses project_id, but eventstore.Filter uses project_ids if "user_id" in params: kwargs["user_id"] = params["user_id"] if "organization_id" in params: kwargs["organization_id"] = params["organization_id"] if "project_id" in params: if projects_to_filter: kwargs["project_ids"] = projects_to_filter else: kwargs["project_ids"] = params["project_id"] if "environment" in params: term = SearchFilter(SearchKey("environment"), "=", SearchValue(params["environment"])) kwargs["conditions"].append( convert_search_filter_to_snuba_query(term)) if "group_ids" in params: kwargs["group_ids"] = to_list(params["group_ids"]) # Deprecated alias, use `group_ids` instead if ISSUE_ID_ALIAS in params: kwargs["group_ids"] = to_list(params["issue.id"]) return eventstore.Filter(**kwargs)
def convert_search_boolean_to_snuba_query(terms, params=None): if len(terms) == 1: return convert_snuba_condition_to_function(terms[0], params) # Filter out any ANDs since we can assume anything without an OR is an AND. Also do some # basic sanitization of the query: can't have two operators next to each other, and can't # start or end a query with an operator. prev = None new_terms = [] for term in terms: if prev: if SearchBoolean.is_operator(prev) and SearchBoolean.is_operator( term): raise InvalidSearchQuery( f"Missing condition in between two condition operators: '{prev} {term}'" ) else: if SearchBoolean.is_operator(term): raise InvalidSearchQuery( f"Condition is missing on the left side of '{term}' operator" ) if term != SearchBoolean.BOOLEAN_AND: new_terms.append(term) prev = term if SearchBoolean.is_operator(term): raise InvalidSearchQuery( f"Condition is missing on the right side of '{term}' operator") terms = new_terms # We put precedence on AND, which sort of counter-intuitevely means we have to split the query # on ORs first, so the ANDs are grouped together. Search through the query for ORs and split the # query on each OR. # We want to maintain a binary tree, so split the terms on the first OR we can find and recurse on # the two sides. If there is no OR, split the first element out to AND index = None lhs, rhs = None, None operator = None try: index = terms.index(SearchBoolean.BOOLEAN_OR) lhs, rhs = terms[:index], terms[index + 1:] operator = SNUBA_OR except Exception: lhs, rhs = terms[:1], terms[1:] operator = SNUBA_AND ( lhs_condition, lhs_having, projects_to_filter, group_ids, ) = convert_search_boolean_to_snuba_query(lhs, params) ( rhs_condition, rhs_having, rhs_projects_to_filter, rhs_group_ids, ) = convert_search_boolean_to_snuba_query(rhs, params) projects_to_filter.extend(rhs_projects_to_filter) group_ids.extend(rhs_group_ids) if operator == SNUBA_OR and (lhs_condition or rhs_condition) and (lhs_having or rhs_having): raise InvalidSearchQuery( "Having an OR between aggregate filters and normal filters is invalid." ) condition, having = None, None if lhs_condition or rhs_condition: args = filter(None, [lhs_condition, rhs_condition]) if not args: condition = None elif len(args) == 1: condition = args[0] else: condition = [operator, args] if lhs_having or rhs_having: args = filter(None, [lhs_having, rhs_having]) if not args: having = None elif len(args) == 1: having = args[0] else: having = [operator, args] return condition, having, projects_to_filter, group_ids
def test_multiple_statements(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] OR user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='OR', right_term=SearchBoolean(left_term=self.term2, operator='OR', right_term=self.term3)) ] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='AND', right_term=SearchBoolean(left_term=self.term2, operator='AND', right_term=self.term3)) ] term4 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer even number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=term4)) ] term5 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer odd number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5))) ] # absurdly long assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5)), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5))))) ]