Files
next.orly.dev/pkg/database/fetch-event-by-serial.go
mleku 54ead81791
Some checks failed
Go / build-and-release (push) Has been cancelled
merge authors/nostruser in neo4j, add compact pubkey/e/p serial refs
2025-12-03 20:49:49 +00:00

141 lines
3.8 KiB
Go

//go:build !(js && wasm)
package database
import (
"bytes"
"fmt"
"github.com/dgraph-io/badger/v4"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/database/indexes"
"next.orly.dev/pkg/database/indexes/types"
"git.mleku.dev/mleku/nostr/encoders/event"
)
// FetchEventBySerial fetches a single event by its serial.
// This function tries multiple storage formats in order:
// 1. cmp (compact format with serial references) - newest, most space-efficient
// 2. sev (small event inline) - legacy Reiser4 optimization
// 3. evt (traditional separate storage) - legacy fallback
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) {
// Create resolver for compact event decoding
resolver := NewDatabaseSerialResolver(d, d.serialCache)
if err = d.View(
func(txn *badger.Txn) (err error) {
// Try cmp (compact format) first - most efficient
ev, err = d.fetchCompactEvent(txn, ser, resolver)
if err == nil && ev != nil {
return nil
}
err = nil // Reset error, try legacy formats
// Helper function to extract inline event data from key
extractInlineData := func(key []byte, prefixLen int) (*event.E, error) {
if len(key) > prefixLen+2 {
sizeIdx := prefixLen
size := int(key[sizeIdx])<<8 | int(key[sizeIdx+1])
dataStart := sizeIdx + 2
if len(key) >= dataStart+size {
eventData := key[dataStart : dataStart+size]
// Check if this is compact format
if len(eventData) > 0 && eventData[0] == CompactFormatVersion {
eventId, idErr := d.GetEventIdBySerial(ser)
if idErr == nil {
return UnmarshalCompactEvent(eventData, eventId, resolver)
}
}
// Legacy binary format
ev := new(event.E)
if err := ev.UnmarshalBinary(bytes.NewBuffer(eventData)); err != nil {
return nil, fmt.Errorf(
"error unmarshaling inline event (size=%d): %w",
size, err,
)
}
return ev, nil
}
}
return nil, nil
}
// Try sev (small event inline) prefix - Reiser4 optimization
smallBuf := new(bytes.Buffer)
if err = indexes.SmallEventEnc(ser).MarshalWrite(smallBuf); chk.E(err) {
return
}
opts := badger.DefaultIteratorOptions
opts.Prefix = smallBuf.Bytes()
opts.PrefetchValues = true
opts.PrefetchSize = 1
it := txn.NewIterator(opts)
defer it.Close()
it.Rewind()
if it.Valid() {
// Found in sev table - extract inline data
key := it.Item().Key()
// Key format: sev|serial|size_uint16|event_data
if ev, err = extractInlineData(key, 8); err != nil {
return err
}
if ev != nil {
return nil
}
}
// Not found in sev table, try evt (traditional) prefix
buf := new(bytes.Buffer)
if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) {
return
}
var item *badger.Item
if item, err = txn.Get(buf.Bytes()); err != nil {
return
}
var v []byte
if v, err = item.ValueCopy(nil); chk.E(err) {
return
}
// Check if this is compact format
if len(v) > 0 && v[0] == CompactFormatVersion {
eventId, idErr := d.GetEventIdBySerial(ser)
if idErr == nil {
ev, err = UnmarshalCompactEvent(v, eventId, resolver)
return
}
}
// Check if we have valid data before attempting to unmarshal
if len(v) < 32+32+1+2+1+1+64 { // ID + Pubkey + min varint fields + Sig
err = fmt.Errorf(
"incomplete event data: got %d bytes, expected at least %d",
len(v), 32+32+1+2+1+1+64,
)
return
}
ev = new(event.E)
if err = ev.UnmarshalBinary(bytes.NewBuffer(v)); err != nil {
// Add more context to EOF errors for debugging
if err.Error() == "EOF" {
err = fmt.Errorf(
"EOF while unmarshaling event (serial=%v, data_len=%d): %w",
ser, len(v), err,
)
}
return
}
return
},
); err != nil {
return
}
return
}