vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/join.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package planbuilder
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"vitess.io/vitess/go/vt/vterrors"
    23  
    24  	"vitess.io/vitess/go/vt/sqlparser"
    25  	"vitess.io/vitess/go/vt/vtgate/engine"
    26  )
    27  
    28  var _ logicalPlan = (*join)(nil)
    29  
    30  // join is used to build a Join primitive.
    31  // It's used to build a normal join or a left join
    32  // operation.
    33  type join struct {
    34  	v3Plan
    35  	order         int
    36  	resultColumns []*resultColumn
    37  	weightStrings map[*resultColumn]int
    38  
    39  	// leftOrder stores the order number of the left node. This is
    40  	// used for a b-tree style traversal towards the target route.
    41  	// Let us assume the following execution tree:
    42  	//      J9
    43  	//     /  \
    44  	//    /    \
    45  	//   J3     J8
    46  	//  / \    /  \
    47  	// R1  R2  J6  R7
    48  	//        / \
    49  	//        R4 R5
    50  	//
    51  	// In the above trees, the suffix numbers indicate the
    52  	// execution order. The leftOrder for the joins will then
    53  	// be as follows:
    54  	// J3: 1
    55  	// J6: 4
    56  	// J8: 6
    57  	// J9: 3
    58  	//
    59  	// The route to R4 would be:
    60  	// Go right from J9->J8 because Left(J9)==3, which is <4.
    61  	// Go left from J8->J6 because Left(J8)==6, which is >=4.
    62  	// Go left from J6->R4 because Left(J6)==4, the destination.
    63  	// Look for 'isOnLeft' to see how these numbers are used.
    64  	leftOrder int
    65  
    66  	// Left and Right are the nodes for the join.
    67  	Left, Right logicalPlan
    68  
    69  	ejoin *engine.Join
    70  }
    71  
    72  // newJoin makes a new join using the two planBuilder. ajoin can be nil
    73  // if the join is on a ',' operator. lpb will contain the resulting join.
    74  // rpb will be discarded.
    75  func newJoin(lpb, rpb *primitiveBuilder, ajoin *sqlparser.JoinTableExpr, reservedVars *sqlparser.ReservedVars) error {
    76  	// This function converts ON clauses to WHERE clauses. The WHERE clause
    77  	// scope can see all tables, whereas the ON clause can only see the
    78  	// participants of the JOIN. However, since the ON clause doesn't allow
    79  	// external references, and the FROM clause doesn't allow duplicates,
    80  	// it's safe to perform this conversion and still expect the same behavior.
    81  
    82  	opcode := engine.InnerJoin
    83  	if ajoin != nil {
    84  		switch {
    85  		case ajoin.Join == sqlparser.LeftJoinType:
    86  			opcode = engine.LeftJoin
    87  
    88  			// For left joins, we have to push the ON clause into the RHS.
    89  			// We do this before creating the join primitive.
    90  			// However, variables of LHS need to be visible. To allow this,
    91  			// we mark the LHS symtab as outer scope to the RHS, just like
    92  			// a subquery. This make the RHS treat the LHS symbols as external.
    93  			// This will prevent constructs from escaping out of the rpb scope.
    94  			// At this point, the LHS symtab also contains symbols of the RHS.
    95  			// But the RHS will hide those, as intended.
    96  			rpb.st.Outer = lpb.st
    97  			if err := rpb.pushFilter(ajoin.Condition.On, sqlparser.WhereStr, reservedVars); err != nil {
    98  				return err
    99  			}
   100  		case ajoin.Condition.Using != nil:
   101  			return vterrors.VT12001("JOIN with USING(column_list) clause for complex queries")
   102  		}
   103  	}
   104  	lpb.plan = &join{
   105  		weightStrings: make(map[*resultColumn]int),
   106  		Left:          lpb.plan,
   107  		Right:         rpb.plan,
   108  		ejoin: &engine.Join{
   109  			Opcode: opcode,
   110  			Vars:   make(map[string]int),
   111  		},
   112  	}
   113  	lpb.plan.Reorder(0)
   114  	if ajoin == nil || opcode == engine.LeftJoin {
   115  		return nil
   116  	}
   117  	return lpb.pushFilter(ajoin.Condition.On, sqlparser.WhereStr, reservedVars)
   118  }
   119  
   120  // Order implements the logicalPlan interface
   121  func (jb *join) Order() int {
   122  	return jb.order
   123  }
   124  
   125  // Reorder implements the logicalPlan interface
   126  func (jb *join) Reorder(order int) {
   127  	jb.Left.Reorder(order)
   128  	jb.leftOrder = jb.Left.Order()
   129  	jb.Right.Reorder(jb.leftOrder)
   130  	jb.order = jb.Right.Order() + 1
   131  }
   132  
   133  // Primitive implements the logicalPlan interface
   134  func (jb *join) Primitive() engine.Primitive {
   135  	jb.ejoin.Left = jb.Left.Primitive()
   136  	jb.ejoin.Right = jb.Right.Primitive()
   137  	return jb.ejoin
   138  }
   139  
   140  // ResultColumns implements the logicalPlan interface
   141  func (jb *join) ResultColumns() []*resultColumn {
   142  	return jb.resultColumns
   143  }
   144  
   145  // Wireup implements the logicalPlan interface
   146  func (jb *join) Wireup(plan logicalPlan, jt *jointab) error {
   147  	err := jb.Right.Wireup(plan, jt)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	return jb.Left.Wireup(plan, jt)
   152  }
   153  
   154  // SupplyVar implements the logicalPlan interface
   155  func (jb *join) SupplyVar(from, to int, col *sqlparser.ColName, varname string) {
   156  	if !jb.isOnLeft(from) {
   157  		jb.Right.SupplyVar(from, to, col, varname)
   158  		return
   159  	}
   160  	if jb.isOnLeft(to) {
   161  		jb.Left.SupplyVar(from, to, col, varname)
   162  		return
   163  	}
   164  	if _, ok := jb.ejoin.Vars[varname]; ok {
   165  		// Looks like somebody else already requested this.
   166  		return
   167  	}
   168  	c := col.Metadata.(*column)
   169  	for i, rc := range jb.resultColumns {
   170  		if jb.ejoin.Cols[i] > 0 {
   171  			continue
   172  		}
   173  		if rc.column == c {
   174  			jb.ejoin.Vars[varname] = -jb.ejoin.Cols[i] - 1
   175  			return
   176  		}
   177  	}
   178  	_, jb.ejoin.Vars[varname] = jb.Left.SupplyCol(col)
   179  }
   180  
   181  // SupplyCol implements the logicalPlan interface
   182  func (jb *join) SupplyCol(col *sqlparser.ColName) (rc *resultColumn, colNumber int) {
   183  	c := col.Metadata.(*column)
   184  	for i, rc := range jb.resultColumns {
   185  		if rc.column == c {
   186  			return rc, i
   187  		}
   188  	}
   189  
   190  	routeNumber := c.Origin().Order()
   191  	var sourceCol int
   192  	if jb.isOnLeft(routeNumber) {
   193  		rc, sourceCol = jb.Left.SupplyCol(col)
   194  		jb.ejoin.Cols = append(jb.ejoin.Cols, -sourceCol-1)
   195  	} else {
   196  		rc, sourceCol = jb.Right.SupplyCol(col)
   197  		jb.ejoin.Cols = append(jb.ejoin.Cols, sourceCol+1)
   198  	}
   199  	jb.resultColumns = append(jb.resultColumns, rc)
   200  	return rc, len(jb.ejoin.Cols) - 1
   201  }
   202  
   203  // SupplyWeightString implements the logicalPlan interface
   204  func (jb *join) SupplyWeightString(colNumber int, alsoAddToGroupBy bool) (weightcolNumber int, err error) {
   205  	rc := jb.resultColumns[colNumber]
   206  	if weightcolNumber, ok := jb.weightStrings[rc]; ok {
   207  		return weightcolNumber, nil
   208  	}
   209  	routeNumber := rc.column.Origin().Order()
   210  	if jb.isOnLeft(routeNumber) {
   211  		sourceCol, err := jb.Left.SupplyWeightString(-jb.ejoin.Cols[colNumber]-1, alsoAddToGroupBy)
   212  		if err != nil {
   213  			return 0, err
   214  		}
   215  		jb.ejoin.Cols = append(jb.ejoin.Cols, -sourceCol-1)
   216  	} else {
   217  		sourceCol, err := jb.Right.SupplyWeightString(jb.ejoin.Cols[colNumber]-1, alsoAddToGroupBy)
   218  		if err != nil {
   219  			return 0, err
   220  		}
   221  		jb.ejoin.Cols = append(jb.ejoin.Cols, sourceCol+1)
   222  	}
   223  	jb.resultColumns = append(jb.resultColumns, rc)
   224  	jb.weightStrings[rc] = len(jb.ejoin.Cols) - 1
   225  	return len(jb.ejoin.Cols) - 1, nil
   226  }
   227  
   228  // Rewrite implements the logicalPlan interface
   229  func (jb *join) Rewrite(inputs ...logicalPlan) error {
   230  	if len(inputs) != 2 {
   231  		return vterrors.VT13001(fmt.Sprintf("join: wrong number of inputs, got: %d, expect: 2", len(inputs)))
   232  	}
   233  	jb.Left = inputs[0]
   234  	jb.Right = inputs[1]
   235  	return nil
   236  }
   237  
   238  // Inputs implements the logicalPlan interface
   239  func (jb *join) Inputs() []logicalPlan {
   240  	return []logicalPlan{jb.Left, jb.Right}
   241  }
   242  
   243  // isOnLeft returns true if the specified route number
   244  // is on the left side of the join. If false, it means
   245  // the node is on the right.
   246  func (jb *join) isOnLeft(nodeNum int) bool {
   247  	return nodeNum <= jb.leftOrder
   248  }