|
23 | 23 | ) |
24 | 24 | from sentry_protos.snuba.v1.trace_item_filter_pb2 import ( |
25 | 25 | ComparisonFilter, |
| 26 | + ExistsFilter, |
26 | 27 | TraceItemFilter, |
27 | 28 | ) |
28 | 29 | from sentry_protos.snuba.v1.trace_item_pb2 import AnyValue, TraceItem |
@@ -83,6 +84,11 @@ def setup(self, eap: None, redis_db: None) -> None: |
83 | 84 | messages = [ |
84 | 85 | _item_message(self.base_time, SESSION_A, CONV_A, "red"), |
85 | 86 | _item_message(self.base_time + timedelta(minutes=1), SESSION_B, CONV_B, "blue"), |
| 87 | + # "green" sets neither: an unset conversation_id ingests as an empty |
| 88 | + # ai_conversation_id, so exists on gen_ai.conversation.id must exclude it. |
| 89 | + # (session_id is intentionally not exercised for exists: an unset session_id |
| 90 | + # ingests as a random non-nil UUID, so absence is indistinguishable there.) |
| 91 | + _item_message(self.base_time + timedelta(minutes=2), "", "", "green"), |
86 | 92 | ] |
87 | 93 | write_raw_unprocessed_events(get_writable_storage(StorageKey("eap_items")), messages) |
88 | 94 |
|
@@ -178,3 +184,17 @@ def test_filter_uses_skip_index_and_matches( |
178 | 184 | ) |
179 | 185 | assert colors == expected_colors |
180 | 186 | assert self._explain_uses_index(sql, expected_index), sql |
| 187 | + |
| 188 | + def test_exists_on_conversation_id_excludes_rows_without_a_value(self) -> None: |
| 189 | + # gen_ai.conversation.id is optional: the "green" span never set it, so its |
| 190 | + # ai_conversation_id is empty. exists() must treat empty as absent and exclude |
| 191 | + # it, rather than matching every row (a non-nullable column is never NULL, so a |
| 192 | + # plain isNotNull check would always be true). |
| 193 | + _, colors = self._execute( |
| 194 | + TraceItemFilter( |
| 195 | + exists_filter=ExistsFilter( |
| 196 | + key=AttributeKey(type=AttributeKey.TYPE_STRING, name="gen_ai.conversation.id") |
| 197 | + ) |
| 198 | + ) |
| 199 | + ) |
| 200 | + assert colors == ["blue", "red"] |
0 commit comments