vitess.io/vitess@v0.16.2/go/cmd/vtgateclienttest/services/echo.go (about)

     1  /*
     2  Copyright 2019 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 services
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"reflect"
    23  	"sort"
    24  	"strings"
    25  
    26  	"context"
    27  
    28  	"vitess.io/vitess/go/sqltypes"
    29  	"vitess.io/vitess/go/vt/callerid"
    30  	"vitess.io/vitess/go/vt/vtgate/vtgateservice"
    31  
    32  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    33  	querypb "vitess.io/vitess/go/vt/proto/query"
    34  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    35  	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
    36  )
    37  
    38  // EchoPrefix is the prefix to send with queries so they go
    39  // through this test suite.
    40  const EchoPrefix = "echo://"
    41  
    42  // echoClient implements vtgateservice.VTGateService, and prints the method
    43  // params into a QueryResult as fields and a single row. This allows checking
    44  // of request/result encoding/decoding.
    45  type echoClient struct {
    46  	fallbackClient
    47  }
    48  
    49  func newEchoClient(fallback vtgateservice.VTGateService) *echoClient {
    50  	return &echoClient{
    51  		fallbackClient: newFallbackClient(fallback),
    52  	}
    53  }
    54  
    55  func printSortedMap(val reflect.Value) []byte {
    56  	var keys []string
    57  	for _, key := range val.MapKeys() {
    58  		keys = append(keys, key.String())
    59  	}
    60  	sort.Strings(keys)
    61  	buf := &bytes.Buffer{}
    62  	buf.WriteString("map[")
    63  	for i, key := range keys {
    64  		if i > 0 {
    65  			buf.WriteRune(' ')
    66  		}
    67  		fmt.Fprintf(buf, "%s:%v", key, val.MapIndex(reflect.ValueOf(key)).Interface())
    68  	}
    69  	buf.WriteRune(']')
    70  	return buf.Bytes()
    71  }
    72  
    73  func echoQueryResult(vals map[string]any) *sqltypes.Result {
    74  	qr := &sqltypes.Result{}
    75  
    76  	var row []sqltypes.Value
    77  
    78  	// The first two returned fields are always a field with a MySQL NULL value,
    79  	// and another field with a zero-length string.
    80  	// Client tests can use this to check that they correctly distinguish the two.
    81  	qr.Fields = append(qr.Fields, &querypb.Field{Name: "null", Type: sqltypes.VarBinary})
    82  	row = append(row, sqltypes.NULL)
    83  	qr.Fields = append(qr.Fields, &querypb.Field{Name: "emptyString", Type: sqltypes.VarBinary})
    84  	row = append(row, sqltypes.NewVarBinary(""))
    85  
    86  	for k, v := range vals {
    87  		qr.Fields = append(qr.Fields, &querypb.Field{Name: k, Type: sqltypes.VarBinary})
    88  
    89  		val := reflect.ValueOf(v)
    90  		if val.Kind() == reflect.Map {
    91  			row = append(row, sqltypes.MakeTrusted(sqltypes.VarBinary, printSortedMap(val)))
    92  			continue
    93  		}
    94  		row = append(row, sqltypes.NewVarBinary(fmt.Sprintf("%v", v)))
    95  	}
    96  	qr.Rows = [][]sqltypes.Value{row}
    97  
    98  	return qr
    99  }
   100  
   101  func (c *echoClient) Execute(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable) (*vtgatepb.Session, *sqltypes.Result, error) {
   102  	if strings.HasPrefix(sql, EchoPrefix) {
   103  		return session, echoQueryResult(map[string]any{
   104  			"callerId": callerid.EffectiveCallerIDFromContext(ctx),
   105  			"query":    sql,
   106  			"bindVars": bindVariables,
   107  			"session":  session,
   108  		}), nil
   109  	}
   110  	return c.fallbackClient.Execute(ctx, session, sql, bindVariables)
   111  }
   112  
   113  func (c *echoClient) StreamExecute(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable, callback func(*sqltypes.Result) error) error {
   114  	if strings.HasPrefix(sql, EchoPrefix) {
   115  		callback(echoQueryResult(map[string]any{
   116  			"callerId": callerid.EffectiveCallerIDFromContext(ctx),
   117  			"query":    sql,
   118  			"bindVars": bindVariables,
   119  			"session":  session,
   120  		}))
   121  		return nil
   122  	}
   123  	return c.fallbackClient.StreamExecute(ctx, session, sql, bindVariables, callback)
   124  }
   125  
   126  func (c *echoClient) ExecuteBatch(ctx context.Context, session *vtgatepb.Session, sqlList []string, bindVariablesList []map[string]*querypb.BindVariable) (*vtgatepb.Session, []sqltypes.QueryResponse, error) {
   127  	if len(sqlList) > 0 && strings.HasPrefix(sqlList[0], EchoPrefix) {
   128  		var queryResponse []sqltypes.QueryResponse
   129  		if bindVariablesList == nil {
   130  			bindVariablesList = make([]map[string]*querypb.BindVariable, len(sqlList))
   131  		}
   132  		for queryNum, query := range sqlList {
   133  			result := echoQueryResult(map[string]any{
   134  				"callerId": callerid.EffectiveCallerIDFromContext(ctx),
   135  				"query":    query,
   136  				"bindVars": bindVariablesList[queryNum],
   137  				"session":  session,
   138  			})
   139  			queryResponse = append(queryResponse, sqltypes.QueryResponse{QueryResult: result, QueryError: nil})
   140  		}
   141  		return session, queryResponse, nil
   142  	}
   143  	return c.fallbackClient.ExecuteBatch(ctx, session, sqlList, bindVariablesList)
   144  }
   145  
   146  func (c *echoClient) VStream(ctx context.Context, tabletType topodatapb.TabletType, vgtid *binlogdatapb.VGtid, filter *binlogdatapb.Filter, flags *vtgatepb.VStreamFlags, callback func([]*binlogdatapb.VEvent) error) error {
   147  	if strings.HasPrefix(vgtid.ShardGtids[0].Shard, EchoPrefix) {
   148  		_ = callback([]*binlogdatapb.VEvent{
   149  			{
   150  				Type:      1,
   151  				Timestamp: 1234,
   152  				Gtid:      "echo-gtid-1",
   153  				Statement: "echo-ddl-1",
   154  				Vgtid:     vgtid,
   155  				RowEvent: &binlogdatapb.RowEvent{
   156  					TableName: "echo-table-1",
   157  				},
   158  			},
   159  			{
   160  				Type:      2,
   161  				Timestamp: 4321,
   162  				Gtid:      "echo-gtid-2",
   163  				Statement: "echo-ddl-2",
   164  				Vgtid:     vgtid,
   165  				FieldEvent: &binlogdatapb.FieldEvent{
   166  					TableName: "echo-table-2",
   167  				},
   168  			},
   169  		})
   170  		return nil
   171  	}
   172  
   173  	return c.fallbackClient.VStream(ctx, tabletType, vgtid, filter, flags, callback)
   174  }
   175  
   176  func (c *echoClient) Prepare(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable) (*vtgatepb.Session, []*querypb.Field, error) {
   177  	if strings.HasPrefix(sql, EchoPrefix) {
   178  		return session, echoQueryResult(map[string]any{
   179  			"callerId": callerid.EffectiveCallerIDFromContext(ctx),
   180  			"query":    sql,
   181  			"bindVars": bindVariables,
   182  			"session":  session,
   183  		}).Fields, nil
   184  	}
   185  	return c.fallbackClient.Prepare(ctx, session, sql, bindVariables)
   186  }