vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/vexplain.go (about)

     1  /*
     2  Copyright 2022 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 engine
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  
    24  	"vitess.io/vitess/go/sqltypes"
    25  	querypb "vitess.io/vitess/go/vt/proto/query"
    26  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    27  	"vitess.io/vitess/go/vt/sqlparser"
    28  	"vitess.io/vitess/go/vt/srvtopo"
    29  	"vitess.io/vitess/go/vt/vterrors"
    30  )
    31  
    32  type (
    33  	ExecuteEntry struct {
    34  		ID        int
    35  		Target    *querypb.Target
    36  		Gateway   srvtopo.Gateway
    37  		Query     string
    38  		FiredFrom Primitive
    39  	}
    40  
    41  	VExplain struct {
    42  		Input Primitive
    43  		Type  sqlparser.VExplainType
    44  	}
    45  )
    46  
    47  var _ Primitive = (*VExplain)(nil)
    48  
    49  // RouteType implements the Primitive interface
    50  func (v *VExplain) RouteType() string {
    51  	return v.Input.RouteType()
    52  }
    53  
    54  // GetKeyspaceName implements the Primitive interface
    55  func (v *VExplain) GetKeyspaceName() string {
    56  	return v.Input.GetKeyspaceName()
    57  }
    58  
    59  // GetTableName implements the Primitive interface
    60  func (v *VExplain) GetTableName() string {
    61  	return v.Input.GetTableName()
    62  }
    63  
    64  // GetFields implements the Primitive interface
    65  func (v *VExplain) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
    66  	return v.Input.GetFields(ctx, vcursor, bindVars)
    67  }
    68  
    69  // NeedsTransaction implements the Primitive interface
    70  func (v *VExplain) NeedsTransaction() bool {
    71  	return v.Input.NeedsTransaction()
    72  }
    73  
    74  // TryExecute implements the Primitive interface
    75  func (v *VExplain) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    76  	vcursor.Session().VExplainLogging()
    77  	_, err := vcursor.ExecutePrimitive(ctx, v.Input, bindVars, wantfields)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return v.convertToResult(ctx, vcursor)
    82  }
    83  
    84  // TryStreamExecute implements the Primitive interface
    85  func (v *VExplain) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
    86  	vcursor.Session().VExplainLogging()
    87  	err := vcursor.StreamExecutePrimitive(ctx, v.Input, bindVars, wantfields, func(result *sqltypes.Result) error {
    88  		return nil
    89  	})
    90  	if err != nil {
    91  		return err
    92  	}
    93  	result, err := v.convertToResult(ctx, vcursor)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return callback(result)
    98  }
    99  
   100  func (v *VExplain) convertToResult(ctx context.Context, vcursor VCursor) (*sqltypes.Result, error) {
   101  	switch v.Type {
   102  	case sqlparser.QueriesVExplainType:
   103  		result := convertToVExplainQueriesResult(vcursor.Session().GetVExplainLogs())
   104  		return result, nil
   105  	case sqlparser.AllVExplainType:
   106  		return v.convertToVExplainAllResult(ctx, vcursor)
   107  	default:
   108  		return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "Unknown type of VExplain plan")
   109  	}
   110  }
   111  
   112  func (v *VExplain) convertToVExplainAllResult(ctx context.Context, vcursor VCursor) (*sqltypes.Result, error) {
   113  	logEntries := vcursor.Session().GetVExplainLogs()
   114  	explainResults := make(map[Primitive]string)
   115  	for _, entry := range logEntries {
   116  		if entry.Target == nil || entry.Gateway == nil || entry.FiredFrom == nil {
   117  			continue
   118  		}
   119  		if explainResults[entry.FiredFrom] != "" {
   120  			continue
   121  		}
   122  		explainQuery := fmt.Sprintf("explain format = json %v", entry.Query)
   123  		// We rely on the parser to see if the query we have is explainable or not
   124  		// If we get an error in parsing then we can't execute explain on the given query, and we skip it
   125  		_, err := sqlparser.Parse(explainQuery)
   126  		if err != nil {
   127  			continue
   128  		}
   129  		// Explain statement should now succeed
   130  		res, err := vcursor.ExecuteStandalone(ctx, nil, explainQuery, nil, &srvtopo.ResolvedShard{
   131  			Target:  entry.Target,
   132  			Gateway: entry.Gateway,
   133  		})
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  		explainResults[entry.FiredFrom] = res.Rows[0][0].ToString()
   138  	}
   139  
   140  	planDescription := primitiveToPlanDescriptionWithSQLResults(v.Input, explainResults)
   141  	resultBytes, err := json.MarshalIndent(planDescription, "", "\t")
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	result := string(resultBytes)
   147  	fields := []*querypb.Field{
   148  		{
   149  			Name: "VExplain", Type: sqltypes.VarChar,
   150  		},
   151  	}
   152  	rows := []sqltypes.Row{
   153  		{
   154  			sqltypes.NewVarChar(result),
   155  		},
   156  	}
   157  	qr := &sqltypes.Result{
   158  		Fields: fields,
   159  		Rows:   rows,
   160  	}
   161  	return qr, nil
   162  }
   163  
   164  // primitiveToPlanDescriptionWithSQLResults transforms a primitive tree into a corresponding PlanDescription tree
   165  // and adds the given res ...
   166  func primitiveToPlanDescriptionWithSQLResults(in Primitive, res map[Primitive]string) PrimitiveDescription {
   167  	this := in.description()
   168  
   169  	if v, found := res[in]; found {
   170  		this.Other["mysql_explain_json"] = json.RawMessage(v)
   171  	}
   172  
   173  	for _, input := range in.Inputs() {
   174  		this.Inputs = append(this.Inputs, primitiveToPlanDescriptionWithSQLResults(input, res))
   175  	}
   176  
   177  	if len(in.Inputs()) == 0 {
   178  		this.Inputs = []PrimitiveDescription{}
   179  	}
   180  
   181  	return this
   182  }
   183  
   184  func convertToVExplainQueriesResult(logs []ExecuteEntry) *sqltypes.Result {
   185  	fields := []*querypb.Field{{
   186  		Name: "#", Type: sqltypes.Int32,
   187  	}, {
   188  		Name: "keyspace", Type: sqltypes.VarChar,
   189  	}, {
   190  		Name: "shard", Type: sqltypes.VarChar,
   191  	}, {
   192  		Name: "query", Type: sqltypes.VarChar,
   193  	}}
   194  	qr := &sqltypes.Result{
   195  		Fields: fields,
   196  	}
   197  	for _, line := range logs {
   198  		qr.Rows = append(qr.Rows, sqltypes.Row{
   199  			sqltypes.NewInt32(int32(line.ID)),
   200  			sqltypes.NewVarChar(line.Target.Keyspace),
   201  			sqltypes.NewVarChar(line.Target.Shard),
   202  			sqltypes.NewVarChar(line.Query),
   203  		})
   204  	}
   205  	return qr
   206  }
   207  
   208  // Inputs implements the Primitive interface
   209  func (v *VExplain) Inputs() []Primitive {
   210  	return []Primitive{v.Input}
   211  }
   212  
   213  func (v *VExplain) description() PrimitiveDescription {
   214  	return PrimitiveDescription{
   215  		OperatorType: "VEXPLAIN",
   216  		Other:        map[string]any{"Type": v.Type.ToString()},
   217  	}
   218  }