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  }