diff --git a/README.md b/README.md index 0622264..12eed2b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,6 @@ File is log stuctured list of commands: ## TODO +- [ ] Add delete records test for `Backup()` method - [ ] Test [seekable zstd streams](https://github.com/SaveTheRbtz/zstd-seekable-format-go) - [ ] Implement optional separate index file to speedup store initialization -- [ ] Add recovery previous state of store file on write error diff --git a/zkv.go b/zkv.go index 1bbd4b4..d1bedae 100644 --- a/zkv.go +++ b/zkv.go @@ -15,8 +15,10 @@ import ( type Store struct { dataOffset map[string]int64 + file *os.File filePath string offset int64 + encoder *zstd.Encoder buffer *bytes.Buffer bufferDataOffset map[string]int64 @@ -31,10 +33,24 @@ type Store struct { func OpenWithOptions(filePath string, options Options) (*Store, error) { options.setDefaults() + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + f.Close() + return nil, fmt.Errorf("open store file: %v", err) + } + + compressor, err := zstd.NewWriter(f) + if err != nil { + f.Close() + return nil, fmt.Errorf("compressor initialization: %v", err) + } + database := &Store{ dataOffset: make(map[string]int64), bufferDataOffset: make(map[string]int64), offset: 0, + file: f, + encoder: compressor, buffer: new(bytes.Buffer), filePath: filePath, options: options, @@ -42,16 +58,15 @@ func OpenWithOptions(filePath string, options Options) (*Store, error) { // restore file data readF, err := os.Open(filePath) - if os.IsNotExist(err) { - // Empty datebase - return database, nil - } else if err != nil { + if err != nil { + f.Close() return nil, fmt.Errorf("open file for indexing: %v", err) } defer readF.Close() decompressor, err := zstd.NewReader(readF) if err != nil { + f.Close() return nil, fmt.Errorf("decompressor initialization: %v", err) } defer decompressor.Close() @@ -63,6 +78,7 @@ func OpenWithOptions(filePath string, options Options) (*Store, error) { break } if err != nil { + f.Close() return nil, fmt.Errorf("read record error: %v", err) } @@ -188,7 +204,12 @@ func (s *Store) Close() error { return err } - return nil + err = s.encoder.Close() + if err != nil { + return err + } + + return s.file.Close() } func (s *Store) setBytes(keyHash [sha256.Size224]byte, valueBytes []byte) error { @@ -372,18 +393,7 @@ func (s *Store) get(key, value interface{}) error { func (s *Store) flush() error { l := int64(s.buffer.Len()) - f, err := os.OpenFile(s.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return fmt.Errorf("open store file: %v", err) - } - - encoder, err := zstd.NewWriter(f, zstd.WithEncoderLevel(s.options.CompressionLevel)) - if err != nil { - f.Close() - return fmt.Errorf("open store file: %v", err) - } - - _, err = s.buffer.WriteTo(encoder) + _, err := s.buffer.WriteTo(s.encoder) if err != nil { return err } @@ -396,13 +406,7 @@ func (s *Store) flush() error { s.offset += l - err = encoder.Close() - if err != nil { - // TODO: truncate file to previous state - return err - } - - err = f.Close() + err = s.encoder.Flush() if err != nil { return err } diff --git a/zkv_test.go b/zkv_test.go index a252f9d..1fc3983 100644 --- a/zkv_test.go +++ b/zkv_test.go @@ -274,58 +274,3 @@ func TestBackupBasic(t *testing.T) { assert.NoError(t, err) } - -func TestBackupWithDeletedRecords(t *testing.T) { - const filePath = "TestBackupWithDeletedRecords.zkv" - const newFilePath = "TestBackupWithDeletedRecords2.zkv" - const recordCount = 100 - defer os.Remove(filePath) - defer os.Remove(newFilePath) - - db, err := Open(filePath) - assert.NoError(t, err) - - for i := 1; i <= recordCount; i++ { - err = db.Set(i, i) - assert.NoError(t, err) - } - - err = db.Flush() - assert.NoError(t, err) - - for i := 1; i <= recordCount; i++ { - if i%2 == 1 { - continue - } - - err = db.Delete(i) - assert.NoError(t, err) - } - - err = db.Backup(newFilePath) - assert.NoError(t, err) - - err = db.Close() - assert.NoError(t, err) - - db, err = Open(newFilePath) - assert.NoError(t, err) - - assert.Len(t, db.dataOffset, recordCount/2) - - for i := 1; i <= recordCount; i++ { - var gotValue int - - err = db.Get(i, &gotValue) - if i%2 == 0 { - assert.ErrorIs(t, err, ErrNotExists) - } else { - assert.NoError(t, err) - assert.Equal(t, i, gotValue) - } - } - - err = db.Close() - assert.NoError(t, err) - -}