@@ -3480,9 +3480,7 @@ def test_uniq_aggregation_with_default_value_double(self) -> None:
34803480 Column (
34813481 conditional_aggregation = AttributeConditionalAggregation (
34823482 aggregate = Function .FUNCTION_UNIQ ,
3483- key = AttributeKey (
3484- type = AttributeKey .TYPE_STRING , name = "user"
3485- ),
3483+ key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "user" ),
34863484 label = "count_unique(user)" ,
34873485 extrapolation_mode = ExtrapolationMode .EXTRAPOLATION_MODE_NONE ,
34883486 default_value_double = 0.0 ,
@@ -3527,9 +3525,7 @@ def test_uniq_formula_with_default_value_double(self) -> None:
35273525 left = Column (
35283526 conditional_aggregation = AttributeConditionalAggregation (
35293527 aggregate = Function .FUNCTION_UNIQ ,
3530- key = AttributeKey (
3531- type = AttributeKey .TYPE_STRING , name = "user"
3532- ),
3528+ key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "user" ),
35333529 label = "count_unique_a" ,
35343530 extrapolation_mode = ExtrapolationMode .EXTRAPOLATION_MODE_NONE ,
35353531 default_value_double = 0.0 ,
@@ -3539,9 +3535,7 @@ def test_uniq_formula_with_default_value_double(self) -> None:
35393535 right = Column (
35403536 conditional_aggregation = AttributeConditionalAggregation (
35413537 aggregate = Function .FUNCTION_UNIQ ,
3542- key = AttributeKey (
3543- type = AttributeKey .TYPE_STRING , name = "user"
3544- ),
3538+ key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "user" ),
35453539 label = "count_unique_b" ,
35463540 extrapolation_mode = ExtrapolationMode .EXTRAPOLATION_MODE_NONE ,
35473541 default_value_double = 0.0 ,
@@ -3744,6 +3738,18 @@ def _str_array(*values: str) -> AnyValue:
37443738 return AnyValue (array_value = ArrayValue (values = [AnyValue (string_value = v ) for v in values ]))
37453739
37463740
3741+ def _int_array (* values : int ) -> AnyValue :
3742+ return AnyValue (array_value = ArrayValue (values = [AnyValue (int_value = v ) for v in values ]))
3743+
3744+
3745+ def _double_array (* values : float ) -> AnyValue :
3746+ return AnyValue (array_value = ArrayValue (values = [AnyValue (double_value = v ) for v in values ]))
3747+
3748+
3749+ def _bool_array (* values : bool ) -> AnyValue :
3750+ return AnyValue (array_value = ArrayValue (values = [AnyValue (bool_value = v ) for v in values ]))
3751+
3752+
37473753class TestArrayWildcardSearch (BaseApiTest ):
37483754 @pytest .mark .clickhouse_db
37493755 @pytest .mark .redis_db
@@ -3835,6 +3841,198 @@ def test_not_like_filter_on_array_attribute(self) -> None:
38353841 # Only the item with ["success", "cached"] should match (no "error" elements)
38363842 assert len (response .column_values [0 ].results ) == 1
38373843
3844+ @pytest .mark .clickhouse_db
3845+ @pytest .mark .redis_db
3846+ def test_trace_item_table_array_op_equals_includes_string_ignore_case (self ) -> None :
3847+ """OP_EQUALS with ignore_case matches a string in a TYPE_ARRAY (element-wise)."""
3848+ span_ts = BASE_TIME - timedelta (minutes = 1 )
3849+ items_storage = get_storage (StorageKey ("eap_items" ))
3850+ write_raw_unprocessed_events (
3851+ items_storage , # type: ignore
3852+ [
3853+ gen_item_message (span_ts , attributes = {"tags" : _str_array ("ERROR" , "other" )}),
3854+ gen_item_message (
3855+ span_ts , attributes = {"tags" : _str_array ("success" , "cached" , "http-error" )}
3856+ ),
3857+ gen_item_message (span_ts , attributes = {"tags" : _str_array ("Error" , "timeout" )}),
3858+ ],
3859+ )
3860+ message = TraceItemTableRequest (
3861+ meta = RequestMeta (
3862+ project_ids = [1 , 2 , 3 ],
3863+ organization_id = 1 ,
3864+ cogs_category = "something" ,
3865+ referrer = "something" ,
3866+ start_timestamp = START_TIMESTAMP ,
3867+ end_timestamp = END_TIMESTAMP ,
3868+ trace_item_type = TraceItemType .TRACE_ITEM_TYPE_SPAN ,
3869+ ),
3870+ filter = TraceItemFilter (
3871+ comparison_filter = ComparisonFilter (
3872+ key = AttributeKey (
3873+ type = AttributeKey .TYPE_ARRAY ,
3874+ name = "tags" ,
3875+ ),
3876+ op = ComparisonFilter .OP_EQUALS ,
3877+ value = AttributeValue (val_str = "error" ),
3878+ ignore_case = True ,
3879+ )
3880+ ),
3881+ columns = [
3882+ Column (key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "sentry.item_id" )),
3883+ Column (key = AttributeKey (type = AttributeKey .TYPE_ARRAY , name = "tags" )),
3884+ ],
3885+ )
3886+ response = EndpointTraceItemTable ().execute (message )
3887+ assert len (response .column_values [0 ].results ) == 2
3888+ by_name = {cv .attribute_name : cv for cv in response .column_values }
3889+ for row in by_name ["tags" ].results :
3890+ vals = [e .val_str for e in row .val_array .values if e .WhichOneof ("value" ) == "val_str" ]
3891+ assert "error" in [val .lower () for val in vals ]
3892+
3893+ @pytest .mark .clickhouse_db
3894+ @pytest .mark .redis_db
3895+ def test_trace_item_table_array_op_equals_includes_int (self ) -> None :
3896+ """OP_EQUALS on TYPE_ARRAY with val_int=45 returns rows where some element is 45."""
3897+ span_ts = BASE_TIME - timedelta (minutes = 1 )
3898+ items_storage = get_storage (StorageKey ("eap_items" ))
3899+ write_raw_unprocessed_events (
3900+ items_storage , # type: ignore
3901+ [
3902+ gen_item_message (span_ts , attributes = {"frame_linenos" : _int_array (1 , 45 , 200 )}),
3903+ gen_item_message (span_ts , attributes = {"frame_linenos" : _int_array (10 , 20 )}),
3904+ gen_item_message (span_ts , attributes = {"frame_linenos" : _int_array (45 , 99 )}),
3905+ ],
3906+ )
3907+ message = TraceItemTableRequest (
3908+ meta = RequestMeta (
3909+ project_ids = [1 , 2 , 3 ],
3910+ organization_id = 1 ,
3911+ cogs_category = "something" ,
3912+ referrer = "something" ,
3913+ start_timestamp = START_TIMESTAMP ,
3914+ end_timestamp = END_TIMESTAMP ,
3915+ trace_item_type = TraceItemType .TRACE_ITEM_TYPE_SPAN ,
3916+ ),
3917+ filter = TraceItemFilter (
3918+ comparison_filter = ComparisonFilter (
3919+ key = AttributeKey (
3920+ type = AttributeKey .TYPE_ARRAY ,
3921+ name = "frame_linenos" ,
3922+ ),
3923+ op = ComparisonFilter .OP_EQUALS ,
3924+ value = AttributeValue (val_int = 45 ),
3925+ )
3926+ ),
3927+ columns = [
3928+ Column (key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "sentry.item_id" )),
3929+ Column (key = AttributeKey (type = AttributeKey .TYPE_ARRAY , name = "frame_linenos" )),
3930+ ],
3931+ )
3932+ response = EndpointTraceItemTable ().execute (message )
3933+ assert len (response .column_values [0 ].results ) == 2
3934+ by_name = {cv .attribute_name : cv for cv in response .column_values }
3935+ assert by_name ["frame_linenos" ].results [0 ].WhichOneof ("value" ) == "val_array"
3936+ for row in by_name ["frame_linenos" ].results :
3937+ int_vals = [
3938+ e .val_int for e in row .val_array .values if e .WhichOneof ("value" ) == "val_int"
3939+ ]
3940+ assert 45 in int_vals
3941+
3942+ @pytest .mark .clickhouse_db
3943+ @pytest .mark .redis_db
3944+ @pytest .mark .parametrize (
3945+ "attr_name,match_attrs,no_match_attrs,filter_value,check_row" ,
3946+ [
3947+ pytest .param (
3948+ "arr_eq_flt" ,
3949+ {"arr_eq_flt" : _double_array (0.0 , 1.5 , 2.0 )},
3950+ {"arr_eq_flt" : _double_array (0.1 , 0.2 )},
3951+ AttributeValue (val_float = 1.5 ),
3952+ lambda row : any (
3953+ isclose (e .val_double , 1.5 )
3954+ for e in row .val_array .values
3955+ if e .WhichOneof ("value" ) == "val_double"
3956+ )
3957+ or any (
3958+ isclose (e .val_float , 1.5 )
3959+ for e in row .val_array .values
3960+ if e .WhichOneof ("value" ) == "val_float"
3961+ ),
3962+ id = "val_float" ,
3963+ ),
3964+ pytest .param (
3965+ "arr_eq_dbl" ,
3966+ {"arr_eq_dbl" : _double_array (9.9 , 1.0 )},
3967+ {"arr_eq_dbl" : _double_array (0.0 , 0.0 )},
3968+ AttributeValue (val_double = 9.9 ),
3969+ lambda row : any (
3970+ e .WhichOneof ("value" ) == "val_double" and e .val_double == 9.9
3971+ for e in row .val_array .values
3972+ ),
3973+ id = "val_double" ,
3974+ ),
3975+ pytest .param (
3976+ "arr_eq_bool" ,
3977+ {"arr_eq_bool" : _bool_array (False , True )},
3978+ {"arr_eq_bool" : _bool_array (False , False )},
3979+ AttributeValue (val_bool = True ),
3980+ lambda row : any (
3981+ e .WhichOneof ("value" ) == "val_bool" and e .val_bool is True
3982+ for e in row .val_array .values
3983+ ),
3984+ id = "val_bool" ,
3985+ ),
3986+ ],
3987+ )
3988+ def test_trace_item_table_array_op_equals_all_scalar_rhs_types (
3989+ self ,
3990+ attr_name : str ,
3991+ match_attrs : dict [str , AnyValue ],
3992+ no_match_attrs : dict [str , AnyValue ],
3993+ filter_value : AttributeValue ,
3994+ check_row : Any ,
3995+ ) -> None :
3996+ """OP_EQUALS on TYPE_ARRAY: each scalar AttributeValue type matches a stored element"""
3997+ span_ts = BASE_TIME - timedelta (minutes = 1 )
3998+ items_storage = get_storage (StorageKey ("eap_items" ))
3999+ write_raw_unprocessed_events (
4000+ items_storage , # type: ignore
4001+ [
4002+ gen_item_message (span_ts , attributes = match_attrs ),
4003+ gen_item_message (span_ts , attributes = no_match_attrs ),
4004+ ],
4005+ )
4006+ message = TraceItemTableRequest (
4007+ meta = RequestMeta (
4008+ project_ids = [1 , 2 , 3 ],
4009+ organization_id = 1 ,
4010+ cogs_category = "something" ,
4011+ referrer = "something" ,
4012+ start_timestamp = START_TIMESTAMP ,
4013+ end_timestamp = END_TIMESTAMP ,
4014+ trace_item_type = TraceItemType .TRACE_ITEM_TYPE_SPAN ,
4015+ ),
4016+ filter = TraceItemFilter (
4017+ comparison_filter = ComparisonFilter (
4018+ key = AttributeKey (
4019+ type = AttributeKey .TYPE_ARRAY ,
4020+ name = attr_name ,
4021+ ),
4022+ op = ComparisonFilter .OP_EQUALS ,
4023+ value = filter_value ,
4024+ )
4025+ ),
4026+ columns = [
4027+ Column (key = AttributeKey (type = AttributeKey .TYPE_STRING , name = "sentry.item_id" )),
4028+ Column (key = AttributeKey (type = AttributeKey .TYPE_ARRAY , name = attr_name )),
4029+ ],
4030+ )
4031+ response = EndpointTraceItemTable ().execute (message )
4032+ by_name = {cv .attribute_name : cv for cv in response .column_values }
4033+ assert len (by_name [attr_name ].results ) == 1
4034+ assert check_row (by_name [attr_name ].results [0 ])
4035+
38384036
38394037class TestTraceItemTableArrayColumn (BaseApiTest ):
38404038 @pytest .mark .clickhouse_db
@@ -3881,10 +4079,7 @@ def test_select_array_column_returns_val_array(self) -> None:
38814079 "beta" ,
38824080 ]
38834081 assert by_name ["cols" ].results [0 ].WhichOneof ("value" ) == "val_array"
3884- assert [e .val_str for e in by_name ["cols" ].results [0 ].val_array .values ] == [
3885- "1" ,
3886- "3" ,
3887- ]
4082+ assert [e .val_int for e in by_name ["cols" ].results [0 ].val_array .values ] == [1 , 3 ]
38884083
38894084
38904085class TestUtils :
0 commit comments