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  }