planner/core: support named window (#8937)
This commit is contained in:
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user