eventstore: a COUNT test and fix many bugs.

This commit is contained in:
fiatjaf
2026-01-18 21:31:12 -03:00
parent b559828c72
commit 459a10294f
6 changed files with 213 additions and 49 deletions

View File

@@ -33,7 +33,7 @@ func (b *BoltBackend) CountEvents(filter nostr.Filter) (uint32, error) {
for { for {
// we already have a k and a v and an err from the cursor setup, so check and use these // we already have a k and a v and an err from the cursor setup, so check and use these
if !bytes.HasPrefix(it.key, q.prefix) { if !bytes.HasPrefix(it.key, q.prefix) || it.exhausted {
// either iteration has errored or we reached the end of this prefix // either iteration has errored or we reached the end of this prefix
break // stop this cursor and move to the next one break // stop this cursor and move to the next one
} }
@@ -54,31 +54,35 @@ func (b *BoltBackend) CountEvents(filter nostr.Filter) (uint32, error) {
} }
// check it against pubkeys without decoding the entire thing // check it against pubkeys without decoding the entire thing
if !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { if extraAuthors != nil && !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) {
it.next() it.next()
continue continue
} }
// check it against kinds without decoding the entire thing // check it against kinds without decoding the entire thing
if !slices.Contains(extraKinds, betterbinary.GetKind(bin)) { if extraKinds != nil && !slices.Contains(extraKinds, betterbinary.GetKind(bin)) {
it.next() it.next()
continue continue
} }
evt := &nostr.Event{} if extraTagKey != "" {
if err := betterbinary.Unmarshal(bin, evt); err != nil { evt := &nostr.Event{}
it.next() if err := betterbinary.Unmarshal(bin, evt); err != nil {
continue it.next()
} continue
}
// if there is still a tag to be checked, do it now // if there is still a tag to be checked, do it now
if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) {
it.next() it.next()
continue continue
}
} }
count++ count++
} }
it.next()
} }
} }

View File

@@ -34,7 +34,8 @@ func (b *LMDBBackend) CountEvents(filter nostr.Filter) (uint32, error) {
for { for {
// we already have a k and a v and an err from the cursor setup, so check and use these // we already have a k and a v and an err from the cursor setup, so check and use these
if it.err != nil || if it.exhausted ||
it.err != nil ||
len(it.key) != q.keySize || len(it.key) != q.keySize ||
!bytes.HasPrefix(it.key, q.prefix) { !bytes.HasPrefix(it.key, q.prefix) {
// either iteration has errored or we reached the end of this prefix // either iteration has errored or we reached the end of this prefix
@@ -59,31 +60,35 @@ func (b *LMDBBackend) CountEvents(filter nostr.Filter) (uint32, error) {
} }
// check it against pubkeys without decoding the entire thing // check it against pubkeys without decoding the entire thing
if !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { if extraAuthors != nil && !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) {
it.next() it.next()
continue continue
} }
// check it against kinds without decoding the entire thing // check it against kinds without decoding the entire thing
if !slices.Contains(extraKinds, betterbinary.GetKind(bin)) { if extraKinds != nil && !slices.Contains(extraKinds, betterbinary.GetKind(bin)) {
it.next() it.next()
continue continue
} }
evt := &nostr.Event{} if extraTagKey != "" {
if err := betterbinary.Unmarshal(bin, evt); err != nil { evt := &nostr.Event{}
it.next() if err := betterbinary.Unmarshal(bin, evt); err != nil {
continue it.next()
} continue
}
// if there is still a tag to be checked, do it now // if there is still a tag to be checked, do it now
if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) {
it.next() it.next()
continue continue
}
} }
count++ count++
} }
it.next()
} }
} }

View File

@@ -31,7 +31,8 @@ func (il *IndexingLayer) CountEvents(filter nostr.Filter) (uint32, error) {
for { for {
// we already have a k and a v and an err from the cursor setup, so check and use these // we already have a k and a v and an err from the cursor setup, so check and use these
if it.err != nil || if it.exhausted ||
it.err != nil ||
len(it.key) != q.keySize || len(it.key) != q.keySize ||
!bytes.HasPrefix(it.key, q.prefix) { !bytes.HasPrefix(it.key, q.prefix) {
// either iteration has errored or we reached the end of this prefix // either iteration has errored or we reached the end of this prefix
@@ -66,16 +67,18 @@ func (il *IndexingLayer) CountEvents(filter nostr.Filter) (uint32, error) {
} }
// decode the entire thing (TODO: do a conditional decode while also checking the extra tag) // decode the entire thing (TODO: do a conditional decode while also checking the extra tag)
event := &nostr.Event{} if extraTagKey != "" {
if err := betterbinary.Unmarshal(bin, event); err != nil { event := &nostr.Event{}
it.next() if err := betterbinary.Unmarshal(bin, event); err != nil {
continue it.next()
} continue
}
// if there is still a tag to be checked, do it now // if there is still a tag to be checked, do it now
if !event.Tags.ContainsAny(extraTagKey, extraTagValues) { if !event.Tags.ContainsAny(extraTagKey, extraTagValues) {
it.next() it.next()
continue continue
}
} }
count++ count++

View File

@@ -175,6 +175,21 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) {
err = b.lmdbEnv.View(func(txn *lmdb.Txn) error { err = b.lmdbEnv.View(func(txn *lmdb.Txn) error {
txn.RawRead = true txn.RawRead = true
nameb := []byte(name)
if idv, err := txn.Get(b.knownLayers, nameb); err == nil {
if err := il.Init(); err != nil {
return fmt.Errorf("failed to init read-only layer %s: %w", name, err)
}
il.id = binary.BigEndian.Uint16(idv)
return nil
} else {
return err
}
})
} else {
err = b.lmdbEnv.Update(func(txn *lmdb.Txn) error {
txn.RawRead = true
nameb := []byte(name) nameb := []byte(name)
if idv, err := txn.Get(b.knownLayers, nameb); lmdb.IsNotFound(err) { if idv, err := txn.Get(b.knownLayers, nameb); lmdb.IsNotFound(err) {
if id, err := b.getNextAvailableLayerId(txn); err != nil { if id, err := b.getNextAvailableLayerId(txn); err != nil {
@@ -195,21 +210,6 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) {
return fmt.Errorf("failed to init old layer %s: %w", name, err) return fmt.Errorf("failed to init old layer %s: %w", name, err)
} }
return nil
} else {
return err
}
})
} else {
err = b.lmdbEnv.Update(func(txn *lmdb.Txn) error {
txn.RawRead = true
nameb := []byte(name)
if idv, err := txn.Get(b.knownLayers, nameb); err == nil {
if err := il.Init(); err != nil {
return fmt.Errorf("failed to init read-only layer %s: %w", name, err)
}
il.id = binary.BigEndian.Uint16(idv)
return nil return nil
} else { } else {
return err return err

View File

@@ -0,0 +1,151 @@
package test
import (
"testing"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore"
"github.com/stretchr/testify/require"
)
func countTest(t *testing.T, db eventstore.Store) {
err := db.Init()
require.NoError(t, err)
pk3 := nostr.GetPublicKey(sk3)
pk4 := nostr.GetPublicKey(sk4)
// create test events for counting
events := []nostr.Event{
{
CreatedAt: 700,
Content: "count test event 1",
Kind: 1,
PubKey: pk3,
Tags: nostr.Tags{
{"t", "plec"},
{"g", "bbbbbb"},
{"r", "https://z.com"},
},
},
{
CreatedAt: 701,
Content: "count test event 2",
Kind: 1,
PubKey: pk3,
Tags: nostr.Tags{
{"t", "plec"},
{"g", "aaaaaa"},
{"r", "https://z.com"},
},
},
{
CreatedAt: 702,
Content: "count test event 3",
Kind: 2,
PubKey: pk4,
Tags: nostr.Tags{
{"t", "test"},
},
},
}
// sign and save events
for _, evt := range events {
if evt.PubKey == pk3 {
evt.Sign(sk3)
} else {
evt.Sign(sk4)
}
err = db.SaveEvent(evt)
require.NoError(t, err)
}
// test count all events
count, err := db.CountEvents(nostr.Filter{})
require.NoError(t, err)
require.Equal(t, uint32(3), count, "should count the 3 new events")
// test count by kind 1
count, err = db.CountEvents(nostr.Filter{
Kinds: []nostr.Kind{1},
})
require.NoError(t, err)
require.Equal(t, uint32(2), count, "should count 2 events of kind 1")
// test count by author
count, err = db.CountEvents(nostr.Filter{
Authors: []nostr.PubKey{pk3},
})
require.NoError(t, err)
require.Equal(t, uint32(2), count, "should count 2 events from sk3")
// test count by author and tag
count, err = db.CountEvents(nostr.Filter{
Authors: []nostr.PubKey{pk4},
Tags: nostr.TagMap{"t": []string{"test"}},
})
require.NoError(t, err)
require.Equal(t, uint32(1), count, "should count 1 event")
count, err = db.CountEvents(nostr.Filter{
Authors: []nostr.PubKey{pk4},
Tags: nostr.TagMap{"t": []string{"somethingelse"}},
})
require.NoError(t, err)
require.Equal(t, uint32(0), count, "should count 0 events")
count, err = db.CountEvents(nostr.Filter{
Authors: []nostr.PubKey{pk3},
Tags: nostr.TagMap{"t": []string{"test"}},
})
require.NoError(t, err)
require.Equal(t, uint32(0), count, "should count 0 events")
// test double tag
count, err = db.CountEvents(nostr.Filter{
Kinds: []nostr.Kind{1, 2},
Tags: nostr.TagMap{"t": []string{"plec"}, "g": []string{"aaaaaa"}},
})
require.NoError(t, err)
require.Equal(t, uint32(1), count, "should count 1 event")
count, err = db.CountEvents(nostr.Filter{
Tags: nostr.TagMap{"t": []string{"plec"}, "g": []string{"aaaaaa"}},
})
require.NoError(t, err)
require.Equal(t, uint32(1), count, "should count 1 event")
count, err = db.CountEvents(nostr.Filter{
Kinds: []nostr.Kind{1},
Tags: nostr.TagMap{"t": []string{"plec"}, "g": []string{"aaaaaa"}},
})
require.NoError(t, err)
require.Equal(t, uint32(1), count, "should count 1 event")
count, err = db.CountEvents(nostr.Filter{
Kinds: []nostr.Kind{1},
Tags: nostr.TagMap{"t": []string{"plec"}, "r": []string{"https://z.com"}},
})
require.NoError(t, err)
require.Equal(t, uint32(2), count, "should count 2 events")
count, err = db.CountEvents(nostr.Filter{
Tags: nostr.TagMap{"t": []string{"plec"}, "r": []string{"https://z.com"}},
})
require.NoError(t, err)
require.Equal(t, uint32(2), count, "should count 2 events")
count, err = db.CountEvents(nostr.Filter{
Kinds: []nostr.Kind{1, 2},
Tags: nostr.TagMap{"t": []string{"plec"}, "r": []string{"https://z.com"}},
})
require.NoError(t, err)
require.Equal(t, uint32(2), count, "should count 2 events")
count, err = db.CountEvents(nostr.Filter{
Tags: nostr.TagMap{"t": []string{"banana"}, "r": []string{"https://z.com"}},
})
require.NoError(t, err)
require.Equal(t, uint32(0), count, "should count 0 events")
}

View File

@@ -32,6 +32,7 @@ var tests = []struct {
{"second", runSecondTestOn}, {"second", runSecondTestOn},
{"manyauthors", manyAuthorsTest}, {"manyauthors", manyAuthorsTest},
{"unbalanced", unbalancedTest}, {"unbalanced", unbalancedTest},
{"count", countTest},
} }
func TestSliceStore(t *testing.T) { func TestSliceStore(t *testing.T) {