github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowcontainer/disk_row_container_test.go (about) 1 // Copyright 2017 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 rowcontainer 12 13 import ( 14 "context" 15 "fmt" 16 math "math" 17 "math/rand" 18 "sort" 19 "testing" 20 21 "github.com/cockroachdb/cockroach/pkg/base" 22 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 23 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 24 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 25 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 26 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 27 "github.com/cockroachdb/cockroach/pkg/sql/types" 28 "github.com/cockroachdb/cockroach/pkg/storage" 29 "github.com/cockroachdb/cockroach/pkg/util/encoding" 30 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 31 "github.com/cockroachdb/cockroach/pkg/util/mon" 32 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 33 "github.com/stretchr/testify/require" 34 ) 35 36 // compareRows compares l and r according to a column ordering. Returns -1 if 37 // l < r, 0 if l == r, and 1 if l > r. If an error is returned the int returned 38 // is invalid. Note that the comparison is only performed on the ordering 39 // columns. 40 func compareRows( 41 lTypes []*types.T, 42 l, r sqlbase.EncDatumRow, 43 e *tree.EvalContext, 44 d *sqlbase.DatumAlloc, 45 ordering sqlbase.ColumnOrdering, 46 ) (int, error) { 47 for _, orderInfo := range ordering { 48 col := orderInfo.ColIdx 49 cmp, err := l[col].Compare(lTypes[col], d, e, &r[orderInfo.ColIdx]) 50 if err != nil { 51 return 0, err 52 } 53 if cmp != 0 { 54 if orderInfo.Direction == encoding.Descending { 55 cmp = -cmp 56 } 57 return cmp, nil 58 } 59 } 60 return 0, nil 61 } 62 63 func TestDiskRowContainer(t *testing.T) { 64 defer leaktest.AfterTest(t)() 65 66 ctx := context.Background() 67 st := cluster.MakeTestingClusterSettings() 68 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 69 if err != nil { 70 t.Fatal(err) 71 } 72 defer tempEngine.Close() 73 74 // These orderings assume at least 4 columns. 75 numCols := 4 76 orderings := []sqlbase.ColumnOrdering{ 77 { 78 sqlbase.ColumnOrderInfo{ 79 ColIdx: 0, 80 Direction: encoding.Ascending, 81 }, 82 }, 83 { 84 sqlbase.ColumnOrderInfo{ 85 ColIdx: 0, 86 Direction: encoding.Descending, 87 }, 88 }, 89 { 90 sqlbase.ColumnOrderInfo{ 91 ColIdx: 3, 92 Direction: encoding.Ascending, 93 }, 94 sqlbase.ColumnOrderInfo{ 95 ColIdx: 1, 96 Direction: encoding.Descending, 97 }, 98 sqlbase.ColumnOrderInfo{ 99 ColIdx: 2, 100 Direction: encoding.Ascending, 101 }, 102 }, 103 } 104 105 rng := rand.New(rand.NewSource(timeutil.Now().UnixNano())) 106 107 evalCtx := tree.MakeTestingEvalContext(st) 108 diskMonitor := mon.MakeMonitor( 109 "test-disk", 110 mon.DiskResource, 111 nil, /* curCount */ 112 nil, /* maxHist */ 113 -1, /* increment: use default block size */ 114 math.MaxInt64, 115 st, 116 ) 117 diskMonitor.Start(ctx, nil /* pool */, mon.MakeStandaloneBudget(math.MaxInt64)) 118 defer diskMonitor.Stop(ctx) 119 t.Run("EncodeDecode", func(t *testing.T) { 120 for i := 0; i < 100; i++ { 121 // Test with different orderings so that we have a mix of key and 122 // value encodings. 123 for _, ordering := range orderings { 124 typs := make([]*types.T, numCols) 125 for i := range typs { 126 typs[i] = sqlbase.RandSortingType(rng) 127 } 128 row := sqlbase.RandEncDatumRowOfTypes(rng, typs) 129 func() { 130 d := MakeDiskRowContainer(&diskMonitor, typs, ordering, tempEngine) 131 defer d.Close(ctx) 132 if err := d.AddRow(ctx, row); err != nil { 133 t.Fatal(err) 134 } 135 136 i := d.NewIterator(ctx) 137 defer i.Close() 138 i.Rewind() 139 if ok, err := i.Valid(); err != nil { 140 t.Fatal(err) 141 } else if !ok { 142 t.Fatal("unexpectedly invalid") 143 } 144 readRow := make(sqlbase.EncDatumRow, len(row)) 145 146 temp, err := i.Row() 147 if err != nil { 148 t.Fatal(err) 149 } 150 copy(readRow, temp) 151 152 // Ensure the datum fields are set and no errors occur when 153 // decoding. 154 for i, encDatum := range readRow { 155 if err := encDatum.EnsureDecoded(typs[i], d.datumAlloc); err != nil { 156 t.Fatal(err) 157 } 158 } 159 160 // Check equality of the row we wrote and the row we read. 161 for i := range row { 162 if cmp, err := readRow[i].Compare(typs[i], d.datumAlloc, &evalCtx, &row[i]); err != nil { 163 t.Fatal(err) 164 } else if cmp != 0 { 165 t.Fatalf("encoded %s but decoded %s", row.String(typs), readRow.String(typs)) 166 } 167 } 168 }() 169 } 170 } 171 }) 172 173 t.Run("SortedOrder", func(t *testing.T) { 174 numRows := 1024 175 for _, ordering := range orderings { 176 // numRows rows with numCols columns of random types. 177 types := sqlbase.RandSortingTypes(rng, numCols) 178 rows := sqlbase.RandEncDatumRowsOfTypes(rng, numRows, types) 179 func() { 180 d := MakeDiskRowContainer(&diskMonitor, types, ordering, tempEngine) 181 defer d.Close(ctx) 182 for i := 0; i < len(rows); i++ { 183 if err := d.AddRow(ctx, rows[i]); err != nil { 184 t.Fatal(err) 185 } 186 } 187 188 // Make another row container that stores all the rows then sort 189 // it to compare equality. 190 var sortedRows MemRowContainer 191 sortedRows.Init(ordering, types, &evalCtx) 192 defer sortedRows.Close(ctx) 193 for _, row := range rows { 194 if err := sortedRows.AddRow(ctx, row); err != nil { 195 t.Fatal(err) 196 } 197 } 198 sortedRows.Sort(ctx) 199 200 i := d.NewIterator(ctx) 201 defer i.Close() 202 203 numKeysRead := 0 204 for i.Rewind(); ; i.Next() { 205 if ok, err := i.Valid(); err != nil { 206 t.Fatal(err) 207 } else if !ok { 208 break 209 } 210 row, err := i.Row() 211 if err != nil { 212 t.Fatal(err) 213 } 214 215 // Ensure datum fields are set and no errors occur when 216 // decoding. 217 for i, encDatum := range row { 218 if err := encDatum.EnsureDecoded(types[i], d.datumAlloc); err != nil { 219 t.Fatal(err) 220 } 221 } 222 223 // Check sorted order. 224 if cmp, err := compareRows( 225 types, sortedRows.EncRow(numKeysRead), row, &evalCtx, d.datumAlloc, ordering, 226 ); err != nil { 227 t.Fatal(err) 228 } else if cmp != 0 { 229 t.Fatalf( 230 "expected %s to be equal to %s", 231 row.String(types), 232 sortedRows.EncRow(numKeysRead).String(types), 233 ) 234 } 235 numKeysRead++ 236 } 237 if numKeysRead != numRows { 238 t.Fatalf("expected to read %d keys but only read %d", numRows, numKeysRead) 239 } 240 }() 241 } 242 }) 243 244 t.Run("DeDupingRowContainer", func(t *testing.T) { 245 numCols := 2 246 numRows := 10 247 ordering := sqlbase.ColumnOrdering{ 248 sqlbase.ColumnOrderInfo{ 249 ColIdx: 0, 250 Direction: encoding.Ascending, 251 }, 252 sqlbase.ColumnOrderInfo{ 253 ColIdx: 1, 254 Direction: encoding.Descending, 255 }, 256 } 257 // Use random types and random rows. 258 types := sqlbase.RandSortingTypes(rng, numCols) 259 numRows, rows := makeUniqueRows(t, &evalCtx, rng, numRows, types, ordering) 260 d := MakeDiskRowContainer(&diskMonitor, types, ordering, tempEngine) 261 defer d.Close(ctx) 262 d.DoDeDuplicate() 263 addRowsRepeatedly := func() { 264 // Add some number of de-duplicated rows using AddRow() to exercise the 265 // code path in DiskRowContainer that gets exercised by 266 // DiskBackedRowContainer when it spills from memory to disk. 267 addRowCalls := rng.Intn(numRows) 268 for i := 0; i < addRowCalls; i++ { 269 require.NoError(t, d.AddRow(ctx, rows[i])) 270 require.Equal(t, d.bufferedRows.NumPutsSinceFlush(), len(d.deDupCache)) 271 } 272 // Repeatedly add the same set of rows. 273 for i := 0; i < 3; i++ { 274 if i == 2 && rng.Intn(2) == 0 { 275 // Clear the de-dup cache so that a SortedDiskMapIterator is needed 276 // to de-dup. 277 d.testingFlushBuffer(ctx) 278 } 279 for j := 0; j < numRows; j++ { 280 idx, err := d.AddRowWithDeDup(ctx, rows[j]) 281 require.NoError(t, err) 282 require.Equal(t, j, idx) 283 } 284 } 285 } 286 addRowsRepeatedly() 287 // Reset and add the rows in a different order. 288 require.NoError(t, d.UnsafeReset(ctx)) 289 rand.Shuffle(len(rows), func(i, j int) { 290 rows[i], rows[j] = rows[j], rows[i] 291 }) 292 addRowsRepeatedly() 293 }) 294 295 t.Run("NumberedRowIterator", func(t *testing.T) { 296 numCols := 2 297 numRows := 10 298 // Use random types and random rows. 299 types := sqlbase.RandSortingTypes(rng, numCols) 300 rows := sqlbase.RandEncDatumRowsOfTypes(rng, numRows, types) 301 // There are no ordering columns when using the numberedRowIterator. 302 d := MakeDiskRowContainer(&diskMonitor, types, nil, tempEngine) 303 defer d.Close(ctx) 304 for i := 0; i < numRows; i++ { 305 require.NoError(t, d.AddRow(ctx, rows[i])) 306 } 307 require.NotEqual(t, 0, d.MeanEncodedRowBytes()) 308 iter := d.newNumberedIterator(ctx) 309 defer iter.Close() 310 // Checks equality of rows[index] and the current position of iter. 311 checkEq := func(index int) { 312 valid, err := iter.Valid() 313 require.True(t, valid) 314 require.NoError(t, err) 315 row, err := iter.Row() 316 require.NoError(t, err) 317 require.Equal(t, rows[index].String(types), row.String(types)) 318 } 319 for i := 0; i < numRows; i++ { 320 // Seek to a random position and iterate until the end. 321 index := rng.Intn(numRows) 322 iter.seekToIndex(index) 323 checkEq(index) 324 for index++; index < numRows; index++ { 325 iter.Next() 326 checkEq(index) 327 } 328 } 329 }) 330 } 331 332 // makeUniqueRows can return a row count < numRows (always > 0 when numRows > 333 // 0), hence it also returns the actual returned count (to remind the caller). 334 func makeUniqueRows( 335 t *testing.T, 336 evalCtx *tree.EvalContext, 337 rng *rand.Rand, 338 numRows int, 339 types []*types.T, 340 ordering sqlbase.ColumnOrdering, 341 ) (int, sqlbase.EncDatumRows) { 342 rows := sqlbase.RandEncDatumRowsOfTypes(rng, numRows, types) 343 // It is possible there was some duplication, so remove duplicates. 344 var alloc sqlbase.DatumAlloc 345 sort.Slice(rows, func(i, j int) bool { 346 cmp, err := rows[i].Compare(types, &alloc, ordering, evalCtx, rows[j]) 347 require.NoError(t, err) 348 return cmp < 0 349 }) 350 deDupedRows := rows[:1] 351 for i := 1; i < len(rows); i++ { 352 cmp, err := rows[i].Compare(types, &alloc, ordering, evalCtx, deDupedRows[len(deDupedRows)-1]) 353 require.NoError(t, err) 354 if cmp != 0 { 355 deDupedRows = append(deDupedRows, rows[i]) 356 } 357 } 358 rows = deDupedRows 359 // Shuffle so that not adding in sorted order. 360 rand.Shuffle(len(rows), func(i, j int) { 361 rows[i], rows[j] = rows[j], rows[i] 362 }) 363 return len(rows), rows 364 } 365 366 func TestDiskRowContainerDiskFull(t *testing.T) { 367 defer leaktest.AfterTest(t)() 368 369 ctx := context.Background() 370 st := cluster.MakeTestingClusterSettings() 371 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 372 if err != nil { 373 t.Fatal(err) 374 } 375 defer tempEngine.Close() 376 377 // Make a monitor with no capacity. 378 monitor := mon.MakeMonitor( 379 "test-disk", 380 mon.DiskResource, 381 nil, /* curCount */ 382 nil, /* maxHist */ 383 -1, /* increment: use default block size */ 384 math.MaxInt64, 385 st, 386 ) 387 monitor.Start(ctx, nil, mon.MakeStandaloneBudget(0 /* capacity */)) 388 389 d := MakeDiskRowContainer( 390 &monitor, 391 []*types.T{types.Int}, 392 sqlbase.ColumnOrdering{sqlbase.ColumnOrderInfo{ColIdx: 0, Direction: encoding.Ascending}}, 393 tempEngine, 394 ) 395 defer d.Close(ctx) 396 397 row := sqlbase.EncDatumRow{sqlbase.DatumToEncDatum(types.Int, tree.NewDInt(tree.DInt(1)))} 398 err = d.AddRow(ctx, row) 399 if code := pgerror.GetPGCode(err); code != pgcode.DiskFull { 400 t.Fatalf("unexpected error: %v", err) 401 } 402 } 403 404 func TestDiskRowContainerFinalIterator(t *testing.T) { 405 defer leaktest.AfterTest(t)() 406 407 ctx := context.Background() 408 st := cluster.MakeTestingClusterSettings() 409 alloc := &sqlbase.DatumAlloc{} 410 evalCtx := tree.MakeTestingEvalContext(st) 411 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 412 if err != nil { 413 t.Fatal(err) 414 } 415 defer tempEngine.Close() 416 417 diskMonitor := mon.MakeMonitor( 418 "test-disk", 419 mon.DiskResource, 420 nil, /* curCount */ 421 nil, /* maxHist */ 422 -1, /* increment: use default block size */ 423 math.MaxInt64, 424 st, 425 ) 426 diskMonitor.Start(ctx, nil /* pool */, mon.MakeStandaloneBudget(math.MaxInt64)) 427 defer diskMonitor.Stop(ctx) 428 429 d := MakeDiskRowContainer(&diskMonitor, sqlbase.OneIntCol, nil /* ordering */, tempEngine) 430 defer d.Close(ctx) 431 432 const numCols = 1 433 const numRows = 100 434 rows := sqlbase.MakeIntRows(numRows, numCols) 435 for _, row := range rows { 436 if err := d.AddRow(ctx, row); err != nil { 437 t.Fatal(err) 438 } 439 } 440 441 // checkEqual checks that the given row is equal to otherRow. 442 checkEqual := func(row sqlbase.EncDatumRow, otherRow sqlbase.EncDatumRow) error { 443 for j, c := range row { 444 if cmp, err := c.Compare(types.Int, alloc, &evalCtx, &otherRow[j]); err != nil { 445 return err 446 } else if cmp != 0 { 447 return fmt.Errorf( 448 "unexpected row %v, expected %v", 449 row.String(sqlbase.OneIntCol), 450 otherRow.String(sqlbase.OneIntCol), 451 ) 452 } 453 } 454 return nil 455 } 456 457 rowsRead := 0 458 func() { 459 i := d.NewFinalIterator(ctx) 460 defer i.Close() 461 for i.Rewind(); rowsRead < numRows/2; i.Next() { 462 if ok, err := i.Valid(); err != nil { 463 t.Fatal(err) 464 } else if !ok { 465 t.Fatalf("unexpectedly reached the end after %d rows read", rowsRead) 466 } 467 row, err := i.Row() 468 if err != nil { 469 t.Fatal(err) 470 } 471 if err := checkEqual(row, rows[rowsRead]); err != nil { 472 t.Fatal(err) 473 } 474 rowsRead++ 475 } 476 }() 477 478 // Verify resumability. 479 func() { 480 i := d.NewFinalIterator(ctx) 481 defer i.Close() 482 for i.Rewind(); ; i.Next() { 483 if ok, err := i.Valid(); err != nil { 484 t.Fatal(err) 485 } else if !ok { 486 break 487 } 488 row, err := i.Row() 489 if err != nil { 490 t.Fatal(err) 491 } 492 if err := checkEqual(row, rows[rowsRead]); err != nil { 493 t.Fatal(err) 494 } 495 rowsRead++ 496 } 497 }() 498 499 if rowsRead != len(rows) { 500 t.Fatalf("only read %d rows, expected %d", rowsRead, len(rows)) 501 } 502 503 // Add a couple extra rows to check that they're picked up by the iterator. 504 extraRows := sqlbase.MakeIntRows(4, 1) 505 for _, row := range extraRows { 506 if err := d.AddRow(ctx, row); err != nil { 507 t.Fatal(err) 508 } 509 } 510 511 i := d.NewFinalIterator(ctx) 512 defer i.Close() 513 for i.Rewind(); ; i.Next() { 514 if ok, err := i.Valid(); err != nil { 515 t.Fatal(err) 516 } else if !ok { 517 break 518 } 519 row, err := i.Row() 520 if err != nil { 521 t.Fatal(err) 522 } 523 if err := checkEqual(row, extraRows[rowsRead-len(rows)]); err != nil { 524 t.Fatal(err) 525 } 526 rowsRead++ 527 } 528 529 if rowsRead != len(rows)+len(extraRows) { 530 t.Fatalf("only read %d rows, expected %d", rowsRead, len(rows)+len(extraRows)) 531 } 532 } 533 534 func TestDiskRowContainerUnsafeReset(t *testing.T) { 535 defer leaktest.AfterTest(t)() 536 537 ctx := context.Background() 538 st := cluster.MakeTestingClusterSettings() 539 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 540 if err != nil { 541 t.Fatal(err) 542 } 543 defer tempEngine.Close() 544 545 monitor := mon.MakeMonitor( 546 "test-disk", 547 mon.DiskResource, 548 nil, /* curCount */ 549 nil, /* maxHist */ 550 -1, /* increment: use default block size */ 551 math.MaxInt64, 552 st, 553 ) 554 monitor.Start(ctx, nil, mon.MakeStandaloneBudget(math.MaxInt64)) 555 556 d := MakeDiskRowContainer(&monitor, sqlbase.OneIntCol, nil /* ordering */, tempEngine) 557 defer d.Close(ctx) 558 559 const ( 560 numCols = 1 561 numRows = 100 562 ) 563 rows := sqlbase.MakeIntRows(numRows, numCols) 564 565 const ( 566 numResets = 4 567 expectedRowsPerReset = numRows / numResets 568 ) 569 for i := 0; i < numResets; i++ { 570 if err := d.UnsafeReset(ctx); err != nil { 571 t.Fatal(err) 572 } 573 if d.Len() != 0 { 574 t.Fatalf("disk row container still contains %d row(s) after a reset", d.Len()) 575 } 576 firstRow := rows[0] 577 for _, row := range rows[:len(rows)/numResets] { 578 if err := d.AddRow(ctx, row); err != nil { 579 t.Fatal(err) 580 } 581 } 582 // Verify that the first row matches up. 583 func() { 584 i := d.NewFinalIterator(ctx) 585 defer i.Close() 586 i.Rewind() 587 if ok, err := i.Valid(); err != nil || !ok { 588 t.Fatalf("unexpected i.Valid() return values: ok=%t, err=%s", ok, err) 589 } 590 row, err := i.Row() 591 if err != nil { 592 t.Fatal(err) 593 } 594 if row.String(sqlbase.OneIntCol) != firstRow.String(sqlbase.OneIntCol) { 595 t.Fatalf("unexpected row read %s, expected %s", row.String(sqlbase.OneIntCol), firstRow.String(sqlbase.OneIntCol)) 596 } 597 }() 598 599 // diskRowFinalIterator does not actually discard rows, so Len() should 600 // still account for the row we just read. 601 if d.Len() != expectedRowsPerReset { 602 t.Fatalf("expected %d rows but got %d", expectedRowsPerReset, d.Len()) 603 } 604 } 605 606 // Verify we read the expected number of rows (note that we already read one 607 // in the last iteration of the numResets loop). 608 i := d.NewFinalIterator(ctx) 609 defer i.Close() 610 rowsRead := 0 611 for i.Rewind(); ; i.Next() { 612 if ok, err := i.Valid(); err != nil { 613 t.Fatal(err) 614 } else if !ok { 615 break 616 } 617 _, err := i.Row() 618 if err != nil { 619 t.Fatal(err) 620 } 621 rowsRead++ 622 } 623 if rowsRead != expectedRowsPerReset-1 { 624 t.Fatalf("read %d rows using a final iterator but expected %d", rowsRead, expectedRowsPerReset) 625 } 626 }