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  }