vitess.io/vitess@v0.16.2/go/vt/vtgate/legacy_scatter_conn_test.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 vtgate 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "sync" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 29 "vitess.io/vitess/go/sqltypes" 30 "vitess.io/vitess/go/test/utils" 31 "vitess.io/vitess/go/vt/discovery" 32 "vitess.io/vitess/go/vt/key" 33 "vitess.io/vitess/go/vt/srvtopo" 34 "vitess.io/vitess/go/vt/vterrors" 35 36 querypb "vitess.io/vitess/go/vt/proto/query" 37 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 38 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 39 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 40 ) 41 42 // This file uses the sandbox_test framework. 43 44 func TestLegacyExecuteFailOnAutocommit(t *testing.T) { 45 46 createSandbox("TestExecuteFailOnAutocommit") 47 hc := discovery.NewFakeHealthCheck(nil) 48 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 49 sbc0 := hc.AddTestTablet("aa", "0", 1, "TestExecuteFailOnAutocommit", "0", topodatapb.TabletType_PRIMARY, true, 1, nil) 50 sbc1 := hc.AddTestTablet("aa", "1", 1, "TestExecuteFailOnAutocommit", "1", topodatapb.TabletType_PRIMARY, true, 1, nil) 51 52 rss := []*srvtopo.ResolvedShard{ 53 { 54 Target: &querypb.Target{ 55 Keyspace: "TestExecuteFailOnAutocommit", 56 Shard: "0", 57 TabletType: topodatapb.TabletType_PRIMARY, 58 }, 59 Gateway: sbc0, 60 }, 61 { 62 Target: &querypb.Target{ 63 Keyspace: "TestExecuteFailOnAutocommit", 64 Shard: "1", 65 TabletType: topodatapb.TabletType_PRIMARY, 66 }, 67 Gateway: sbc1, 68 }, 69 } 70 queries := []*querypb.BoundQuery{ 71 { 72 // This will fail to go to shard. It will be rejected at vtgate. 73 Sql: "query1", 74 BindVariables: map[string]*querypb.BindVariable{ 75 "bv0": sqltypes.Int64BindVariable(0), 76 }, 77 }, 78 { 79 // This will go to shard. 80 Sql: "query2", 81 BindVariables: map[string]*querypb.BindVariable{ 82 "bv1": sqltypes.Int64BindVariable(1), 83 }, 84 }, 85 } 86 // shard 0 - has transaction 87 // shard 1 - does not have transaction. 88 session := &vtgatepb.Session{ 89 InTransaction: true, 90 ShardSessions: []*vtgatepb.Session_ShardSession{ 91 { 92 Target: &querypb.Target{Keyspace: "TestExecuteFailOnAutocommit", Shard: "0", TabletType: topodatapb.TabletType_PRIMARY, Cell: "aa"}, 93 TransactionId: 123, 94 TabletAlias: nil, 95 }, 96 }, 97 Autocommit: false, 98 } 99 _, errs := sc.ExecuteMultiShard(ctx, nil, rss, queries, NewSafeSession(session), true /*autocommit*/, false) 100 err := vterrors.Aggregate(errs) 101 require.Error(t, err) 102 require.Contains(t, err.Error(), "in autocommit mode, transactionID should be zero but was: 123") 103 utils.MustMatch(t, 0, len(sbc0.Queries), "") 104 utils.MustMatch(t, []*querypb.BoundQuery{queries[1]}, sbc1.Queries, "") 105 } 106 107 func TestScatterConnExecuteMulti(t *testing.T) { 108 testScatterConnGeneric(t, "TestScatterConnExecuteMultiShard", func(sc *ScatterConn, shards []string) (*sqltypes.Result, error) { 109 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 110 rss, err := res.ResolveDestination(ctx, "TestScatterConnExecuteMultiShard", topodatapb.TabletType_REPLICA, key.DestinationShards(shards)) 111 if err != nil { 112 return nil, err 113 } 114 115 queries := make([]*querypb.BoundQuery, len(rss)) 116 for i := range rss { 117 queries[i] = &querypb.BoundQuery{ 118 Sql: "query", 119 BindVariables: nil, 120 } 121 } 122 123 qr, errs := sc.ExecuteMultiShard(ctx, nil, rss, queries, NewSafeSession(nil), false /*autocommit*/, false) 124 return qr, vterrors.Aggregate(errs) 125 }) 126 } 127 128 func TestScatterConnStreamExecuteMulti(t *testing.T) { 129 testScatterConnGeneric(t, "TestScatterConnStreamExecuteMulti", func(sc *ScatterConn, shards []string) (*sqltypes.Result, error) { 130 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 131 rss, err := res.ResolveDestination(ctx, "TestScatterConnStreamExecuteMulti", topodatapb.TabletType_REPLICA, key.DestinationShards(shards)) 132 if err != nil { 133 return nil, err 134 } 135 bvs := make([]map[string]*querypb.BindVariable, len(rss)) 136 qr := new(sqltypes.Result) 137 var mu sync.Mutex 138 errors := sc.StreamExecuteMulti(ctx, nil, "query", rss, bvs, NewSafeSession(&vtgatepb.Session{InTransaction: true}), true /* autocommit */, func(r *sqltypes.Result) error { 139 mu.Lock() 140 defer mu.Unlock() 141 qr.AppendResult(r) 142 return nil 143 }) 144 return qr, vterrors.Aggregate(errors) 145 }) 146 } 147 148 // verifyScatterConnError checks that a returned error has the expected message, 149 // type, and error code. 150 func verifyScatterConnError(t *testing.T, err error, wantErr string, wantCode vtrpcpb.Code) { 151 t.Helper() 152 assert.EqualError(t, err, wantErr) 153 assert.Equal(t, wantCode, vterrors.Code(err)) 154 } 155 156 func testScatterConnGeneric(t *testing.T, name string, f func(sc *ScatterConn, shards []string) (*sqltypes.Result, error)) { 157 hc := discovery.NewFakeHealthCheck(nil) 158 159 // no shard 160 s := createSandbox(name) 161 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 162 qr, err := f(sc, nil) 163 require.NoError(t, err) 164 if qr.RowsAffected != 0 { 165 t.Errorf("want 0, got %v", qr.RowsAffected) 166 } 167 168 // single shard 169 s.Reset() 170 sc = newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 171 sbc := hc.AddTestTablet("aa", "0", 1, name, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 172 sbc.MustFailCodes[vtrpcpb.Code_INVALID_ARGUMENT] = 1 173 _, err = f(sc, []string{"0"}) 174 want := fmt.Sprintf("target: %v.0.replica: INVALID_ARGUMENT error", name) 175 // Verify server error string. 176 if err == nil || err.Error() != want { 177 t.Errorf("want %s, got %v", want, err) 178 } 179 // Ensure that we tried only once. 180 if execCount := sbc.ExecCount.Get(); execCount != 1 { 181 t.Errorf("want 1, got %v", execCount) 182 } 183 184 // two shards 185 s.Reset() 186 hc.Reset() 187 sc = newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 188 sbc0 := hc.AddTestTablet("aa", "0", 1, name, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 189 sbc1 := hc.AddTestTablet("aa", "1", 1, name, "1", topodatapb.TabletType_REPLICA, true, 1, nil) 190 sbc0.MustFailCodes[vtrpcpb.Code_INVALID_ARGUMENT] = 1 191 sbc1.MustFailCodes[vtrpcpb.Code_INVALID_ARGUMENT] = 1 192 _, err = f(sc, []string{"0", "1"}) 193 // Verify server errors are consolidated. 194 want = fmt.Sprintf("target: %v.0.replica: INVALID_ARGUMENT error\ntarget: %v.1.replica: INVALID_ARGUMENT error", name, name) 195 verifyScatterConnError(t, err, want, vtrpcpb.Code_INVALID_ARGUMENT) 196 // Ensure that we tried only once. 197 if execCount := sbc0.ExecCount.Get(); execCount != 1 { 198 t.Errorf("want 1, got %v", execCount) 199 } 200 if execCount := sbc1.ExecCount.Get(); execCount != 1 { 201 t.Errorf("want 1, got %v", execCount) 202 } 203 204 // two shards with different errors 205 s.Reset() 206 hc.Reset() 207 sc = newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 208 sbc0 = hc.AddTestTablet("aa", "0", 1, name, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 209 sbc1 = hc.AddTestTablet("aa", "1", 1, name, "1", topodatapb.TabletType_REPLICA, true, 1, nil) 210 sbc0.MustFailCodes[vtrpcpb.Code_INVALID_ARGUMENT] = 1 211 sbc1.MustFailCodes[vtrpcpb.Code_RESOURCE_EXHAUSTED] = 1 212 _, err = f(sc, []string{"0", "1"}) 213 // Verify server errors are consolidated. 214 want = fmt.Sprintf("target: %v.0.replica: INVALID_ARGUMENT error\ntarget: %v.1.replica: RESOURCE_EXHAUSTED error", name, name) 215 // We should only surface the higher priority error code 216 verifyScatterConnError(t, err, want, vtrpcpb.Code_INVALID_ARGUMENT) 217 // Ensure that we tried only once. 218 if execCount := sbc0.ExecCount.Get(); execCount != 1 { 219 t.Errorf("want 1, got %v", execCount) 220 } 221 if execCount := sbc1.ExecCount.Get(); execCount != 1 { 222 t.Errorf("want 1, got %v", execCount) 223 } 224 225 // duplicate shards 226 s.Reset() 227 hc.Reset() 228 sc = newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 229 sbc = hc.AddTestTablet("aa", "0", 1, name, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 230 _, _ = f(sc, []string{"0", "0"}) 231 // Ensure that we executed only once. 232 if execCount := sbc.ExecCount.Get(); execCount != 1 { 233 t.Errorf("want 1, got %v", execCount) 234 } 235 236 // no errors 237 s.Reset() 238 hc.Reset() 239 sc = newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 240 sbc0 = hc.AddTestTablet("aa", "0", 1, name, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 241 sbc1 = hc.AddTestTablet("aa", "1", 1, name, "1", topodatapb.TabletType_REPLICA, true, 1, nil) 242 qr, err = f(sc, []string{"0", "1"}) 243 if err != nil { 244 t.Fatalf("want nil, got %v", err) 245 } 246 if execCount := sbc0.ExecCount.Get(); execCount != 1 { 247 t.Errorf("want 1, got %v", execCount) 248 } 249 if execCount := sbc1.ExecCount.Get(); execCount != 1 { 250 t.Errorf("want 1, got %v", execCount) 251 } 252 if qr.RowsAffected != 0 { 253 t.Errorf("want 0, got %v", qr.RowsAffected) 254 } 255 if len(qr.Rows) != 2 { 256 t.Errorf("want 2, got %v", len(qr.Rows)) 257 } 258 } 259 260 func TestMaxMemoryRows(t *testing.T) { 261 save := maxMemoryRows 262 maxMemoryRows = 3 263 defer func() { maxMemoryRows = save }() 264 265 createSandbox("TestMaxMemoryRows") 266 hc := discovery.NewFakeHealthCheck(nil) 267 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 268 sbc0 := hc.AddTestTablet("aa", "0", 1, "TestMaxMemoryRows", "0", topodatapb.TabletType_REPLICA, true, 1, nil) 269 sbc1 := hc.AddTestTablet("aa", "1", 1, "TestMaxMemoryRows", "1", topodatapb.TabletType_REPLICA, true, 1, nil) 270 271 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 272 rss, _, err := res.ResolveDestinations(ctx, "TestMaxMemoryRows", topodatapb.TabletType_REPLICA, nil, 273 []key.Destination{key.DestinationShard("0"), key.DestinationShard("1")}) 274 require.NoError(t, err) 275 276 session := NewSafeSession(&vtgatepb.Session{InTransaction: true}) 277 queries := []*querypb.BoundQuery{{ 278 Sql: "query1", 279 BindVariables: map[string]*querypb.BindVariable{}, 280 }, { 281 Sql: "query1", 282 BindVariables: map[string]*querypb.BindVariable{}, 283 }} 284 tworows := &sqltypes.Result{ 285 Rows: [][]sqltypes.Value{{ 286 sqltypes.NewInt64(1), 287 }, { 288 sqltypes.NewInt64(1), 289 }}, 290 RowsAffected: 1, 291 InsertID: 1, 292 } 293 294 testCases := []struct { 295 ignoreMaxMemoryRows bool 296 err string 297 }{ 298 {true, ""}, 299 {false, "in-memory row count exceeded allowed limit of 3"}, 300 } 301 302 for _, test := range testCases { 303 sbc0.SetResults([]*sqltypes.Result{tworows, tworows}) 304 sbc1.SetResults([]*sqltypes.Result{tworows, tworows}) 305 306 _, errs := sc.ExecuteMultiShard(ctx, nil, rss, queries, session, false, test.ignoreMaxMemoryRows) 307 if test.ignoreMaxMemoryRows { 308 require.NoError(t, err) 309 } else { 310 assert.EqualError(t, errs[0], test.err) 311 } 312 } 313 } 314 315 func TestLegaceHealthCheckFailsOnReservedConnections(t *testing.T) { 316 keyspace := "keyspace" 317 createSandbox(keyspace) 318 hc := discovery.NewFakeHealthCheck(nil) 319 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 320 321 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 322 323 session := NewSafeSession(&vtgatepb.Session{InTransaction: false, InReservedConn: true}) 324 destinations := []key.Destination{key.DestinationShard("0")} 325 rss, _, err := res.ResolveDestinations(ctx, keyspace, topodatapb.TabletType_REPLICA, nil, destinations) 326 require.NoError(t, err) 327 328 var queries []*querypb.BoundQuery 329 330 for range rss { 331 queries = append(queries, &querypb.BoundQuery{ 332 Sql: "query1", 333 BindVariables: map[string]*querypb.BindVariable{}, 334 }) 335 } 336 337 _, errs := sc.ExecuteMultiShard(ctx, nil, rss, queries, session, false, false) 338 require.Error(t, vterrors.Aggregate(errs)) 339 } 340 341 func executeOnShards(t *testing.T, res *srvtopo.Resolver, keyspace string, sc *ScatterConn, session *SafeSession, destinations []key.Destination) { 342 t.Helper() 343 require.Empty(t, executeOnShardsReturnsErr(t, res, keyspace, sc, session, destinations)) 344 } 345 346 func executeOnShardsReturnsErr(t *testing.T, res *srvtopo.Resolver, keyspace string, sc *ScatterConn, session *SafeSession, destinations []key.Destination) error { 347 t.Helper() 348 rss, _, err := res.ResolveDestinations(ctx, keyspace, topodatapb.TabletType_REPLICA, nil, destinations) 349 require.NoError(t, err) 350 351 var queries []*querypb.BoundQuery 352 353 for range rss { 354 queries = append(queries, &querypb.BoundQuery{ 355 Sql: "query1", 356 BindVariables: map[string]*querypb.BindVariable{}, 357 }) 358 } 359 360 _, errs := sc.ExecuteMultiShard(ctx, nil, rss, queries, session, false, false) 361 return vterrors.Aggregate(errs) 362 } 363 364 func TestMultiExecs(t *testing.T) { 365 createSandbox("TestMultiExecs") 366 hc := discovery.NewFakeHealthCheck(nil) 367 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 368 sbc0 := hc.AddTestTablet("aa", "0", 1, "TestMultiExecs", "0", topodatapb.TabletType_REPLICA, true, 1, nil) 369 sbc1 := hc.AddTestTablet("aa", "1", 1, "TestMultiExecs", "1", topodatapb.TabletType_REPLICA, true, 1, nil) 370 371 rss := []*srvtopo.ResolvedShard{ 372 { 373 Target: &querypb.Target{ 374 Keyspace: "TestMultiExecs", 375 Shard: "0", 376 TabletType: topodatapb.TabletType_REPLICA, 377 }, 378 Gateway: sbc0, 379 }, 380 { 381 Target: &querypb.Target{ 382 Keyspace: "TestMultiExecs", 383 Shard: "1", 384 TabletType: topodatapb.TabletType_REPLICA, 385 }, 386 Gateway: sbc1, 387 }, 388 } 389 queries := []*querypb.BoundQuery{ 390 { 391 Sql: "query1", 392 BindVariables: map[string]*querypb.BindVariable{ 393 "bv0": sqltypes.Int64BindVariable(0), 394 }, 395 }, 396 { 397 Sql: "query2", 398 BindVariables: map[string]*querypb.BindVariable{ 399 "bv1": sqltypes.Int64BindVariable(1), 400 }, 401 }, 402 } 403 404 session := NewSafeSession(&vtgatepb.Session{}) 405 _, err := sc.ExecuteMultiShard(ctx, nil, rss, queries, session, false, false) 406 require.NoError(t, vterrors.Aggregate(err)) 407 if len(sbc0.Queries) == 0 || len(sbc1.Queries) == 0 { 408 t.Fatalf("didn't get expected query") 409 } 410 wantVars0 := map[string]*querypb.BindVariable{ 411 "bv0": queries[0].BindVariables["bv0"], 412 } 413 if !reflect.DeepEqual(sbc0.Queries[0].BindVariables, wantVars0) { 414 t.Errorf("got %v, want %v", sbc0.Queries[0].BindVariables, wantVars0) 415 } 416 wantVars1 := map[string]*querypb.BindVariable{ 417 "bv1": queries[1].BindVariables["bv1"], 418 } 419 if !reflect.DeepEqual(sbc1.Queries[0].BindVariables, wantVars1) { 420 t.Errorf("got %+v, want %+v", sbc0.Queries[0].BindVariables, wantVars1) 421 } 422 sbc0.Queries = nil 423 sbc1.Queries = nil 424 425 rss = []*srvtopo.ResolvedShard{ 426 { 427 Target: &querypb.Target{ 428 Keyspace: "TestMultiExecs", 429 Shard: "0", 430 }, 431 Gateway: sbc0, 432 }, 433 { 434 Target: &querypb.Target{ 435 Keyspace: "TestMultiExecs", 436 Shard: "1", 437 }, 438 Gateway: sbc1, 439 }, 440 } 441 bvs := []map[string]*querypb.BindVariable{ 442 { 443 "bv0": sqltypes.Int64BindVariable(0), 444 }, 445 { 446 "bv1": sqltypes.Int64BindVariable(1), 447 }, 448 } 449 _ = sc.StreamExecuteMulti(ctx, nil, "query", rss, bvs, session, false /* autocommit */, func(*sqltypes.Result) error { 450 return nil 451 }) 452 if !reflect.DeepEqual(sbc0.Queries[0].BindVariables, wantVars0) { 453 t.Errorf("got %+v, want %+v", sbc0.Queries[0].BindVariables, wantVars0) 454 } 455 if !reflect.DeepEqual(sbc1.Queries[0].BindVariables, wantVars1) { 456 t.Errorf("got %+v, want %+v", sbc0.Queries[0].BindVariables, wantVars1) 457 } 458 } 459 460 func TestScatterConnSingleDB(t *testing.T) { 461 createSandbox("TestScatterConnSingleDB") 462 hc := discovery.NewFakeHealthCheck(nil) 463 464 hc.Reset() 465 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 466 hc.AddTestTablet("aa", "0", 1, "TestScatterConnSingleDB", "0", topodatapb.TabletType_PRIMARY, true, 1, nil) 467 hc.AddTestTablet("aa", "1", 1, "TestScatterConnSingleDB", "1", topodatapb.TabletType_PRIMARY, true, 1, nil) 468 469 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 470 rss0, err := res.ResolveDestination(ctx, "TestScatterConnSingleDB", topodatapb.TabletType_PRIMARY, key.DestinationShard("0")) 471 require.NoError(t, err) 472 rss1, err := res.ResolveDestination(ctx, "TestScatterConnSingleDB", topodatapb.TabletType_PRIMARY, key.DestinationShard("1")) 473 require.NoError(t, err) 474 475 want := "multi-db transaction attempted" 476 477 // TransactionMode_SINGLE in session 478 session := NewSafeSession(&vtgatepb.Session{InTransaction: true, TransactionMode: vtgatepb.TransactionMode_SINGLE}) 479 queries := []*querypb.BoundQuery{{Sql: "query1"}} 480 _, errors := sc.ExecuteMultiShard(ctx, nil, rss0, queries, session, false, false) 481 require.Empty(t, errors) 482 _, errors = sc.ExecuteMultiShard(ctx, nil, rss1, queries, session, false, false) 483 require.Error(t, errors[0]) 484 assert.Contains(t, errors[0].Error(), want) 485 486 // TransactionMode_SINGLE in txconn 487 sc.txConn.mode = vtgatepb.TransactionMode_SINGLE 488 session = NewSafeSession(&vtgatepb.Session{InTransaction: true}) 489 _, errors = sc.ExecuteMultiShard(ctx, nil, rss0, queries, session, false, false) 490 require.Empty(t, errors) 491 _, errors = sc.ExecuteMultiShard(ctx, nil, rss1, queries, session, false, false) 492 require.Error(t, errors[0]) 493 assert.Contains(t, errors[0].Error(), want) 494 495 // TransactionMode_MULTI in txconn. Should not fail. 496 sc.txConn.mode = vtgatepb.TransactionMode_MULTI 497 session = NewSafeSession(&vtgatepb.Session{InTransaction: true}) 498 _, errors = sc.ExecuteMultiShard(ctx, nil, rss0, queries, session, false, false) 499 require.Empty(t, errors) 500 _, errors = sc.ExecuteMultiShard(ctx, nil, rss1, queries, session, false, false) 501 require.Empty(t, errors) 502 } 503 504 func TestAppendResult(t *testing.T) { 505 qr := new(sqltypes.Result) 506 innerqr1 := &sqltypes.Result{ 507 Fields: []*querypb.Field{}, 508 Rows: [][]sqltypes.Value{}, 509 } 510 innerqr2 := &sqltypes.Result{ 511 Fields: []*querypb.Field{ 512 {Name: "foo", Type: sqltypes.Int8}, 513 }, 514 RowsAffected: 1, 515 InsertID: 1, 516 Rows: [][]sqltypes.Value{ 517 {sqltypes.NewVarBinary("abcd")}, 518 }, 519 } 520 // test one empty result 521 qr.AppendResult(innerqr1) 522 qr.AppendResult(innerqr2) 523 if len(qr.Fields) != 1 { 524 t.Errorf("want 1, got %v", len(qr.Fields)) 525 } 526 if qr.RowsAffected != 1 { 527 t.Errorf("want 1, got %v", qr.RowsAffected) 528 } 529 if qr.InsertID != 1 { 530 t.Errorf("want 1, got %v", qr.InsertID) 531 } 532 if len(qr.Rows) != 1 { 533 t.Errorf("want 1, got %v", len(qr.Rows)) 534 } 535 // test two valid results 536 qr = new(sqltypes.Result) 537 qr.AppendResult(innerqr2) 538 qr.AppendResult(innerqr2) 539 if len(qr.Fields) != 1 { 540 t.Errorf("want 1, got %v", len(qr.Fields)) 541 } 542 if qr.RowsAffected != 2 { 543 t.Errorf("want 2, got %v", qr.RowsAffected) 544 } 545 if qr.InsertID != 1 { 546 t.Errorf("want 1, got %v", qr.InsertID) 547 } 548 if len(qr.Rows) != 2 { 549 t.Errorf("want 2, got %v", len(qr.Rows)) 550 } 551 } 552 553 func TestReservePrequeries(t *testing.T) { 554 keyspace := "keyspace" 555 createSandbox(keyspace) 556 hc := discovery.NewFakeHealthCheck(nil) 557 sc := newTestScatterConn(hc, newSandboxForCells([]string{"aa"}), "aa") 558 sbc0 := hc.AddTestTablet("aa", "0", 1, keyspace, "0", topodatapb.TabletType_REPLICA, true, 1, nil) 559 sbc1 := hc.AddTestTablet("aa", "1", 1, keyspace, "1", topodatapb.TabletType_REPLICA, true, 1, nil) 560 561 // empty results 562 sbc0.SetResults([]*sqltypes.Result{{}}) 563 sbc1.SetResults([]*sqltypes.Result{{}}) 564 565 res := srvtopo.NewResolver(newSandboxForCells([]string{"aa"}), sc.gateway, "aa") 566 567 session := NewSafeSession(&vtgatepb.Session{ 568 InTransaction: false, 569 InReservedConn: true, 570 SystemVariables: map[string]string{ 571 "s1": "'value'", 572 "s2": "42", 573 }, 574 }) 575 destinations := []key.Destination{key.DestinationShard("0")} 576 577 executeOnShards(t, res, keyspace, sc, session, destinations) 578 assert.Equal(t, 1+1, len(sbc0.StringQueries())) 579 } 580 581 func newTestScatterConn(hc discovery.HealthCheck, serv srvtopo.Server, cell string) *ScatterConn { 582 // The topo.Server is used to start watching the cells described 583 // in '-cells_to_watch' command line parameter, which is 584 // empty by default. So it's unused in this test, set to nil. 585 gw := NewTabletGateway(ctx, hc, serv, cell) 586 tc := NewTxConn(gw, vtgatepb.TransactionMode_TWOPC) 587 return NewScatterConn("", tc, gw) 588 } 589 590 var ctx = context.Background()