Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add order by construct in window function and logical plans #463

Merged
merged 1 commit into from
Jun 3, 2021

Conversation

jimexist
Copy link
Member

@jimexist jimexist commented Jun 1, 2021

Which issue does this PR close?

add sort_by construct in window function and logical plans.

Related and partly implements #360

Rationale for this change

Implementing order by constructs and its logical planning by following a similar approach to PostgreSQL.

Coming up: adding window frame unit and type defs, and allow window functions to operate differently depending on RANGE or ROW window frame spec.

What changes are included in this PR?

  • update to ballista proto
  • add logical plan for window functions with order by
  • add unit test

Are there any user-facing changes?

@jimexist jimexist force-pushed the add-sort-in-window branch 4 times, most recently from faa28c6 to c4f2779 Compare June 2, 2021 03:04
@codecov-commenter
Copy link

codecov-commenter commented Jun 2, 2021

Codecov Report

Merging #463 (febc427) into master (c3fc0c7) will decrease coverage by 0.00%.
The diff coverage is 74.20%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #463      +/-   ##
==========================================
- Coverage   75.84%   75.83%   -0.01%     
==========================================
  Files         153      153              
  Lines       25876    26078     +202     
==========================================
+ Hits        19626    19777     +151     
- Misses       6250     6301      +51     
Impacted Files Coverage Δ
...sta/rust/core/src/serde/logical_plan/from_proto.rs 35.96% <0.00%> (-0.26%) ⬇️
...ta/rust/core/src/serde/physical_plan/from_proto.rs 38.79% <0.00%> (-1.00%) ⬇️
datafusion/src/optimizer/utils.rs 47.51% <0.00%> (-2.49%) ⬇️
datafusion/src/physical_plan/planner.rs 80.32% <0.00%> (-0.14%) ⬇️
...lista/rust/core/src/serde/logical_plan/to_proto.rs 62.48% <16.66%> (+0.07%) ⬆️
datafusion/src/logical_plan/plan.rs 81.06% <75.00%> (+0.43%) ⬆️
datafusion/src/optimizer/projection_push_down.rs 98.46% <87.50%> (+<0.01%) ⬆️
datafusion/src/sql/utils.rs 64.92% <91.26%> (+17.02%) ⬆️
datafusion/src/logical_plan/expr.rs 84.60% <91.66%> (+0.07%) ⬆️
datafusion/src/sql/planner.rs 84.37% <97.87%> (+0.26%) ⬆️
... and 13 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c3fc0c7...febc427. Read the comment docs.

@jimexist jimexist force-pushed the add-sort-in-window branch 2 times, most recently from 1684b78 to 177506a Compare June 2, 2021 07:13
@jimexist jimexist marked this pull request as ready for review June 2, 2021 07:14
@jimexist jimexist changed the title Add sort in window functions add sort_by construct in window function and logical plans Jun 2, 2021
@jimexist jimexist force-pushed the add-sort-in-window branch from 5aa2b4e to cc51a62 Compare June 2, 2021 07:38
@@ -317,14 +323,6 @@ message AggregateNode {
message WindowNode {
LogicalPlanNode input = 1;
repeated LogicalExprNode window_expr = 2;
repeated LogicalExprNode partition_by_expr = 3;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out these are no longer useful

@jimexist jimexist force-pushed the add-sort-in-window branch from cc51a62 to 3261152 Compare June 2, 2021 08:10
@jimexist jimexist changed the title add sort_by construct in window function and logical plans add order by construct in window function and logical plans Jun 2, 2021
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the code and tests carefully. Nice work @jimexist -- this is looking great.

@@ -174,6 +174,12 @@ message WindowExprNode {
// udaf = 3
}
LogicalExprNode expr = 4;
// repeated LogicalExprNode partition_by = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can probably just delete the old fields in the protobuf files -- I suspect no one is using them in a way that requires backwards compatibility

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these commented out ones are not old, they are reminders of future fields.

fun: fun.clone(),
args: expressions.to_vec(),
}),
Expr::WindowFunction { fun, .. } => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this pattern is kind of ugly -- at some point I would love to rewrite all this in terms of the expression visitors. Some day (TM) lol

let args = args
.iter()
.map(|e| {
self.create_physical_expr(e, physical_input_schema, ctx_state)
})
.collect::<Result<Vec<_>>>()?;
// if !order_by.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out? It seems a better idea to generate an error than to silently error out to me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would bring them back when it comes to implementing exec plan for sort, but maybe later

datafusion/src/sql/planner.rs Show resolved Hide resolved
&& window.order_by.is_empty()
&& window.window_frame.is_none()
{
if window.partition_by.is_empty() && window.window_frame.is_none() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the check for partition_by.is_empty() and window_frame.is_none() -- wouldn't we want to aways process the window clause?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they need to be empty otherwise it goes to the unsupported error clause which is needed to guard unintended usage.

quick_test(sql, expected);
}

/// psql result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for including the postgres results

/// -> Seq Scan on orders (cost=0.00..20.00 rows=1000 width=8)
/// ```
///
/// FIXME: for now we are not detecting prefix of sorting keys in order to save one sort exec phase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I think the FIXME is fine to do later -- let's get the functionality (with tests) working first and then we can optimize afterwards.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of room for optimization. there are several considerations when it comes to:

  1. how to compute order by and partition by and re-order them to optimize
  2. how to compute window aggregations given a possibility of either a ever-growing window or a shifting window (that can shrink and expand, depending on # of rows or the absolute values)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for 1, e.g. with max(a) over (partition by b order by c) you can either:

  1. hash partition by b, merge, and then sort by c
  2. sort by (b, c) so it's easier to implement but you lose parallelism here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for 2, for ever growing window, accumulative scan can be used, but for shrinking or shifting window, vec dequeue can be used, but also there's segment tree...

@@ -2749,14 +2772,139 @@ mod tests {
);
}

/// psql result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it really matters, but the max, min and avg window functions don't actually depend on order (and so in theory all of the sorts here could be optimized away.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they do. with order by they compute accumulative sum/avg/max/min, not a full partition one.

fun: WindowFunction::AggregateFunction(AggregateFunction::Max),
args: vec![col("name")],
order_by: vec![
Expr::Sort {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI you can use the sort method here for less verbosity if you want: https://docs.rs/datafusion/4.0.0/datafusion/logical_plan/enum.Expr.html#method.sort

So something like order_by: vec![col("age").sort(true, true)]

let sort_expr = col("foo").sort(true, true); // SORT ASC NULLS_FIRST

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the reminder - i plan to optimize this in subsequent PRs - as there would be more to comp

\n WindowAggr: windowExpr=[[MAX(#qty)]] partitionBy=[]\
\n Sort: #qty ASC NULLS FIRST, #order_id ASC NULLS FIRST\
\n WindowAggr: windowExpr=[[MIN(#qty)]] partitionBy=[]\
\n Sort: #order_id ASC NULLS FIRST, #qty ASC NULLS FIRST\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to remove the defaults (i.e. nulls first)

@alamb
Copy link
Contributor

alamb commented Jun 3, 2021

I am merging this one in so that @jimexist can continue with a minimum number of PRs open. Thanks again for keeping the 🚋 rolling!

@alamb alamb merged commit e82d053 into apache:master Jun 3, 2021
@jimexist jimexist deleted the add-sort-in-window branch June 4, 2021 00:04
@houqp houqp added ballista datafusion Changes in the datafusion crate enhancement New feature or request labels Jul 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
datafusion Changes in the datafusion crate enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants