Ejemplo n.º 1
0
 def validate(self) -> None:
     if not isinstance(self.exp, (Column, CurriedFunction, Function)):
         raise InvalidExpressionError(
             "OrderBy expression must be a Column, CurriedFunction or Function"
         )
     if not isinstance(self.direction, Direction):
         raise InvalidExpressionError("OrderBy direction must be a Direction")
Ejemplo n.º 2
0
 def validate(self) -> None:
     if not isinstance(self.value, str) or len(self.value) == 0:
         raise InvalidExpressionError(
             f"{self.name} must be a non-empty string")
     elif not FLAG_RE.match(self.value):
         raise InvalidExpressionError(
             f"{self.name} contains invalid characters")
Ejemplo n.º 3
0
def validate_sequence_of_type(
    name: str, val: Any, type: type, minimum_length: Optional[int]
) -> None:
    if not isinstance(val, Sequence):
        raise InvalidExpressionError(f"{name}: '{val}' must be a sequence of {type}")
    if minimum_length is not None and len(val) < minimum_length:
        raise InvalidExpressionError(
            f"{name}: '{val}' must be contain at least {minimum_length} elements"
        )
    for el in val:
        if not isinstance(el, type):
            raise InvalidExpressionError(
                f"{name}: Invalid element '{el}' which must be a list composed entirely of {type}"
            )
Ejemplo n.º 4
0
    def validate(self) -> None:
        if not isinstance(self.exp, Column):
            raise InvalidExpressionError(
                "aliased expressions can only contain a Column")

        if self.alias is not None:
            if not isinstance(self.alias, str) or self.alias == "":
                raise InvalidExpressionError(
                    f"alias '{self.alias}' of expression must be None or a non-empty string"
                )
            if not ALIAS_RE.match(self.alias):
                raise InvalidExpressionError(
                    f"alias '{self.alias}' of expression contains invalid characters"
                )
Ejemplo n.º 5
0
    def validate(self) -> None:
        if not isinstance(self.relationships,
                          (list, tuple)) or not self.relationships:
            raise InvalidExpressionError(
                "Join must have at least one Relationship")
        elif not all(isinstance(x, Relationship) for x in self.relationships):
            raise InvalidExpressionError(
                "Join expects a list of Relationship objects")

        seen: MutableMapping[str, str] = {}
        for alias, entity in self.get_alias_mappings():
            if alias in seen and seen[alias] != entity:
                entities = sorted([entity, seen[alias]])
                raise InvalidExpressionError(
                    f"alias '{alias}' is duplicated for entities {', '.join(entities)}"
                )
            seen[alias] = entity
Ejemplo n.º 6
0
    def validate(self) -> None:
        def valid_entity(e: Any) -> None:
            if not isinstance(e, Entity):
                raise InvalidExpressionError(f"'{e}' must be an Entity")
            elif e.alias is None:
                raise InvalidExpressionError(f"{e} must have a valid alias")

        valid_entity(self.lhs)
        valid_entity(self.rhs)

        if not isinstance(self.name, str) or not self.name:
            raise InvalidExpressionError(
                f"'{self.name}' is not a valid relationship name")
Ejemplo n.º 7
0
    def _stringify_scalar(self, value: ScalarType) -> str:
        if value is None:
            return "NULL"
        elif isinstance(value, bool):
            return "TRUE" if value else "FALSE"
        if isinstance(value, (str, bytes)):
            if isinstance(value, bytes):
                decoded = value.decode()
            else:
                decoded = value

            # The ' and \ character are escaped in the string to ensure
            # the query is valid. They are de-escaped in the SnQL parser.
            # Also escape newlines since they break the SnQL grammar.
            decoded = (decoded.replace("\\", "\\\\").replace("'",
                                                             "\\'").replace(
                                                                 "\n", "\\n"))
            return f"'{decoded}'"
        elif isinstance(value, (int, float)):
            return f"{value}"
        elif isinstance(value, datetime):
            # Snuba expects naive UTC datetimes, so convert to that
            if value.tzinfo is not None:
                delta = value.utcoffset()
                assert delta is not None
                value = value - delta
                value = value.replace(tzinfo=None)
            return f"toDateTime('{value.isoformat()}')"
        elif isinstance(value, date):
            return f"toDateTime('{value.isoformat()}')"
        elif isinstance(value, Expression):
            return self.visit(value)
        elif isinstance(value, list):
            is_scalar(value)  # Throws on an invalid array
            return f"array({', '.join([self._stringify_scalar(v) for v in value])})"
        elif isinstance(value, tuple):
            is_scalar(value)  # Throws on an invalid tuple
            return f"tuple({', '.join([self._stringify_scalar(v) for v in value])})"

        raise InvalidExpressionError(f"'{value}' is not a valid scalar")
Ejemplo n.º 8
0
 def validate(self) -> None:
     if not isinstance(self.value, bool):
         raise InvalidExpressionError(f"{self.name} must be a boolean")
Ejemplo n.º 9
0
 def valid_entity(e: Any) -> None:
     if not isinstance(e, Entity):
         raise InvalidExpressionError(f"'{e}' must be an Entity")
     elif e.alias is None:
         raise InvalidExpressionError(f"{e} must have a valid alias")
Ejemplo n.º 10
0
from snuba_sdk.visitors import Translation

tests = [
    pytest.param(Column("stuff"), "things", "stuff AS things", None, id="simple"),
    pytest.param(
        Column("stuff"),
        "things[1.c-2:3_]",
        "stuff AS things[1.c-2:3_]",
        None,
        id="complex alias",
    ),
    pytest.param(
        "stuff",
        "things[1.c-2:3]",
        None,
        InvalidExpressionError("aliased expressions can only contain a Column"),
        id="exp must be a Column",
    ),
    pytest.param(
        Column("stuff"),
        "",
        None,
        InvalidExpressionError(
            "alias '' of expression must be None or a non-empty string"
        ),
        id="alias can't be empty string",
    ),
    pytest.param(
        Column("stuff"),
        1,
        None,
Ejemplo n.º 11
0
 def validate(self) -> None:
     validate_sequence_of_type("LimitBy columns", self.columns, Column, 1)
     if not isinstance(self.count, int) or self.count <= 0 or self.count > 10000:
         raise InvalidExpressionError(
             "LimitBy count must be a positive integer (max 10,000)"
         )
Ejemplo n.º 12
0
import re
from typing import Any, Optional

import pytest

from snuba_sdk.column import Column
from snuba_sdk.expressions import Granularity, InvalidExpressionError, Limit, Offset
from snuba_sdk.function import Function
from snuba_sdk.orderby import Direction, LimitBy, OrderBy

limit_tests = [
    pytest.param(1, None),
    pytest.param(10, None),
    pytest.param(-1, InvalidExpressionError("limit '-1' must be at least 1")),
    pytest.param("5", InvalidExpressionError("limit '5' must be an integer")),
    pytest.param(1.5,
                 InvalidExpressionError("limit '1.5' must be an integer")),
    pytest.param(10.0,
                 InvalidExpressionError("limit '10.0' must be an integer")),
    pytest.param(
        1000000,
        InvalidExpressionError("limit '1000000' is capped at 10,000")),
]


@pytest.mark.parametrize("value, exception", limit_tests)
def test_limit(value: Any, exception: Optional[Exception]) -> None:
    if exception is not None:
        with pytest.raises(type(exception), match=re.escape(str(exception))):
            Limit(value)
    else:
Ejemplo n.º 13
0
TRANSLATOR = Translation(use_entity_aliases=True)

relationship_tests = [
    pytest.param(
        Entity("events", "ev", 100.0),
        "contains",
        Entity("transactions", "t"),
        "(ev: events SAMPLE 100.0) -[contains]-> (t: transactions)",
        None,
    ),
    pytest.param(
        "",
        "contains",
        Entity("transactions", "t"),
        None,
        InvalidExpressionError("'' must be an Entity"),
    ),
    pytest.param(
        Entity("events", None, 100.0),
        "contains",
        Entity("transactions", "t"),
        None,
        InvalidExpressionError(
            "Entity('events', sample=100.0) must have a valid alias"),
    ),
    pytest.param(
        Entity("events", "ev", 100.0),
        1,
        Entity("transactions", "t"),
        None,
        InvalidExpressionError("'1' is not a valid relationship name"),