Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
 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]
Ejemplo n.º 3
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
Ejemplo n.º 4
0
 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),
Ejemplo n.º 5
0
 .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),
Ejemplo n.º 6
0
 def to_or() -> Or:
     return Or(conditions)
Ejemplo n.º 7
0
             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(
                 [
Ejemplo n.º 8
0
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"]
Ejemplo n.º 9
0
    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