github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/roachpb/batch_test.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package roachpb 12 13 import ( 14 "fmt" 15 "reflect" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/concurrency/lock" 19 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 20 "github.com/cockroachdb/cockroach/pkg/util/hlc" 21 "github.com/kr/pretty" 22 "github.com/stretchr/testify/require" 23 ) 24 25 func TestBatchIsCompleteTransaction(t *testing.T) { 26 get := &GetRequest{} 27 put := &PutRequest{} 28 etA := &EndTxnRequest{Commit: false} 29 etC := &EndTxnRequest{Commit: true} 30 withSeq := func(r Request, s enginepb.TxnSeq) Request { 31 c := r.ShallowCopy() 32 h := c.Header() 33 h.Sequence = s 34 c.SetHeader(h) 35 return c 36 } 37 testCases := []struct { 38 reqs []Request 39 isComplete bool 40 }{ 41 {[]Request{get, put}, false}, 42 {[]Request{put, get}, false}, 43 {[]Request{etA}, false}, 44 {[]Request{etC}, false}, 45 {[]Request{get, etA}, false}, 46 {[]Request{get, etC}, false}, 47 {[]Request{put, get, etA}, false}, 48 {[]Request{put, get, etC}, false}, 49 {[]Request{withSeq(etA, 1)}, false}, 50 {[]Request{withSeq(etC, 1)}, true}, 51 {[]Request{put, withSeq(etC, 3)}, false}, 52 {[]Request{withSeq(put, 1), withSeq(etC, 3)}, false}, 53 {[]Request{withSeq(put, 2), withSeq(etC, 3)}, false}, 54 {[]Request{withSeq(put, 1), withSeq(put, 2), withSeq(etA, 3)}, false}, 55 {[]Request{withSeq(put, 1), withSeq(put, 2), withSeq(etC, 3)}, true}, 56 {[]Request{withSeq(put, 1), withSeq(put, 2), withSeq(etC, 4)}, false}, 57 {[]Request{withSeq(put, 1), withSeq(put, 2), withSeq(put, 3), withSeq(etA, 4)}, false}, 58 {[]Request{withSeq(put, 1), withSeq(put, 2), withSeq(put, 3), withSeq(etC, 4)}, true}, 59 {[]Request{withSeq(get, 0), withSeq(put, 1), withSeq(get, 1), withSeq(etC, 3)}, false}, 60 {[]Request{withSeq(get, 0), withSeq(get, 1), withSeq(put, 2), withSeq(etC, 3)}, false}, 61 {[]Request{withSeq(get, 0), withSeq(put, 1), withSeq(put, 2), withSeq(get, 2), withSeq(etC, 3)}, true}, 62 {[]Request{withSeq(put, 1), withSeq(get, 1), withSeq(put, 2), withSeq(etC, 4)}, false}, 63 {[]Request{withSeq(get, 0), withSeq(put, 1), withSeq(put, 2), withSeq(put, 3), withSeq(get, 3), withSeq(etC, 4)}, true}, 64 } 65 for i, test := range testCases { 66 ba := BatchRequest{} 67 for _, args := range test.reqs { 68 ba.Add(args) 69 } 70 complete := ba.IsCompleteTransaction() 71 if complete != test.isComplete { 72 t.Errorf("%d: expected IsCompleteTransaction=%t, found %t", i, test.isComplete, complete) 73 } 74 } 75 } 76 77 func TestBatchSplit(t *testing.T) { 78 get := &GetRequest{} 79 scan := &ScanRequest{} 80 put := &PutRequest{} 81 spl := &AdminSplitRequest{} 82 dr := &DeleteRangeRequest{} 83 et := &EndTxnRequest{} 84 qi := &QueryIntentRequest{} 85 rv := &ReverseScanRequest{} 86 testCases := []struct { 87 reqs []Request 88 sizes []int 89 canSplitET bool 90 }{ 91 {[]Request{get, put}, []int{1, 1}, true}, 92 {[]Request{put, et}, []int{1, 1}, true}, 93 {[]Request{get, get, get, put, put, get, get}, []int{3, 2, 2}, true}, 94 {[]Request{spl, get, scan, spl, get}, []int{1, 2, 1, 1}, true}, 95 {[]Request{spl, spl, get, spl}, []int{1, 1, 1, 1}, true}, 96 {[]Request{get, scan, get, dr, rv, put, et}, []int{3, 1, 1, 1, 1}, true}, 97 // Same one again, but this time don't allow EndTxn to be split. 98 {[]Request{get, scan, get, dr, rv, put, et}, []int{3, 1, 1, 2}, false}, 99 // An invalid request in real life, but it demonstrates that we'll 100 // always split **after** an EndTxn (because either the next request 101 // wants to be alone, or its flags can't match the current flags, which 102 // have isAlone set). Could be useful if we ever want to allow executing 103 // multiple batches back-to-back. 104 {[]Request{et, scan, et}, []int{1, 2}, false}, 105 {[]Request{et, et}, []int{1, 1}, false}, 106 // QueryIntents count as headers that are always compatible with the 107 // request that follows. 108 {[]Request{get, qi, put}, []int{1, 2}, true}, 109 {[]Request{get, qi, qi, qi, qi, put}, []int{1, 5}, true}, 110 {[]Request{qi, get, qi, get, qi, get, qi, put, qi, put, qi, get, qi, get}, []int{6, 4, 4}, true}, 111 {[]Request{qi, spl, qi, get, scan, qi, qi, spl, qi, get}, []int{1, 1, 5, 1, 2}, true}, 112 {[]Request{scan, qi, qi, qi, et}, []int{4, 1}, true}, 113 {[]Request{scan, qi, qi, qi, et}, []int{5}, false}, 114 {[]Request{put, qi, qi, qi, et}, []int{1, 3, 1}, true}, 115 {[]Request{put, qi, qi, qi, et}, []int{5}, false}, 116 } 117 118 for i, test := range testCases { 119 ba := BatchRequest{} 120 for _, args := range test.reqs { 121 ba.Add(args) 122 } 123 var partLen []int 124 var recombined []RequestUnion 125 for _, part := range ba.Split(test.canSplitET) { 126 recombined = append(recombined, part...) 127 partLen = append(partLen, len(part)) 128 } 129 if !reflect.DeepEqual(partLen, test.sizes) { 130 t.Errorf("%d: expected chunks %v, got %v", i, test.sizes, partLen) 131 } 132 if !reflect.DeepEqual(recombined, ba.Requests) { 133 t.Errorf("%d: started with:\n%+v\ngot back:\n%+v", i, ba.Requests, recombined) 134 } 135 } 136 } 137 138 func TestBatchRequestGetArg(t *testing.T) { 139 get := RequestUnion{ 140 Value: &RequestUnion_Get{Get: &GetRequest{}}, 141 } 142 end := RequestUnion{ 143 Value: &RequestUnion_EndTxn{EndTxn: &EndTxnRequest{}}, 144 } 145 testCases := []struct { 146 bu []RequestUnion 147 expB, expG bool 148 }{ 149 {[]RequestUnion{}, false, false}, 150 {[]RequestUnion{get}, false, true}, 151 {[]RequestUnion{end, get}, false, true}, 152 {[]RequestUnion{get, end}, true, true}, 153 } 154 155 for i, c := range testCases { 156 br := BatchRequest{Requests: c.bu} 157 if _, r := br.GetArg(EndTxn); r != c.expB { 158 t.Errorf("%d: unexpected batch request for %v: %v", i, c.bu, r) 159 } 160 if _, r := br.GetArg(Get); r != c.expG { 161 t.Errorf("%d: unexpected get match for %v: %v", i, c.bu, r) 162 } 163 164 } 165 } 166 167 func TestBatchRequestSummary(t *testing.T) { 168 // The Summary function is generated automatically, so the tests don't need to 169 // be exhaustive. 170 testCases := []struct { 171 reqs []Request 172 expected string 173 }{ 174 { 175 reqs: []Request{}, 176 expected: "empty batch", 177 }, 178 { 179 reqs: []Request{&GetRequest{}}, 180 expected: "1 Get", 181 }, 182 { 183 reqs: []Request{&PutRequest{}}, 184 expected: "1 Put", 185 }, 186 { 187 reqs: []Request{&ConditionalPutRequest{}}, 188 expected: "1 CPut", 189 }, 190 { 191 reqs: []Request{&ReverseScanRequest{}}, 192 expected: "1 RevScan", 193 }, 194 { 195 reqs: []Request{ 196 &GetRequest{}, &GetRequest{}, &PutRequest{}, &ScanRequest{}, &ScanRequest{}, 197 }, 198 expected: "2 Get, 1 Put, 2 Scan", 199 }, 200 { 201 reqs: []Request{ 202 &CheckConsistencyRequest{}, &InitPutRequest{}, &TruncateLogRequest{}, 203 }, 204 expected: "1 TruncLog, 1 ChkConsistency, 1 InitPut", 205 }, 206 } 207 for i, tc := range testCases { 208 var br BatchRequest 209 for _, v := range tc.reqs { 210 var ru RequestUnion 211 ru.MustSetInner(v) 212 br.Requests = append(br.Requests, ru) 213 } 214 if str := br.Summary(); str != tc.expected { 215 t.Errorf("%d: got '%s', expected '%s', batch: %+v", i, str, tc.expected, br) 216 } 217 } 218 } 219 220 func TestLockSpanIterate(t *testing.T) { 221 type testReq struct { 222 req Request 223 resp Response 224 span Span 225 resume Span 226 } 227 testReqs := []testReq{ 228 {&ScanRequest{}, &ScanResponse{}, sp("a", "c"), sp("b", "c")}, 229 {&ReverseScanRequest{}, &ReverseScanResponse{}, sp("d", "f"), sp("d", "e")}, 230 {&PutRequest{}, &PutResponse{}, sp("m", ""), sp("", "")}, 231 {&DeleteRangeRequest{}, &DeleteRangeResponse{}, sp("n", "p"), sp("o", "p")}, 232 {&ScanRequest{KeyLocking: lock.Exclusive}, &ScanResponse{}, sp("g", "i"), sp("h", "i")}, 233 {&ReverseScanRequest{KeyLocking: lock.Exclusive}, &ReverseScanResponse{}, sp("j", "l"), sp("k", "l")}, 234 } 235 236 // NB: can't import testutils for RunTrueAndFalse. 237 for _, resume := range []bool{false, true} { 238 t.Run(fmt.Sprintf("resume=%t", resume), func(t *testing.T) { 239 // A batch request with a batch response with no ResumeSpan. 240 ba := BatchRequest{} 241 br := BatchResponse{} 242 for i := range testReqs { 243 tr := &testReqs[i] 244 tr.req.SetHeader(RequestHeaderFromSpan(tr.span)) 245 ba.Add(tr.req) 246 if resume { 247 tr.resp.SetHeader(ResponseHeader{ResumeSpan: &tr.resume}) 248 } 249 br.Add(tr.resp) 250 } 251 252 var spans [lock.MaxDurability + 1][]Span 253 fn := func(span Span, dur lock.Durability) { 254 spans[dur] = append(spans[dur], span) 255 } 256 ba.LockSpanIterate(&br, fn) 257 258 toExpSpans := func(trs ...testReq) []Span { 259 exp := make([]Span, len(trs)) 260 for i, tr := range trs { 261 exp[i] = tr.span 262 if resume { 263 exp[i].EndKey = tr.resume.Key 264 } 265 } 266 return exp 267 } 268 269 // The intent writes are replicated locking request. 270 require.Equal(t, toExpSpans(testReqs[2], testReqs[3]), spans[lock.Replicated]) 271 272 // The scans with KeyLocking are unreplicated locking requests. 273 require.Equal(t, toExpSpans(testReqs[4], testReqs[5]), spans[lock.Unreplicated]) 274 }) 275 } 276 } 277 278 func TestRefreshSpanIterate(t *testing.T) { 279 testCases := []struct { 280 req Request 281 resp Response 282 span Span 283 resume Span 284 }{ 285 {&ConditionalPutRequest{}, &ConditionalPutResponse{}, sp("a", ""), Span{}}, 286 {&PutRequest{}, &PutResponse{}, sp("a-put", ""), Span{}}, 287 {&InitPutRequest{}, &InitPutResponse{}, sp("a-initput", ""), Span{}}, 288 {&IncrementRequest{}, &IncrementResponse{}, sp("a-inc", ""), Span{}}, 289 {&ScanRequest{}, &ScanResponse{}, sp("a", "c"), sp("b", "c")}, 290 {&GetRequest{}, &GetResponse{}, sp("b", ""), Span{}}, 291 {&ReverseScanRequest{}, &ReverseScanResponse{}, sp("d", "f"), sp("d", "e")}, 292 {&DeleteRangeRequest{}, &DeleteRangeResponse{}, sp("g", "i"), sp("h", "i")}, 293 } 294 295 // A batch request with a batch response with no ResumeSpan. 296 ba := BatchRequest{} 297 br := BatchResponse{} 298 for _, tc := range testCases { 299 tc.req.SetHeader(RequestHeaderFromSpan(tc.span)) 300 ba.Add(tc.req) 301 br.Add(tc.resp) 302 } 303 304 var readSpans []Span 305 fn := func(span Span) { 306 readSpans = append(readSpans, span) 307 } 308 ba.RefreshSpanIterate(&br, fn) 309 // The conditional put and init put are not considered read spans. 310 expReadSpans := []Span{testCases[4].span, testCases[5].span, testCases[6].span, testCases[7].span} 311 require.Equal(t, expReadSpans, readSpans) 312 313 // Batch responses with ResumeSpans. 314 ba = BatchRequest{} 315 br = BatchResponse{} 316 for _, tc := range testCases { 317 tc.req.SetHeader(RequestHeaderFromSpan(tc.span)) 318 ba.Add(tc.req) 319 if tc.resume.Key != nil { 320 resume := tc.resume 321 tc.resp.SetHeader(ResponseHeader{ResumeSpan: &resume}) 322 } 323 br.Add(tc.resp) 324 } 325 326 readSpans = []Span{} 327 ba.RefreshSpanIterate(&br, fn) 328 expReadSpans = []Span{ 329 sp("a", "b"), 330 sp("b", ""), 331 sp("e", "f"), 332 sp("g", "h"), 333 } 334 require.Equal(t, expReadSpans, readSpans) 335 } 336 337 func TestBatchResponseCombine(t *testing.T) { 338 br := &BatchResponse{} 339 { 340 txn := MakeTransaction( 341 "test", nil /* baseKey */, NormalUserPriority, 342 hlc.Timestamp{WallTime: 123}, 0, /* maxOffsetNs */ 343 ) 344 brTxn := &BatchResponse{ 345 BatchResponse_Header: BatchResponse_Header{ 346 Txn: &txn, 347 }, 348 } 349 if err := br.Combine(brTxn, nil); err != nil { 350 t.Fatal(err) 351 } 352 if br.Txn.Name != "test" { 353 t.Fatal("Combine() did not update the header") 354 } 355 } 356 357 br.Responses = make([]ResponseUnion, 1) 358 359 singleScanBR := func() *BatchResponse { 360 var union ResponseUnion 361 union.MustSetInner(&ScanResponse{ 362 Rows: []KeyValue{{ 363 Key: Key("bar"), 364 }}, 365 IntentRows: []KeyValue{{ 366 Key: Key("baz"), 367 }}, 368 }) 369 return &BatchResponse{ 370 Responses: []ResponseUnion{union}, 371 } 372 } 373 // Combine twice with a single scan result: first one should simply copy over, second 374 // one should add on top. Slightly different code paths internally, hence the distinction. 375 for i := 0; i < 2; i++ { 376 if err := br.Combine(singleScanBR(), []int{0}); err != nil { 377 t.Fatal(err) 378 } 379 scan := br.Responses[0].GetInner().(*ScanResponse) 380 if exp := i + 1; len(scan.Rows) != exp { 381 t.Fatalf("expected %d rows, got %+v", exp, br) 382 } 383 if exp := i + 1; len(scan.IntentRows) != exp { 384 t.Fatalf("expected %d intent rows, got %+v", exp, br) 385 } 386 } 387 388 var union ResponseUnion 389 union.MustSetInner(&PutResponse{}) 390 br.Responses = []ResponseUnion{union, br.Responses[0]} 391 392 // Now we have br = [Put, Scan]. Combine should use the position to 393 // combine singleScanBR on top of the Scan at index one. 394 if err := br.Combine(singleScanBR(), []int{1}); err != nil { 395 t.Fatal(err) 396 } 397 scan := br.Responses[1].GetInner().(*ScanResponse) 398 expRows := 3 399 if len(scan.Rows) != expRows { 400 t.Fatalf("expected %d rows, got %s", expRows, pretty.Sprint(scan)) 401 } 402 if len(scan.IntentRows) != expRows { 403 t.Fatalf("expected %d intent rows, got %s", expRows, pretty.Sprint(scan)) 404 } 405 if err := br.Combine(singleScanBR(), []int{0}); err.Error() != 406 `can not combine *roachpb.PutResponse and *roachpb.ScanResponse` { 407 t.Fatal(err) 408 } 409 } 410 411 func sp(start, end string) Span { 412 res := Span{Key: Key(start)} 413 if end != "" { 414 res.EndKey = Key(end) 415 } 416 return res 417 }