vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/projection_pushing.go (about) 1 /* 2 Copyright 2022 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/sqlparser" 23 "vitess.io/vitess/go/vt/vterrors" 24 "vitess.io/vitess/go/vt/vtgate/engine" 25 "vitess.io/vitess/go/vt/vtgate/planbuilder/operators" 26 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 27 "vitess.io/vitess/go/vt/vtgate/semantics" 28 ) 29 30 // pushProjection pushes a projection to the plan. 31 func pushProjection( 32 ctx *plancontext.PlanningContext, 33 expr *sqlparser.AliasedExpr, 34 plan logicalPlan, 35 inner, reuseCol, hasAggregation bool, 36 ) (offset int, added bool, err error) { 37 switch node := plan.(type) { 38 case *limit, *projection, *pulloutSubquery, *distinct, *filter: 39 // All of these either push to the single source, or push to the LHS 40 src := node.Inputs()[0] 41 return pushProjection(ctx, expr, src, inner, reuseCol, hasAggregation) 42 case *routeGen4: 43 return addExpressionToRoute(ctx, node, expr, reuseCol) 44 case *hashJoin: 45 return pushProjectionIntoHashJoin(ctx, expr, node, reuseCol, inner, hasAggregation) 46 case *joinGen4: 47 return pushProjectionIntoJoin(ctx, expr, node, reuseCol, inner, hasAggregation) 48 case *simpleProjection: 49 return pushProjectionIntoSimpleProj(ctx, expr, node, inner, hasAggregation, reuseCol) 50 case *orderedAggregate: 51 return pushProjectionIntoOA(ctx, expr, node, inner, hasAggregation) 52 case *vindexFunc: 53 return pushProjectionIntoVindexFunc(node, expr, reuseCol) 54 case *semiJoin: 55 return pushProjectionIntoSemiJoin(ctx, expr, reuseCol, node, inner, hasAggregation) 56 case *concatenateGen4: 57 return pushProjectionIntoConcatenate(ctx, expr, hasAggregation, node, inner, reuseCol) 58 default: 59 return 0, false, vterrors.VT13001(fmt.Sprintf("push projection does not yet support: %T", node)) 60 } 61 } 62 63 func pushProjectionIntoVindexFunc(node *vindexFunc, expr *sqlparser.AliasedExpr, reuseCol bool) (int, bool, error) { 64 colsBefore := len(node.eVindexFunc.Cols) 65 i, err := node.SupplyProjection(expr, reuseCol) 66 if err != nil { 67 return 0, false, err 68 } 69 return i /* col added */, len(node.eVindexFunc.Cols) > colsBefore, nil 70 } 71 72 func pushProjectionIntoConcatenate(ctx *plancontext.PlanningContext, expr *sqlparser.AliasedExpr, hasAggregation bool, node *concatenateGen4, inner bool, reuseCol bool) (int, bool, error) { 73 if hasAggregation { 74 return 0, false, vterrors.VT12001("aggregation on UNIONs") 75 } 76 offset, added, err := pushProjection(ctx, expr, node.sources[0], inner, reuseCol, hasAggregation) 77 if err != nil { 78 return 0, false, err 79 } 80 if added && ctx.SemTable.DirectDeps(expr.Expr).NonEmpty() { 81 return 0, false, vterrors.VT13001(fmt.Sprintf("pushing projection %v on concatenate should reference an existing column", sqlparser.String(expr))) 82 } 83 if added { 84 for _, source := range node.sources[1:] { 85 _, _, err := pushProjection(ctx, expr, source, inner, reuseCol, hasAggregation) 86 if err != nil { 87 return 0, false, err 88 } 89 } 90 } 91 return offset, added, nil 92 } 93 94 func pushProjectionIntoSemiJoin( 95 ctx *plancontext.PlanningContext, 96 expr *sqlparser.AliasedExpr, 97 reuseCol bool, 98 node *semiJoin, 99 inner, hasAggregation bool, 100 ) (int, bool, error) { 101 passDownReuseCol := reuseCol 102 if !reuseCol { 103 passDownReuseCol = expr.As.IsEmpty() 104 } 105 offset, added, err := pushProjection(ctx, expr, node.lhs, inner, passDownReuseCol, hasAggregation) 106 if err != nil { 107 return 0, false, err 108 } 109 column := -(offset + 1) 110 if reuseCol && !added { 111 for idx, col := range node.cols { 112 if column == col { 113 return idx, false, nil 114 } 115 } 116 } 117 node.cols = append(node.cols, column) 118 return len(node.cols) - 1, true, nil 119 } 120 121 func pushProjectionIntoOA(ctx *plancontext.PlanningContext, expr *sqlparser.AliasedExpr, node *orderedAggregate, inner, hasAggregation bool) (int, bool, error) { 122 colName, isColName := expr.Expr.(*sqlparser.ColName) 123 for _, aggregate := range node.aggregates { 124 if ctx.SemTable.EqualsExpr(aggregate.Expr, expr.Expr) { 125 return aggregate.Col, false, nil 126 } 127 if isColName && colName.Name.EqualString(aggregate.Alias) { 128 return aggregate.Col, false, nil 129 } 130 } 131 for _, key := range node.groupByKeys { 132 if ctx.SemTable.EqualsExpr(key.Expr, expr.Expr) { 133 return key.KeyCol, false, nil 134 } 135 } 136 offset, _, err := pushProjection(ctx, expr, node.input, inner, true, hasAggregation) 137 if err != nil { 138 return 0, false, err 139 } 140 node.aggregates = append(node.aggregates, &engine.AggregateParams{ 141 Opcode: engine.AggregateRandom, 142 Col: offset, 143 Alias: expr.ColumnName(), 144 Expr: expr.Expr, 145 Original: expr, 146 }) 147 return offset, true, nil 148 } 149 150 func pushProjectionIntoSimpleProj( 151 ctx *plancontext.PlanningContext, 152 expr *sqlparser.AliasedExpr, 153 node *simpleProjection, 154 inner, hasAggregation, reuseCol bool, 155 ) (int, bool, error) { 156 offset, _, err := pushProjection(ctx, expr, node.input, inner, true, hasAggregation) 157 if err != nil { 158 return 0, false, err 159 } 160 for i, value := range node.eSimpleProj.Cols { 161 // we return early if we already have the column in the simple projection's 162 // output list so we do not add it again. 163 if reuseCol && value == offset { 164 return i, false, nil 165 } 166 } 167 node.eSimpleProj.Cols = append(node.eSimpleProj.Cols, offset) 168 return len(node.eSimpleProj.Cols) - 1, true, nil 169 } 170 171 func pushProjectionIntoJoin( 172 ctx *plancontext.PlanningContext, 173 expr *sqlparser.AliasedExpr, 174 node *joinGen4, 175 reuseCol, inner, hasAggregation bool, 176 ) (int, bool, error) { 177 lhsSolves := node.Left.ContainsTables() 178 rhsSolves := node.Right.ContainsTables() 179 deps := ctx.SemTable.RecursiveDeps(expr.Expr) 180 var column int 181 var appended bool 182 passDownReuseCol := reuseCol 183 if !reuseCol { 184 passDownReuseCol = expr.As.IsEmpty() 185 } 186 switch { 187 case deps.IsSolvedBy(lhsSolves): 188 offset, added, err := pushProjection(ctx, expr, node.Left, inner, passDownReuseCol, hasAggregation) 189 if err != nil { 190 return 0, false, err 191 } 192 column = -(offset + 1) 193 appended = added 194 case deps.IsSolvedBy(rhsSolves): 195 offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, hasAggregation) 196 if err != nil { 197 return 0, false, err 198 } 199 column = offset + 1 200 appended = added 201 default: 202 // if an expression has aggregation, then it should not be split up and pushed to both sides, 203 // for example an expression like count(*) will have dependencies on both sides, but we should not push it 204 // instead we should return an error 205 if hasAggregation { 206 return 0, false, vterrors.VT12001("cross-shard query with aggregates") 207 } 208 // now we break the expression into left and right side dependencies and rewrite the left ones to bind variables 209 bvName, cols, rewrittenExpr, err := operators.BreakExpressionInLHSandRHS(ctx, expr.Expr, lhsSolves) 210 if err != nil { 211 return 0, false, err 212 } 213 // go over all the columns coming from the left side of the tree and push them down. While at it, also update the bind variable map. 214 // It is okay to reuse the columns on the left side since 215 // the final expression which will be selected will be pushed into the right side. 216 for i, col := range cols { 217 colOffset, _, err := pushProjection(ctx, &sqlparser.AliasedExpr{Expr: col}, node.Left, inner, true, false) 218 if err != nil { 219 return 0, false, err 220 } 221 node.Vars[bvName[i]] = colOffset 222 } 223 // push the rewritten expression on the right side of the tree. Here we should take care whether we want to reuse the expression or not. 224 expr.Expr = rewrittenExpr 225 offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, false) 226 if err != nil { 227 return 0, false, err 228 } 229 column = offset + 1 230 appended = added 231 } 232 if reuseCol && !appended { 233 for idx, col := range node.Cols { 234 if column == col { 235 return idx, false, nil 236 } 237 } 238 // the column was not appended to either child, but we could not find it in out cols list, 239 // so we'll still add it 240 } 241 node.Cols = append(node.Cols, column) 242 return len(node.Cols) - 1, true, nil 243 } 244 245 func pushProjectionIntoHashJoin( 246 ctx *plancontext.PlanningContext, 247 expr *sqlparser.AliasedExpr, 248 node *hashJoin, 249 reuseCol, inner, hasAggregation bool, 250 ) (int, bool, error) { 251 lhsSolves := node.Left.ContainsTables() 252 rhsSolves := node.Right.ContainsTables() 253 deps := ctx.SemTable.RecursiveDeps(expr.Expr) 254 var column int 255 var appended bool 256 passDownReuseCol := reuseCol 257 if !reuseCol { 258 passDownReuseCol = expr.As.IsEmpty() 259 } 260 switch { 261 case deps.IsSolvedBy(lhsSolves): 262 offset, added, err := pushProjection(ctx, expr, node.Left, inner, passDownReuseCol, hasAggregation) 263 if err != nil { 264 return 0, false, err 265 } 266 column = -(offset + 1) 267 appended = added 268 case deps.IsSolvedBy(rhsSolves): 269 offset, added, err := pushProjection(ctx, expr, node.Right, inner && node.Opcode != engine.LeftJoin, passDownReuseCol, hasAggregation) 270 if err != nil { 271 return 0, false, err 272 } 273 column = offset + 1 274 appended = added 275 default: 276 // if an expression has aggregation, then it should not be split up and pushed to both sides, 277 // for example an expression like count(*) will have dependencies on both sides, but we should not push it 278 // instead we should return an error 279 if hasAggregation { 280 return 0, false, vterrors.VT12001("cross-shard query with aggregates") 281 } 282 return 0, false, vterrors.VT12001("hash join with projection from both sides of the join") 283 } 284 if reuseCol && !appended { 285 for idx, col := range node.Cols { 286 if column == col { 287 return idx, false, nil 288 } 289 } 290 // the column was not appended to either child, but we could not find it in out cols list, 291 // so we'll still add it 292 } 293 node.Cols = append(node.Cols, column) 294 return len(node.Cols) - 1, true, nil 295 } 296 297 func addExpressionToRoute(ctx *plancontext.PlanningContext, rb *routeGen4, expr *sqlparser.AliasedExpr, reuseCol bool) (int, bool, error) { 298 if reuseCol { 299 if i := checkIfAlreadyExists(expr, rb.Select, ctx.SemTable); i != -1 { 300 return i, false, nil 301 } 302 } 303 sqlparser.RemoveKeyspaceFromColName(expr.Expr) 304 sel, isSel := rb.Select.(*sqlparser.Select) 305 if !isSel { 306 return 0, false, vterrors.VT12001(fmt.Sprintf("pushing projection '%s' on %T", sqlparser.String(expr), rb.Select)) 307 } 308 309 if ctx.RewriteDerivedExpr { 310 // if we are trying to push a projection that belongs to a DerivedTable 311 // we rewrite that expression, so it matches the column name used inside 312 // that derived table. 313 err := rewriteProjectionOfDerivedTable(expr, ctx.SemTable) 314 if err != nil { 315 return 0, false, err 316 } 317 } 318 319 offset := len(sel.SelectExprs) 320 sel.SelectExprs = append(sel.SelectExprs, expr) 321 return offset, true, nil 322 } 323 324 func rewriteProjectionOfDerivedTable(expr *sqlparser.AliasedExpr, semTable *semantics.SemTable) error { 325 ti, err := semTable.TableInfoForExpr(expr.Expr) 326 if err != nil && err != semantics.ErrNotSingleTable { 327 return err 328 } 329 _, isDerivedTable := ti.(*semantics.DerivedTable) 330 if isDerivedTable { 331 expr.Expr = semantics.RewriteDerivedTableExpression(expr.Expr, ti) 332 } 333 return nil 334 }