github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/file_table_persister_test.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package nbs
    23  
    24  import (
    25  	"context"
    26  	"crypto/rand"
    27  	"fmt"
    28  	"os"
    29  	"path/filepath"
    30  	"testing"
    31  
    32  	"github.com/dolthub/dolt/go/libraries/utils/file"
    33  	"github.com/dolthub/dolt/go/store/hash"
    34  
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  func makeTempDir(t *testing.T) string {
    40  	dir, err := os.MkdirTemp("", "")
    41  	require.NoError(t, err)
    42  	return dir
    43  }
    44  
    45  func writeTableData(dir string, chunx ...[]byte) (hash.Hash, error) {
    46  	tableData, name, err := buildTable(chunx)
    47  
    48  	if err != nil {
    49  		return hash.Hash{}, err
    50  	}
    51  
    52  	err = os.WriteFile(filepath.Join(dir, name.String()), tableData, 0666)
    53  
    54  	if err != nil {
    55  		return hash.Hash{}, err
    56  	}
    57  
    58  	return name, nil
    59  }
    60  
    61  func removeTables(dir string, names ...hash.Hash) error {
    62  	for _, name := range names {
    63  		if err := file.Remove(filepath.Join(dir, name.String())); err != nil {
    64  			return err
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  func TestFSTablePersisterPersist(t *testing.T) {
    71  	ctx := context.Background()
    72  	assert := assert.New(t)
    73  	dir := makeTempDir(t)
    74  	defer file.RemoveAll(dir)
    75  	fts := newFSTablePersister(dir, &UnlimitedQuotaProvider{})
    76  
    77  	src, err := persistTableData(fts, testChunks...)
    78  	require.NoError(t, err)
    79  	defer src.close()
    80  	if assert.True(mustUint32(src.count()) > 0) {
    81  		buff, err := os.ReadFile(filepath.Join(dir, src.hash().String()))
    82  		require.NoError(t, err)
    83  		ti, err := parseTableIndexByCopy(ctx, buff, &UnlimitedQuotaProvider{})
    84  		require.NoError(t, err)
    85  		tr, err := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
    86  		require.NoError(t, err)
    87  		defer tr.close()
    88  		assertChunksInReader(testChunks, tr, assert)
    89  	}
    90  }
    91  
    92  func persistTableData(p tablePersister, chunx ...[]byte) (src chunkSource, err error) {
    93  	mt := newMemTable(testMemTableSize)
    94  	for _, c := range chunx {
    95  		if mt.addChunk(computeAddr(c), c) == chunkNotAdded {
    96  			return nil, fmt.Errorf("memTable too full to add %s", computeAddr(c))
    97  		}
    98  	}
    99  	return p.Persist(context.Background(), mt, nil, &Stats{})
   100  }
   101  
   102  func TestFSTablePersisterPersistNoData(t *testing.T) {
   103  	assert := assert.New(t)
   104  	mt := newMemTable(testMemTableSize)
   105  	existingTable := newMemTable(testMemTableSize)
   106  
   107  	for _, c := range testChunks {
   108  		assert.Equal(mt.addChunk(computeAddr(c), c), chunkAdded)
   109  		assert.Equal(existingTable.addChunk(computeAddr(c), c), chunkAdded)
   110  	}
   111  
   112  	dir := makeTempDir(t)
   113  	defer file.RemoveAll(dir)
   114  	fts := newFSTablePersister(dir, &UnlimitedQuotaProvider{})
   115  
   116  	src, err := fts.Persist(context.Background(), mt, existingTable, &Stats{})
   117  	require.NoError(t, err)
   118  	assert.True(mustUint32(src.count()) == 0)
   119  
   120  	_, err = os.Stat(filepath.Join(dir, src.hash().String()))
   121  	assert.True(os.IsNotExist(err), "%v", err)
   122  }
   123  
   124  func TestFSTablePersisterConjoinAll(t *testing.T) {
   125  	ctx := context.Background()
   126  	assert := assert.New(t)
   127  	assert.True(len(testChunks) > 1, "Whoops, this test isn't meaningful")
   128  	sources := make(chunkSources, len(testChunks))
   129  
   130  	dir := makeTempDir(t)
   131  	defer file.RemoveAll(dir)
   132  	fts := newFSTablePersister(dir, &UnlimitedQuotaProvider{})
   133  
   134  	for i, c := range testChunks {
   135  		randChunk := make([]byte, (i+1)*13)
   136  		_, err := rand.Read(randChunk)
   137  		require.NoError(t, err)
   138  		name, err := writeTableData(dir, c, randChunk)
   139  		require.NoError(t, err)
   140  		sources[i], err = fts.Open(ctx, name, 2, nil)
   141  		require.NoError(t, err)
   142  	}
   143  	defer func() {
   144  		for _, s := range sources {
   145  			s.close()
   146  		}
   147  	}()
   148  
   149  	src, _, err := fts.ConjoinAll(ctx, sources, &Stats{})
   150  	require.NoError(t, err)
   151  	defer src.close()
   152  
   153  	if assert.True(mustUint32(src.count()) > 0) {
   154  		buff, err := os.ReadFile(filepath.Join(dir, src.hash().String()))
   155  		require.NoError(t, err)
   156  		ti, err := parseTableIndexByCopy(ctx, buff, &UnlimitedQuotaProvider{})
   157  		require.NoError(t, err)
   158  		tr, err := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
   159  		require.NoError(t, err)
   160  		defer tr.close()
   161  		assertChunksInReader(testChunks, tr, assert)
   162  	}
   163  }
   164  
   165  func TestFSTablePersisterConjoinAllDups(t *testing.T) {
   166  	ctx := context.Background()
   167  	assert := assert.New(t)
   168  	dir := makeTempDir(t)
   169  	defer file.RemoveAll(dir)
   170  	fts := newFSTablePersister(dir, &UnlimitedQuotaProvider{})
   171  
   172  	reps := 3
   173  	sources := make(chunkSources, reps)
   174  	mt := newMemTable(1 << 10)
   175  	for _, c := range testChunks {
   176  		mt.addChunk(computeAddr(c), c)
   177  	}
   178  
   179  	var err error
   180  	sources[0], err = fts.Persist(ctx, mt, nil, &Stats{})
   181  	require.NoError(t, err)
   182  	sources[1], err = sources[0].clone()
   183  	require.NoError(t, err)
   184  	sources[2], err = sources[0].clone()
   185  	require.NoError(t, err)
   186  
   187  	src, cleanup, err := fts.ConjoinAll(ctx, sources, &Stats{})
   188  	require.NoError(t, err)
   189  	defer src.close()
   190  
   191  	// After ConjoinAll runs, we can close the sources and
   192  	// call the cleanup func.
   193  	for _, s := range sources {
   194  		s.close()
   195  	}
   196  	cleanup()
   197  
   198  	if assert.True(mustUint32(src.count()) > 0) {
   199  		buff, err := os.ReadFile(filepath.Join(dir, src.hash().String()))
   200  		require.NoError(t, err)
   201  		ti, err := parseTableIndexByCopy(ctx, buff, &UnlimitedQuotaProvider{})
   202  		require.NoError(t, err)
   203  		tr, err := newTableReader(ti, tableReaderAtFromBytes(buff), fileBlockSize)
   204  		require.NoError(t, err)
   205  		defer tr.close()
   206  		assertChunksInReader(testChunks, tr, assert)
   207  		assert.EqualValues(reps*len(testChunks), mustUint32(tr.count()))
   208  	}
   209  }