vitess.io/vitess@v0.16.2/go/vt/vtctl/workflow/vexec/query_planner.go (about) 1 /* 2 Copyright 2021 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 vexec 18 19 import ( 20 "errors" 21 "fmt" 22 23 "vitess.io/vitess/go/vt/sqlparser" 24 "vitess.io/vitess/go/vt/vttablet/tmclient" 25 ) 26 27 var ( // Query planning errors. 28 // ErrCannotUpdateImmutableColumn is returned when attempting to plan a 29 // query that updates a column that should be treated as immutable. 30 ErrCannotUpdateImmutableColumn = errors.New("cannot update immutable column") 31 // ErrUnsupportedQueryConstruct is returned when a particular query 32 // construct is unsupported by a QueryPlanner, despite the more general kind 33 // of query being supported. 34 // 35 // For example, VReplication supports DELETEs, but does not support DELETEs 36 // with LIMIT clauses, so planning a "DELETE ... LIMIT" will return 37 // ErrUnsupportedQueryConstruct rather than a "CREATE TABLE", which would 38 // return an ErrUnsupportedQuery. 39 ErrUnsupportedQueryConstruct = errors.New("unsupported query construct") 40 ) 41 42 var ( // Query execution errors. 43 // ErrUnpreparedQuery is returned when attempting to execute an unprepared 44 // QueryPlan. 45 ErrUnpreparedQuery = errors.New("attempted to execute unprepared query") 46 ) 47 48 // QueryPlanner defines the interface that VExec uses to build QueryPlans for 49 // various vexec workflows. A given vexec table, which is to say a table in the 50 // "_vt" database, will have at most one QueryPlanner implementation, which is 51 // responsible for defining both what queries are supported for that table, as 52 // well as how to build plans for those queries. 53 // 54 // VReplicationQueryPlanner is a good example implementation to refer to. 55 type QueryPlanner interface { 56 // (NOTE:@ajm188) I don't think this method fits on the query planner. To 57 // me, especially given that it's only implemented by the vrep query planner 58 // in the old implementation (the schema migration query planner no-ops this 59 // method), this fits better on our workflow.Manager struct, probably as a 60 // method called something like "VReplicationExec(ctx, query, Options{DryRun: true})" 61 // DryRun(ctx context.Context) error 62 63 // PlanQuery constructs and returns a QueryPlan for a given statement. The 64 // resulting QueryPlan is suitable for repeated, concurrent use. 65 PlanQuery(stmt sqlparser.Statement) (QueryPlan, error) 66 // QueryParams returns a struct of column parameters the QueryPlanner uses. 67 // It is used primarily to abstract the adding of default WHERE clauses to 68 // queries by a private function of this package, and may be removed from 69 // the interface later. 70 QueryParams() QueryParams 71 } 72 73 // QueryParams is a struct that QueryPlanner implementations can provide to 74 // control the addition of default WHERE clauses to their queries. 75 type QueryParams struct { 76 // DBName is the value that the column referred to by DBNameColumn should 77 // equal in a WHERE clause, if set. 78 DBName string 79 // DBNameColumn is the name of the column that DBName should equal in a 80 // WHERE clause, if set. 81 DBNameColumn string 82 // Workflow is the value that the column referred to by WorkflowColumn 83 // should equal in a WHERE clause, if set. 84 Workflow string 85 // WorkflowColumn is the name of the column that Workflow should equal in a 86 // WHERE clause, if set. 87 WorkflowColumn string 88 } 89 90 // VReplicationQueryPlanner implements the QueryPlanner interface for queries on 91 // the _vt.vreplication table. 92 type VReplicationQueryPlanner struct { 93 tmc tmclient.TabletManagerClient 94 95 dbname string 96 workflow string 97 } 98 99 // NewVReplicationQueryPlanner returns a new VReplicationQueryPlanner. It is 100 // valid to pass empty strings for both the dbname and workflow parameters. 101 func NewVReplicationQueryPlanner(tmc tmclient.TabletManagerClient, workflow string, dbname string) *VReplicationQueryPlanner { 102 return &VReplicationQueryPlanner{ 103 tmc: tmc, 104 dbname: dbname, 105 workflow: workflow, 106 } 107 } 108 109 // PlanQuery is part of the QueryPlanner interface. 110 // 111 // For vreplication query planners, only SELECT, UPDATE, and DELETE queries are 112 // supported. 113 // 114 // For UPDATE queries, ORDER BY and LIMIT clauses are not supported. Attempting 115 // to update vreplication.id is an error. 116 // 117 // For DELETE queries, USING, PARTITION, ORDER BY, and LIMIT clauses are not 118 // supported. 119 func (planner *VReplicationQueryPlanner) PlanQuery(stmt sqlparser.Statement) (plan QueryPlan, err error) { 120 switch stmt := stmt.(type) { 121 case *sqlparser.Select: 122 plan, err = planner.planSelect(stmt) 123 case *sqlparser.Insert: 124 err = ErrUnsupportedQuery 125 case *sqlparser.Update: 126 plan, err = planner.planUpdate(stmt) 127 case *sqlparser.Delete: 128 plan, err = planner.planDelete(stmt) 129 default: 130 err = ErrUnsupportedQuery 131 } 132 133 if err != nil { 134 return nil, fmt.Errorf("%w: %s", err, sqlparser.String(stmt)) 135 } 136 137 return plan, nil 138 } 139 140 // QueryParams is part of the QueryPlanner interface. A VReplicationQueryPlanner 141 // will attach the following WHERE clauses iff (a) DBName, Workflow are set, 142 // respectively, and (b) db_name and workflow do not appear in the original 143 // query's WHERE clause: 144 // 145 // WHERE (db_name = {{ .DBName }} AND)? (workflow = {{ .Workflow }} AND)? {{ .OriginalWhere }} 146 func (planner *VReplicationQueryPlanner) QueryParams() QueryParams { 147 return QueryParams{ 148 DBName: planner.dbname, 149 DBNameColumn: "db_name", 150 Workflow: planner.workflow, 151 WorkflowColumn: "workflow", 152 } 153 } 154 155 func (planner *VReplicationQueryPlanner) planDelete(del *sqlparser.Delete) (*FixedQueryPlan, error) { 156 if del.Targets != nil { 157 return nil, fmt.Errorf( 158 "%w: DELETE must not have USING clause (have: %v): %v", 159 ErrUnsupportedQueryConstruct, 160 del.Targets, 161 sqlparser.String(del), 162 ) 163 } 164 165 if del.Partitions != nil { 166 return nil, fmt.Errorf( 167 "%w: DELETE must not have explicit partitions (have: %v): %v", 168 ErrUnsupportedQueryConstruct, 169 del.Partitions, 170 sqlparser.String(del), 171 ) 172 } 173 174 if del.OrderBy != nil || del.Limit != nil { 175 return nil, fmt.Errorf( 176 "%w: DELETE must not have explicit ordering (have: %v) or limit clauses (have: %v): %v", 177 ErrUnsupportedQueryConstruct, 178 del.OrderBy, 179 del.Limit, 180 sqlparser.String(del), 181 ) 182 } 183 184 del.Where = addDefaultWheres(planner, del.Where) 185 186 buf := sqlparser.NewTrackedBuffer(nil) 187 buf.Myprintf("%v", del) 188 189 return &FixedQueryPlan{ 190 ParsedQuery: buf.ParsedQuery(), 191 workflow: planner.workflow, 192 tmc: planner.tmc, 193 }, nil 194 } 195 196 func (planner *VReplicationQueryPlanner) planSelect(sel *sqlparser.Select) (*FixedQueryPlan, error) { 197 sel.Where = addDefaultWheres(planner, sel.Where) 198 199 buf := sqlparser.NewTrackedBuffer(nil) 200 buf.Myprintf("%v", sel) 201 202 return &FixedQueryPlan{ 203 ParsedQuery: buf.ParsedQuery(), 204 workflow: planner.workflow, 205 tmc: planner.tmc, 206 }, nil 207 } 208 209 func (planner *VReplicationQueryPlanner) planUpdate(upd *sqlparser.Update) (*FixedQueryPlan, error) { 210 if upd.OrderBy != nil || upd.Limit != nil { 211 return nil, fmt.Errorf( 212 "%w: UPDATE must not have explicit ordering (have: %v) or limit clauses (have: %v): %v", 213 ErrUnsupportedQueryConstruct, 214 upd.OrderBy, 215 upd.Limit, 216 sqlparser.String(upd), 217 ) 218 } 219 220 // For updates on the _vt.vreplication table, we ban updates to the `id` 221 // column, and allow updates to all other columns. 222 for _, expr := range upd.Exprs { 223 if expr.Name.Name.EqualString("id") { 224 return nil, fmt.Errorf( 225 "%w %+v: %v", 226 ErrCannotUpdateImmutableColumn, 227 expr.Name.Name, 228 sqlparser.String(expr), 229 ) 230 } 231 } 232 233 upd.Where = addDefaultWheres(planner, upd.Where) 234 235 buf := sqlparser.NewTrackedBuffer(nil) 236 buf.Myprintf("%v", upd) 237 238 return &FixedQueryPlan{ 239 ParsedQuery: buf.ParsedQuery(), 240 workflow: planner.workflow, 241 tmc: planner.tmc, 242 }, nil 243 } 244 245 // VReplicationLogQueryPlanner implements the QueryPlanner interface for queries 246 // on the _vt.vreplication_log table. 247 type VReplicationLogQueryPlanner struct { 248 tmc tmclient.TabletManagerClient 249 tabletStreamIDs map[string][]int64 250 } 251 252 // NewVReplicationLogQueryPlanner returns a new VReplicationLogQueryPlanner. The 253 // tabletStreamIDs map determines what stream_ids are expected to have vrep_log 254 // rows, keyed by tablet alias string. 255 func NewVReplicationLogQueryPlanner(tmc tmclient.TabletManagerClient, tabletStreamIDs map[string][]int64) *VReplicationLogQueryPlanner { 256 return &VReplicationLogQueryPlanner{ 257 tmc: tmc, 258 tabletStreamIDs: tabletStreamIDs, 259 } 260 } 261 262 // PlanQuery is part of the QueryPlanner interface. 263 // 264 // For vreplication_log query planners, only SELECT queries are supported. 265 func (planner *VReplicationLogQueryPlanner) PlanQuery(stmt sqlparser.Statement) (plan QueryPlan, err error) { 266 switch stmt := stmt.(type) { 267 case *sqlparser.Select: 268 plan, err = planner.planSelect(stmt) 269 case *sqlparser.Insert: 270 err = ErrUnsupportedQuery 271 case *sqlparser.Update: 272 err = ErrUnsupportedQuery 273 case *sqlparser.Delete: 274 err = ErrUnsupportedQuery 275 default: 276 err = ErrUnsupportedQuery 277 } 278 279 if err != nil { 280 return nil, fmt.Errorf("%w: %s", err, sqlparser.String(stmt)) 281 } 282 283 return plan, nil 284 } 285 286 // QueryParams is part of the QueryPlanner interface. 287 func (planner *VReplicationLogQueryPlanner) QueryParams() QueryParams { 288 return QueryParams{} 289 } 290 291 func (planner *VReplicationLogQueryPlanner) planSelect(sel *sqlparser.Select) (QueryPlan, error) { 292 where := sel.Where 293 cols := extractWhereComparisonColumns(where) 294 hasVReplIDCol := false 295 296 for _, col := range cols { 297 if col == "vrepl_id" { 298 hasVReplIDCol = true 299 } 300 } 301 302 if hasVReplIDCol { // we're not injecting per-target parameters, return a Fixed plan 303 buf := sqlparser.NewTrackedBuffer(nil) 304 buf.Myprintf("%v", sel) 305 306 return &FixedQueryPlan{ 307 ParsedQuery: buf.ParsedQuery(), 308 tmc: planner.tmc, 309 }, nil 310 } 311 312 // Construct a where clause to filter by vrepl_id, parameterized by target 313 // streamIDs. 314 queriesByTarget := make(map[string]*sqlparser.ParsedQuery, len(planner.tabletStreamIDs)) 315 for target, streamIDs := range planner.tabletStreamIDs { 316 targetWhere := &sqlparser.Where{ 317 Type: sqlparser.WhereClause, 318 } 319 320 var expr sqlparser.Expr 321 switch len(streamIDs) { 322 case 0: // WHERE vreplication_log.vrepl_id IN () => WHERE 1 != 1 323 one := sqlparser.NewIntLiteral("1") 324 expr = &sqlparser.ComparisonExpr{ 325 Operator: sqlparser.NotEqualOp, 326 Left: one, 327 Right: one, 328 } 329 case 1: // WHERE vreplication_log.vrepl_id = ? 330 expr = &sqlparser.ComparisonExpr{ 331 Operator: sqlparser.EqualOp, 332 Left: &sqlparser.ColName{ 333 Name: sqlparser.NewIdentifierCI("vrepl_id"), 334 }, 335 Right: sqlparser.NewIntLiteral(fmt.Sprintf("%d", streamIDs[0])), 336 } 337 default: // WHERE vreplication_log.vrepl_id IN (?) 338 vals := []sqlparser.Expr{} 339 for _, streamID := range streamIDs { 340 vals = append(vals, sqlparser.NewIntLiteral(fmt.Sprintf("%d", streamID))) 341 } 342 343 var tuple sqlparser.ValTuple = vals 344 expr = &sqlparser.ComparisonExpr{ 345 Operator: sqlparser.InOp, 346 Left: &sqlparser.ColName{ 347 Name: sqlparser.NewIdentifierCI("vrepl_id"), 348 }, 349 Right: tuple, 350 } 351 } 352 353 switch where { 354 case nil: 355 targetWhere.Expr = expr 356 default: 357 targetWhere.Expr = &sqlparser.AndExpr{ 358 Left: expr, 359 Right: where.Expr, 360 } 361 } 362 363 sel.Where = targetWhere 364 365 buf := sqlparser.NewTrackedBuffer(nil) 366 buf.Myprintf("%v", sel) 367 368 queriesByTarget[target] = buf.ParsedQuery() 369 } 370 371 return &PerTargetQueryPlan{ 372 ParsedQueries: queriesByTarget, 373 tmc: planner.tmc, 374 }, nil 375 } 376 377 func addDefaultWheres(planner QueryPlanner, where *sqlparser.Where) *sqlparser.Where { 378 cols := extractWhereComparisonColumns(where) 379 380 params := planner.QueryParams() 381 hasDBNameCol := false 382 hasWorkflowCol := false 383 384 for _, col := range cols { 385 switch col { 386 case params.DBNameColumn: 387 hasDBNameCol = true 388 case params.WorkflowColumn: 389 hasWorkflowCol = true 390 } 391 } 392 393 newWhere := where 394 395 if !hasDBNameCol { 396 expr := &sqlparser.ComparisonExpr{ 397 Left: &sqlparser.ColName{ 398 Name: sqlparser.NewIdentifierCI(params.DBNameColumn), 399 }, 400 Operator: sqlparser.EqualOp, 401 Right: sqlparser.NewStrLiteral(params.DBName), 402 } 403 404 switch newWhere { 405 case nil: 406 newWhere = &sqlparser.Where{ 407 Type: sqlparser.WhereClause, 408 Expr: expr, 409 } 410 default: 411 newWhere.Expr = &sqlparser.AndExpr{ 412 Left: newWhere.Expr, 413 Right: expr, 414 } 415 } 416 } 417 418 if !hasWorkflowCol && params.Workflow != "" { 419 expr := &sqlparser.ComparisonExpr{ 420 Left: &sqlparser.ColName{ 421 Name: sqlparser.NewIdentifierCI(params.WorkflowColumn), 422 }, 423 Operator: sqlparser.EqualOp, 424 Right: sqlparser.NewStrLiteral(params.Workflow), 425 } 426 427 newWhere.Expr = &sqlparser.AndExpr{ 428 Left: newWhere.Expr, 429 Right: expr, 430 } 431 } 432 433 return newWhere 434 } 435 436 // extractWhereComparisonColumns extracts the column names used in AND-ed 437 // comparison expressions in a where clause, given the following assumptions: 438 // - (1) The column name is always the left-hand side of the comparison. 439 // - (2) There are no compound expressions within the where clause involving OR. 440 func extractWhereComparisonColumns(where *sqlparser.Where) []string { 441 if where == nil { 442 return nil 443 } 444 445 exprs := sqlparser.SplitAndExpression(nil, where.Expr) 446 cols := make([]string, 0, len(exprs)) 447 448 for _, expr := range exprs { 449 switch expr := expr.(type) { 450 case *sqlparser.ComparisonExpr: 451 if qualifiedName, ok := expr.Left.(*sqlparser.ColName); ok { 452 cols = append(cols, qualifiedName.Name.String()) 453 } 454 } 455 } 456 457 return cols 458 }