github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/blobstore/inmem.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  package blobstore
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  	"io"
    22  	"sync"
    23  
    24  	"golang.org/x/sync/errgroup"
    25  
    26  	"github.com/google/uuid"
    27  )
    28  
    29  type byteSliceReadCloser struct {
    30  	io.Reader
    31  	io.Closer
    32  }
    33  
    34  func newByteSliceReadCloser(data []byte) *byteSliceReadCloser {
    35  	reader := bytes.NewReader(data)
    36  	return &byteSliceReadCloser{reader, io.NopCloser(reader)}
    37  }
    38  
    39  // InMemoryBlobstore provides an in memory implementation of the Blobstore interface
    40  type InMemoryBlobstore struct {
    41  	path     string
    42  	mutex    sync.RWMutex
    43  	blobs    map[string][]byte
    44  	versions map[string]string
    45  }
    46  
    47  var _ Blobstore = &InMemoryBlobstore{}
    48  
    49  // NewInMemoryBlobstore creates an instance of an InMemoryBlobstore
    50  func NewInMemoryBlobstore(path string) *InMemoryBlobstore {
    51  	return &InMemoryBlobstore{
    52  		path:     path,
    53  		blobs:    make(map[string][]byte),
    54  		versions: make(map[string]string),
    55  	}
    56  }
    57  
    58  func (bs *InMemoryBlobstore) Path() string {
    59  	return bs.path
    60  }
    61  
    62  // Get retrieves an io.reader for the portion of a blob specified by br along with
    63  // its version
    64  func (bs *InMemoryBlobstore) Get(ctx context.Context, key string, br BlobRange) (io.ReadCloser, string, error) {
    65  	bs.mutex.Lock()
    66  	defer bs.mutex.Unlock()
    67  
    68  	if val, ok := bs.blobs[key]; ok {
    69  		if ver, ok := bs.versions[key]; ok && ver != "" {
    70  			var byteRange []byte
    71  			if br.isAllRange() {
    72  				byteRange = val
    73  			} else {
    74  				posBR := br.positiveRange(int64(len(val)))
    75  				if posBR.length == 0 {
    76  					byteRange = val[posBR.offset:]
    77  				} else {
    78  					byteRange = val[posBR.offset : posBR.offset+posBR.length]
    79  				}
    80  			}
    81  
    82  			return newByteSliceReadCloser(byteRange), ver, nil
    83  		}
    84  
    85  		panic("Blob without version, or with invalid version, should no be possible.")
    86  	}
    87  
    88  	return nil, "", NotFound{key}
    89  }
    90  
    91  // Put sets the blob and the version for a key
    92  func (bs *InMemoryBlobstore) Put(ctx context.Context, key string, totalSize int64, reader io.Reader) (string, error) {
    93  	bs.mutex.Lock()
    94  	defer bs.mutex.Unlock()
    95  	return bs.put(ctx, key, reader)
    96  }
    97  
    98  // CheckAndPut will check the current version of a blob against an expectedVersion, and if the
    99  // versions match it will update the data and version associated with the key
   100  func (bs *InMemoryBlobstore) CheckAndPut(ctx context.Context, expectedVersion, key string, totalSize int64, reader io.Reader) (string, error) {
   101  	bs.mutex.Lock()
   102  	defer bs.mutex.Unlock()
   103  
   104  	ver, ok := bs.versions[key]
   105  	check := !ok && expectedVersion == "" || ok && expectedVersion == ver
   106  
   107  	if !check {
   108  		return "", CheckAndPutError{key, expectedVersion, ver}
   109  	}
   110  	return bs.put(ctx, key, reader)
   111  }
   112  
   113  // Exists returns true if a blob exists for the given key, and false if it does not.
   114  // For InMemoryBlobstore instances error should never be returned (though other
   115  // implementations of this interface can)
   116  func (bs *InMemoryBlobstore) Exists(ctx context.Context, key string) (bool, error) {
   117  	_, ok := bs.blobs[key]
   118  	return ok, nil
   119  }
   120  
   121  func (bs *InMemoryBlobstore) Concatenate(ctx context.Context, key string, sources []string) (string, error) {
   122  	// recursively compose sources (mirrors GCS impl)
   123  	for len(sources) > composeBatch {
   124  		// compose subsets of |sources| in batches,
   125  		// store tmp composite objects in |next|
   126  		var next []string
   127  		var batches [][]string
   128  		for len(sources) > 0 {
   129  			k := min(composeBatch, len(sources))
   130  			batches = append(batches, sources[:k])
   131  			next = append(next, uuid.New().String())
   132  			sources = sources[k:]
   133  		}
   134  		// execute compose calls concurrently (mirrors GCS impl)
   135  		eg, _ := errgroup.WithContext(ctx)
   136  		for i := 0; i < len(batches); i++ {
   137  			idx := i
   138  			eg.Go(func() error {
   139  				blob, err := bs.composeObjects(batches[idx])
   140  				if err != nil {
   141  					return err
   142  				}
   143  				_, err = bs.Put(ctx, next[idx], int64(len(blob)), bytes.NewReader(blob))
   144  				return err
   145  			})
   146  		}
   147  		if err := eg.Wait(); err != nil {
   148  			return "", err
   149  		}
   150  		sources = next
   151  	}
   152  
   153  	blob, err := bs.composeObjects(sources)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	return bs.Put(ctx, key, int64(len(blob)), bytes.NewReader(blob))
   158  }
   159  
   160  func (bs *InMemoryBlobstore) put(ctx context.Context, key string, reader io.Reader) (string, error) {
   161  	ver := uuid.New().String()
   162  	data, err := io.ReadAll(reader)
   163  
   164  	if err != nil {
   165  		return "", err
   166  	}
   167  
   168  	bs.blobs[key] = data
   169  	bs.versions[key] = ver
   170  
   171  	return ver, nil
   172  }
   173  
   174  func (bs *InMemoryBlobstore) composeObjects(sources []string) (blob []byte, err error) {
   175  	bs.mutex.RLock()
   176  	defer bs.mutex.RUnlock()
   177  	if len(sources) > composeBatch {
   178  		return nil, fmt.Errorf("too many objects to compose (%d > %d)", len(sources), composeBatch)
   179  	}
   180  	var sz int
   181  	for _, k := range sources {
   182  		sz += len(bs.blobs[k])
   183  	}
   184  	blob = make([]byte, 0, sz)
   185  	for _, k := range sources {
   186  		blob = append(blob, bs.blobs[k]...)
   187  	}
   188  	return
   189  }