eventstore: SortedMerge() helper for combining results from different eventstores.
This commit is contained in:
61
eventstore/combine.go
Normal file
61
eventstore/combine.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"iter"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func SortedMerge(it1, it2 iter.Seq[nostr.Event]) iter.Seq[nostr.Event] {
|
||||
next1, done1 := iter.Pull(it1)
|
||||
next2, done2 := iter.Pull(it2)
|
||||
|
||||
return func(yield func(nostr.Event) bool) {
|
||||
defer done1()
|
||||
defer done2()
|
||||
|
||||
evt1, ok1 := next1()
|
||||
evt2, ok2 := next2()
|
||||
|
||||
both:
|
||||
if ok1 && ok2 {
|
||||
if evt2.CreatedAt > evt1.CreatedAt {
|
||||
if !yield(evt2) {
|
||||
return
|
||||
}
|
||||
evt2, ok2 = next2()
|
||||
goto both
|
||||
} else {
|
||||
if !yield(evt1) {
|
||||
return
|
||||
}
|
||||
evt1, ok1 = next1()
|
||||
goto both
|
||||
}
|
||||
}
|
||||
|
||||
if !ok2 {
|
||||
only1:
|
||||
if ok1 {
|
||||
if !yield(evt1) {
|
||||
return
|
||||
}
|
||||
evt1, ok1 = next1()
|
||||
goto only1
|
||||
}
|
||||
}
|
||||
|
||||
if !ok1 {
|
||||
only2:
|
||||
if ok2 {
|
||||
if !yield(evt2) {
|
||||
return
|
||||
}
|
||||
evt2, ok2 = next2()
|
||||
goto only2
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
52
eventstore/combine_test.go
Normal file
52
eventstore/combine_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func FuzzSortedMerge(f *testing.F) {
|
||||
f.Add(uint(4), uint(4), uint(3), uint(7), uint8(2), uint8(1))
|
||||
f.Add(uint(0), uint(4), uint(3), uint(7), uint8(2), uint8(1))
|
||||
f.Fuzz(func(t *testing.T, len1, len2 uint, start1, start2 uint, diff1, diff2 uint8) {
|
||||
maxxx := max(len1*uint(diff1), len2*uint(diff2))
|
||||
start1 += maxxx
|
||||
start2 += maxxx
|
||||
|
||||
merged := SortedMerge(
|
||||
func(yield func(nostr.Event) bool) {
|
||||
for range len1 {
|
||||
if !yield(nostr.Event{CreatedAt: nostr.Timestamp(start1)}) {
|
||||
return
|
||||
}
|
||||
start1 -= uint(diff1)
|
||||
}
|
||||
},
|
||||
func(yield func(nostr.Event) bool) {
|
||||
for range len2 {
|
||||
if !yield(nostr.Event{CreatedAt: nostr.Timestamp(start2)}) {
|
||||
return
|
||||
}
|
||||
start2 -= uint(diff2)
|
||||
}
|
||||
},
|
||||
)
|
||||
result := slices.Collect(merged)
|
||||
|
||||
// assert length
|
||||
if len(result) != int(len1+len2) {
|
||||
t.Fatalf("expected %d events, got %d", len1+len2, len(result))
|
||||
}
|
||||
|
||||
// assert sorted descending
|
||||
slices.IsSortedFunc(result, func(a, b nostr.Event) int { return -1 * cmp.Compare(a.CreatedAt, b.CreatedAt) })
|
||||
for i := 1; i < len(result); i++ {
|
||||
if result[i].CreatedAt > result[i-1].CreatedAt {
|
||||
t.Fatalf("events not sorted descending at index %d: %d > %d", i, result[i].CreatedAt, result[i-1].CreatedAt)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user