diff --git a/eventstore/boltdb/count.go b/eventstore/boltdb/count.go index f52d5e7..9291a6e 100644 --- a/eventstore/boltdb/count.go +++ b/eventstore/boltdb/count.go @@ -33,7 +33,7 @@ func (b *BoltBackend) CountEvents(filter nostr.Filter) (uint32, error) { for { // 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 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 - if !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { + if extraAuthors != nil && !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { it.next() continue } // 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() continue } - evt := &nostr.Event{} - if err := betterbinary.Unmarshal(bin, evt); err != nil { - it.next() - continue - } + if extraTagKey != "" { + evt := &nostr.Event{} + if err := betterbinary.Unmarshal(bin, evt); err != nil { + it.next() + continue + } - // if there is still a tag to be checked, do it now - if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { - it.next() - continue + // if there is still a tag to be checked, do it now + if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { + it.next() + continue + } } count++ } + + it.next() } } diff --git a/eventstore/lmdb/count.go b/eventstore/lmdb/count.go index e955802..538840c 100644 --- a/eventstore/lmdb/count.go +++ b/eventstore/lmdb/count.go @@ -34,7 +34,8 @@ func (b *LMDBBackend) CountEvents(filter nostr.Filter) (uint32, error) { for { // 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 || !bytes.HasPrefix(it.key, q.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 - if !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { + if extraAuthors != nil && !slices.Contains(extraAuthors, betterbinary.GetPubKey(bin)) { it.next() continue } // 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() continue } - evt := &nostr.Event{} - if err := betterbinary.Unmarshal(bin, evt); err != nil { - it.next() - continue - } + if extraTagKey != "" { + evt := &nostr.Event{} + if err := betterbinary.Unmarshal(bin, evt); err != nil { + it.next() + continue + } - // if there is still a tag to be checked, do it now - if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { - it.next() - continue + // if there is still a tag to be checked, do it now + if !evt.Tags.ContainsAny(extraTagKey, extraTagValues) { + it.next() + continue + } } count++ } + + it.next() } } diff --git a/eventstore/mmm/count.go b/eventstore/mmm/count.go index fcf0f58..bd0d6e6 100644 --- a/eventstore/mmm/count.go +++ b/eventstore/mmm/count.go @@ -31,7 +31,8 @@ func (il *IndexingLayer) CountEvents(filter nostr.Filter) (uint32, error) { for { // 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 || !bytes.HasPrefix(it.key, q.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) - event := &nostr.Event{} - if err := betterbinary.Unmarshal(bin, event); err != nil { - it.next() - continue - } + if extraTagKey != "" { + event := &nostr.Event{} + if err := betterbinary.Unmarshal(bin, event); err != nil { + it.next() + continue + } - // if there is still a tag to be checked, do it now - if !event.Tags.ContainsAny(extraTagKey, extraTagValues) { - it.next() - continue + // if there is still a tag to be checked, do it now + if !event.Tags.ContainsAny(extraTagKey, extraTagValues) { + it.next() + continue + } } count++ diff --git a/eventstore/mmm/mmmm.go b/eventstore/mmm/mmmm.go index 417efca..269c60f 100644 --- a/eventstore/mmm/mmmm.go +++ b/eventstore/mmm/mmmm.go @@ -175,6 +175,21 @@ func (b *MultiMmapManager) EnsureLayer(name string) (*IndexingLayer, error) { err = b.lmdbEnv.View(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 + } 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); lmdb.IsNotFound(err) { 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 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 } else { return err diff --git a/eventstore/test/count_test.go b/eventstore/test/count_test.go new file mode 100644 index 0000000..b2294d8 --- /dev/null +++ b/eventstore/test/count_test.go @@ -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") +} diff --git a/eventstore/test/db_test.go b/eventstore/test/db_test.go index 8fbbb59..f228b4f 100644 --- a/eventstore/test/db_test.go +++ b/eventstore/test/db_test.go @@ -32,6 +32,7 @@ var tests = []struct { {"second", runSecondTestOn}, {"manyauthors", manyAuthorsTest}, {"unbalanced", unbalancedTest}, + {"count", countTest}, } func TestSliceStore(t *testing.T) {