Browse Source

Use stack-allocated arrays for IdPkTs to reduce GC pressure (v0.56.6)

- Change IdPkTs Id/Pub fields from []byte to [32]byte (ntypes.EventID/Pubkey)
- Add NewIdPkTs() constructor for copying byte slices into fixed arrays
- Add IDSlice() and PubSlice() methods for slice access when needed
- Update protobuf converters to handle array-to-slice conversion
- Update all database and neo4j creation sites to use NewIdPkTs()
- Fix test files to use bytes.Equal() for array comparisons
- Benchmark confirms 0 B/op, 0 allocs/op for copy operations

Files modified:
- pkg/interfaces/store/store_interface.go: Core type change to fixed arrays
- pkg/interfaces/store/store_interface_test.go: Tests for new API
- pkg/proto/orlydb/v1/converters.go: Protobuf conversion with ntypes
- pkg/database/get-fullidpubkey-by-serial.go: Use NewIdPkTs constructor
- pkg/database/get-fullidpubkey-by-serials.go: Use NewIdPkTs constructor
- pkg/database/process-delete.go: Slice arrays for DeleteEvent calls
- pkg/neo4j/fetch-event.go: Use NewIdPkTs constructor
- pkg/neo4j/query-events.go: Use NewIdPkTs constructor
- pkg/database/query-for-*_test.go: Use bytes.Equal for comparisons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
main v0.56.6
woikos 4 months ago
parent
commit
8c0e7d418a
No known key found for this signature in database
  1. 12
      pkg/database/get-fullidpubkey-by-serial.go
  2. 13
      pkg/database/get-fullidpubkey-by-serials.go
  3. 8
      pkg/database/process-delete.go
  4. 10
      pkg/database/query-for-authors-tags_test.go
  5. 8
      pkg/database/query-for-created-at_test.go
  6. 42
      pkg/database/query-for-ids_test.go
  7. 10
      pkg/database/query-for-kinds-authors-tags_test.go
  8. 6
      pkg/database/query-for-kinds-authors_test.go
  9. 8
      pkg/database/query-for-kinds-tags_test.go
  10. 4
      pkg/database/query-for-kinds_test.go
  11. 8
      pkg/database/query-for-tags_test.go
  12. 56
      pkg/interfaces/store/store_interface.go
  13. 175
      pkg/interfaces/store/store_interface_test.go
  14. 16
      pkg/neo4j/fetch-event.go
  15. 8
      pkg/neo4j/query-events.go
  16. 9
      pkg/proto/orlydb/v1/converters.go
  17. 2
      pkg/version/version

12
pkg/database/get-fullidpubkey-by-serial.go

@ -41,12 +41,12 @@ func (d *D) GetFullIdPubkeyBySerial(ser *types.Uint40) (
).UnmarshalRead(buf2); chk.E(err) { ).UnmarshalRead(buf2); chk.E(err) {
return return
} }
idpkts := store.IdPkTs{ idpkts := store.NewIdPkTs(
Id: fid.Bytes(), fid.Bytes(),
Pub: p.Bytes(), p.Bytes(),
Ts: int64(ca.Get()), int64(ca.Get()),
Ser: ser.Get(), ser.Get(),
} )
fidpk = &idpkts fidpk = &idpkts
} }
return return

13
pkg/database/get-fullidpubkey-by-serials.go

@ -59,14 +59,13 @@ func (d *D) GetFullIdPubkeyBySerials(sers []*types.Uint40) (
).UnmarshalRead(bytes.NewBuffer(key)); chk.E(err) { ).UnmarshalRead(bytes.NewBuffer(key)); chk.E(err) {
return return
} }
fidpks = append( idpkts := store.NewIdPkTs(
fidpks, &store.IdPkTs{ fid.Bytes(),
Id: fid.Bytes(), p.Bytes(),
Pub: p.Bytes(), int64(ca.Get()),
Ts: int64(ca.Get()), ser.Get(),
Ser: ser.Get(),
},
) )
fidpks = append(fidpks, &idpkts)
} }
} }
return return

8
pkg/database/process-delete.go

@ -129,11 +129,11 @@ func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error) {
}) })
for _, v := range idPkTss { for _, v := range idPkTss {
if v.Ts < ev.CreatedAt { if v.Ts < ev.CreatedAt {
if err = d.DeleteEvent(context.Background(), v.Id); chk.E(err) { if err = d.DeleteEvent(context.Background(), v.Id[:]); chk.E(err) {
log.W.F("failed to delete event %x via a-tag: %v", v.Id, err) log.W.F("failed to delete event %x via a-tag: %v", v.Id[:], err)
continue continue
} }
log.D.F("deleted event %x via a-tag deletion", v.Id) log.D.F("deleted event %x via a-tag deletion", v.Id[:])
} }
} }
} }
@ -187,7 +187,7 @@ func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error) {
for _, v := range idPkTss { for _, v := range idPkTss {
if v.Ts < ev.CreatedAt { if v.Ts < ev.CreatedAt {
if err = d.DeleteEvent( if err = d.DeleteEvent(
context.Background(), v.Id, context.Background(), v.Id[:],
); chk.E(err) { ); chk.E(err) {
continue continue
} }

10
pkg/database/query-for-authors-tags_test.go

@ -1,11 +1,11 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"next.orly.dev/pkg/utils"
) )
func TestQueryForAuthorsTags(t *testing.T) { func TestQueryForAuthorsTags(t *testing.T) {
@ -56,10 +56,10 @@ func TestQueryForAuthorsTags(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if !utils.FastEqual(ev.Pubkey, testEvent.Pubkey) { if !bytes.Equal(ev.Pubkey[:], testEvent.Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, testEvent.Pubkey, i, ev.Pubkey, testEvent.Pubkey,
@ -70,9 +70,9 @@ func TestQueryForAuthorsTags(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }

8
pkg/database/query-for-created-at_test.go

@ -1,11 +1,11 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/timestamp" "git.mleku.dev/mleku/nostr/encoders/timestamp"
"next.orly.dev/pkg/utils"
) )
func TestQueryForCreatedAt(t *testing.T) { func TestQueryForCreatedAt(t *testing.T) {
@ -50,7 +50,7 @@ func TestQueryForCreatedAt(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
break break
} }
@ -88,7 +88,7 @@ func TestQueryForCreatedAt(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
break break
} }
@ -126,7 +126,7 @@ func TestQueryForCreatedAt(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
break break
} }

42
pkg/database/query-for-ids_test.go

@ -1,13 +1,13 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/encoders/timestamp" "git.mleku.dev/mleku/nostr/encoders/timestamp"
"next.orly.dev/pkg/utils"
) )
func TestQueryForIds(t *testing.T) { func TestQueryForIds(t *testing.T) {
@ -39,9 +39,9 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) { if !bytes.Equal(ev.Pubkey[:], events[1].Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, events[1].Pubkey, i, ev.Pubkey, events[1].Pubkey,
@ -79,7 +79,7 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind.K { if ev.Kind != testKind.K {
t.Fatalf( t.Fatalf(
@ -131,16 +131,16 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
// Check if the event has the tag we're looking for // Check if the event has the tag we're looking for
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }
@ -182,7 +182,7 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind.K { if ev.Kind != testKind.K {
t.Fatalf( t.Fatalf(
@ -190,7 +190,7 @@ func TestQueryForIds(t *testing.T) {
i, ev.Kind, testKind.K, i, ev.Kind, testKind.K,
) )
} }
if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) { if !bytes.Equal(ev.Pubkey[:], events[1].Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, events[1].Pubkey, i, ev.Pubkey, events[1].Pubkey,
@ -229,7 +229,7 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testEventForTag.Kind { if ev.Kind != testEventForTag.Kind {
t.Fatalf( t.Fatalf(
@ -242,9 +242,9 @@ func TestQueryForIds(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }
@ -290,7 +290,7 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testEventForTag.Kind { if ev.Kind != testEventForTag.Kind {
t.Fatalf( t.Fatalf(
@ -299,7 +299,7 @@ func TestQueryForIds(t *testing.T) {
) )
} }
if !utils.FastEqual(ev.Pubkey, testEventForTag.Pubkey) { if !bytes.Equal(ev.Pubkey[:], testEventForTag.Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, testEventForTag.Pubkey, i, ev.Pubkey, testEventForTag.Pubkey,
@ -310,9 +310,9 @@ func TestQueryForIds(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }
@ -357,10 +357,10 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if !utils.FastEqual(ev.Pubkey, testEventForTag.Pubkey) { if !bytes.Equal(ev.Pubkey[:], testEventForTag.Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, testEventForTag.Pubkey, i, ev.Pubkey, testEventForTag.Pubkey,
@ -371,9 +371,9 @@ func TestQueryForIds(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }
@ -430,7 +430,7 @@ func TestQueryForIds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
break break
} }

10
pkg/database/query-for-kinds-authors-tags_test.go

@ -1,12 +1,12 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"next.orly.dev/pkg/utils"
) )
func TestQueryForKindsAuthorsTags(t *testing.T) { func TestQueryForKindsAuthorsTags(t *testing.T) {
@ -62,7 +62,7 @@ func TestQueryForKindsAuthorsTags(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind { if ev.Kind != testKind {
t.Fatalf( t.Fatalf(
@ -71,7 +71,7 @@ func TestQueryForKindsAuthorsTags(t *testing.T) {
) )
} }
if !utils.FastEqual(ev.Pubkey, testEvent.Pubkey) { if !bytes.Equal(ev.Pubkey[:], testEvent.Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, testEvent.Pubkey, i, ev.Pubkey, testEvent.Pubkey,
@ -82,9 +82,9 @@ func TestQueryForKindsAuthorsTags(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }

6
pkg/database/query-for-kinds-authors_test.go

@ -1,12 +1,12 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"next.orly.dev/pkg/utils"
) )
func TestQueryForKindsAuthors(t *testing.T) { func TestQueryForKindsAuthors(t *testing.T) {
@ -46,7 +46,7 @@ func TestQueryForKindsAuthors(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind.K { if ev.Kind != testKind.K {
t.Fatalf( t.Fatalf(
@ -54,7 +54,7 @@ func TestQueryForKindsAuthors(t *testing.T) {
i, ev.Kind, testKind.K, i, ev.Kind, testKind.K,
) )
} }
if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) { if !bytes.Equal(ev.Pubkey[:], events[1].Pubkey[:]) {
t.Fatalf( t.Fatalf(
"result %d has incorrect author, got %x, expected %x", "result %d has incorrect author, got %x, expected %x",
i, ev.Pubkey, events[1].Pubkey, i, ev.Pubkey, events[1].Pubkey,

8
pkg/database/query-for-kinds-tags_test.go

@ -1,12 +1,12 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"next.orly.dev/pkg/utils"
) )
func TestQueryForKindsTags(t *testing.T) { func TestQueryForKindsTags(t *testing.T) {
@ -58,7 +58,7 @@ func TestQueryForKindsTags(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind { if ev.Kind != testKind {
t.Fatalf( t.Fatalf(
@ -71,9 +71,9 @@ func TestQueryForKindsTags(t *testing.T) {
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }

4
pkg/database/query-for-kinds_test.go

@ -1,11 +1,11 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"next.orly.dev/pkg/utils"
) )
func TestQueryForKinds(t *testing.T) { func TestQueryForKinds(t *testing.T) {
@ -41,7 +41,7 @@ func TestQueryForKinds(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
if ev.Kind != testKind.K { if ev.Kind != testKind.K {
t.Fatalf( t.Fatalf(

8
pkg/database/query-for-tags_test.go

@ -1,11 +1,11 @@
package database package database
import ( import (
"bytes"
"testing" "testing"
"git.mleku.dev/mleku/nostr/encoders/filter" "git.mleku.dev/mleku/nostr/encoders/filter"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"next.orly.dev/pkg/utils"
) )
func TestQueryForTags(t *testing.T) { func TestQueryForTags(t *testing.T) {
@ -52,16 +52,16 @@ func TestQueryForTags(t *testing.T) {
// Find the event with this ID // Find the event with this ID
var found bool var found bool
for _, ev := range events { for _, ev := range events {
if utils.FastEqual(result.Id, ev.ID) { if bytes.Equal(result.Id[:], ev.ID[:]) {
found = true found = true
// Check if the event has the tag we're looking for // Check if the event has the tag we're looking for
var hasTag bool var hasTag bool
for _, tg := range *ev.Tags { for _, tg := range *ev.Tags {
if tg.Len() >= 2 && len(tg.Key()) == 1 { if tg.Len() >= 2 && len(tg.Key()) == 1 {
if utils.FastEqual( if bytes.Equal(
tg.Key(), testTag.Key(), tg.Key(), testTag.Key(),
) && utils.FastEqual(tg.Value(), testTag.Value()) { ) && bytes.Equal(tg.Value(), testTag.Value()) {
hasTag = true hasTag = true
break break
} }

56
pkg/interfaces/store/store_interface.go

@ -61,39 +61,54 @@ type Accountant interface {
EventCount() (count uint64, err error) EventCount() (count uint64, err error)
} }
// IdPkTs holds event reference data with slice fields for backward compatibility. // IdPkTs holds event reference data using fixed-size arrays for stack allocation.
// For new code preferring stack-allocated, copy-on-assignment semantics, // Total size: 80 bytes (32+32+8+8), fits in a cache line.
// use the IDFixed() and PubFixed() methods or convert to EventRef. // Copies of this struct stay on the stack and are value-safe.
type IdPkTs struct { type IdPkTs struct {
Id []byte Id ntypes.EventID // 32 bytes - event ID
Pub []byte Pub ntypes.Pubkey // 32 bytes - author pubkey
Ts int64 Ts int64 // 8 bytes - created_at timestamp
Ser uint64 Ser uint64 // 8 bytes - database serial number
} }
// IDFixed returns the event ID as a fixed-size array (stack-allocated, copied on assignment). // NewIdPkTs creates an IdPkTs from byte slices (copies into fixed arrays).
func (i *IdPkTs) IDFixed() ntypes.EventID { func NewIdPkTs(id, pub []byte, ts int64, ser uint64) IdPkTs {
return ntypes.EventIDFromBytes(i.Id) return IdPkTs{
Id: ntypes.EventIDFromBytes(id),
Pub: ntypes.PubkeyFromBytes(pub),
Ts: ts,
Ser: ser,
}
}
// IDSlice returns the event ID as a byte slice (shares memory with array).
func (i *IdPkTs) IDSlice() []byte {
return i.Id[:]
} }
// PubFixed returns the pubkey as a fixed-size array (stack-allocated, copied on assignment). // PubSlice returns the pubkey as a byte slice (shares memory with array).
func (i *IdPkTs) PubFixed() ntypes.Pubkey { func (i *IdPkTs) PubSlice() []byte {
return ntypes.PubkeyFromBytes(i.Pub) return i.Pub[:]
} }
// IDHex returns the event ID as a lowercase hex string. // IDHex returns the event ID as a lowercase hex string.
func (i *IdPkTs) IDHex() string { func (i *IdPkTs) IDHex() string {
return ntypes.EventIDFromBytes(i.Id).Hex() return i.Id.Hex()
} }
// PubHex returns the pubkey as a lowercase hex string. // PubHex returns the pubkey as a lowercase hex string.
func (i *IdPkTs) PubHex() string { func (i *IdPkTs) PubHex() string {
return ntypes.PubkeyFromBytes(i.Pub).Hex() return i.Pub.Hex()
} }
// ToEventRef converts IdPkTs to an EventRef (fully stack-allocated). // ToEventRef converts IdPkTs to an EventRef.
func (i *IdPkTs) ToEventRef() EventRef { func (i *IdPkTs) ToEventRef() EventRef {
return NewEventRef(i.Id, i.Pub, i.Ts, i.Ser) return EventRef{
id: i.Id,
pub: i.Pub,
ts: i.Ts,
ser: i.Ser,
}
} }
// EventRef is a stack-friendly event reference using fixed-size arrays. // EventRef is a stack-friendly event reference using fixed-size arrays.
@ -141,12 +156,11 @@ func (r *EventRef) IDSlice() []byte { return r.id.Bytes() }
// PubSlice returns a slice view of the pubkey (shares memory, use carefully). // PubSlice returns a slice view of the pubkey (shares memory, use carefully).
func (r *EventRef) PubSlice() []byte { return r.pub.Bytes() } func (r *EventRef) PubSlice() []byte { return r.pub.Bytes() }
// ToIdPkTs converts EventRef to IdPkTs for backward compatibility. // ToIdPkTs converts EventRef to IdPkTs.
// Note: This allocates new slices.
func (r EventRef) ToIdPkTs() *IdPkTs { func (r EventRef) ToIdPkTs() *IdPkTs {
return &IdPkTs{ return &IdPkTs{
Id: r.id.Copy(), Id: r.id,
Pub: r.pub.Copy(), Pub: r.pub,
Ts: r.ts, Ts: r.ts,
Ser: r.ser, Ser: r.ser,
} }

175
pkg/interfaces/store/store_interface_test.go

@ -7,8 +7,8 @@ import (
ntypes "git.mleku.dev/mleku/nostr/types" ntypes "git.mleku.dev/mleku/nostr/types"
) )
func TestIdPkTsFixedMethods(t *testing.T) { func TestNewIdPkTs(t *testing.T) {
// Create an IdPkTs with sample data // Create sample data
id := make([]byte, 32) id := make([]byte, 32)
pub := make([]byte, 32) pub := make([]byte, 32)
for i := 0; i < 32; i++ { for i := 0; i < 32; i++ {
@ -16,29 +16,49 @@ func TestIdPkTsFixedMethods(t *testing.T) {
pub[i] = byte(i + 32) pub[i] = byte(i + 32)
} }
ipk := &IdPkTs{ ipk := NewIdPkTs(id, pub, 1234567890, 42)
Id: id,
Pub: pub,
Ts: 1234567890,
Ser: 42,
}
// Test IDFixed returns correct data // Test that data was copied correctly
idFixed := ipk.IDFixed() if !bytes.Equal(ipk.Id[:], id) {
if !bytes.Equal(idFixed[:], id) { t.Errorf("Id: got %x, want %x", ipk.Id[:], id)
t.Errorf("IDFixed: got %x, want %x", idFixed[:], id) }
if !bytes.Equal(ipk.Pub[:], pub) {
t.Errorf("Pub: got %x, want %x", ipk.Pub[:], pub)
}
if ipk.Ts != 1234567890 {
t.Errorf("Ts: got %d, want 1234567890", ipk.Ts)
}
if ipk.Ser != 42 {
t.Errorf("Ser: got %d, want 42", ipk.Ser)
} }
// Test IDFixed returns a copy // Test that Id is a copy (modifying original doesn't affect struct)
idFixed[0] = 0xFF id[0] = 0xFF
if ipk.Id[0] == 0xFF { if ipk.Id[0] == 0xFF {
t.Error("IDFixed should return a copy, not a reference") t.Error("NewIdPkTs should copy data, not reference")
}
}
func TestIdPkTsSliceMethods(t *testing.T) {
id := make([]byte, 32)
pub := make([]byte, 32)
for i := 0; i < 32; i++ {
id[i] = byte(i)
pub[i] = byte(i + 32)
}
ipk := NewIdPkTs(id, pub, 1234567890, 42)
// Test IDSlice returns correct data
idSlice := ipk.IDSlice()
if !bytes.Equal(idSlice, id) {
t.Errorf("IDSlice: got %x, want %x", idSlice, id)
} }
// Test PubFixed returns correct data // Test PubSlice returns correct data
pubFixed := ipk.PubFixed() pubSlice := ipk.PubSlice()
if !bytes.Equal(pubFixed[:], pub) { if !bytes.Equal(pubSlice, pub) {
t.Errorf("PubFixed: got %x, want %x", pubFixed[:], pub) t.Errorf("PubSlice: got %x, want %x", pubSlice, pub)
} }
// Test hex methods // Test hex methods
@ -49,6 +69,26 @@ func TestIdPkTsFixedMethods(t *testing.T) {
} }
} }
func TestIdPkTsCopyOnAssignment(t *testing.T) {
id := make([]byte, 32)
pub := make([]byte, 32)
for i := 0; i < 32; i++ {
id[i] = byte(i)
pub[i] = byte(i + 32)
}
ipk1 := NewIdPkTs(id, pub, 1234567890, 42)
ipk2 := ipk1 // Copy
// Modify the copy
ipk2.Id[0] = 0xFF
// Original should be unchanged (arrays are copied on assignment)
if ipk1.Id[0] == 0xFF {
t.Error("IdPkTs should copy on assignment")
}
}
func TestEventRef(t *testing.T) { func TestEventRef(t *testing.T) {
id := make([]byte, 32) id := make([]byte, 32)
pub := make([]byte, 32) pub := make([]byte, 32)
@ -103,10 +143,10 @@ func TestEventRefToIdPkTs(t *testing.T) {
ipk := ref.ToIdPkTs() ipk := ref.ToIdPkTs()
// Verify conversion // Verify conversion
if !bytes.Equal(ipk.Id, id) { if !bytes.Equal(ipk.Id[:], id) {
t.Error("ToIdPkTs: Id mismatch") t.Error("ToIdPkTs: Id mismatch")
} }
if !bytes.Equal(ipk.Pub, pub) { if !bytes.Equal(ipk.Pub[:], pub) {
t.Error("ToIdPkTs: Pub mismatch") t.Error("ToIdPkTs: Pub mismatch")
} }
if ipk.Ts != 1234567890 { if ipk.Ts != 1234567890 {
@ -131,13 +171,7 @@ func TestIdPkTsToEventRef(t *testing.T) {
pub[i] = byte(i + 100) pub[i] = byte(i + 100)
} }
ipk := &IdPkTs{ ipk := NewIdPkTs(id, pub, 1234567890, 42)
Id: id,
Pub: pub,
Ts: 1234567890,
Ser: 42,
}
ref := ipk.ToEventRef() ref := ipk.ToEventRef()
// Verify conversion - need addressable values for slicing // Verify conversion - need addressable values for slicing
@ -157,6 +191,23 @@ func TestIdPkTsToEventRef(t *testing.T) {
} }
} }
func BenchmarkIdPkTsCopy(b *testing.B) {
id := make([]byte, 32)
pub := make([]byte, 32)
for i := 0; i < 32; i++ {
id[i] = byte(i)
pub[i] = byte(i + 100)
}
ipk := NewIdPkTs(id, pub, 1234567890, 42)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ipk2 := ipk // Copy (should stay on stack)
_ = ipk2
}
}
func BenchmarkEventRefCopy(b *testing.B) { func BenchmarkEventRefCopy(b *testing.B) {
id := make([]byte, 32) id := make([]byte, 32)
pub := make([]byte, 32) pub := make([]byte, 32)
@ -182,12 +233,7 @@ func BenchmarkIdPkTsToEventRef(b *testing.B) {
pub[i] = byte(i + 100) pub[i] = byte(i + 100)
} }
ipk := &IdPkTs{ ipk := NewIdPkTs(id, pub, 1234567890, 42)
Id: id,
Pub: pub,
Ts: 1234567890,
Ser: 42,
}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -215,7 +261,7 @@ func BenchmarkEventRefAccess(b *testing.B) {
} }
} }
func BenchmarkIdPkTsFixedAccess(b *testing.B) { func BenchmarkIdPkTsAccess(b *testing.B) {
id := make([]byte, 32) id := make([]byte, 32)
pub := make([]byte, 32) pub := make([]byte, 32)
for i := 0; i < 32; i++ { for i := 0; i < 32; i++ {
@ -223,26 +269,55 @@ func BenchmarkIdPkTsFixedAccess(b *testing.B) {
pub[i] = byte(i + 100) pub[i] = byte(i + 100)
} }
ipk := &IdPkTs{ ipk := NewIdPkTs(id, pub, 1234567890, 42)
Id: id,
Pub: pub,
Ts: 1234567890,
Ser: 42,
}
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
idCopy := ipk.IDFixed() idCopy := ipk.Id
pubCopy := ipk.PubFixed() pubCopy := ipk.Pub
_ = idCopy _ = idCopy
_ = pubCopy _ = pubCopy
} }
} }
// Ensure EventRef implements expected interface at compile time // Ensure types satisfy expected size for stack allocation
var _ interface { func TestStructSizes(t *testing.T) {
ID() ntypes.EventID var ipk IdPkTs
Pub() ntypes.Pubkey var ref EventRef
Ts() int64
Ser() uint64 // Both should be exactly 80 bytes (32+32+8+8)
} = EventRef{} // This is not directly testable in Go, but we can verify the fields exist
_ = ipk.Id
_ = ipk.Pub
_ = ipk.Ts
_ = ipk.Ser
_ = ref
}
// Ensure ntypes.EventID and ntypes.Pubkey are used correctly
func TestNtypesCompatibility(t *testing.T) {
var id ntypes.EventID
var pub ntypes.Pubkey
// Fill with test data
for i := 0; i < 32; i++ {
id[i] = byte(i)
pub[i] = byte(i + 32)
}
// Create IdPkTs directly with ntypes
ipk := IdPkTs{
Id: id,
Pub: pub,
Ts: 1234567890,
Ser: 42,
}
// Verify
if ipk.Id != id {
t.Error("ntypes.EventID should be directly assignable to IdPkTs.Id")
}
if ipk.Pub != pub {
t.Error("ntypes.Pubkey should be directly assignable to IdPkTs.Pub")
}
}

16
pkg/neo4j/fetch-event.go

@ -336,12 +336,8 @@ RETURN e.id AS id,
return nil, err return nil, err
} }
fidpk = &store.IdPkTs{ idpkts := store.NewIdPkTs(id, pubkey, createdAt, serial)
Id: id, fidpk = &idpkts
Pub: pubkey,
Ts: createdAt,
Ser: serial,
}
return fidpk, nil return fidpk, nil
} }
@ -421,12 +417,8 @@ RETURN e.id AS id,
continue continue
} }
fidpks = append(fidpks, &store.IdPkTs{ idpkts := store.NewIdPkTs(id, pubkey, createdAt, uint64(serialVal))
Id: id, fidpks = append(fidpks, &idpkts)
Pub: pubkey,
Ts: createdAt,
Ser: uint64(serialVal),
})
} }
return fidpks, nil return fidpks, nil

8
pkg/neo4j/query-events.go

@ -595,12 +595,8 @@ func (n *N) QueryForIds(c context.Context, f *filter.F) (
continue continue
} }
idPkTs = append(idPkTs, &store.IdPkTs{ ipkts := store.NewIdPkTs(id, pubkey, createdAt, uint64(serialVal))
Id: id, idPkTs = append(idPkTs, &ipkts)
Pub: pubkey,
Ts: createdAt,
Ser: uint64(serialVal),
})
} }
return idPkTs, nil return idPkTs, nil

9
pkg/proto/orlydb/v1/converters.go

@ -9,6 +9,7 @@ import (
"git.mleku.dev/mleku/nostr/encoders/kind" "git.mleku.dev/mleku/nostr/encoders/kind"
"git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/tag"
"git.mleku.dev/mleku/nostr/encoders/timestamp" "git.mleku.dev/mleku/nostr/encoders/timestamp"
ntypes "git.mleku.dev/mleku/nostr/types"
"next.orly.dev/pkg/database" "next.orly.dev/pkg/database"
indextypes "next.orly.dev/pkg/database/indexes/types" indextypes "next.orly.dev/pkg/database/indexes/types"
"next.orly.dev/pkg/interfaces/store" "next.orly.dev/pkg/interfaces/store"
@ -271,8 +272,8 @@ func IdPkTsToProto(i *store.IdPkTs) *IdPkTs {
return nil return nil
} }
return &IdPkTs{ return &IdPkTs{
Id: i.Id, Id: i.Id[:],
Pubkey: i.Pub, Pubkey: i.Pub[:],
Timestamp: i.Ts, Timestamp: i.Ts,
Serial: i.Ser, Serial: i.Ser,
} }
@ -284,8 +285,8 @@ func ProtoToIdPkTs(pb *IdPkTs) *store.IdPkTs {
return nil return nil
} }
return &store.IdPkTs{ return &store.IdPkTs{
Id: pb.Id, Id: ntypes.EventIDFromBytes(pb.Id),
Pub: pb.Pubkey, Pub: ntypes.PubkeyFromBytes(pb.Pubkey),
Ts: pb.Timestamp, Ts: pb.Timestamp,
Ser: pb.Serial, Ser: pb.Serial,
} }

2
pkg/version/version

@ -1 +1 @@
v0.56.5 v0.56.6

Loading…
Cancel
Save