Uh oh!
There was an error while loading. Please reload this page.
Add simplify method to aggregate function - #10354
Conversation
7325587 to
0e50d42Comparemilenkovicm
commented
May 2, 2024
the change is quite similar to #9906 there are quite a lot optional/unused parameters in the method call, but i personally find it easier to understand as it is equivalent to AggregateFunction signature. I wonder if |
42000ac to
f96a6d8Comparejsut thinking aloud, maybe if we change: pubenumExprSimplifyResult{/// The function call was simplified to an entirely new ExprSimplified(Expr),/// the function call could not be simplified, and the arguments/// are return unmodified.Original(Vec<Expr>),}to: pubenumExprSimplifyResult<T>{/// The function call was simplified to an entirely new ExprSimplified(Expr),/// the function call could not be simplified, and the arguments/// are return unmodified.Original(T),}simplify method would change from: pubfn simplify(&self,args:Vec<Expr>,distinct:&bool,filter:&Option<Box<Expr>>,order_by:&Option<Vec<Expr>>,null_treatment:&Option<NullTreatment>,info:&dynSimplifyInfo,) -> Result<ExprSimplifyResult>{to: pubfnsimplify(&self,args:Vec<Expr>,distinct:bool,filter:Option<Box<Expr>>,order_by:Option<Vec<Expr>>,null_treatment:Option<NullTreatment>,info:&dynSimplifyInfo,) -> Result<ExprSimplifyResult<(Vec<Expr>,bool,Option<Box<Expr>> ...)>>{}so in both cases, when we create replacement function or we re-assemble original function we would not need to clone parameters. maybe hand full of changes, from Downside is that we would have to re-create expression if no simplification, plus change would be backward incompatible |
| /// See [`AggregateUDFImpl::simplify`] for more details. | ||
| pub fn simplify( | ||
| &self, | ||
| args: Vec<Expr>, |
There was a problem hiding this comment.
I think we should reuse args in AggregateFunction, maybe create a new struct with AggregateArgs?
datafusion/datafusion/expr/src/expr.rs
Lines 568 to 582 in 8190cb9
There was a problem hiding this comment.
I don't know, mixed feelings about it. IMHO I see no benefits, would not simplify anything and another structure would be introduced.
There was a problem hiding this comment.
One benefit is that we could potentially add new fields without causing an API change 🤔
Another benefit could be that we could add a default method that recreated the expression. For example
implAggegateArgs{// recreate the original aggregate fucntionsfninto_expr(self) -> Expr{ ...}
...
}Another benefit is, as @jayzhan211 says, that it would be more consistent with the other APIs
There was a problem hiding this comment.
Do we change signature of pub struct AggregateFunction moving args, distinct, filter ... to AggegateArgs or we keep AggegateArgs as auxiliary structure?
jayzhan211
commented
May 4, 2024
I think it is a good idea. |
| fn simplify( | ||
| &self, | ||
| args: Vec<Expr>, | ||
| distinct: &bool, |
There was a problem hiding this comment.
should we also pass others args with owned value for rewrite like args: Vec<Expr>?
There was a problem hiding this comment.
I agree, if we make ExprSimplifyResult generic, as I pointed out in the comment. If we change it without change of ExprSimplifyResult it would lead to cloning of arguments even if simplify does nothing
There was a problem hiding this comment.
Given the work we have been doing on #9637 let's not add an API that forces cloning
Another potential thing is to use Transformed directlt
alamb
commented
May 6, 2024
I think if we are going to change the signature, I think we should use Transformed directly (and it is already parameterized) |
alamb
left a comment
There was a problem hiding this comment.
Thanks @milenkovicm and @jayzhan211 -- I agree this API is a little akward. It would be great to figure out how to avoid cloning when unecessary
| /// See [`AggregateUDFImpl::simplify`] for more details. | ||
| pub fn simplify( | ||
| &self, | ||
| args: Vec<Expr>, |
There was a problem hiding this comment.
One benefit is that we could potentially add new fields without causing an API change 🤔
Another benefit could be that we could add a default method that recreated the expression. For example
implAggegateArgs{// recreate the original aggregate fucntionsfninto_expr(self) -> Expr{ ...}
...
}Another benefit is, as @jayzhan211 says, that it would be more consistent with the other APIs
| ExprSimplifyResult::Simplified(expr) => Transformed::yes(expr), | ||
| }, | ||
| Expr::AggregateFunction(AggregateFunction { |
There was a problem hiding this comment.
It seems like in order to be able to make this API avoid copying, it would need to pass the distinct, filter, etc fields in and then get them back
Another thing we could do is to use Transformed directly
What if we made something like
structAggregateFunctionsArgs{args:Vec<Expr>,distinct:bool,filter:Option<Expr>,
order_by: ...
}And then the call to simplify to be
traitAggreagateUDFImpl{ ...
fnsimplify(&self,agg_args:AggregateFunctionsArgs,) -> Result<Transformed<AggregateFunctionsArgs>{Ok(Transformed::no(agg_args))}There was a problem hiding this comment.
I agree with AggregateFunctionsArgs, consistency part got me on board 😀
I'm not sure about Transformed part, reason being is that ExprSimplifyResult returns two types, Expr and currently Vec<Expr>
in that case simplify signature should be changed to:
traitAggreagateUDFImpl{ ...
fnsimplify(&self,agg_args:AggregateFunctionsArgs,) -> Result<Transformed<Expr>{let original :Expr = agg_args.into();Ok(Transformed::no(original))}Thus simplify function should return 're-composed' original function or new function ...
Anyway, I'll have a look what can be done, we can discuss
| fn simplify( | ||
| &self, | ||
| args: Vec<Expr>, | ||
| distinct: &bool, |
There was a problem hiding this comment.
Given the work we have been doing on #9637 let's not add an API that forces cloning
Another potential thing is to use Transformed directlt
f96a6d8 to
62d381eCompareAnother option could be to make a simplify return optional closure fnsimplify(&self) -> Option<Box<dynFn(AggregateFunction) -> datafusion_common::tree_node::Transformed<Expr>>>{None}in this case expr_simplifier part would become: Expr::AggregateFunction(AggregateFunction{func_def:AggregateFunctionDefinition::UDF(ref udaf), ..}) => {match(udaf.simplify(), expr){(Some(simplify),Expr::AggregateFunction(a)) => simplify(a),(_, e) => Transformed::no(e),}}this solution:
other issues:
|
Another option is to introduce Expr::AggregateFunction(AggregateFunction{func_def:AggregateFunctionDefinition::UDF(udaf),args:AggregateFunctionArgs,})if udaf.has_simplify() => {Transformed::yes(udaf.simplify()?)}
fn simplify() -> Result<Expr>The downside is that the user needs to both define |
milenkovicm
commented
May 8, 2024
I personally try to avoid situations like this when creating API, this should be documented that both functions have to be implemented plus it introduces more corner cases. Anyway, closure was a brain dump from yesterday, after a good sleep I don't think we need it, will try to elaborate in next comment |
milenkovicm
commented
May 8, 2024
Option 1 - Original expression as a parameterfnsimplify(&self,expr:Expr,) -> Result<datafusion_common::tree_node::Transformed<Expr>>{// we know it'll always be AggregateFunction but we have to match on it // which is not terribly hard but it makes api a bit odd ifletExpr::AggregateFunction(agg) = expr {
...Ok(datafusion_common::tree_node::Transformed::yes(...))}else{// no re-creation of expression if not used Ok(datafusion_common::tree_node::Transformed::no(expr))}}default implementation is just: fnsimplify(&self,expr:Expr,) -> Result<datafusion_common::tree_node::Transformed<Expr>>{// no re-creation of expression if not used Ok(datafusion_common::tree_node::Transformed::no(expr))}Option 2 - AggregationFunction as a parameterfnsimplify(&self,agg:AggregateFunction,) -> Result<datafusion_common::tree_node::Transformed<Expr>>{// we know its aggregate function, no need to do extra matching, but we need to re-create expression // issue user can return `no` with new expression in it Ok(datafusion_common::tree_node::Transformed::no(Expr::AggregateFunction(agg)))}Option 3 - AggregationFunction as a parameter with parametrised |
Given these 3, I prefer 2, because I think re-create expression is not a high cost that we should avoid. And we can do even further, If user does not implement simplify, it is equivalent to Transformed::no or ExprSimplifyResult::Original, so we can just have fnsimplify(&self,agg:AggregateFunction,) -> Result<Expr>{Ok(Expr::AggregateFunction(agg))} |
62d381e to
cd68b7fCompareI apologise for making noise, but it looks like all of the non closure options have issue with borrowing: It looks like |
d0e6842 to
de51434Comparemilenkovicm
commented
May 9, 2024
@jayzhan211 and @alamb whenever you get chance please have a look, IMHO I find proposal with closure to tick all the boxes, but this one is definitely simpler. |
After playing around, I feel like maybe optional closure is our answer 🤔 fnsimplify(&self,_info:&dynSimplifyInfo,) -> Option<Box<dynFn(AggregateArgs) -> Expr>>{} |
milenkovicm
commented
May 9, 2024
Damn! I should have created a branch with that code 😀, if we have consensus on that direction I'll redo it |
My final answer for today is // UDAFfn simplify(&self,args:AggregateArgs,_info:&dynSimplifyInfo,) -> Option<Box<dynFn(AggregateArgs) -> Result<Expr>>>{// UDF, not necessary but for consistencyfn simplify(&self,args:Vec<Expr>,_info:&dynSimplifyInfo,) -> Option<Box<dynFn(Vec<Expr>) -> Result<Expr>>>{Go with optional closure! |
milenkovicm
commented
May 9, 2024
you mean // UDFfn simplify(&self,) -> Option<Box<dynFn(AggregateFunction,&dynSimplifyInfo) -> Result<Expr>>>{or // UDFfn simplify(&self,info:&dynSimplifyInfo) -> Option<Box<dynFn(AggregateFunction) -> Result<Expr>>>{ |
jayzhan211
commented
May 9, 2024
It should be this. // UDFfn simplify(&self,) -> Option<Box<dynFn(AggregateFunction,&dynSimplifyInfo) -> Result<Expr>>>{The reason for optional closure is that I assume we need to return |
milenkovicm
commented
May 9, 2024
We're on the same page @jayzhan211 |
If we don't need pubfnsimplify(&self,args:AggregateArgs,info:&dynSimplifyInfo,) -> Result<Transformed<AggregateArgs>>{}I think the next problem is whether we need |
milenkovicm
commented
May 9, 2024
I think we need Expr, this way we can replace function with something else |
jayzhan211
commented
May 9, 2024
I agree. |
jayzhan211
commented
May 9, 2024
You can define closure in |
47108f1 to
a678e6dCompare
alamb
left a comment
There was a problem hiding this comment.
Thanks @milenkovicm and @jayzhan211 -- I think it looks good and the API is clean. Let's give it a try 🚀
Looks like there is a small conflict to resolve and then we'll be good to go
a678e6d to
c40690cComparesimplify method to aggregate functionalamb
commented
May 13, 2024
Thanks again @jayzhan211 and @milenkovicm 🙏 |
* add simplify method for aggregate function * simplify returns closure
Which issue does this PR close?
Closes#9526.
Rationale for this change
What changes are included in this PR?
Are these changes tested?
yes, added new test and example
Are there any user-facing changes?
no, changes are backward compatible