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 }