github.com/scottcagno/storage@v1.8.0/pkg/lsmt/sstable/ss-table.go (about)

     1  package sstable
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/scottcagno/storage/pkg/lsmt/binary"
     6  	"github.com/scottcagno/storage/pkg/util"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strconv"
    12  )
    13  
    14  func DataFileNameFromIndex(index int64) string {
    15  	hexa := strconv.FormatInt(index, 16)
    16  	return fmt.Sprintf("%s%010s%s", filePrefix, hexa, dataFileSuffix)
    17  }
    18  
    19  func IndexFromDataFileName(name string) (int64, error) {
    20  	hexa := name[len(filePrefix) : len(name)-len(dataFileSuffix)]
    21  	return strconv.ParseInt(hexa, 16, 32)
    22  }
    23  
    24  type SSTable struct {
    25  	path  string
    26  	file  *os.File
    27  	open  bool
    28  	index *SSTIndex
    29  }
    30  
    31  func OpenSSTable(base string, index int64) (*SSTable, error) {
    32  	// make sure we are working with absolute paths
    33  	base, err := filepath.Abs(base)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	// sanitize any path separators
    38  	base = filepath.ToSlash(base)
    39  	// create any directories if they are not there
    40  	err = os.MkdirAll(base, os.ModeDir)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	// create new data file path
    45  	path := filepath.Join(base, DataFileNameFromIndex(index))
    46  	// open data file
    47  	file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	// init sstable gindex
    52  	ssi, err := OpenSSTIndex(base, index)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	// init and return SSTable
    57  	sst := &SSTable{
    58  		path:  path, // path is the filepath for the data
    59  		file:  file, // file is the file descriptor for the data
    60  		open:  true, // open reports the status of the file
    61  		index: ssi,  // SSIndex is an SSTableIndex file
    62  	}
    63  	return sst, nil
    64  }
    65  
    66  func (sst *SSTable) errorCheckFileAndIndex() error {
    67  	// make sure file is not closed
    68  	if !sst.open {
    69  		return binary.ErrFileClosed
    70  	}
    71  	// make sure gindex is open
    72  	if sst.index == nil {
    73  		util.DEBUG("DEBUG: [SSTable.Index error]\n")
    74  		return ErrSSTIndexNotFound
    75  	}
    76  	return nil
    77  }
    78  
    79  func (sst *SSTable) Read(key string) (*binary.Entry, error) {
    80  	// error check
    81  	err := sst.errorCheckFileAndIndex()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	// find gindex using key
    86  	i, err := sst.index.Find(key)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	// use gindex offset to read data
    91  	e, err := binary.DecodeEntryAt(sst.file, i.Offset)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	// found it
    96  	return e, nil
    97  }
    98  
    99  func (sst *SSTable) ReadIndex(key string) (*binary.Index, error) {
   100  	// error check
   101  	err := sst.errorCheckFileAndIndex()
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	// find gindex using key
   106  	i, err := sst.index.Find(key)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	// found it
   111  	return i, nil
   112  }
   113  
   114  func (sst *SSTable) ReadAt(offset int64) (*binary.Entry, error) {
   115  	// error check
   116  	err := sst.errorCheckFileAndIndex()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	// use gindex offset to read data
   121  	e, err := binary.DecodeEntryAt(sst.file, offset)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	// found it
   126  	return e, nil
   127  }
   128  
   129  func (sst *SSTable) Write(e *binary.Entry) error {
   130  	// error check
   131  	err := sst.errorCheckFileAndIndex()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	// write entry to data file
   136  	offset, err := binary.EncodeEntry(sst.file, e)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	// write entry to ss-index
   141  	err = sst.index.Write(e.Key, offset)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	return nil
   146  }
   147  
   148  func (sst *SSTable) WriteBatch(b *binary.Batch) error {
   149  	// error check
   150  	err := sst.errorCheckFileAndIndex()
   151  	if err != nil {
   152  		return err
   153  	}
   154  	// error check batch
   155  	if b == nil {
   156  		return ErrSSTEmptyBatch
   157  	}
   158  	// check to see if batch is sorted
   159  	if !sort.IsSorted(b) {
   160  		// if not, sort
   161  		sort.Stable(b)
   162  	}
   163  	// range batch and write
   164  	for i := range b.Entries {
   165  		// entry
   166  		e := b.Entries[i]
   167  		// write entry to data file
   168  		offset, err := binary.EncodeEntry(sst.file, e)
   169  		if err != nil {
   170  			return err
   171  		}
   172  		// write entry info to gindex file
   173  		err = sst.index.Write(e.Key, offset)
   174  		if err != nil {
   175  			return err
   176  		}
   177  	}
   178  	return nil
   179  }
   180  
   181  func (sst *SSTable) Scan(iter func(e *binary.Entry) bool) error {
   182  	// error check
   183  	err := sst.errorCheckFileAndIndex()
   184  	if err != nil {
   185  		return err
   186  	}
   187  	for {
   188  		// decode next data entry
   189  		e, err := binary.DecodeEntry(sst.file)
   190  		if err != nil {
   191  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   192  				break
   193  			}
   194  			return err
   195  		}
   196  		if !iter(e) {
   197  			break
   198  		}
   199  	}
   200  	return nil
   201  }
   202  
   203  func (sst *SSTable) ScanAt(offset int64, iter func(e *binary.Entry) bool) error {
   204  	// error check
   205  	err := sst.errorCheckFileAndIndex()
   206  	if err != nil {
   207  		return err
   208  	}
   209  	// get current offset, so we can return here when were done
   210  	cur, err := binary.Offset(sst.file)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	// seek to provided location
   215  	_, err = sst.file.Seek(offset, io.SeekStart)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	for {
   220  		// decode next data entry
   221  		e, err := binary.DecodeEntry(sst.file)
   222  		if err != nil {
   223  			if err == io.EOF || err == io.ErrUnexpectedEOF {
   224  				break
   225  			}
   226  			return err
   227  		}
   228  		if !iter(e) {
   229  			break
   230  		}
   231  	}
   232  	// go back to where the file was at the beginning
   233  	_, err = sst.file.Seek(cur, io.SeekStart)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	return nil
   238  }
   239  
   240  func (sst *SSTable) Sync() error {
   241  	err := sst.file.Sync()
   242  	if err != nil {
   243  		return err
   244  	}
   245  	return nil
   246  }
   247  
   248  func (sst *SSTable) Close() error {
   249  	if sst.open {
   250  		err := sst.file.Sync()
   251  		if err != nil {
   252  			return err
   253  		}
   254  		err = sst.file.Close()
   255  		if err != nil {
   256  			return err
   257  		}
   258  	}
   259  	if sst.index != nil {
   260  		err := sst.index.Close()
   261  		if err != nil {
   262  			return err
   263  		}
   264  	}
   265  	sst.open = false
   266  	return nil
   267  }
   268  
   269  func between(data, first, last string) bool {
   270  	return first <= data && data <= last
   271  }
   272  
   273  func (sst *SSTable) KeyInTableRange(k string) bool {
   274  	return between(k, sst.index.first, sst.index.last)
   275  }