Пример #1
0
 def test_large_durations(self):
     max_duration = 999999999 * 24 * 60 * 60 * 1000
     assert parse_duration(999999999, "d") == max_duration
     assert parse_duration(999999999 * 24, "h") == max_duration
     assert parse_duration(999999999 * 24 * 60, "m") == max_duration
     assert parse_duration(999999999 * 24 * 60 * 60, "s") == max_duration
     assert parse_duration(999999999 * 24 * 60 * 60 * 1000, "ms") == max_duration
Пример #2
0
    def test_overflow_durations(self):
        with self.assertRaises(InvalidQuery):
            assert parse_duration(999999999 + 1, "d")

        with self.assertRaises(InvalidQuery):
            assert parse_duration((999999999 + 1) * 24, "h")

        with self.assertRaises(InvalidQuery):
            assert parse_duration((999999999 + 1) * 24 * 60 + 1, "m")

        with self.assertRaises(InvalidQuery):
            assert parse_duration((999999999 + 1) * 24 * 60 * 60 + 1, "s")

        with self.assertRaises(InvalidQuery):
            assert parse_duration((999999999 + 1) * 24 * 60 * 60 * 1000 + 1, "ms")
Пример #3
0
    def visit_aggregate_filter(self, node, children):
        (negation, search_key, _, operator, search_value) = children
        operator = self.handle_negation(negation, operator)
        search_value = search_value[0] if not isinstance(search_value, RegexNode) else search_value

        try:
            aggregate_value = None
            if search_value.expr_name in ["duration_format", "percentage_format"]:
                # Even if the search value matches duration format, only act as duration for certain columns
                function = resolve_field(
                    search_key.name, self.params, functions_acl=FUNCTIONS.keys()
                )
                if function.aggregate is not None:
                    if search_value.expr_name == "percentage_format" and self.is_percentage_key(
                        function.aggregate[0]
                    ):
                        aggregate_value = parse_percentage(*search_value.match.groups())
                    # Extract column and function name out so we can check if we should parse as duration
                    elif search_value.expr_name == "duration_format" and self.is_duration_key(
                        function.aggregate[1]
                    ):
                        aggregate_value = parse_duration(*search_value.match.groups())

            if aggregate_value is None:
                aggregate_value = parse_numeric_value(*search_value.match.groups())
        except ValueError:
            raise InvalidSearchQuery(f"Invalid aggregate query condition: {search_key}")
        except InvalidQuery as exc:
            raise InvalidSearchQuery(str(exc))
        return AggregateFilter(search_key, operator, SearchValue(aggregate_value))
Пример #4
0
    def visit_aggregate_duration_filter(self, node, children):
        (negation, search_key, _, operator, search_value) = children
        operator = handle_negation(negation, operator)

        try:
            # Even if the search value matches duration format, only act as
            # duration for certain columns
            function = resolve_field(search_key.name, self.params, functions_acl=FUNCTIONS.keys())

            is_duration_key = False
            if function.aggregate is not None:
                args = function.aggregate[1]
                if isinstance(args, list):
                    is_duration_key = all(self.is_duration_key(arg) for arg in args)
                else:
                    is_duration_key = self.is_duration_key(args)

            if is_duration_key:
                aggregate_value = parse_duration(*search_value)
            else:
                # Duration overlaps with numeric values with `m` (million vs
                # minutes). So we fall through to numeric if it's not a
                # duration key
                #
                # TODO(epurkhiser): Should we validate that the field is
                # numeric and do some other fallback if it's not?
                aggregate_value = parse_numeric_value(*search_value)
        except ValueError:
            raise InvalidSearchQuery(f"Invalid aggregate query condition: {search_key}")
        except InvalidQuery as exc:
            raise InvalidSearchQuery(str(exc))

        return AggregateFilter(search_key, operator, SearchValue(aggregate_value))
Пример #5
0
    def visit_duration_filter(self, node, children):
        (search_key, sep, operator, search_value) = children

        operator = operator[0] if not isinstance(operator, Node) else "="
        if self.is_duration_key(search_key.name):
            try:
                search_value = parse_duration(*search_value.match.groups())
            except InvalidQuery as exc:
                raise InvalidSearchQuery(str(exc))
            return SearchFilter(search_key, operator, SearchValue(search_value))
        elif self.is_numeric_key(search_key.name):
            return self.visit_numeric_filter(node, (search_key, sep, ((operator, search_value),)))
        else:
            search_value = operator + search_value.text if operator != "=" else search_value.text
            return self._handle_basic_filter(search_key, "=", SearchValue(search_value))
Пример #6
0
    def visit_duration_filter(self, node, children):
        (search_key, sep, operator, search_value) = children

        operator = operator[0] if not isinstance(operator, Node) else "="
        if self.is_duration_key(search_key.name):
            try:
                search_value = parse_duration(*search_value)
            except InvalidQuery as exc:
                raise InvalidSearchQuery(str(exc))
            return SearchFilter(search_key, operator, SearchValue(search_value))

        # Durations overlap with numeric `m` suffixes
        if self.is_numeric_key(search_key.name):
            return self._handle_numeric_filter(search_key, operator, search_value)

        search_value = "".join(search_value)
        search_value = operator + search_value if operator != "=" else search_value
        return self._handle_basic_filter(search_key, "=", SearchValue(search_value))
Пример #7
0
    def visit_duration_filter(self, node, children):
        (negation, search_key, _, operator, search_value) = children
        if self.is_duration_key(search_key.name) or self.is_numeric_key(search_key.name):
            operator = handle_negation(negation, operator)
        else:
            operator = get_operator_value(operator)

        if self.is_duration_key(search_key.name):
            try:
                search_value = parse_duration(*search_value)
            except InvalidQuery as exc:
                raise InvalidSearchQuery(str(exc))
            return SearchFilter(search_key, operator, SearchValue(search_value))

        # Durations overlap with numeric `m` suffixes
        if self.is_numeric_key(search_key.name):
            return self._handle_numeric_filter(search_key, operator, search_value)

        search_value = "".join(search_value)
        search_value = operator + search_value if operator not in ("=", "!=") else search_value
        operator = "!=" if is_negated(negation) else "="
        return self._handle_basic_filter(search_key, operator, SearchValue(search_value))
Пример #8
0
    def node_visitor(token):
        if token["type"] == "spaces":
            return None

        if token["type"] == "filter":
            # Filters with an invalid reason raises to signal to the test
            # runner that we should expect this exception
            if token.get("invalid"):
                raise InvalidSearchQuery(token["invalid"]["reason"])

            # Transform the operator to match for list values
            if token["value"]["type"] in ["valueTextList", "valueNumberList"]:
                operator = "NOT IN" if token["negated"] else "IN"
            else:
                # Negate the operator if the filter is negated to match
                operator = token["operator"] or "="
                operator = f"!{operator}" if token["negated"] else operator

            key = node_visitor(token["key"])
            value = node_visitor(token["value"])

            if token["filter"] == "boolean" and token["negated"]:
                operator = "="
                value = SearchValue(raw_value=1 if value.raw_value == 0 else 0)

            return SearchFilter(key, operator, value)

        if token["type"] == "keySimple":
            return SearchKey(name=token["value"])

        if token["type"] == "keyExplicitTag":
            return SearchKey(name=f"tags[{token['key']['value']}]")

        if token["type"] == "keyAggregate":
            name = node_visitor(token["name"]).name
            # Consistent join aggregate function parameters
            args = ", ".join(arg["value"]["value"]
                             for arg in token["args"]["args"])
            return AggregateKey(name=f"{name}({args})")

        if token["type"] == "valueText":
            # Noramlize values by removing the escaped quotes
            value = token["value"].replace('\\"', '"')
            return SearchValue(raw_value=value)

        if token["type"] == "valueNumber":
            return SearchValue(
                raw_value=parse_numeric_value(token["value"], token["unit"]))

        if token["type"] == "valueTextList":
            return SearchValue(
                raw_value=[item["value"]["value"] for item in token["items"]])

        if token["type"] == "valueNumberList":
            return SearchValue(raw_value=[
                item["value"]["rawValue"] for item in token["items"]
            ])

        if token["type"] == "valueIso8601Date":
            return SearchValue(raw_value=parse_datetime_string(token["value"]))

        if token["type"] == "valueDuration":
            return SearchValue(
                raw_value=parse_duration(token["value"], token["unit"]))

        if token["type"] == "valueRelativeDate":
            return SearchValue(
                raw_value=parse_duration(token["value"], token["unit"]))

        if token["type"] == "valueBoolean":
            return SearchValue(raw_value=int(token["value"]))

        if token["type"] == "freeText":
            if token["quoted"]:
                # Normalize quotes
                value = token["value"].replace('\\"', '"')
            else:
                # Normalize spacing
                value = token["value"].strip(" ")

            if value == "":
                return None

            return SearchFilter(
                key=SearchKey(name="message"),
                operator="=",
                value=SearchValue(raw_value=value),
            )
Пример #9
0
    def test_errors(self):
        with self.assertRaises(InvalidQuery):
            parse_duration("test", "ms")

        with self.assertRaises(InvalidQuery):
            parse_duration(123, "test")
Пример #10
0
 def test_weeks(self):
     assert parse_duration(890, "wk") == 890 * 7 * 24 * 60 * 60 * 1000
     assert parse_duration(890, "w") == 890 * 7 * 24 * 60 * 60 * 1000
Пример #11
0
 def test_days(self):
     assert parse_duration(567, "day") == 567 * 24 * 60 * 60 * 1000
     assert parse_duration(567, "d") == 567 * 24 * 60 * 60 * 1000
Пример #12
0
 def test_hours(self):
     assert parse_duration(234, "hr") == 234 * 60 * 60 * 1000
     assert parse_duration(234, "h") == 234 * 60 * 60 * 1000
Пример #13
0
 def test_minutes(self):
     assert parse_duration(789, "min") == 789 * 60 * 1000
     assert parse_duration(789, "m") == 789 * 60 * 1000
Пример #14
0
 def test_sec(self):
     assert parse_duration(456, "s") == 456000
Пример #15
0
 def test_ms(self):
     assert parse_duration(123, "ms") == 123