planner/core: support named window (#8937)

This commit is contained in:
Haibin Xie
2019-01-07 15:35:57 +08:00
committed by GitHub
parent 39e1dfe4c2
commit 23c1946d21
4 changed files with 133 additions and 0 deletions

View File

@ -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
}

View File

@ -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 = "<unnamed window>"
}
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) {

View File

@ -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 '<unnamed 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)

View File

@ -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.