vitess.io/vitess@v0.16.2/go/vt/vtctl/workflow/vexec/query_plan.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  	"context"
    21  	"fmt"
    22  	"sync"
    23  
    24  	"vitess.io/vitess/go/vt/concurrency"
    25  	"vitess.io/vitess/go/vt/log"
    26  	"vitess.io/vitess/go/vt/sqlparser"
    27  	"vitess.io/vitess/go/vt/topo"
    28  	"vitess.io/vitess/go/vt/vterrors"
    29  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    30  
    31  	querypb "vitess.io/vitess/go/vt/proto/query"
    32  )
    33  
    34  // QueryPlan defines the interface to executing a preprared vexec query on one
    35  // or more tablets. Implementations should ensure that it is safe to call the
    36  // various Execute* methods repeatedly and in multiple goroutines.
    37  type QueryPlan interface {
    38  	// Execute executes the planned query on a single target.
    39  	Execute(ctx context.Context, target *topo.TabletInfo) (*querypb.QueryResult, error)
    40  	// ExecuteScatter executes the planned query on the specified targets concurrently,
    41  	// returning a mapping of the target tablet to a querypb.QueryResult.
    42  	ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error)
    43  }
    44  
    45  // FixedQueryPlan wraps a planned query produced by a QueryPlanner. It executes
    46  // the same query with the same bind vals, regardless of the target.
    47  type FixedQueryPlan struct {
    48  	ParsedQuery *sqlparser.ParsedQuery
    49  
    50  	workflow string
    51  	tmc      tmclient.TabletManagerClient
    52  }
    53  
    54  // Execute is part of the QueryPlan interface.
    55  func (qp *FixedQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) {
    56  	if qp.ParsedQuery == nil {
    57  		return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery)
    58  	}
    59  
    60  	targetAliasStr := target.AliasString()
    61  
    62  	defer func() {
    63  		if err != nil {
    64  			log.Warningf("Result on %v: %v", targetAliasStr, err)
    65  			return
    66  		}
    67  	}()
    68  
    69  	qr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, qp.ParsedQuery.Query)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return qr, nil
    74  }
    75  
    76  // ExecuteScatter is part of the QueryPlan interface. For a FixedQueryPlan, the
    77  // exact same query is executed on each target, and errors from individual
    78  // targets are aggregated into a singular error.
    79  func (qp *FixedQueryPlan) ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error) {
    80  	if qp.ParsedQuery == nil {
    81  		// This check is an "optimization" on error handling. We check here,
    82  		// even though we will check this during the individual Execute calls,
    83  		// so that we return one error, rather than the same error aggregated
    84  		// len(targets) times.
    85  		return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery)
    86  	}
    87  
    88  	var (
    89  		m       sync.Mutex
    90  		wg      sync.WaitGroup
    91  		rec     concurrency.AllErrorRecorder
    92  		results = make(map[*topo.TabletInfo]*querypb.QueryResult, len(targets))
    93  	)
    94  
    95  	for _, target := range targets {
    96  		wg.Add(1)
    97  
    98  		go func(ctx context.Context, target *topo.TabletInfo) {
    99  			defer wg.Done()
   100  
   101  			qr, err := qp.Execute(ctx, target)
   102  			if err != nil {
   103  				rec.RecordError(err)
   104  
   105  				return
   106  			}
   107  
   108  			m.Lock()
   109  			defer m.Unlock()
   110  
   111  			results[target] = qr
   112  		}(ctx, target)
   113  	}
   114  
   115  	wg.Wait()
   116  
   117  	return results, rec.AggrError(vterrors.Aggregate)
   118  }
   119  
   120  // PerTargetQueryPlan implements the QueryPlan interface. Unlike FixedQueryPlan,
   121  // this implementation implements different queries, keyed by tablet alias, on
   122  // different targets.
   123  //
   124  // It is the callers responsibility to ensure that the shape of the QueryResult
   125  // (i.e. fields returned) is consistent for each target's planned query, but
   126  // this is not enforced.
   127  type PerTargetQueryPlan struct {
   128  	ParsedQueries map[string]*sqlparser.ParsedQuery
   129  
   130  	tmc tmclient.TabletManagerClient
   131  }
   132  
   133  // Execute is part of the QueryPlan interface.
   134  //
   135  // It returns ErrUnpreparedQuery if there is no ParsedQuery for the target's
   136  // tablet alias.
   137  func (qp *PerTargetQueryPlan) Execute(ctx context.Context, target *topo.TabletInfo) (qr *querypb.QueryResult, err error) {
   138  	if qp.ParsedQueries == nil {
   139  		return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery)
   140  	}
   141  
   142  	targetAliasStr := target.AliasString()
   143  	query, ok := qp.ParsedQueries[targetAliasStr]
   144  	if !ok {
   145  		return nil, fmt.Errorf("%w: no prepared query for target %s", ErrUnpreparedQuery, targetAliasStr)
   146  	}
   147  
   148  	defer func() {
   149  		if err != nil {
   150  			log.Warningf("Result on %v: %v", targetAliasStr, err)
   151  			return
   152  		}
   153  	}()
   154  
   155  	qr, err = qp.tmc.VReplicationExec(ctx, target.Tablet, query.Query)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return qr, nil
   161  }
   162  
   163  // ExecuteScatter is part of the QueryPlan interface.
   164  func (qp *PerTargetQueryPlan) ExecuteScatter(ctx context.Context, targets ...*topo.TabletInfo) (map[*topo.TabletInfo]*querypb.QueryResult, error) {
   165  	if qp.ParsedQueries == nil {
   166  		// This check is an "optimization" on error handling. We check here,
   167  		// even though we will check this during the individual Execute calls,
   168  		// so that we return one error, rather than the same error aggregated
   169  		// len(targets) times.
   170  		return nil, fmt.Errorf("%w: call PlanQuery on a query planner first", ErrUnpreparedQuery)
   171  	}
   172  
   173  	var (
   174  		m       sync.Mutex
   175  		wg      sync.WaitGroup
   176  		rec     concurrency.AllErrorRecorder
   177  		results = make(map[*topo.TabletInfo]*querypb.QueryResult, len(targets))
   178  	)
   179  
   180  	for _, target := range targets {
   181  		wg.Add(1)
   182  
   183  		go func(ctx context.Context, target *topo.TabletInfo) {
   184  			defer wg.Done()
   185  
   186  			qr, err := qp.Execute(ctx, target)
   187  			if err != nil {
   188  				rec.RecordError(err)
   189  
   190  				return
   191  			}
   192  
   193  			m.Lock()
   194  			defer m.Unlock()
   195  
   196  			results[target] = qr
   197  		}(ctx, target)
   198  	}
   199  
   200  	wg.Wait()
   201  
   202  	return results, rec.AggrError(vterrors.Aggregate)
   203  }