1- from typing import Type
1+ from typing import Any , Dict , List , Optional , Sequence , Type
22
33from sentry_protos .snuba .v1 .endpoint_delete_trace_items_pb2 import (
44 DeleteTraceItemsRequest ,
55 DeleteTraceItemsResponse ,
66)
7+ from sentry_protos .snuba .v1 .request_common_pb2 import TraceItemFilterWithType
8+ from sentry_protos .snuba .v1 .trace_item_filter_pb2 import ComparisonFilter
79
810from snuba .attribution .appid import AppID
911from snuba .datasets .storages .factory import get_writable_storage
1012from snuba .datasets .storages .storage_key import StorageKey
11- from snuba .web .bulk_delete_query import delete_from_storage
13+ from snuba .web .bulk_delete_query import AttributeConditions , delete_from_storage
1214from snuba .web .rpc import RPCEndpoint
1315from snuba .web .rpc .common .exceptions import BadSnubaRPCRequestException
1416
1517
18+ def _extract_attribute_value (comparison_filter : ComparisonFilter ) -> Any :
19+ """Extract the value from a ComparisonFilter's AttributeValue."""
20+ value_field = comparison_filter .value .WhichOneof ("value" )
21+ if value_field == "val_str" :
22+ return comparison_filter .value .val_str
23+ elif value_field == "val_int" :
24+ return comparison_filter .value .val_int
25+ elif value_field == "val_double" :
26+ return comparison_filter .value .val_double
27+ elif value_field == "val_bool" :
28+ return comparison_filter .value .val_bool
29+ elif value_field == "val_str_array" :
30+ return list (comparison_filter .value .val_str_array .values )
31+ elif value_field == "val_int_array" :
32+ return list (comparison_filter .value .val_int_array .values )
33+ elif value_field == "val_double_array" :
34+ return list (comparison_filter .value .val_double_array .values )
35+ else :
36+ raise BadSnubaRPCRequestException (f"Unsupported attribute value type: { value_field } " )
37+
38+
39+ def _trace_item_filters_to_attribute_conditions (
40+ item_type : int ,
41+ filters : Sequence [TraceItemFilterWithType ],
42+ ) -> AttributeConditions :
43+ """
44+ Convert TraceItemFilters to AttributeConditions for deletion.
45+
46+ Only supports ComparisonFilter with OP_EQUALS or OP_IN operations.
47+ All filters are combined with AND logic.
48+
49+ Args:
50+ item_type: The trace item type (e.g., occurrence, span)
51+ filters: List of TraceItemFilterWithType from the request
52+
53+ Returns:
54+ AttributeConditions object containing item_type and attribute mappings
55+
56+ Raises:
57+ BadSnubaRPCRequestException: If unsupported filter types or operations are encountered
58+ """
59+ attributes : Dict [str , List [Any ]] = {}
60+
61+ for filter_with_type in filters :
62+ # Extract the actual filter from TraceItemFilterWithType
63+ trace_filter = filter_with_type .filter
64+ # Only support comparison filters for deletion
65+ if not trace_filter .HasField ("comparison_filter" ):
66+ raise BadSnubaRPCRequestException (
67+ "Only comparison filters are supported for deletion. "
68+ "AND, OR, and NOT filters are not supported."
69+ )
70+
71+ comparison_filter = trace_filter .comparison_filter
72+ op = comparison_filter .op
73+
74+ # Only support equality operations for deletion
75+ if op not in (ComparisonFilter .OP_EQUALS , ComparisonFilter .OP_IN ):
76+ op_name = ComparisonFilter .Op .Name (op )
77+ raise BadSnubaRPCRequestException (
78+ f"Only OP_EQUALS and OP_IN operations are supported for deletion. Got: { op_name } "
79+ )
80+
81+ attribute_name = comparison_filter .key .name
82+ value = _extract_attribute_value (comparison_filter )
83+
84+ # Convert single values to lists for consistency
85+ if not isinstance (value , list ):
86+ value = [value ]
87+
88+ # If the attribute already exists, extend the list (OR logic within same attribute)
89+ if attribute_name in attributes :
90+ attributes [attribute_name ].extend (value )
91+ else :
92+ attributes [attribute_name ] = value
93+
94+ return AttributeConditions (item_type = item_type , attributes = attributes )
95+
96+
1697class EndpointDeleteTraceItems (RPCEndpoint [DeleteTraceItemsRequest , DeleteTraceItemsResponse ]):
1798 @classmethod
1899 def version (cls ) -> str :
@@ -36,9 +117,6 @@ def _execute(self, request: DeleteTraceItemsRequest) -> DeleteTraceItemsResponse
36117 if has_trace_ids and has_filters :
37118 raise BadSnubaRPCRequestException ("Provide only one of trace_ids or filters, not both." )
38119
39- if has_filters :
40- raise NotImplementedError ("Currently, only delete by trace_ids is supported" )
41-
42120 attribution_info = {
43121 "app_id" : AppID ("eap" ),
44122 "referrer" : request .meta .referrer ,
@@ -51,14 +129,37 @@ def _execute(self, request: DeleteTraceItemsRequest) -> DeleteTraceItemsResponse
51129 "parent_api" : "eap_delete_trace_items" ,
52130 }
53131
132+ # Build base conditions that apply to all deletions
133+ conditions : Dict [str , List [Any ]] = {
134+ "organization_id" : [request .meta .organization_id ],
135+ "project_id" : list (request .meta .project_ids ),
136+ }
137+
138+ attribute_conditions : Optional [AttributeConditions ] = None
139+
140+ if has_trace_ids :
141+ # Delete by trace_ids (no attribute filtering)
142+ conditions ["trace_id" ] = [str (tid ) for tid in request .trace_ids ]
143+ else :
144+ # Delete by filters (with attribute filtering)
145+ # item_type must be specified in the request metadata for attribute-based deletion
146+ if request .meta .trace_item_type == 0 : # TRACE_ITEM_TYPE_UNSPECIFIED
147+ raise BadSnubaRPCRequestException (
148+ "trace_item_type must be specified in metadata when using filters"
149+ )
150+
151+ attribute_conditions = _trace_item_filters_to_attribute_conditions (
152+ request .meta .trace_item_type ,
153+ request .filters ,
154+ )
155+ # Add item_type to conditions for the delete query
156+ conditions ["item_type" ] = [attribute_conditions .item_type ]
157+
54158 delete_result = delete_from_storage (
55159 get_writable_storage (StorageKey .EAP_ITEMS ),
56- {
57- "organization_id" : [request .meta .organization_id ],
58- "trace_id" : list (request .trace_ids ),
59- "project_id" : list (request .meta .project_ids ),
60- },
160+ conditions ,
61161 attribution_info ,
162+ attribute_conditions ,
62163 )
63164
64165 response = DeleteTraceItemsResponse ()
0 commit comments