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 }