vitess.io/vitess@v0.16.2/go/vt/wrangler/vexec_plan.go (about) 1 /* 2 Copyright 2020 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 wrangler 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "vitess.io/vitess/go/vt/log" 25 querypb "vitess.io/vitess/go/vt/proto/query" 26 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 27 "vitess.io/vitess/go/vt/schema" 28 "vitess.io/vitess/go/vt/sqlparser" 29 30 "github.com/olekukonko/tablewriter" 31 ) 32 33 // vexecPlan contains the final query to be sent to the tablets 34 type vexecPlan struct { 35 opcode int 36 parsedQuery *sqlparser.ParsedQuery 37 } 38 39 // vexecPlannerParams controls how some queries/columns are handled 40 type vexecPlannerParams struct { 41 dbNameColumn string 42 workflowColumn string 43 immutableColumnNames []string 44 updatableColumnNames []string 45 updateTemplates []string 46 insertTemplates []string 47 } 48 49 // vexecPlanner generates and executes a plan 50 type vexecPlanner interface { 51 params() *vexecPlannerParams 52 exec(ctx context.Context, primaryAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error) 53 dryRun(ctx context.Context) error 54 } 55 56 // vreplicationPlanner is a vexecPlanner implementation, specific to _vt.vreplication table 57 type vreplicationPlanner struct { 58 vx *vexec 59 d *vexecPlannerParams 60 } 61 62 func newVReplicationPlanner(vx *vexec) vexecPlanner { 63 return &vreplicationPlanner{ 64 vx: vx, 65 d: &vexecPlannerParams{ 66 dbNameColumn: "db_name", 67 workflowColumn: "workflow", 68 immutableColumnNames: []string{"id"}, 69 updatableColumnNames: []string{}, 70 }, 71 } 72 } 73 func (p vreplicationPlanner) params() *vexecPlannerParams { return p.d } 74 func (p vreplicationPlanner) exec( 75 ctx context.Context, primaryAlias *topodatapb.TabletAlias, query string, 76 ) (*querypb.QueryResult, error) { 77 qr, err := p.vx.wr.VReplicationExec(ctx, primaryAlias, query) 78 if err != nil { 79 return nil, err 80 } 81 if qr.RowsAffected == 0 { 82 log.Infof("no matching streams found for workflow %s, tablet %s, query %s", p.vx.workflow, primaryAlias, query) 83 } 84 return qr, nil 85 } 86 func (p vreplicationPlanner) dryRun(ctx context.Context) error { 87 rsr, err := p.vx.wr.getStreams(p.vx.ctx, p.vx.workflow, p.vx.keyspace) 88 if err != nil { 89 return err 90 } 91 92 p.vx.wr.Logger().Printf("Query: %s\nwill be run on the following streams in keyspace %s for workflow %s:\n\n", 93 p.vx.plannedQuery, p.vx.keyspace, p.vx.workflow) 94 tableString := &strings.Builder{} 95 table := tablewriter.NewWriter(tableString) 96 table.SetHeader([]string{"Tablet", "ID", "BinLogSource", "State", "DBName", "Current GTID"}) 97 for _, primary := range p.vx.primaries { 98 key := fmt.Sprintf("%s/%s", primary.Shard, primary.AliasString()) 99 for _, stream := range rsr.ShardStatuses[key].PrimaryReplicationStatuses { 100 table.Append([]string{key, fmt.Sprintf("%d", stream.ID), stream.Bls.String(), stream.State, stream.DBName, stream.Pos}) 101 } 102 } 103 table.SetAutoMergeCellsByColumnIndex([]int{0}) 104 table.SetRowLine(true) 105 table.Render() 106 p.vx.wr.Logger().Printf(tableString.String()) 107 p.vx.wr.Logger().Printf("\n\n") 108 109 return nil 110 } 111 112 // schemaMigrationsPlanner is a vexecPlanner implementation, specific to _vt.schema_migrations table 113 type schemaMigrationsPlanner struct { 114 vx *vexec 115 d *vexecPlannerParams 116 } 117 118 func newSchemaMigrationsPlanner(vx *vexec) vexecPlanner { 119 return &schemaMigrationsPlanner{ 120 vx: vx, 121 d: &vexecPlannerParams{ 122 dbNameColumn: "mysql_schema", 123 workflowColumn: "migration_uuid", 124 updateTemplates: []string{ 125 `update _vt.schema_migrations set migration_status='val1'`, 126 `update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2'`, 127 `update _vt.schema_migrations set migration_status='val1' where migration_uuid='val2' and shard='val3'`, 128 }, 129 insertTemplates: []string{ 130 `INSERT IGNORE INTO _vt.schema_migrations ( 131 migration_uuid, 132 keyspace, 133 shard, 134 mysql_schema, 135 mysql_table, 136 migration_statement, 137 strategy, 138 options, 139 ddl_action, 140 requested_timestamp, 141 migration_context, 142 migration_status 143 ) VALUES ( 144 'val', 'val', 'val', 'val', 'val', 'val', 'val', 'val', 'val', FROM_UNIXTIME(0), 'val', 'val' 145 )`, 146 }, 147 }, 148 } 149 } 150 func (p schemaMigrationsPlanner) params() *vexecPlannerParams { return p.d } 151 func (p schemaMigrationsPlanner) exec(ctx context.Context, primaryAlias *topodatapb.TabletAlias, query string) (*querypb.QueryResult, error) { 152 qr, err := p.vx.wr.GenericVExec(ctx, primaryAlias, query, p.vx.workflow, p.vx.keyspace) 153 if err != nil { 154 return nil, err 155 } 156 return qr, nil 157 } 158 func (p schemaMigrationsPlanner) dryRun(ctx context.Context) error { return nil } 159 160 // make sure these planners implement vexecPlanner interface 161 var _ vexecPlanner = vreplicationPlanner{} 162 var _ vexecPlanner = schemaMigrationsPlanner{} 163 164 const ( 165 updateQuery = iota 166 deleteQuery 167 insertQuery 168 selectQuery 169 ) 170 171 // extractTableName returns the qualified table name (e.g. "_vt.schema_migrations") from a SELECT/DELETE/UPDATE statement 172 func extractTableName(stmt sqlparser.Statement) (string, error) { 173 switch stmt := stmt.(type) { 174 case *sqlparser.Update: 175 return sqlparser.String(stmt.TableExprs), nil 176 case *sqlparser.Delete: 177 return sqlparser.String(stmt.TableExprs), nil 178 case *sqlparser.Insert: 179 return sqlparser.String(stmt.Table), nil 180 case *sqlparser.Select: 181 return sqlparser.ToString(stmt.From), nil 182 } 183 return "", fmt.Errorf("query not supported by vexec: %+v", sqlparser.String(stmt)) 184 } 185 186 // qualifiedTableName qualifies a table with "_vt." schema 187 func qualifiedTableName(tableName string) string { 188 return fmt.Sprintf("%s.%s", vexecTableQualifier, tableName) 189 } 190 191 // getPlanner returns a specific planner appropriate for the queried table 192 func (vx *vexec) getPlanner(ctx context.Context) error { 193 switch vx.tableName { 194 case qualifiedTableName(schema.SchemaMigrationsTableName): 195 vx.planner = newSchemaMigrationsPlanner(vx) 196 case qualifiedTableName(vreplicationTableName): 197 vx.planner = newVReplicationPlanner(vx) 198 default: 199 return fmt.Errorf("table not supported by vexec: %v", vx.tableName) 200 } 201 return nil 202 } 203 204 // buildPlan builds an execution plan. More specifically, it generates the query which is then sent to 205 // relevant vttablet servers 206 func (vx *vexec) buildPlan(ctx context.Context) (plan *vexecPlan, err error) { 207 switch stmt := vx.stmt.(type) { 208 case *sqlparser.Update: 209 plan, err = vx.buildUpdatePlan(ctx, vx.planner, stmt) 210 case *sqlparser.Delete: 211 plan, err = vx.buildDeletePlan(ctx, vx.planner, stmt) 212 case *sqlparser.Insert: 213 plan, err = vx.buildInsertPlan(ctx, vx.planner, stmt) 214 case *sqlparser.Select: 215 plan, err = vx.buildSelectPlan(ctx, vx.planner, stmt) 216 default: 217 return nil, fmt.Errorf("query not supported by vexec: %s", sqlparser.String(stmt)) 218 } 219 return plan, err 220 } 221 222 // analyzeWhereEqualsColumns identifies column names in a WHERE clause that have a comparison expression 223 func (vx *vexec) analyzeWhereEqualsColumns(where *sqlparser.Where) []string { 224 var cols []string 225 if where == nil { 226 return cols 227 } 228 exprs := sqlparser.SplitAndExpression(nil, where.Expr) 229 for _, expr := range exprs { 230 switch expr := expr.(type) { 231 case *sqlparser.ComparisonExpr: 232 qualifiedName, ok := expr.Left.(*sqlparser.ColName) 233 if ok { 234 cols = append(cols, qualifiedName.Name.String()) 235 } 236 } 237 } 238 return cols 239 } 240 241 // addDefaultWheres modifies the query to add, if appropriate, the workflow and DB-name column modifiers 242 func (vx *vexec) addDefaultWheres(planner vexecPlanner, where *sqlparser.Where) *sqlparser.Where { 243 cols := vx.analyzeWhereEqualsColumns(where) 244 var hasDBName, hasWorkflow bool 245 plannerParams := planner.params() 246 for _, col := range cols { 247 if col == plannerParams.dbNameColumn { 248 hasDBName = true 249 } else if col == plannerParams.workflowColumn { 250 hasWorkflow = true 251 } 252 } 253 newWhere := where 254 if !hasDBName { 255 expr := &sqlparser.ComparisonExpr{ 256 Left: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI(plannerParams.dbNameColumn)}, 257 Operator: sqlparser.EqualOp, 258 Right: sqlparser.NewStrLiteral(vx.primaries[0].DbName()), 259 } 260 if newWhere == nil { 261 newWhere = &sqlparser.Where{ 262 Type: sqlparser.WhereClause, 263 Expr: expr, 264 } 265 } else { 266 newWhere.Expr = &sqlparser.AndExpr{ 267 Left: newWhere.Expr, 268 Right: expr, 269 } 270 } 271 } 272 if !hasWorkflow && vx.workflow != "" { 273 expr := &sqlparser.ComparisonExpr{ 274 Left: &sqlparser.ColName{Name: sqlparser.NewIdentifierCI(plannerParams.workflowColumn)}, 275 Operator: sqlparser.EqualOp, 276 Right: sqlparser.NewStrLiteral(vx.workflow), 277 } 278 newWhere.Expr = &sqlparser.AndExpr{ 279 Left: newWhere.Expr, 280 Right: expr, 281 } 282 } 283 return newWhere 284 } 285 286 // buildUpdatePlan builds a plan for an UPDATE query 287 func (vx *vexec) buildUpdatePlan(ctx context.Context, planner vexecPlanner, upd *sqlparser.Update) (*vexecPlan, error) { 288 if upd.OrderBy != nil || upd.Limit != nil { 289 return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(upd)) 290 } 291 plannerParams := planner.params() 292 if immutableColumnNames := plannerParams.immutableColumnNames; len(immutableColumnNames) > 0 { 293 // we must never allow changing an immutable column 294 for _, expr := range upd.Exprs { 295 for _, immutableColName := range plannerParams.immutableColumnNames { 296 if expr.Name.Name.EqualString(immutableColName) { 297 return nil, fmt.Errorf("%s cannot be changed: %v", immutableColName, sqlparser.String(expr)) 298 } 299 } 300 } 301 } 302 if updatableColumnNames := plannerParams.updatableColumnNames; len(updatableColumnNames) > 0 { 303 // if updatableColumnNames is non empty, then we must only accept changes to columns listed there 304 for _, expr := range upd.Exprs { 305 isUpdatable := false 306 for _, updatableColName := range updatableColumnNames { 307 if expr.Name.Name.EqualString(updatableColName) { 308 isUpdatable = true 309 } 310 } 311 if !isUpdatable { 312 return nil, fmt.Errorf("%+v cannot be changed: %v", expr.Name.Name, sqlparser.String(expr)) 313 } 314 } 315 } 316 if templates := plannerParams.updateTemplates; len(templates) > 0 { 317 match, err := sqlparser.QueryMatchesTemplates(vx.query, templates) 318 if err != nil { 319 return nil, err 320 } 321 if !match { 322 return nil, fmt.Errorf("Query must match one of these templates: %s", strings.Join(templates, "; ")) 323 } 324 } 325 upd.Where = vx.addDefaultWheres(planner, upd.Where) 326 327 buf := sqlparser.NewTrackedBuffer(nil) 328 buf.Myprintf("%v", upd) 329 330 return &vexecPlan{ 331 opcode: updateQuery, 332 parsedQuery: buf.ParsedQuery(), 333 }, nil 334 } 335 336 // buildUpdatePlan builds a plan for a DELETE query 337 func (vx *vexec) buildDeletePlan(ctx context.Context, planner vexecPlanner, del *sqlparser.Delete) (*vexecPlan, error) { 338 if del.Targets != nil { 339 return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del)) 340 } 341 if del.Partitions != nil { 342 return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del)) 343 } 344 if del.OrderBy != nil || del.Limit != nil { 345 return nil, fmt.Errorf("unsupported construct: %v", sqlparser.String(del)) 346 } 347 348 del.Where = vx.addDefaultWheres(planner, del.Where) 349 350 buf := sqlparser.NewTrackedBuffer(nil) 351 buf.Myprintf("%v", del) 352 353 return &vexecPlan{ 354 opcode: deleteQuery, 355 parsedQuery: buf.ParsedQuery(), 356 }, nil 357 } 358 359 // buildInsertPlan builds a plan for a INSERT query 360 func (vx *vexec) buildInsertPlan(ctx context.Context, planner vexecPlanner, ins *sqlparser.Insert) (*vexecPlan, error) { 361 plannerParams := planner.params() 362 templates := plannerParams.insertTemplates 363 if len(templates) == 0 { 364 // at this time INSERT is only supported if an insert template exists 365 // Remove this conditional if there's any new case for INSERT 366 return nil, fmt.Errorf("query not supported by vexec: %s", sqlparser.String(ins)) 367 } 368 if len(templates) > 0 { 369 match, err := sqlparser.QueryMatchesTemplates(vx.query, templates) 370 if err != nil { 371 return nil, err 372 } 373 if !match { 374 return nil, fmt.Errorf("Query must match one of these templates: %s", strings.Join(templates, "; ")) 375 } 376 } 377 378 buf := sqlparser.NewTrackedBuffer(nil) 379 buf.Myprintf("%v", ins) 380 381 return &vexecPlan{ 382 opcode: insertQuery, 383 parsedQuery: buf.ParsedQuery(), 384 }, nil 385 } 386 387 // buildUpdatePlan builds a plan for a SELECT query 388 func (vx *vexec) buildSelectPlan(ctx context.Context, planner vexecPlanner, sel *sqlparser.Select) (*vexecPlan, error) { 389 sel.Where = vx.addDefaultWheres(planner, sel.Where) 390 buf := sqlparser.NewTrackedBuffer(nil) 391 buf.Myprintf("%v", sel) 392 393 return &vexecPlan{ 394 opcode: selectQuery, 395 parsedQuery: buf.ParsedQuery(), 396 }, nil 397 }