From 47bc7caec31438daf0a64b8dc21a8e75af64225d Mon Sep 17 00:00:00 2001 From: Marty Schoch Date: Tue, 4 Nov 2014 08:34:49 -0500 Subject: [PATCH] added getRollbackID() and rollbackTo() to the ForestDB store --- index/store/forestdb/store.go | 43 ++++- index/store/forestdb/store_test.go | 254 +++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 2 deletions(-) diff --git a/index/store/forestdb/store.go b/index/store/forestdb/store.go index 28043e75..7908da8a 100644 --- a/index/store/forestdb/store.go +++ b/index/store/forestdb/store.go @@ -12,6 +12,8 @@ package forestdb import ( + "bytes" + "encoding/binary" "fmt" "sync" @@ -102,12 +104,49 @@ func (ldbs *Store) newBatch() store.KVBatch { return newBatch(ldbs) } -func (s *Store) newSnapshot() (*forestdb.Database, error) { +func (s *Store) getSeqNum() (forestdb.SeqNum, error) { dbinfo, err := s.db.DbInfo() + if err != nil { + return 0, err + } + return dbinfo.LastSeqNum(), nil +} + +func (s *Store) newSnapshot() (*forestdb.Database, error) { + seqNum, err := s.getSeqNum() if err != nil { return nil, err } - return s.db.SnapshotOpen(dbinfo.LastSeqNum()) + return s.db.SnapshotOpen(seqNum) +} + +func (s *Store) getRollbackID() ([]byte, error) { + seqNum, err := s.getSeqNum() + if err != nil { + return nil, err + } + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.LittleEndian, seqNum) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (s *Store) rollbackTo(rollbackId []byte) error { + s.writer.Lock() + defer s.writer.Unlock() + buf := bytes.NewReader(rollbackId) + var seqNum forestdb.SeqNum + err := binary.Read(buf, binary.LittleEndian, &seqNum) + if err != nil { + return err + } + err = s.db.Rollback(seqNum) + if err != nil { + return err + } + return nil } func StoreConstructor(config map[string]interface{}) (store.KVStore, error) { diff --git a/index/store/forestdb/store_test.go b/index/store/forestdb/store_test.go index 94a694f4..85b5e0c0 100644 --- a/index/store/forestdb/store_test.go +++ b/index/store/forestdb/store_test.go @@ -41,3 +41,257 @@ func TestReaderIsolation(t *testing.T) { store_test.CommonTestReaderIsolation(t, s) } + +// TestRollbackSameHandle tries to rollback a handle +// and ensure that subsequent reads from it also +// reflect the rollback +func TestRollbackSameHandle(t *testing.T) { + defer os.RemoveAll("test") + + s, err := Open("test", true) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + writer, err := s.Writer() + if err != nil { + t.Fatal(err) + } + + // create 2 docs a and b + err = writer.Set([]byte("a"), []byte("val-a")) + if err != nil { + t.Error(err) + } + + err = writer.Set([]byte("b"), []byte("val-b")) + if err != nil { + t.Error(err) + } + + // get the rollback id + rollbackId, err := s.getRollbackID() + if err != nil { + t.Error(err) + } + + // create a 3rd doc c + err = writer.Set([]byte("c"), []byte("val-c")) + if err != nil { + t.Error(err) + } + + err = writer.Close() + if err != nil { + t.Error(err) + } + + // make sure c is there + reader, err := s.Reader() + if err != nil { + t.Error(err) + } + val, err := reader.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if string(val) != "val-c" { + t.Errorf("expected value 'val-c' got '%s'", val) + } + reader.Close() + + // now rollback + err = s.rollbackTo(rollbackId) + if err != nil { + t.Fatal(err) + } + + // now make sure c is not there + reader, err = s.Reader() + if err != nil { + t.Error(err) + } + val, err = reader.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if val != nil { + t.Errorf("expected missing, got '%s'", val) + } + reader.Close() +} + +// TestRollbackNewHandle tries to rollback the +// database, then open a new handle, and ensure +// that the rollback is reflected there as well +func TestRollbackNewHandle(t *testing.T) { + defer os.RemoveAll("test") + + s, err := Open("test", true) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + writer, err := s.Writer() + if err != nil { + t.Fatal(err) + } + + // create 2 docs a and b + err = writer.Set([]byte("a"), []byte("val-a")) + if err != nil { + t.Error(err) + } + + err = writer.Set([]byte("b"), []byte("val-b")) + if err != nil { + t.Error(err) + } + + // get the rollback id + rollbackId, err := s.getRollbackID() + if err != nil { + t.Error(err) + } + + // create a 3rd doc c + err = writer.Set([]byte("c"), []byte("val-c")) + if err != nil { + t.Error(err) + } + + err = writer.Close() + if err != nil { + t.Error(err) + } + + // make sure c is there + reader, err := s.Reader() + if err != nil { + t.Error(err) + } + val, err := reader.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if string(val) != "val-c" { + t.Errorf("expected value 'val-c' got '%s'", val) + } + reader.Close() + + // now rollback + err = s.rollbackTo(rollbackId) + if err != nil { + t.Fatal(err) + } + + // now lets open another handle + s2, err := Open("test", true) + if err != nil { + t.Fatal(err) + } + defer s2.Close() + + // now make sure c is not there + reader2, err := s2.Reader() + if err != nil { + t.Error(err) + } + val, err = reader2.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if val != nil { + t.Errorf("expected missing, got '%s'", val) + } + reader2.Close() +} + +// TestRollbackOtherHandle tries to create 2 handles +// at the begining, then rollback one of them +// and ensure it affects the other +func TestRollbackOtherHandle(t *testing.T) { + defer os.RemoveAll("test") + + s, err := Open("test", true) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + // open another handle at the same time + s2, err := Open("test", true) + if err != nil { + t.Fatal(err) + } + defer s2.Close() + + writer, err := s.Writer() + if err != nil { + t.Fatal(err) + } + + // create 2 docs a and b + err = writer.Set([]byte("a"), []byte("val-a")) + if err != nil { + t.Error(err) + } + + err = writer.Set([]byte("b"), []byte("val-b")) + if err != nil { + t.Error(err) + } + + // get the rollback id + rollbackId, err := s.getRollbackID() + if err != nil { + t.Error(err) + } + + // create a 3rd doc c + err = writer.Set([]byte("c"), []byte("val-c")) + if err != nil { + t.Error(err) + } + + err = writer.Close() + if err != nil { + t.Error(err) + } + + // make sure c is there + reader, err := s.Reader() + if err != nil { + t.Error(err) + } + val, err := reader.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if string(val) != "val-c" { + t.Errorf("expected value 'val-c' got '%s'", val) + } + reader.Close() + + // now rollback + err = s.rollbackTo(rollbackId) + if err != nil { + t.Fatal(err) + } + + // now make sure c is not on the other handle + reader2, err := s2.Reader() + if err != nil { + t.Error(err) + } + val, err = reader2.Get([]byte("c")) + if err != nil { + t.Error(err) + } + if val != nil { + t.Errorf("expected missing, got '%s'", val) + } + reader2.Close() +}