github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/nbs/mem_table_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  	"bytes"
    26  	"context"
    27  	"io/ioutil"
    28  	"os"
    29  	"testing"
    30  
    31  	"github.com/golang/snappy"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  	"golang.org/x/sync/errgroup"
    35  
    36  	"github.com/dolthub/dolt/go/store/chunks"
    37  	"github.com/dolthub/dolt/go/store/d"
    38  	"github.com/dolthub/dolt/go/store/types"
    39  )
    40  
    41  var testMDChunks = []chunks.Chunk{
    42  	mustChunk(types.EncodeValue(types.String("Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, "), types.Format_7_18)),
    43  	mustChunk(types.EncodeValue(types.String("and nothing particular to interest me on shore, I thought I would sail about a little and see the watery "), types.Format_7_18)),
    44  	mustChunk(types.EncodeValue(types.String("part of the world. It is a way I have of driving off the spleen and regulating the "), types.Format_7_18)),
    45  	mustChunk(types.EncodeValue(types.String("circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly "), types.Format_7_18)),
    46  	mustChunk(types.EncodeValue(types.String("November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing "), types.Format_7_18)),
    47  	mustChunk(types.EncodeValue(types.String("funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires "), types.Format_7_18)),
    48  	mustChunk(types.EncodeValue(types.String("a strong moral principle to prevent me from deliberately stepping into the street, and methodically "), types.Format_7_18)),
    49  	mustChunk(types.EncodeValue(types.String("knocking people’s hats off—then, I account it high time to get to sea as soon as I can."), types.Format_7_18)),
    50  }
    51  
    52  var testMDChunksSize uint64
    53  
    54  func init() {
    55  	for _, chunk := range testMDChunks {
    56  		testMDChunksSize += uint64(len(chunk.Data()))
    57  	}
    58  }
    59  
    60  func mustChunk(chunk chunks.Chunk, err error) chunks.Chunk {
    61  	d.PanicIfError(err)
    62  	return chunk
    63  }
    64  
    65  func TestWriteChunks(t *testing.T) {
    66  	name, data, err := WriteChunks(testMDChunks)
    67  	if err != nil {
    68  		t.Error(err)
    69  	}
    70  
    71  	dir, err := ioutil.TempDir("", "write_chunks_test")
    72  	if err != nil {
    73  		t.Error(err)
    74  	}
    75  
    76  	err = ioutil.WriteFile(dir+name, data, os.ModePerm)
    77  	if err != nil {
    78  		t.Error(err)
    79  	}
    80  }
    81  
    82  func TestMemTableAddHasGetChunk(t *testing.T) {
    83  	assert := assert.New(t)
    84  	mt := newMemTable(1024)
    85  
    86  	chunks := [][]byte{
    87  		[]byte("hello2"),
    88  		[]byte("goodbye2"),
    89  		[]byte("badbye2"),
    90  	}
    91  
    92  	for _, c := range chunks {
    93  		assert.True(mt.addChunk(computeAddr(c), c))
    94  	}
    95  
    96  	assertChunksInReader(chunks, mt, assert)
    97  
    98  	for _, c := range chunks {
    99  		data, err := mt.get(context.Background(), computeAddr(c), &Stats{})
   100  		require.NoError(t, err)
   101  		assert.Equal(bytes.Compare(c, data), 0)
   102  	}
   103  
   104  	notPresent := []byte("nope")
   105  	assert.False(mt.has(computeAddr(notPresent)))
   106  	assert.Nil(mt.get(context.Background(), computeAddr(notPresent), &Stats{}))
   107  }
   108  
   109  func TestMemTableAddOverflowChunk(t *testing.T) {
   110  	memTableSize := uint64(1024)
   111  
   112  	assert := assert.New(t)
   113  	big := make([]byte, memTableSize)
   114  	little := []byte{0x01}
   115  	{
   116  		bigAddr := computeAddr(big)
   117  		mt := newMemTable(memTableSize)
   118  		assert.True(mt.addChunk(bigAddr, big))
   119  		assert.True(mt.has(bigAddr))
   120  		assert.False(mt.addChunk(computeAddr(little), little))
   121  		assert.False(mt.has(computeAddr(little)))
   122  	}
   123  
   124  	{
   125  		big := big[:memTableSize-1]
   126  		bigAddr := computeAddr(big)
   127  		mt := newMemTable(memTableSize)
   128  		assert.True(mt.addChunk(bigAddr, big))
   129  		assert.True(mt.has(bigAddr))
   130  		assert.True(mt.addChunk(computeAddr(little), little))
   131  		assert.True(mt.has(computeAddr(little)))
   132  		other := []byte("o")
   133  		assert.False(mt.addChunk(computeAddr(other), other))
   134  		assert.False(mt.has(computeAddr(other)))
   135  	}
   136  }
   137  
   138  func TestMemTableWrite(t *testing.T) {
   139  	assert := assert.New(t)
   140  	mt := newMemTable(1024)
   141  
   142  	chunks := [][]byte{
   143  		[]byte("hello2"),
   144  		[]byte("goodbye2"),
   145  		[]byte("badbye2"),
   146  	}
   147  
   148  	for _, c := range chunks {
   149  		assert.True(mt.addChunk(computeAddr(c), c))
   150  	}
   151  
   152  	td1, _, err := buildTable(chunks[1:2])
   153  	require.NoError(t, err)
   154  	ti1, err := parseTableIndex(td1)
   155  	require.NoError(t, err)
   156  	tr1 := newTableReader(ti1, tableReaderAtFromBytes(td1), fileBlockSize)
   157  	assert.True(tr1.has(computeAddr(chunks[1])))
   158  
   159  	td2, _, err := buildTable(chunks[2:])
   160  	require.NoError(t, err)
   161  	ti2, err := parseTableIndex(td2)
   162  	require.NoError(t, err)
   163  	tr2 := newTableReader(ti2, tableReaderAtFromBytes(td2), fileBlockSize)
   164  	assert.True(tr2.has(computeAddr(chunks[2])))
   165  
   166  	_, data, count, err := mt.write(chunkReaderGroup{tr1, tr2}, &Stats{})
   167  	require.NoError(t, err)
   168  	assert.Equal(uint32(1), count)
   169  
   170  	ti, err := parseTableIndex(data)
   171  	require.NoError(t, err)
   172  	outReader := newTableReader(ti, tableReaderAtFromBytes(data), fileBlockSize)
   173  	assert.True(outReader.has(computeAddr(chunks[0])))
   174  	assert.False(outReader.has(computeAddr(chunks[1])))
   175  	assert.False(outReader.has(computeAddr(chunks[2])))
   176  }
   177  
   178  type tableReaderAtAdapter struct {
   179  	*bytes.Reader
   180  }
   181  
   182  func tableReaderAtFromBytes(b []byte) tableReaderAt {
   183  	return tableReaderAtAdapter{bytes.NewReader(b)}
   184  }
   185  
   186  func (adapter tableReaderAtAdapter) ReadAtWithStats(ctx context.Context, p []byte, off int64, stats *Stats) (n int, err error) {
   187  	return adapter.ReadAt(p, off)
   188  }
   189  
   190  func TestMemTableSnappyWriteOutOfLine(t *testing.T) {
   191  	assert := assert.New(t)
   192  	mt := newMemTable(1024)
   193  
   194  	chunks := [][]byte{
   195  		[]byte("hello2"),
   196  		[]byte("goodbye2"),
   197  		[]byte("badbye2"),
   198  	}
   199  
   200  	for _, c := range chunks {
   201  		assert.True(mt.addChunk(computeAddr(c), c))
   202  	}
   203  	mt.snapper = &outOfLineSnappy{[]bool{false, true, false}} // chunks[1] should trigger a panic
   204  
   205  	assert.Panics(func() { mt.write(nil, &Stats{}) })
   206  }
   207  
   208  type outOfLineSnappy struct {
   209  	policy []bool
   210  }
   211  
   212  func (o *outOfLineSnappy) Encode(dst, src []byte) []byte {
   213  	outOfLine := false
   214  	if len(o.policy) > 0 {
   215  		outOfLine = o.policy[0]
   216  		o.policy = o.policy[1:]
   217  	}
   218  	if outOfLine {
   219  		return snappy.Encode(nil, src)
   220  	}
   221  	return snappy.Encode(dst, src)
   222  }
   223  
   224  type chunkReaderGroup []chunkReader
   225  
   226  func (crg chunkReaderGroup) has(h addr) (bool, error) {
   227  	for _, haver := range crg {
   228  		ok, err := haver.has(h)
   229  
   230  		if err != nil {
   231  			return false, err
   232  		}
   233  		if ok {
   234  			return true, nil
   235  		}
   236  	}
   237  
   238  	return false, nil
   239  }
   240  
   241  func (crg chunkReaderGroup) get(ctx context.Context, h addr, stats *Stats) ([]byte, error) {
   242  	for _, haver := range crg {
   243  		if data, err := haver.get(ctx, h, stats); err != nil {
   244  			return nil, err
   245  		} else if data != nil {
   246  			return data, nil
   247  		}
   248  	}
   249  
   250  	return nil, nil
   251  }
   252  
   253  func (crg chunkReaderGroup) hasMany(addrs []hasRecord) (bool, error) {
   254  	for _, haver := range crg {
   255  		remaining, err := haver.hasMany(addrs)
   256  
   257  		if err != nil {
   258  			return false, err
   259  		}
   260  
   261  		if !remaining {
   262  			return false, nil
   263  		}
   264  	}
   265  	return true, nil
   266  }
   267  
   268  func (crg chunkReaderGroup) getMany(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(*chunks.Chunk), stats *Stats) (bool, error) {
   269  	for _, haver := range crg {
   270  		remaining, err := haver.getMany(ctx, eg, reqs, found, stats)
   271  		if err != nil {
   272  			return true, err
   273  		}
   274  		if !remaining {
   275  			return false, nil
   276  		}
   277  	}
   278  	return true, nil
   279  }
   280  
   281  func (crg chunkReaderGroup) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(CompressedChunk), stats *Stats) (bool, error) {
   282  	for _, haver := range crg {
   283  		remaining, err := haver.getManyCompressed(ctx, eg, reqs, found, stats)
   284  		if err != nil {
   285  			return true, err
   286  		}
   287  		if !remaining {
   288  			return false, nil
   289  		}
   290  	}
   291  	return true, nil
   292  }
   293  
   294  func (crg chunkReaderGroup) count() (count uint32, err error) {
   295  	for _, haver := range crg {
   296  		count += mustUint32(haver.count())
   297  	}
   298  	return
   299  }
   300  
   301  func (crg chunkReaderGroup) uncompressedLen() (data uint64, err error) {
   302  	for _, haver := range crg {
   303  		data += mustUint64(haver.uncompressedLen())
   304  	}
   305  	return
   306  }
   307  
   308  func (crg chunkReaderGroup) extract(ctx context.Context, chunks chan<- extractRecord) error {
   309  	for _, haver := range crg {
   310  		err := haver.extract(ctx, chunks)
   311  
   312  		if err != nil {
   313  			return err
   314  		}
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  func (crg chunkReaderGroup) Close() error {
   321  	var firstErr error
   322  	for _, c := range crg {
   323  		err := c.Close()
   324  		if err != nil && firstErr == nil {
   325  			firstErr = err
   326  		}
   327  	}
   328  	return firstErr
   329  }