diff --git a/cmd/explaintest/main.go b/cmd/explaintest/main.go index 2ac30aa2b81cd..1fb0e8f238dd2 100644 --- a/cmd/explaintest/main.go +++ b/cmd/explaintest/main.go @@ -87,6 +87,7 @@ func newTester(name string) *tester { t.name = name t.enableQueryLog = true t.ctx = mock.NewContext() + t.ctx.GetSessionVars().EnableWindowFunction = true return t } diff --git a/cmd/explaintest/r/window_function.result b/cmd/explaintest/r/window_function.result new file mode 100644 index 0000000000000..c1da15629e80b --- /dev/null +++ b/cmd/explaintest/r/window_function.result @@ -0,0 +1,51 @@ +use test; +drop table if exists t; +create table t (a int, b int, c timestamp, index idx(a)); +set @@tidb_enable_window_function = 1; +explain select sum(a) over() from t; +id count task operator info +Projection_7 10000.00 root sum(a) over() +└─Window_8 10000.00 root sum(cast(test.t.a)) over() + └─TableReader_10 10000.00 root data:TableScan_9 + └─TableScan_9 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo +explain select sum(a) over(partition by a) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a) + └─IndexReader_11 10000.00 root index:IndexScan_10 + └─IndexScan_10 10000.00 cop table:t, index:a, range:[NULL,+inf], keep order:true, stats:pseudo +explain select sum(a) over(partition by a order by b) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a order by b) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a order by test.t.b asc) + └─Sort_14 10000.00 root test.t.a:asc, test.t.b:asc + └─TableReader_13 10000.00 root data:TableScan_12 + └─TableScan_12 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo +explain select sum(a) over(partition by a order by b rows unbounded preceding) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a order by b rows unbounded preceding) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a order by test.t.b asc rows between unbounded preceding and current row) + └─Sort_14 10000.00 root test.t.a:asc, test.t.b:asc + └─TableReader_13 10000.00 root data:TableScan_12 + └─TableScan_12 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo +explain select sum(a) over(partition by a order by b rows between 1 preceding and 1 following) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a order by b rows between 1 preceding and 1 following) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a order by test.t.b asc rows between 1 preceding and 1 following) + └─Sort_14 10000.00 root test.t.a:asc, test.t.b:asc + └─TableReader_13 10000.00 root data:TableScan_12 + └─TableScan_12 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo +explain select sum(a) over(partition by a order by b range between 1 preceding and 1 following) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a order by b range between 1 preceding and 1 following) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a order by test.t.b asc range between 1 preceding and 1 following) + └─Sort_14 10000.00 root test.t.a:asc, test.t.b:asc + └─TableReader_13 10000.00 root data:TableScan_12 + └─TableScan_12 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo +explain select sum(a) over(partition by a order by c range between interval '2:30' minute_second preceding and interval '2:30' minute_second following) from t; +id count task operator info +Projection_7 10000.00 root sum(a) over(partition by a order by c range between interval '2:30' minute_second preceding and interval '2:30' minute_second following) +└─Window_8 10000.00 root sum(cast(test.t.a)) over(partition by test.t.a order by test.t.c asc range between interval "2:30" "MINUTE_SECOND" preceding and interval "2:30" "MINUTE_SECOND" following) + └─Sort_14 10000.00 root test.t.a:asc, test.t.c:asc + └─TableReader_13 10000.00 root data:TableScan_12 + └─TableScan_12 10000.00 cop table:t, range:[-inf,+inf], keep order:false, stats:pseudo diff --git a/cmd/explaintest/t/window_function.test b/cmd/explaintest/t/window_function.test new file mode 100644 index 0000000000000..0659c4247c97a --- /dev/null +++ b/cmd/explaintest/t/window_function.test @@ -0,0 +1,11 @@ +use test; +drop table if exists t; +create table t (a int, b int, c timestamp, index idx(a)); +set @@tidb_enable_window_function = 1; +explain select sum(a) over() from t; +explain select sum(a) over(partition by a) from t; +explain select sum(a) over(partition by a order by b) from t; +explain select sum(a) over(partition by a order by b rows unbounded preceding) from t; +explain select sum(a) over(partition by a order by b rows between 1 preceding and 1 following) from t; +explain select sum(a) over(partition by a order by b range between 1 preceding and 1 following) from t; +explain select sum(a) over(partition by a order by c range between interval '2:30' minute_second preceding and interval '2:30' minute_second following) from t; diff --git a/planner/core/exhaust_physical_plans.go b/planner/core/exhaust_physical_plans.go index 6518c635d1817..67f89fe21c1c6 100644 --- a/planner/core/exhaust_physical_plans.go +++ b/planner/core/exhaust_physical_plans.go @@ -782,6 +782,7 @@ func (p *LogicalWindow) exhaustPhysicalPlans(prop *property.PhysicalProperty) [] WindowFuncDesc: p.WindowFuncDesc, PartitionBy: p.PartitionBy, OrderBy: p.OrderBy, + Frame: p.Frame, }.Init(p.ctx, p.stats.ScaleByExpectCnt(prop.ExpectedCnt), childProperty) window.SetSchema(p.Schema()) return []PhysicalPlan{window} diff --git a/planner/core/explain.go b/planner/core/explain.go index 44e9ef4c81099..6f19dc3b39464 100644 --- a/planner/core/explain.go +++ b/planner/core/explain.go @@ -17,6 +17,7 @@ import ( "bytes" "fmt" + "github.com/pingcap/parser/ast" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/expression/aggregation" ) @@ -298,8 +299,73 @@ func (p *PhysicalTopN) ExplainInfo() string { return buffer.String() } +func (p *PhysicalWindow) formatFrameBound(buffer *bytes.Buffer, bound *FrameBound) { + if bound.Type == ast.CurrentRow { + buffer.WriteString("current row") + return + } + if bound.UnBounded { + buffer.WriteString("unbounded") + } else if bound.DateCalcFunc != nil { + sf := bound.DateCalcFunc.(*expression.ScalarFunction) + // for `interval '2:30' minute_second`. + fmt.Fprintf(buffer, "interval %s %s", sf.GetArgs()[1].ExplainInfo(), sf.GetArgs()[2].ExplainInfo()) + } else { + fmt.Fprintf(buffer, "%d", bound.Num) + } + if bound.Type == ast.Preceding { + buffer.WriteString(" preceding") + } else { + buffer.WriteString(" following") + } +} + // ExplainInfo implements PhysicalPlan interface. func (p *PhysicalWindow) ExplainInfo() string { - // TODO: Add explain info for partition by, order by and frame. - return p.WindowFuncDesc.String() + buffer := bytes.NewBufferString(p.WindowFuncDesc.String()) + buffer.WriteString(" over(") + isFirst := true + if len(p.PartitionBy) > 0 { + buffer.WriteString("partition by ") + for i, item := range p.PartitionBy { + fmt.Fprintf(buffer, "%s", item.Col.ExplainInfo()) + if i+1 < len(p.PartitionBy) { + buffer.WriteString(", ") + } + } + isFirst = false + } + if len(p.OrderBy) > 0 { + if !isFirst { + buffer.WriteString(" ") + } + buffer.WriteString("order by ") + for i, item := range p.OrderBy { + order := "asc" + if item.Desc { + order = "desc" + } + fmt.Fprintf(buffer, "%s %s", item.Col.ExplainInfo(), order) + if i+1 < len(p.OrderBy) { + buffer.WriteString(", ") + } + } + isFirst = false + } + if p.Frame != nil { + if !isFirst { + buffer.WriteString(" ") + } + if p.Frame.Type == ast.Rows { + buffer.WriteString("rows") + } else { + buffer.WriteString("range") + } + buffer.WriteString(" between ") + p.formatFrameBound(buffer, p.Frame.Start) + buffer.WriteString(" and ") + p.formatFrameBound(buffer, p.Frame.End) + } + buffer.WriteString(")") + return buffer.String() } diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 3125676b79400..649335dd0ab2c 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -2051,23 +2051,23 @@ func (s *testPlanSuite) TestWindowFunction(c *C) { }{ { sql: "select a, avg(a) over(partition by a) from t", - result: "TableReader(Table(t))->Window(avg(cast(test.t.a)))->Projection", + result: "TableReader(Table(t))->Window(avg(cast(test.t.a)) over(partition by test.t.a))->Projection", }, { sql: "select a, avg(a) over(partition by b) from t", - result: "TableReader(Table(t))->Sort->Window(avg(cast(test.t.a)))->Projection", + result: "TableReader(Table(t))->Sort->Window(avg(cast(test.t.a)) over(partition by test.t.b))->Projection", }, { sql: "select a, avg(a+1) over(partition by (a+1)) from t", - result: "TableReader(Table(t))->Projection->Sort->Window(avg(cast(2_proj_window_3)))->Projection", + result: "TableReader(Table(t))->Projection->Sort->Window(avg(cast(2_proj_window_3)) over(partition by 2_proj_window_2))->Projection", }, { sql: "select a, avg(a) over(order by a asc, b desc) from t order by a asc, b desc", - result: "TableReader(Table(t))->Sort->Window(avg(cast(test.t.a)))->Projection", + result: "TableReader(Table(t))->Sort->Window(avg(cast(test.t.a)) over(order by test.t.a asc, test.t.b desc))->Projection", }, { sql: "select a, b as a, avg(a) over(partition by a) from t", - result: "TableReader(Table(t))->Window(avg(cast(test.t.a)))->Projection", + result: "TableReader(Table(t))->Window(avg(cast(test.t.a)) over(partition by test.t.a))->Projection", }, { sql: "select a, b as z, sum(z) over() from t", @@ -2075,27 +2075,27 @@ func (s *testPlanSuite) TestWindowFunction(c *C) { }, { sql: "select a, b as z from t order by (sum(z) over())", - result: "TableReader(Table(t))->Window(sum(cast(test.t.z)))->Sort->Projection", + result: "TableReader(Table(t))->Window(sum(cast(test.t.z)) over())->Sort->Projection", }, { sql: "select sum(avg(a)) over() from t", - result: "TableReader(Table(t)->StreamAgg)->StreamAgg->Window(sum(sel_agg_2))->Projection", + result: "TableReader(Table(t)->StreamAgg)->StreamAgg->Window(sum(sel_agg_2) over())->Projection", }, { sql: "select b from t order by(sum(a) over())", - result: "TableReader(Table(t))->Window(sum(cast(test.t.a)))->Sort->Projection", + result: "TableReader(Table(t))->Window(sum(cast(test.t.a)) over())->Sort->Projection", }, { sql: "select b from t order by(sum(a) over(partition by a))", - result: "TableReader(Table(t))->Window(sum(cast(test.t.a)))->Sort->Projection", + result: "TableReader(Table(t))->Window(sum(cast(test.t.a)) over(partition by test.t.a))->Sort->Projection", }, { sql: "select b from t order by(sum(avg(a)) over())", - result: "TableReader(Table(t)->StreamAgg)->StreamAgg->Window(sum(sel_agg_2))->Sort->Projection", + result: "TableReader(Table(t)->StreamAgg)->StreamAgg->Window(sum(sel_agg_2) over())->Sort->Projection", }, { sql: "select a from t having (select sum(a) over() as w from t tt where a > t.a)", - result: "Apply{TableReader(Table(t))->TableReader(Table(t)->Sel([gt(tt.a, test.t.a)]))->Window(sum(cast(tt.a)))->MaxOneRow->Sel([w])}->Projection", + result: "Apply{TableReader(Table(t))->TableReader(Table(t)->Sel([gt(tt.a, test.t.a)]))->Window(sum(cast(tt.a)) over())->MaxOneRow->Sel([w])}->Projection", }, { sql: "select avg(a) over() as w from t having w > 1", @@ -2135,11 +2135,11 @@ func (s *testPlanSuite) TestWindowFunction(c *C) { }, { sql: "select sum(a) over(w1), avg(a) over(w2) from t window w1 as (partition by a), w2 as (w1)", - result: "TableReader(Table(t))->Window(sum(cast(test.t.a)))->Window(avg(cast(test.t.a)))->Projection", + result: "TableReader(Table(t))->Window(sum(cast(test.t.a)) over(partition by test.t.a))->Window(avg(cast(test.t.a)) over())->Projection", }, { sql: "select a from t window w1 as (partition by a) order by (sum(a) over(w1))", - result: "TableReader(Table(t))->Window(sum(cast(test.t.a)))->Sort->Projection", + result: "TableReader(Table(t))->Window(sum(cast(test.t.a)) over(partition by test.t.a))->Sort->Projection", }, { sql: "select sum(a) over(groups 1 preceding) from t", diff --git a/planner/core/physical_plans.go b/planner/core/physical_plans.go index 12d829cf3f9d6..1e7ef13556085 100644 --- a/planner/core/physical_plans.go +++ b/planner/core/physical_plans.go @@ -383,4 +383,5 @@ type PhysicalWindow struct { WindowFuncDesc *aggregation.WindowFuncDesc PartitionBy []property.Item OrderBy []property.Item + Frame *WindowFrame } diff --git a/planner/core/stringer.go b/planner/core/stringer.go index 368b8fa66f1d6..b91e8348162b6 100644 --- a/planner/core/stringer.go +++ b/planner/core/stringer.go @@ -223,7 +223,7 @@ func toString(in Plan, strs []string, idxs []int) ([]string, []int) { case *LogicalWindow: str = fmt.Sprintf("Window(%s)", x.WindowFuncDesc.String()) case *PhysicalWindow: - str = fmt.Sprintf("Window(%s)", x.WindowFuncDesc.String()) + str = fmt.Sprintf("Window(%s)", x.ExplainInfo()) default: str = fmt.Sprintf("%T", in) }