From 23c1946d2195e51fd54fc783acb0759afae130e6 Mon Sep 17 00:00:00 2001 From: Haibin Xie Date: Mon, 7 Jan 2019 15:35:57 +0800 Subject: [PATCH] planner/core: support named window (#8937) --- planner/core/errors.go | 18 +++++++ planner/core/logical_plan_builder.go | 77 ++++++++++++++++++++++++++++ planner/core/logical_plan_test.go | 36 +++++++++++++ planner/core/planbuilder.go | 2 + 4 files changed, 133 insertions(+) diff --git a/planner/core/errors.go b/planner/core/errors.go index 6cf557cb24..562657db2b 100644 --- a/planner/core/errors.go +++ b/planner/core/errors.go @@ -54,6 +54,12 @@ const ( codePrivilegeCheckFail = mysql.ErrUnknown codeWindowInvalidWindowFuncUse = mysql.ErrWindowInvalidWindowFuncUse codeWindowInvalidWindowFuncAliasUse = mysql.ErrWindowInvalidWindowFuncAliasUse + codeWindowNoSuchWindow = mysql.ErrWindowNoSuchWindow + codeWindowCircularityInWindowGraph = mysql.ErrWindowCircularityInWindowGraph + codeWindowNoChildPartitioning = mysql.ErrWindowNoChildPartitioning + codeWindowNoInherentFrame = mysql.ErrWindowNoInherentFrame + codeWindowNoRedefineOrderBy = mysql.ErrWindowNoRedefineOrderBy + codeWindowDuplicateName = mysql.ErrWindowDuplicateName ) // error definitions. @@ -94,6 +100,12 @@ var ( ErrPrivilegeCheckFail = terror.ClassOptimizer.New(codePrivilegeCheckFail, "privilege check fail") ErrWindowInvalidWindowFuncUse = terror.ClassOptimizer.New(codeWindowInvalidWindowFuncUse, mysql.MySQLErrName[mysql.ErrWindowInvalidWindowFuncUse]) ErrWindowInvalidWindowFuncAliasUse = terror.ClassOptimizer.New(codeWindowInvalidWindowFuncAliasUse, mysql.MySQLErrName[mysql.ErrWindowInvalidWindowFuncAliasUse]) + ErrWindowNoSuchWindow = terror.ClassOptimizer.New(codeWindowNoSuchWindow, mysql.MySQLErrName[mysql.ErrWindowNoSuchWindow]) + ErrWindowCircularityInWindowGraph = terror.ClassOptimizer.New(codeWindowCircularityInWindowGraph, mysql.MySQLErrName[mysql.ErrWindowCircularityInWindowGraph]) + ErrWindowNoChildPartitioning = terror.ClassOptimizer.New(codeWindowNoChildPartitioning, mysql.MySQLErrName[mysql.ErrWindowNoChildPartitioning]) + ErrWindowNoInherentFrame = terror.ClassOptimizer.New(codeWindowNoInherentFrame, mysql.MySQLErrName[mysql.ErrWindowNoInherentFrame]) + ErrWindowNoRedefineOrderBy = terror.ClassOptimizer.New(codeWindowNoRedefineOrderBy, mysql.MySQLErrName[mysql.ErrWindowNoRedefineOrderBy]) + ErrWindowDuplicateName = terror.ClassOptimizer.New(codeWindowDuplicateName, mysql.MySQLErrName[mysql.ErrWindowDuplicateName]) ) func init() { @@ -124,6 +136,12 @@ func init() { codeWindowInvalidWindowFuncUse: mysql.ErrWindowInvalidWindowFuncUse, codeWindowInvalidWindowFuncAliasUse: mysql.ErrWindowInvalidWindowFuncAliasUse, + codeWindowNoSuchWindow: mysql.ErrWindowNoSuchWindow, + codeWindowCircularityInWindowGraph: mysql.ErrWindowCircularityInWindowGraph, + codeWindowNoChildPartitioning: mysql.ErrWindowNoChildPartitioning, + codeWindowNoInherentFrame: mysql.ErrWindowNoInherentFrame, + codeWindowNoRedefineOrderBy: mysql.ErrWindowNoRedefineOrderBy, + codeWindowDuplicateName: mysql.ErrWindowDuplicateName, } terror.ErrClassToMySQLCodes[terror.ClassOptimizer] = mysqlErrCodeMap } diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 1199dc80fe..a65812350e 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -1878,6 +1878,11 @@ func (b *PlanBuilder) buildSelect(sel *ast.SelectStmt) (p LogicalPlan, err error } } + b.windowSpecs, err = buildWindowSpecs(sel.WindowSpecs) + if err != nil { + return nil, err + } + if hasWindowFuncField { // Now we build the window function fields. p, oldLen, err = b.buildProjection(p, sel.Fields.Fields, windowMap, true) @@ -2592,6 +2597,16 @@ func (b *PlanBuilder) buildProjectionForWindow(p LogicalPlan, expr *ast.WindowFu var items []*ast.ByItem spec := expr.Spec + if spec.Ref.L != "" { + ref, ok := b.windowSpecs[spec.Ref.L] + if !ok { + return nil, nil, nil, ErrWindowNoSuchWindow.GenWithStackByArgs(spec.Ref.O) + } + err := mergeWindowSpec(&spec, &ref) + if err != nil { + return nil, nil, nil, err + } + } if spec.PartitionBy != nil { items = append(items, spec.PartitionBy.Items...) } @@ -2679,6 +2694,68 @@ func (b *PlanBuilder) buildWindowFunction(p LogicalPlan, expr *ast.WindowFuncExp return window, nil } +// resolveWindowSpec resolve window specifications for sql like `select ... from t window w1 as (w2), w2 as (partition by a)`. +// We need to resolve the referenced window to get the definition of current window spec. +func resolveWindowSpec(spec *ast.WindowSpec, specs map[string]ast.WindowSpec, inStack map[string]bool) error { + if inStack[spec.Name.L] { + return errors.Trace(ErrWindowCircularityInWindowGraph) + } + if spec.Ref.L == "" { + return nil + } + ref, ok := specs[spec.Ref.L] + if !ok { + return ErrWindowNoSuchWindow.GenWithStackByArgs(spec.Ref.O) + } + inStack[spec.Name.L] = true + err := resolveWindowSpec(&ref, specs, inStack) + if err != nil { + return err + } + inStack[spec.Name.L] = false + return mergeWindowSpec(spec, &ref) +} + +func mergeWindowSpec(spec, ref *ast.WindowSpec) error { + if ref.Frame != nil { + return ErrWindowNoInherentFrame.GenWithStackByArgs(ref.Name.O) + } + if ref.OrderBy != nil { + if spec.OrderBy != nil { + name := spec.Name.O + if name == "" { + name = "" + } + return ErrWindowNoRedefineOrderBy.GenWithStackByArgs(name, ref.Name.O) + } + spec.OrderBy = ref.OrderBy + } + if spec.PartitionBy != nil { + return errors.Trace(ErrWindowNoChildPartitioning) + } + spec.PartitionBy = ref.PartitionBy + spec.Ref = model.NewCIStr("") + return nil +} + +func buildWindowSpecs(specs []ast.WindowSpec) (map[string]ast.WindowSpec, error) { + specsMap := make(map[string]ast.WindowSpec, len(specs)) + for _, spec := range specs { + if _, ok := specsMap[spec.Name.L]; ok { + return nil, ErrWindowDuplicateName.GenWithStackByArgs(spec.Name.O) + } + specsMap[spec.Name.L] = spec + } + inStack := make(map[string]bool, len(specs)) + for _, spec := range specs { + err := resolveWindowSpec(&spec, specsMap, inStack) + if err != nil { + return nil, err + } + } + return specsMap, nil +} + // extractTableList extracts all the TableNames from node. func extractTableList(node ast.ResultSetNode, input []*ast.TableName) []*ast.TableName { switch x := node.(type) { diff --git a/planner/core/logical_plan_test.go b/planner/core/logical_plan_test.go index 58c55b2b0a..677b4ad2a7 100644 --- a/planner/core/logical_plan_test.go +++ b/planner/core/logical_plan_test.go @@ -1960,6 +1960,42 @@ func (s *testPlanSuite) TestWindowFunction(c *C) { sql: "select sum(a) over() as sum_a from t group by sum_a", result: "[planner:1247]Reference 'sum_a' not supported (reference to window function)", }, + { + sql: "select sum(a) over() from t window w1 as (w2)", + result: "[planner:3579]Window name 'w2' is not defined.", + }, + { + sql: "select sum(a) over(w) from t", + result: "[planner:3579]Window name 'w' is not defined.", + }, + { + sql: "select sum(a) over() from t window w1 as (w2), w2 as (w1)", + result: "[planner:3580]There is a circularity in the window dependency graph.", + }, + { + sql: "select sum(a) over(w partition by a) from t window w as ()", + result: "[planner:3581]A window which depends on another cannot define partitioning.", + }, + { + sql: "select sum(a) over(w) from t window w as (rows between 1 preceding AND 1 following)", + result: "[planner:3582]Window 'w' has a frame definition, so cannot be referenced by another window.", + }, + { + sql: "select sum(a) over(w order by b) from t window w as (order by a)", + result: "[planner:3583]Window '' cannot inherit 'w' since both contain an ORDER BY clause.", + }, + { + sql: "select sum(a) over() from t window w1 as (), w1 as ()", + result: "[planner:3591]Window 'w1' is defined twice.", + }, + { + 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(test.t.a))->Window(avg(test.t.a))->Projection", + }, + { + sql: "select a from t window w1 as (partition by a) order by (sum(a) over(w1))", + result: "TableReader(Table(t))->Window(sum(test.t.a))->Sort->Projection", + }, } s.Parser.EnableWindowFunc(true) diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index ee723274ff..ccd0f72e2f 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -137,6 +137,8 @@ type PlanBuilder struct { // inStraightJoin represents whether the current "SELECT" statement has // "STRAIGHT_JOIN" option. inStraightJoin bool + + windowSpecs map[string]ast.WindowSpec } // GetVisitInfo gets the visitInfo of the PlanBuilder.