Skip to content

Commit ff29038

Browse files
ref(search): Decouple IN syntax from equals syntax (#26186)
1 parent 8ffe8b1 commit ff29038

File tree

1 file changed

+51
-43
lines changed

1 file changed

+51
-43
lines changed

src/sentry/api/event_search.py

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,21 @@
5252
/ specific_time_filter
5353
/ duration_filter
5454
/ boolean_filter
55+
/ numeric_in_filter
5556
/ numeric_filter
5657
/ aggregate_filter
5758
/ aggregate_date_filter
5859
/ aggregate_rel_date_filter
5960
/ has_filter
6061
/ is_filter
62+
/ text_in_filter
6163
/ text_filter
6264
6365
# standard key:val filter
64-
text_filter = negation? text_key sep ((open_bracket text_value (comma spaces text_value)* closed_bracket) / search_value)
66+
text_filter = negation? text_key sep search_value
67+
68+
# in filter key:[val1, val2]
69+
text_in_filter = negation? text_key sep text_in_list
6570
6671
# filter for dates
6772
time_filter = search_key sep operator iso_8601_date_format
@@ -75,13 +80,16 @@
7580
# exact time filter for dates
7681
specific_time_filter = search_key sep iso_8601_date_format
7782
78-
# Numeric comparison filter
79-
numeric_filter = search_key sep ((operator? numeric_value) / (open_bracket numeric_value (comma spaces numeric_value)* closed_bracket))
83+
# numeric comparison filter
84+
numeric_filter = search_key sep operator? numeric_value
85+
86+
# numeric in filter
87+
numeric_in_filter = search_key sep numeric_in_list
8088
81-
# Boolean comparison filter
89+
# boolean comparison filter
8290
boolean_filter = negation? search_key sep boolean_value
8391
84-
# Aggregate numeric filter
92+
# aggregate numeric filter
8593
aggregate_filter = negation? aggregate_key sep operator? (duration_format / numeric_value / percentage_format)
8694
aggregate_date_filter = negation? aggregate_key sep operator? iso_8601_date_format
8795
aggregate_rel_date_filter = negation? aggregate_key sep operator? rel_date_format
@@ -104,6 +112,8 @@
104112
text_key = explicit_tag_key / search_key
105113
text_value = quoted_value / in_value
106114
quoted_value = ~r"\"((?:\\\"|[^\"])*)?\""s
115+
numeric_in_list = open_bracket numeric_value (comma spaces numeric_value)* closed_bracket
116+
text_in_list = open_bracket text_value (comma spaces text_value)* closed_bracket
107117
108118
# Formats
109119
iso_8601_date_format = ~r"(\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,6})?)?(Z|([+-]\d{2}:\d{2}))?)(?=\s|\)|$)"
@@ -375,12 +385,8 @@ def visit_boolean_filter(self, node, children):
375385
(
376386
search_key,
377387
sep,
378-
(
379-
(
380-
"=",
381-
search_value,
382-
),
383-
),
388+
"=",
389+
search_value,
384390
),
385391
)
386392

@@ -405,41 +411,38 @@ def process_list(self, first, remaining):
405411
]
406412

407413
def visit_numeric_filter(self, node, children):
408-
(search_key, _, (value,)) = children
409-
operator = value[0]
414+
(search_key, _, operator, search_value) = children
410415
if isinstance(operator, Node):
411-
if isinstance(operator.expr, Optional):
412-
operator = "="
413-
else:
414-
operator = operator.text
416+
operator = "=" if isinstance(operator.expr, Optional) else operator.text
415417
else:
416418
operator = operator[0]
417419

418-
if operator == "[":
419-
operator = "IN"
420-
search_value = self.process_list(value[1], value[2])
420+
if self.is_numeric_key(search_key.name):
421+
try:
422+
search_value = SearchValue(parse_numeric_value(*search_value.match.groups()))
423+
except InvalidQuery as exc:
424+
raise InvalidSearchQuery(str(exc))
425+
return SearchFilter(search_key, operator, search_value)
421426
else:
422-
search_value = value[1]
427+
search_value = search_value.text
428+
search_value = SearchValue(operator + search_value if operator != "=" else search_value)
429+
return self._handle_basic_filter(search_key, "=", search_value)
430+
431+
def visit_numeric_in_filter(self, node, children):
432+
(search_key, _, value) = children
433+
operator = "IN"
434+
search_value = self.process_list(value[1], value[2])
423435

424436
if self.is_numeric_key(search_key.name):
425437
try:
426438
search_value = SearchValue(
427439
[parse_numeric_value(*val.match.groups()) for val in search_value]
428-
if operator == "IN"
429-
else parse_numeric_value(*search_value.match.groups())
430440
)
431441
except InvalidQuery as exc:
432442
raise InvalidSearchQuery(str(exc))
433443
return SearchFilter(search_key, operator, search_value)
434444
else:
435-
if operator != "IN":
436-
search_value = search_value.text
437-
else:
438-
search_value = [v.text for v in search_value]
439-
search_value = SearchValue(
440-
operator + search_value if operator not in ("=", "IN") else search_value
441-
)
442-
operator = "=" if operator not in ("=", "IN") else operator
445+
search_value = SearchValue([v.text for v in search_value])
443446
return self._handle_basic_filter(search_key, operator, search_value)
444447

445448
def handle_negation(self, negation, operator):
@@ -542,7 +545,7 @@ def visit_duration_filter(self, node, children):
542545
raise InvalidSearchQuery(str(exc))
543546
return SearchFilter(search_key, operator, SearchValue(search_value))
544547
elif self.is_numeric_key(search_key.name):
545-
return self.visit_numeric_filter(node, (search_key, sep, ((operator, search_value),)))
548+
return self.visit_numeric_filter(node, (search_key, sep, operator, search_value))
546549
else:
547550
search_value = operator + search_value.text if operator != "=" else search_value.text
548551
return self._handle_basic_filter(search_key, "=", SearchValue(search_value))
@@ -605,18 +608,23 @@ def is_negated(self, node):
605608
return node.text == "!"
606609

607610
def visit_text_filter(self, node, children):
608-
(negation, search_key, _, (search_value,)) = children
611+
(negation, search_key, _, search_value) = children
609612
operator = "="
610-
if isinstance(search_value, list):
611-
operator = "IN"
612-
search_value = SearchValue(
613-
self.process_list(search_value[1], [(_, _, val) for _, _, val in search_value[2]])
614-
)
615-
else:
616-
# XXX: We check whether the text in the node itself is actually empty, so
617-
# we can tell the difference between an empty quoted string and no string
618-
if not search_value.raw_value and not node.children[3].text:
619-
raise InvalidSearchQuery(f"Empty string after '{search_key.name}:'")
613+
# XXX: We check whether the text in the node itself is actually empty, so
614+
# we can tell the difference between an empty quoted string and no string
615+
if not search_value.raw_value and not node.children[3].text:
616+
raise InvalidSearchQuery(f"Empty string after '{search_key.name}:'")
617+
618+
operator = self.handle_negation(negation, operator)
619+
620+
return self._handle_basic_filter(search_key, operator, search_value)
621+
622+
def visit_text_in_filter(self, node, children):
623+
(negation, search_key, _, search_value) = children
624+
operator = "IN"
625+
search_value = SearchValue(
626+
self.process_list(search_value[1], [(_, _, val) for _, _, val in search_value[2]])
627+
)
620628

621629
operator = self.handle_negation(negation, operator)
622630

0 commit comments

Comments
 (0)