Implement streaming WAL segment iterator
Currently, WALSegmentIterator implementations read to the end of the end of their list of segments and return EOF. This commit adds the ability to push additional segments to in-process iterators and notify their callers that new segments are available. This is only implemented for the file-based iterator but other segment iterators may get this implementation in the future or have a wrapping iterator provide a polling-based implementation.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package litestream_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/benbjohnson/litestream"
|
||||
@@ -133,3 +134,133 @@ func TestReplicaClient_WALSegmentPath(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileWALSegmentIterator_Append(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-itr.NotifyCh():
|
||||
default:
|
||||
t.Fatal("expected notification")
|
||||
}
|
||||
|
||||
if !itr.Next() {
|
||||
t.Fatal("expected next")
|
||||
} else if got, want := itr.WALSegment(), (litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); got != want {
|
||||
t.Fatalf("info=%#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MultiOffset", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-itr.NotifyCh():
|
||||
default:
|
||||
t.Fatal("expected notification")
|
||||
}
|
||||
|
||||
if !itr.Next() {
|
||||
t.Fatal("expected next")
|
||||
} else if got, want := itr.WALSegment(), (litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); got != want {
|
||||
t.Fatalf("info=%#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
if !itr.Next() {
|
||||
t.Fatal("expected next")
|
||||
} else if got, want := itr.WALSegment(), (litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 1}); got != want {
|
||||
t.Fatalf("info=%#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MultiIndex", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 1, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 1, Offset: 1}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 2, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got, want := itr.Indexes(), []int{1, 2}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("indexes=%v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrGenerationMismatch", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0000000000000000", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err == nil || err.Error() != `generation mismatch` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrBelowMaxIndex", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 1, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err == nil || err.Error() != `appended index "0000000000000000" below max index "0000000000000001"` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrAboveMaxIndex", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 1, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 3, Offset: 0}); err == nil || err.Error() != `appended index "0000000000000003" skips index "0000000000000002"` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrBelowCurrentIndex", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 1, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err == nil || err.Error() != `appended index "0000000000000000" below current index "0000000000000001"` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrSkipsNextIndex", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 2, Offset: 0}); err == nil || err.Error() != `appended index "0000000000000002" skips next index "0000000000000001"` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrBelowOffset", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 5}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 4}); err == nil || err.Error() != `appended offset 0000000000000000/0000000000000004 before last offset 0000000000000000/0000000000000005` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ErrDuplicateOffset", func(t *testing.T) {
|
||||
itr := litestream.NewFileWALSegmentIterator(t.TempDir(), "0123456789abcdef", nil)
|
||||
if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 5}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err := itr.Append(litestream.WALSegmentInfo{Generation: "0123456789abcdef", Index: 0, Offset: 5}); err == nil || err.Error() != `duplicate offset 0000000000000000/0000000000000005 appended` {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user