@@ -782,8 +782,7 @@ def convert_search_filter_to_snuba_query(search_filter, key=None):
782782 # allow snuba's prewhere optimizer to find this condition.
783783 return [name , search_filter .operator , value ]
784784 elif name == USER_DISPLAY_ALIAS :
785- # Slice off the function without an alias.
786- user_display_expr = FIELD_ALIASES [USER_DISPLAY_ALIAS ]["fields" ][0 ][0 :2 ]
785+ user_display_expr = FIELD_ALIASES [USER_DISPLAY_ALIAS ]["expression" ]
787786
788787 # Handle 'has' condition
789788 if search_filter .value .raw_value == "" :
@@ -1179,12 +1178,8 @@ def get_filter(query=None, params=None):
11791178FIELD_ALIASES = {
11801179 "project" : {"fields" : ["project.id" ], "column_alias" : "project.id" },
11811180 "issue" : {"fields" : ["issue.id" ], "column_alias" : "issue.id" },
1182- # TODO(mark) This alias doesn't work inside count_unique().
1183- # The column resolution code is really convoluted and expanding
1184- # it to support functions that need columns resolved inside of
1185- # aggregations is complicated. Until that gets sorted user.display
1186- # should not be added to the public field list/docs.
11871181 "user.display" : {
1182+ "expression" : ["coalesce" , ["user.email" , "user.username" , "user.ip" ]],
11881183 "fields" : [["coalesce" , ["user.email" , "user.username" , "user.ip" ], "user.display" ]],
11891184 "column_alias" : "user.display" ,
11901185 },
@@ -1231,19 +1226,39 @@ def has_default(self, params):
12311226 return False
12321227
12331228
1229+ class NullColumn (FunctionArg ):
1230+ """
1231+ Convert the provided column to null so that we
1232+ can drop it. Used to make count() not have a
1233+ required argument that we ignore.
1234+ """
1235+
1236+ def has_default (self , params ):
1237+ return None
1238+
1239+ def normalize (self , value ):
1240+ return None
1241+
1242+
12341243class CountColumn (FunctionArg ):
12351244 def has_default (self , params ):
12361245 return None
12371246
12381247 def normalize (self , value ):
12391248 if value is None :
1249+ raise InvalidFunctionArgument ("a column is required" )
1250+
1251+ if value not in FIELD_ALIASES :
12401252 return value
12411253
1242- # If we use an alias inside an aggregate, resolve it here
1243- if value in FIELD_ALIASES :
1244- value = FIELD_ALIASES [value ].get ("column_alias" , value )
1254+ alias = FIELD_ALIASES [value ]
12451255
1246- return value
1256+ # If the alias has an expression prefer that over the column alias
1257+ # This enables user.display to work in aggregates
1258+ if "expression" in alias :
1259+ return alias ["expression" ]
1260+
1261+ return alias .get ("column_alias" , value )
12471262
12481263
12491264class NumericColumn (FunctionArg ):
@@ -1410,7 +1425,7 @@ def has_default(self, params):
14101425 "column" : [
14111426 "multiply" ,
14121427 [
1413- ["floor" , [["divide" , [u"{ column}" , ArgValue ("bucket_size" )]]]],
1428+ ["floor" , [["divide" , [ArgValue ( " column" ) , ArgValue ("bucket_size" )]]]],
14141429 ArgValue ("bucket_size" ),
14151430 ],
14161431 None ,
@@ -1420,38 +1435,35 @@ def has_default(self, params):
14201435 "count_unique" : {
14211436 "name" : "count_unique" ,
14221437 "args" : [CountColumn ("column" )],
1423- "aggregate" : ["uniq" , u"{ column}" , None ],
1438+ "aggregate" : ["uniq" , ArgValue ( " column" ) , None ],
14241439 "result_type" : "integer" ,
14251440 },
1426- # TODO(evanh) Count doesn't accept parameters in the frontend, but we support it here
1427- # for backwards compatibility. Once we've migrated existing queries this should get
1428- # changed to accept no parameters.
14291441 "count" : {
14301442 "name" : "count" ,
1431- "args" : [CountColumn ("column" )],
1443+ "args" : [NullColumn ("column" )],
14321444 "aggregate" : ["count" , None , None ],
14331445 "result_type" : "integer" ,
14341446 },
14351447 "min" : {
14361448 "name" : "min" ,
14371449 "args" : [NumericColumnNoLookup ("column" )],
1438- "aggregate" : ["min" , u"{ column}" , None ],
1450+ "aggregate" : ["min" , ArgValue ( " column" ) , None ],
14391451 },
14401452 "max" : {
14411453 "name" : "max" ,
14421454 "args" : [NumericColumnNoLookup ("column" )],
1443- "aggregate" : ["max" , u"{ column}" , None ],
1455+ "aggregate" : ["max" , ArgValue ( " column" ) , None ],
14441456 },
14451457 "avg" : {
14461458 "name" : "avg" ,
14471459 "args" : [DurationColumnNoLookup ("column" )],
1448- "aggregate" : ["avg" , u"{ column}" , None ],
1460+ "aggregate" : ["avg" , ArgValue ( " column" ) , None ],
14491461 "result_type" : "duration" ,
14501462 },
14511463 "sum" : {
14521464 "name" : "sum" ,
14531465 "args" : [DurationColumnNoLookup ("column" )],
1454- "aggregate" : ["sum" , u"{ column}" , None ],
1466+ "aggregate" : ["sum" , ArgValue ( " column" ) , None ],
14551467 "result_type" : "duration" ,
14561468 },
14571469 # Currently only being used by the baseline PoC
@@ -1560,7 +1572,9 @@ def resolve_function(field, match=None, params=None):
15601572 aggregate [0 ] = aggregate [0 ].format (** arguments )
15611573 if isinstance (aggregate [1 ], six .string_types ):
15621574 aggregate [1 ] = aggregate [1 ].format (** arguments )
1563-
1575+ elif isinstance (aggregate [1 ], ArgValue ):
1576+ arg = aggregate [1 ].arg
1577+ aggregate [1 ] = arguments [arg ]
15641578 if aggregate [2 ] is None :
15651579 aggregate [2 ] = get_function_alias_with_columns (
15661580 function ["name" ], columns if not used_default else []
@@ -1744,6 +1758,10 @@ def resolve_field_list(fields, snuba_filter, auto_fields=True):
17441758 if column [0 ] == "transform" :
17451759 # When there's a project transform, we already group by project_id
17461760 continue
1761+ if column [2 ] == USER_DISPLAY_ALIAS :
1762+ # user.display needs to be grouped by its coalesce function
1763+ groupby .append (column )
1764+ continue
17471765 groupby .append (column [2 ])
17481766 else :
17491767 groupby .append (column )
0 commit comments