def test_environment_param(self): self.params["environment"] = ["", "prod"] query = QueryBuilder(Dataset.Discover, self.params, selected_columns=["environment"]) self.assertCountEqual( query.where, [ *self.default_conditions, Or([ Condition(Column("environment"), Op.IS_NULL), Condition(Column("environment"), Op.EQ, "prod"), ]), ], ) query.get_snql_query().validate() self.params["environment"] = ["dev", "prod"] query = QueryBuilder(Dataset.Discover, self.params, selected_columns=["environment"]) self.assertCountEqual( query.where, [ *self.default_conditions, Condition(Column("environment"), Op.IN, ["dev", "prod"]), ], ) query.get_snql_query().validate()
def _environment_filter_converter( self, search_filter: SearchFilter, _: str, ) -> WhereType: # conditions added to env_conditions can be OR'ed env_conditions = [] value = search_filter.value.value values = set(value if isinstance(value, (list, tuple)) else [value]) # sorted for consistency values = sorted(f"{value}" for value in values) environment = self.column("environment") # the "no environment" environment is null in snuba if "" in values: values.remove("") operator = Op.IS_NULL if search_filter.operator == "=" else Op.IS_NOT_NULL env_conditions.append(Condition(environment, operator)) if len(values) == 1: operator = Op.EQ if search_filter.operator in EQUALITY_OPERATORS else Op.NEQ env_conditions.append(Condition(environment, operator, values.pop())) elif values: operator = Op.IN if search_filter.operator in EQUALITY_OPERATORS else Op.NOT_IN env_conditions.append(Condition(environment, operator, values)) if len(env_conditions) > 1: return Or(conditions=env_conditions) else: return env_conditions[0]
def json_to_snql(body: Mapping[str, Any], entity: str) -> Query: """ This will output a Query object that matches the Legacy query body that was passed in. The entity is necessary since the SnQL API requires an explicit entity. This doesn't support subquery or joins. :param body: The legacy API body. :type body: Mapping[str, Any] :param entity: The name of the entity being queried. :type entity: str :raises InvalidExpressionError, InvalidQueryError: If the legacy body is invalid, the SDK will raise an exception. """ dataset = body.get("dataset") or entity sample = body.get("sample") if sample is not None: sample = float(sample) query = Query(dataset, Entity(entity, None, sample)) selected_columns = [] for a in body.get("aggregations", []): selected_columns.append(parse_exp(list(a))) selected = [] for s in body.get("selected_columns", []): if isinstance(s, tuple): selected.append(list(s)) else: selected.append(s) selected_columns.extend(list(map(parse_exp, selected))) arrayjoin = body.get("arrayjoin") if arrayjoin: query = query.set_array_join([Column(arrayjoin)]) query = query.set_select(selected_columns) groupby = body.get("groupby", []) if groupby and not isinstance(groupby, list): groupby = [groupby] parsed_groupby = [] for g in groupby: if isinstance(g, tuple): g = list(g) parsed_groupby.append(parse_exp(g)) query = query.set_groupby(parsed_groupby) conditions: list[Union[Or, Condition]] = [] if body.get("organization"): org_cond = parse_extension_condition("org_id", body["organization"]) if org_cond: conditions.append(org_cond) assert isinstance(query.match, Entity) time_column = get_required_time_column(query.match.name) if time_column: time_cols = (("from_date", Op.GTE), ("to_date", Op.LT)) for col, op in time_cols: date_val = body.get(col) if date_val: conditions.append( Condition(Column(time_column), op, parse_datetime(date_val))) if body.get("project"): proj_cond = parse_extension_condition("project_id", body["project"], True) if proj_cond: conditions.append(proj_cond) for cond in body.get("conditions", []): if not is_condition(cond): or_conditions = [] for or_cond in cond: or_conditions.append(parse_condition(or_cond)) if len(or_conditions) > 1: conditions.append(Or(or_conditions)) else: conditions.extend(or_conditions) else: conditions.append(parse_condition(cond)) query = query.set_where(conditions) having: list[Union[Or, Condition]] = [] for cond in body.get("having", []): if not is_condition(cond): or_conditions = [] for or_cond in cond: or_conditions.append(parse_condition(or_cond)) having.append(Or(or_conditions)) else: having.append(parse_condition(cond)) query = query.set_having(having) order_by = body.get("orderby") if order_by: if not isinstance(order_by, list): order_by = [order_by] order_bys = [] for o in order_by: direction = Direction.ASC if isinstance(o, list): first = o[0] if isinstance(first, str) and first.startswith("-"): o[0] = first.lstrip("-") direction = Direction.DESC part = parse_exp(o) elif isinstance(o, str): if o.startswith("-"): direction = Direction.DESC part = parse_exp(o.lstrip("-")) else: part = parse_exp(o) order_bys.append(OrderBy(part, direction)) query = query.set_orderby(order_bys) limitby = body.get("limitby") if limitby: limit, name = limitby query = query.set_limitby(LimitBy([Column(name)], int(limit))) extras = ( "limit", "offset", "granularity", "totals", "consistent", "turbo", "debug", "dry_run", "parent_api", ) for extra in extras: if body.get(extra) is not None: query = getattr(query, f"set_{extra}")(body.get(extra)) query.set_legacy(True) return query
pytest.param( [ Condition(Column("required2"), Op.EQ, 1), Condition(Column("time"), Op.GTE, BEFORE), Condition(Column("time"), Op.LT, AFTER), ], ENTITY, RequiredColumnError( "where clause is missing required condition on column 'required1'" ), id="missing required columns", ), pytest.param( [ Or([ Condition(Column("required1"), Op.IN, [1, 2, 3]), Condition(Column("required2"), Op.EQ, 1), ]), Condition(Column("time"), Op.GTE, BEFORE), Condition(Column("time"), Op.LT, AFTER), ], ENTITY, RequiredColumnError( "where clause is missing required conditions on columns 'required1', 'required2'" ), id="all required columns in OR", ), pytest.param( [ Condition(Column("required1"), Op.NOT_IN, [1, 2, 3]), Condition(Column("required2"), Op.EQ, 1), Condition(Column("time"), Op.GTE, BEFORE),
.set_groupby( [ Column("group_id", Entity("events", "e")), Column("span_id", Entity("sessions", "s")), Column("trace_id", Entity("transactions", "t")), ] ) .set_where( [ Or( [ Condition( Column("timestamp", Entity("events", "e")), Op.IS_NOT_NULL ), Condition( Column("timestamp", Entity("sessions", "s")), Op.IS_NOT_NULL ), Condition( Column("timestamp", Entity("transactions", "t")), Op.IS_NOT_NULL, ), ] ) ], ) .set_orderby( [OrderBy(Column("timestamp", Entity("events", "e")), Direction.DESC)] ) .set_limit(10) .set_offset(1) .set_granularity(3600) .set_consistent(True),
def to_or() -> Or: return Or(conditions)
Condition(Column("a"), Op.EQ, 1), Condition(Column("b"), Op.EQ, 2), Condition(Column("c"), Op.EQ, 3), ], ), "(a = 1 AND b = 2 AND c = 3)", None, id="more than two boolean test", ), pytest.param( and_cond( [ Condition(Column("a"), Op.EQ, 1), Or( [ Condition(Column("b"), Op.EQ, 2), Condition(Column("c"), Op.EQ, 3), ], ), And( [ Condition(Column("d"), Op.EQ, 4), Condition(Column("e"), Op.EQ, 5), ], ), ], ), And( [ Condition(Column("a"), Op.EQ, 1), Or( [
def __get_release_from_filters( org_id, project_id, release, scope, scope_value, stats_start, scope_operation, scope_direction, release_operation, release_direction, limit, crash_free_function=None, environments=None, ): """ Helper function that based on the passed args, constructs a snuba query and runs it to fetch a release Inputs: * org_id: Organisation Id * project_id * release: release version * scope: Sort order criteria -> sessions, users, crash_free_sessions, crash_free_users * scope_value: The value/count of the scope argument * stats_period: duration * scope_operation: Indicates which operation should be used to compare the releases' scope value with current release scope value. either Op.GT or Op.LT * scope_direction: Indicates which ordering should be used to order releases' scope value by either ASC or DESC * release_operation: Indicates which operation should be used to compare the releases' version with current release version. either Op.GT or Op.LT * release_direction: Indicates which ordering should be used to order releases' version by either ASC or DESC * crash_free_function: optional arg that is passed when a function needs to be applied in query like in the case of crash_free_sessions and crash_free_users * environments Return: List of releases that either contains one release or none at all """ release_conditions = [ Condition(Column("started"), Op.GTE, stats_start), Condition( Column("started"), Op.LT, datetime.utcnow(), ), Condition(Column("project_id"), Op.EQ, project_id), Condition(Column("org_id"), Op.EQ, org_id), ] if environments is not None: release_conditions.append(Condition(Column("environment"), Op.IN, environments)) # Get select statements and append to the select statement list a function if # crash_free_option whether it is crash_free_users or crash_free_sessions is picked select = [ Column("project_id"), Column("release"), ] if crash_free_function is not None: select.append(crash_free_function) having = [ Or( conditions=[ Condition(Column(scope), scope_operation, scope_value), And( conditions=[ Condition(Column(scope), Op.EQ, scope_value), Condition(Column("release"), release_operation, release), ] ), ] ) ] orderby = [ OrderBy(direction=scope_direction, exp=Column(scope)), OrderBy(direction=release_direction, exp=Column("release")), ] query = ( Query( dataset=Dataset.Sessions.value, match=Entity("sessions"), select=select, where=release_conditions, having=having, groupby=[Column("release"), Column("project_id")], orderby=orderby, ) .set_limit(limit) .set_offset(0) ) return raw_snql_query(query, referrer="sessions.get_prev_or_next_release")["data"]
def resolve_top_event_conditions(self, top_events: List[Dict[str, Any]], other: bool) -> Optional[WhereType]: """Given a list of top events construct the conditions""" conditions = [] for field in self.fields: # If we have a project field, we need to limit results by project so we don't hit the result limit if field in ["project", "project.id"] and top_events: # Iterate through the existing conditions to find the project one # the project condition is a requirement of queries so there should always be one project_condition = [ condition for condition in self.where if type(condition) == Condition and condition.lhs == self.column("project_id") ][0] self.where.remove(project_condition) if field == "project": projects = list({ self.project_slugs[event["project"]] for event in top_events }) else: projects = list( {event["project.id"] for event in top_events}) self.where.append( Condition(self.column("project_id"), Op.IN, projects)) continue resolved_field = self.resolve_column(field) values: Set[Any] = set() for event in top_events: if field in event: alias = field elif self.is_column_function( resolved_field) and resolved_field.alias in event: alias = resolved_field.alias else: continue # Note that because orderby shouldn't be an array field its not included in the values if isinstance(event.get(alias), list): continue else: values.add(event.get(alias)) values_list = list(values) if values_list: if field == "timestamp" or field.startswith("timestamp.to_"): if not other: # timestamp fields needs special handling, creating a big OR instead function, operator = Or, Op.EQ else: # Needs to be a big AND when negated function, operator = And, Op.NEQ if len(values_list) > 1: conditions.append( function(conditions=[ Condition(resolved_field, operator, value) for value in sorted(values_list) ])) else: conditions.append( Condition(resolved_field, operator, values_list[0])) elif None in values_list: # one of the values was null, but we can't do an in with null values, so split into two conditions non_none_values = [ value for value in values_list if value is not None ] null_condition = Condition( Function("isNull", [resolved_field]), Op.EQ if not other else Op.NEQ, 1) if non_none_values: non_none_condition = Condition( resolved_field, Op.IN if not other else Op.NOT_IN, non_none_values) if not other: conditions.append( Or(conditions=[ null_condition, non_none_condition ])) else: conditions.append( And(conditions=[ null_condition, non_none_condition ])) else: conditions.append(null_condition) else: conditions.append( Condition(resolved_field, Op.IN if not other else Op.NOT_IN, values_list)) if len(conditions) > 1: final_function = And if not other else Or final_condition = final_function(conditions=conditions) elif len(conditions) == 1: final_condition = conditions[0] else: final_condition = None return final_condition