예제 #1
0
def _stringify_scalar(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

        decoded = unescaped_quotes.sub("\\'", decoded)
        decoded = unescaped_newline.sub("\\\\n", decoded)
        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, list):
        is_scalar(value)  # Throws on an invalid array
        return f"array({', '.join([_stringify_scalar(v) for v in value])})"
    elif isinstance(value, tuple):
        is_scalar(value)  # Throws on an invalid tuple
        return f"tuple({', '.join([_stringify_scalar(v) for v in value])})"

    raise InvalidExpression(f"'{value}' is not a valid scalar")
예제 #2
0
    def _visit_curried_function(self, func: CurriedFunction) -> str:
        alias = "" if func.alias is None else f" AS {func.alias}"
        initialize_clause = ""
        if func.initializers is not None:
            initializers = []
            for initer in func.initializers:
                if isinstance(initer, Column):
                    initializers.append(self.visit(initer))
                elif isinstance(initer, tuple(Scalar)):
                    initializers.append(_stringify_scalar(initer))

            initialize_clause = f"({', '.join(initializers)})"

        param_clause = ""
        if func.parameters is not None:
            params = []
            for param in func.parameters:
                if isinstance(param, (Column, CurriedFunction, Function)):
                    params.append(self.visit(param))
                elif is_scalar(param):
                    params.append(_stringify_scalar(param))

            param_clause = f"({', '.join(params)})"

        return f"{func.function}{initialize_clause}{param_clause}{alias}"
예제 #3
0
    def _visit_condition(self, cond: Condition) -> str:
        rhs = None
        if cond.is_unary():
            rhs = ""
        elif isinstance(cond.rhs, (Column, CurriedFunction, Function)):
            rhs = f" {self.visit(cond.rhs)}"
        elif is_scalar(cond.rhs):
            rhs = f" {_stringify_scalar(cond.rhs)}"

        assert rhs is not None
        return f"{self.visit(cond.lhs)} {cond.op.value}{rhs}"
예제 #4
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")
예제 #5
0
    def validate(self) -> None:
        if not isinstance(self.function, str):
            raise InvalidFunctionError(
                f"function '{self.function}' must be a string")
        if self.function == "":
            # TODO: Have a whitelist of valid functions to check, maybe even with more
            # specific parameter type checking
            raise InvalidFunctionError("function cannot be empty")
        if not function_name_re.match(self.function):
            raise InvalidFunctionError(
                f"function '{self.function}' contains invalid characters")

        if self.initializers is not None:
            if not isinstance(self.initializers, Sequence):
                raise InvalidFunctionError(
                    f"initializers of function {self.function} must be a Sequence"
                )
            elif not all(
                    isinstance(param, Column) or is_literal(param)
                    for param in self.initializers):
                raise InvalidFunctionError(
                    f"initializers to function {self.function} must be a scalar or column"
                )

        if self.alias is not None:
            if not isinstance(self.alias, str) or self.alias == "":
                raise InvalidFunctionError(
                    f"alias '{self.alias}' of function {self.function} must be None or a non-empty string"
                )
            if not ALIAS_RE.match(self.alias):
                raise InvalidFunctionError(
                    f"alias '{self.alias}' of function {self.function} contains invalid characters"
                )

        if self.parameters is not None:
            if not isinstance(self.parameters, Sequence):
                raise InvalidFunctionError(
                    f"parameters of function {self.function} must be a Sequence"
                )
            for param in self.parameters:
                if not isinstance(
                        param, (Column, CurriedFunction, Function, Identifier,
                                Lambda)) and not is_scalar(param):
                    assert not isinstance(param, bytes)  # mypy
                    raise InvalidFunctionError(
                        f"parameter '{param}' of function {self.function} is an invalid type"
                    )
예제 #6
0
    def validate(self) -> None:
        if not isinstance(self.lhs, (Column, CurriedFunction, Function)):
            raise InvalidConditionError(
                f"invalid condition: LHS of a condition must be a Column, CurriedFunction or Function, not {type(self.lhs)}"
            )
        if not isinstance(self.op, Op):
            raise InvalidConditionError(
                "invalid condition: operator of a condition must be an Op")

        if is_unary(self.op):
            if self.rhs is not None:
                raise InvalidConditionError(
                    "invalid condition: unary operators don't have rhs conditions"
                )

        if not isinstance(
                self.rhs,
            (Column, CurriedFunction, Function)) and not is_scalar(self.rhs):
            raise InvalidConditionError(
                f"invalid condition: RHS of a condition must be a Column, CurriedFunction, Function or Scalar not {type(self.rhs)}"
            )