*: support outer query for correlated sub query.
This commit is contained in:
@ -57,7 +57,13 @@ type Planner interface {
|
||||
|
||||
// Row represents a record row.
|
||||
type Row struct {
|
||||
Data []interface{}
|
||||
// Data is the output record data for current Plan.
|
||||
Data []interface{}
|
||||
DataFields []*field.ResultField
|
||||
// FromData is the first origin record data, generated by From.
|
||||
FromData []interface{}
|
||||
FromDataFields []*field.ResultField
|
||||
|
||||
RowKeys []*RowKeyEntry
|
||||
}
|
||||
|
||||
|
||||
@ -67,7 +67,12 @@ func (r *SelectFieldsDefaultPlan) Next(ctx context.Context) (row *plan.Row, err
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
r.evalArgs[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.Src.GetFields(), srcRow.Data, field.DefaultFieldFlag)
|
||||
v, err0 := GetIdentValue(name, r.Src.GetFields(), srcRow.Data, field.DefaultFieldFlag)
|
||||
if err0 == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
row = &plan.Row{
|
||||
Data: make([]interface{}, len(r.Fields)),
|
||||
|
||||
@ -96,7 +96,13 @@ func (r *GroupByDefaultPlan) evalGroupKey(ctx context.Context, k []interface{},
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return r.getFieldValueByName(name, outRow)
|
||||
v, err = r.getFieldValueByName(name, outRow)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
|
||||
m[expressions.ExprEvalPositionFunc] = func(position int) (interface{}, error) {
|
||||
@ -128,7 +134,13 @@ func (r *GroupByDefaultPlan) getFieldValueByName(name string, out []interface{})
|
||||
func (r *GroupByDefaultPlan) evalNoneAggFields(ctx context.Context, out []interface{},
|
||||
m map[interface{}]interface{}, in []interface{}) error {
|
||||
m[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.Src.GetFields(), in, field.DefaultFieldFlag)
|
||||
v, err := GetIdentValue(name, r.Src.GetFields(), in, field.DefaultFieldFlag)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -150,7 +162,13 @@ func (r *GroupByDefaultPlan) evalAggFields(ctx context.Context, out []interface{
|
||||
for i := range r.AggFields {
|
||||
if i < r.HiddenFieldOffset {
|
||||
m[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.Src.GetFields(), in, field.DefaultFieldFlag)
|
||||
v, err := GetIdentValue(name, r.Src.GetFields(), in, field.DefaultFieldFlag)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
} else {
|
||||
// having may contain aggregate function and we will add it to hidden field,
|
||||
@ -166,7 +184,13 @@ func (r *GroupByDefaultPlan) evalAggFields(ctx context.Context, out []interface{
|
||||
|
||||
// if we can not find in table, we will try to find in un-hidden select list
|
||||
// only hidden field can use this
|
||||
return r.getFieldValueByName(name, out)
|
||||
v, err = r.getFieldValueByName(name, out)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +337,7 @@ func (r *GroupByDefaultPlan) fetchAll(ctx context.Context) error {
|
||||
if err != nil || out == nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
row := &plan.Row{Data: out}
|
||||
row := &plan.Row{Data: out, DataFields: r.ResultFields}
|
||||
r.rows = append(r.rows, &groupRow{Row: row, Args: map[interface{}]interface{}{}})
|
||||
} else {
|
||||
for _, row := range r.rows {
|
||||
|
||||
@ -63,7 +63,13 @@ func (r *HavingPlan) Next(ctx context.Context) (row *plan.Row, err error) {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
r.evalArgs[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.Src.GetFields(), srcRow.Data, field.CheckFieldFlag)
|
||||
v, err0 := GetIdentValue(name, r.Src.GetFields(), srcRow.Data, field.CheckFieldFlag)
|
||||
if err0 == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
r.evalArgs[expressions.ExprEvalPositionFunc] = func(position int) (interface{}, error) {
|
||||
// position is in [1, len(fields)], so we must decrease 1 to get correct index
|
||||
|
||||
@ -158,7 +158,13 @@ func (r *OrderByDefaultPlan) fetchAll(ctx context.Context) error {
|
||||
break
|
||||
}
|
||||
evalArgs[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.ResultFields, row.Data, field.CheckFieldFlag)
|
||||
v, err := GetIdentValue(name, r.ResultFields, row.Data, field.CheckFieldFlag)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
|
||||
evalArgs[expressions.ExprEvalPositionFunc] = func(position int) (interface{}, error) {
|
||||
|
||||
186
plan/plans/outer_query.go
Normal file
186
plan/plans/outer_query.go
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright 2015 PingCAP, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package plans
|
||||
|
||||
import (
|
||||
"github.com/juju/errors"
|
||||
"github.com/pingcap/tidb/context"
|
||||
"github.com/pingcap/tidb/expression"
|
||||
"github.com/pingcap/tidb/field"
|
||||
"github.com/pingcap/tidb/plan"
|
||||
"github.com/pingcap/tidb/util/format"
|
||||
)
|
||||
|
||||
// Phase type for plan.
|
||||
const (
|
||||
FromPhase = iota + 1
|
||||
WherePhase
|
||||
LockPhase
|
||||
GroupByPhase
|
||||
HavingPhase
|
||||
SelectFieldsPhase
|
||||
DistinctPhase
|
||||
OrderByPhase
|
||||
LimitPhase
|
||||
BeforeFinalPhase
|
||||
FinalPhase
|
||||
)
|
||||
|
||||
// OuterQueryPlan is for subquery fetching value from outer query.
|
||||
// e.g, select c1 from t where c1 = (select c2 from t2 where t2.c2 = t.c2).
|
||||
// You can see that the subquery uses outer table t's column in where condition.
|
||||
type OuterQueryPlan struct {
|
||||
Src plan.Plan
|
||||
|
||||
// SrcPhase is the phase for the src plan.
|
||||
SrcPhase int
|
||||
|
||||
HiddenFieldOffset int
|
||||
}
|
||||
|
||||
// Explain implements the plan.Plan Explain interface.
|
||||
func (p *OuterQueryPlan) Explain(w format.Formatter) {
|
||||
p.Src.Explain(w)
|
||||
}
|
||||
|
||||
// GetFields implements the plan.Plan GetFields interface.
|
||||
func (p *OuterQueryPlan) GetFields() []*field.ResultField {
|
||||
return p.Src.GetFields()
|
||||
}
|
||||
|
||||
// Filter implements the plan.Plan Filter interface.
|
||||
func (p *OuterQueryPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
|
||||
r, b, err := p.Src.Filter(ctx, expr)
|
||||
if !b {
|
||||
return r, false, errors.Trace(err)
|
||||
}
|
||||
|
||||
p.Src = r
|
||||
return p, true, nil
|
||||
}
|
||||
|
||||
// Next implements the plan.Plan Next interface.
|
||||
func (p *OuterQueryPlan) Next(ctx context.Context) (*plan.Row, error) {
|
||||
row, err := p.Src.Next(ctx)
|
||||
if row == nil || err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// We can let subquery fetching outer table data only after the following phase.
|
||||
switch p.SrcPhase {
|
||||
case FromPhase:
|
||||
// Here row.Data is the whole data from table.
|
||||
// We set it to FromData so that following phase can access the table reference.
|
||||
row.FromData = row.Data
|
||||
row.FromDataFields = p.Src.GetFields()
|
||||
pushOuterQuery(ctx, row)
|
||||
case WherePhase, LockPhase:
|
||||
updateTopOuterQuery(ctx, row)
|
||||
case GroupByPhase, SelectFieldsPhase, HavingPhase, DistinctPhase:
|
||||
row.DataFields = p.Src.GetFields()[0:p.HiddenFieldOffset]
|
||||
updateTopOuterQuery(ctx, row)
|
||||
case BeforeFinalPhase:
|
||||
popOuterQuery(ctx)
|
||||
}
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// Close implements the plan.Plan Close interface.
|
||||
func (p *OuterQueryPlan) Close() error {
|
||||
return p.Src.Close()
|
||||
}
|
||||
|
||||
// A dummy type to avoid naming collision in context.
|
||||
type outerQueryKeyType int
|
||||
|
||||
// String defines a Stringer function for debugging and pretty printing.
|
||||
func (k outerQueryKeyType) String() string {
|
||||
return "outer query"
|
||||
}
|
||||
|
||||
// outerQueryKey is used to retrive outer table references for sub query.
|
||||
const outerQueryKey outerQueryKeyType = 0
|
||||
|
||||
// outerQuery saves the outer table references.
|
||||
// For every select, we will push a outerQuery to a stack for inner sub query use,
|
||||
// and the top outerQuery is for current select.
|
||||
// e.g, select c1 from t1 where c2 = (select c1 from t2 where t2.c1 = t1.c2 limit 1),
|
||||
// the "select c1 from t1" is the outer query for the sub query in where phase, we will
|
||||
// first push a outerQuery to the stack saving the row data for "select c1 from t1", then
|
||||
// push the second outerQuery to the stack saving the row data for "select c1 from t2".
|
||||
type outerQuery struct {
|
||||
// outer is the last outer table reference.
|
||||
last *outerQuery
|
||||
row *plan.Row
|
||||
}
|
||||
|
||||
// We will push a outerQuery after the from phase and pop it before the final phase.
|
||||
// So we can guarantee that there is at least one outerQuery certainly.
|
||||
func pushOuterQuery(ctx context.Context, row *plan.Row) {
|
||||
t := &outerQuery{
|
||||
row: row,
|
||||
}
|
||||
|
||||
last := ctx.Value(outerQueryKey)
|
||||
if last != nil {
|
||||
// must be outerQuery
|
||||
t.last = last.(*outerQuery)
|
||||
}
|
||||
|
||||
ctx.SetValue(outerQueryKey, t)
|
||||
}
|
||||
|
||||
func getOuterQuery(ctx context.Context) *outerQuery {
|
||||
v := ctx.Value(outerQueryKey)
|
||||
// Cannot empty and must be outerQuery
|
||||
t := v.(*outerQuery)
|
||||
return t
|
||||
}
|
||||
|
||||
func popOuterQuery(ctx context.Context) {
|
||||
t := getOuterQuery(ctx)
|
||||
if t.last == nil {
|
||||
ctx.ClearValue(outerQueryKey)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetValue(outerQueryKey, t.last)
|
||||
}
|
||||
|
||||
func updateTopOuterQuery(ctx context.Context, row *plan.Row) {
|
||||
t := getOuterQuery(ctx)
|
||||
t.row = row
|
||||
}
|
||||
|
||||
func getIdentValueFromOuterQuery(ctx context.Context, name string) (interface{}, error) {
|
||||
t := getOuterQuery(ctx)
|
||||
// The top is current outerQuery, use its last.
|
||||
t = t.last
|
||||
for ; t != nil; t = t.last {
|
||||
// first try to get from outer table reference.
|
||||
v, err := GetIdentValue(name, t.row.FromDataFields, t.row.FromData, field.DefaultFieldFlag)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// then try to get from outer select list.
|
||||
v, err = GetIdentValue(name, t.row.DataFields, t.row.Data, field.FieldNameFlag)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("can not find value from outer query for field %s", name)
|
||||
}
|
||||
@ -58,7 +58,13 @@ func (r *FilterDefaultPlan) Next(ctx context.Context) (row *plan.Row, err error)
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
r.evalArgs[expressions.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
||||
return GetIdentValue(name, r.GetFields(), row.Data, field.DefaultFieldFlag)
|
||||
v, err0 := GetIdentValue(name, r.GetFields(), row.Data, field.DefaultFieldFlag)
|
||||
if err0 == nil {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// try to find in outer query
|
||||
return getIdentValueFromOuterQuery(ctx, name)
|
||||
}
|
||||
var meet bool
|
||||
meet, err = r.meetCondition(ctx)
|
||||
|
||||
37
rset/rsets/outer_query.go
Normal file
37
rset/rsets/outer_query.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 PingCAP, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rsets
|
||||
|
||||
import (
|
||||
"github.com/pingcap/tidb/context"
|
||||
"github.com/pingcap/tidb/plan"
|
||||
"github.com/pingcap/tidb/plan/plans"
|
||||
)
|
||||
|
||||
var (
|
||||
_ plan.Planner = (*OuterQueryRset)(nil)
|
||||
)
|
||||
|
||||
// OuterQueryRset is to generate OuterQueryPlan,
|
||||
// so that sub query can fetch value from the table reference from outer query.
|
||||
type OuterQueryRset struct {
|
||||
Src plan.Plan
|
||||
SrcPhase int
|
||||
HiddenFieldOffset int
|
||||
}
|
||||
|
||||
// Plan gets OuterQueryPlan.
|
||||
func (r *OuterQueryRset) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
return &plans.OuterQueryPlan{Src: r.Src, SrcPhase: r.SrcPhase, HiddenFieldOffset: r.HiddenFieldOffset}, nil
|
||||
}
|
||||
40
rset/rsets/outer_query_test.go
Normal file
40
rset/rsets/outer_query_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2015 PingCAP, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rsets
|
||||
|
||||
import (
|
||||
. "github.com/pingcap/check"
|
||||
"github.com/pingcap/tidb/plan/plans"
|
||||
)
|
||||
|
||||
var _ = Suite(&testOuterQueryRsetSuite{})
|
||||
|
||||
type testOuterQueryRsetSuite struct {
|
||||
r *OuterQueryRset
|
||||
}
|
||||
|
||||
func (s *testOuterQueryRsetSuite) SetUpSuite(c *C) {
|
||||
names := []string{"id", "name"}
|
||||
tblPlan := newTestTablePlan(testData, names)
|
||||
|
||||
s.r = &OuterQueryRset{Src: tblPlan, HiddenFieldOffset: len(tblPlan.GetFields()), SrcPhase: plans.FromPhase}
|
||||
}
|
||||
|
||||
func (s *testOuterQueryRsetSuite) TestOuterQueryRsetPlan(c *C) {
|
||||
p, err := s.r.Plan(nil)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, ok := p.(*plans.OuterQueryPlan)
|
||||
c.Assert(ok, IsTrue)
|
||||
}
|
||||
@ -140,11 +140,17 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
|
||||
}
|
||||
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.FromPhase,
|
||||
HiddenFieldOffset: 0}).Plan(ctx)
|
||||
|
||||
if w := s.Where; w != nil {
|
||||
r, err = (&rsets.WhereRset{Expr: w.Expr, Src: r}).Plan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.WherePhase,
|
||||
HiddenFieldOffset: 0}).Plan(ctx)
|
||||
}
|
||||
lock := s.Lock
|
||||
if variable.IsAutocommit(ctx) {
|
||||
@ -195,12 +201,17 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
SelectList: selectList}).Plan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.SelectFieldsPhase,
|
||||
HiddenFieldOffset: selectList.HiddenFieldOffset}).Plan(ctx)
|
||||
default:
|
||||
if r, err = (&rsets.GroupByRset{By: groupBy,
|
||||
Src: r,
|
||||
SelectList: selectList}).Plan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.GroupByPhase,
|
||||
HiddenFieldOffset: selectList.HiddenFieldOffset}).Plan(ctx)
|
||||
}
|
||||
|
||||
if s := s.Having; s != nil {
|
||||
@ -209,6 +220,8 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
Expr: s.Expr}).Plan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.HavingPhase,
|
||||
HiddenFieldOffset: selectList.HiddenFieldOffset}).Plan(ctx)
|
||||
}
|
||||
|
||||
if s.Distinct {
|
||||
@ -216,6 +229,8 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
SelectList: selectList}).Plan(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.DistinctPhase,
|
||||
HiddenFieldOffset: selectList.HiddenFieldOffset}).Plan(ctx)
|
||||
}
|
||||
|
||||
if s := s.OrderBy; s != nil {
|
||||
@ -237,6 +252,9 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) {
|
||||
}
|
||||
}
|
||||
|
||||
r, _ = (&rsets.OuterQueryRset{Src: r, SrcPhase: plans.BeforeFinalPhase,
|
||||
HiddenFieldOffset: selectList.HiddenFieldOffset}).Plan(ctx)
|
||||
|
||||
if r, err = (&rsets.SelectFinalRset{Src: r,
|
||||
SelectList: selectList}).Plan(ctx); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user