Files
orly/database/query-events.go
mleku 96b852d6f1 Refactor and enhance event handling, database logic, and replaceable event processing
- **main.go**: Replace `ratel` storage backend with `database.New` for enhanced reliability and error handling.
- **database/query-events.go**: Rewrite event querying to handle replaceable and parameterized replaceable events; introduce deletion-based filtering, and improve sorting logic.
- **realy/addEvent.go**: Remove unnecessary logging for ephemeral events to optimize processing.
- **database/query-events_test.go**: Add comprehensive tests for replaceable events, parameterized replaceable events, and deletion handling to validate new logic.
2025-07-13 09:41:11 +01:00

285 lines
7.8 KiB
Go

package database
import (
"bytes"
"orly.dev/chk"
"orly.dev/context"
"orly.dev/database/indexes/types"
"orly.dev/event"
"orly.dev/filter"
"orly.dev/hex"
"orly.dev/interfaces/store"
"orly.dev/ints"
"orly.dev/kind"
"orly.dev/sha256"
"orly.dev/tag"
"sort"
"strconv"
)
// QueryEvents retrieves events based on the provided filter. If the filter
// contains Ids, it fetches events by those Ids directly, overriding other
// filter criteria. Otherwise, it queries by other filter criteria and fetches
// matching events. Results are returned in reverse chronological order of their
// creation timestamps.
func (d *D) QueryEvents(c context.T, f *filter.F) (evs event.S, err error) {
// if there is Ids in the query, this overrides anything else
if f.Ids != nil && f.Ids.Len() > 0 {
var idxs []Range
if idxs, err = GetIndexesFromFilter(f); chk.E(err) {
return
}
for _, idx := range idxs {
// we know there is only Ids in this, so run the ID query and fetch.
var ser *types.Uint40
if ser, err = d.GetSerialById(idx.Start); chk.E(err) {
continue
}
// fetch the events
var ev *event.E
if ev, err = d.FetchEventBySerial(ser); chk.E(err) {
continue
}
evs = append(evs, ev)
}
// sort the events by timestamp
sort.Slice(
evs, func(i, j int) bool {
return evs[i].CreatedAt.I64() > evs[j].CreatedAt.I64()
},
)
} else {
var idPkTs []store.IdPkTs
if idPkTs, err = d.QueryForIds(c, f); chk.E(err) {
return
}
// Create a map to store the latest version of replaceable events
replaceableEvents := make(map[string]*event.E)
// Create a map to store the latest version of parameterized replaceable
// events
paramReplaceableEvents := make(map[string]map[string]*event.E)
// Regular events that are not replaceable
var regularEvents event.S
// Map to track deletion events by kind and pubkey (for replaceable events)
deletionsByKindPubkey := make(map[string]bool)
// Map to track deletion events by kind, pubkey, and d-tag (for
// parameterized replaceable events)
deletionsByKindPubkeyDTag := make(map[string]map[string]bool)
// First pass: collect all deletion events
for _, idpk := range idPkTs {
var ev *event.E
ser := new(types.Uint40)
if err = ser.Set(idpk.Ser); chk.E(err) {
continue
}
if ev, err = d.FetchEventBySerial(ser); err != nil {
continue
}
// Process deletion events to build our deletion maps
if ev.Kind.Equal(kind.Deletion) {
// Check for 'e' tags that directly reference event IDs
eTags := ev.Tags.GetAll(tag.New([]byte{'e'}))
for _, eTag := range eTags.ToSliceOfTags() {
if eTag.Len() < 2 {
continue
}
// We don't need to do anything with direct event ID references
// as we'll filter those out in the second pass
}
// Check for 'a' tags that reference parameterized replaceable events
aTags := ev.Tags.GetAll(tag.New([]byte{'a'}))
for _, aTag := range aTags.ToSliceOfTags() {
if aTag.Len() < 2 {
continue
}
// Parse the 'a' tag value: kind:pubkey:d-tag
split := bytes.Split(aTag.Value(), []byte{':'})
if len(split) != 3 {
continue
}
// Parse the kind
kin := ints.New(uint16(0))
if _, err = kin.Unmarshal(split[0]); err != nil {
continue
}
kk := kind.New(kin.Uint16())
// Only process parameterized replaceable events
if !kk.IsParameterizedReplaceable() {
continue
}
// Parse the pubkey
var pk []byte
if pk, err = hex.DecAppend(nil, split[1]); err != nil {
continue
}
// Only allow users to delete their own events
if !bytes.Equal(pk, ev.Pubkey) {
continue
}
// Create the key for the deletion map
key := string(pk) + ":" + strconv.Itoa(int(kk.K))
// Initialize the inner map if it doesn't exist
if _, exists := deletionsByKindPubkeyDTag[key]; !exists {
deletionsByKindPubkeyDTag[key] = make(map[string]bool)
}
// Mark this d-tag as deleted
dValue := string(split[2])
deletionsByKindPubkeyDTag[key][dValue] = true
}
// For replaceable events, we need to check if there are any
// e-tags that reference events with the same kind and pubkey
for _, eTag := range eTags.ToSliceOfTags() {
if eTag.Len() < 2 {
continue
}
// Get the event ID from the e-tag
evId := make([]byte, sha256.Size)
if _, err = hex.DecBytes(evId, eTag.Value()); err != nil {
continue
}
// Query for the event
var targetEvs event.S
targetEvs, err = d.QueryEvents(
c, &filter.F{Ids: tag.New(evId)},
)
if err != nil || len(targetEvs) == 0 {
continue
}
targetEv := targetEvs[0]
// Only allow users to delete their own events
if !bytes.Equal(targetEv.Pubkey, ev.Pubkey) {
continue
}
// If the event is replaceable, mark it as deleted
if targetEv.Kind.IsReplaceable() {
key := string(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind.K))
deletionsByKindPubkey[key] = true
}
}
}
}
// Second pass: process all events, filtering out deleted ones
for _, idpk := range idPkTs {
var ev *event.E
ser := new(types.Uint40)
if err = ser.Set(idpk.Ser); chk.E(err) {
continue
}
if ev, err = d.FetchEventBySerial(ser); err != nil {
continue
}
// Skip events with kind 5 (Deletion)
if ev.Kind.Equal(kind.Deletion) {
continue
}
// Check if this event's ID is in the filter
isIdInFilter := false
if f.Ids != nil && f.Ids.Len() > 0 {
for i := 0; i < f.Ids.Len(); i++ {
if bytes.Equal(ev.Id, f.Ids.B(i)) {
isIdInFilter = true
break
}
}
}
if ev.Kind.IsReplaceable() {
// For replaceable events, we only keep the latest version for
// each pubkey and kind, and only if it hasn't been deleted
key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
// Skip this event if it has been deleted and its ID is not in
// the filter
if deletionsByKindPubkey[key] && !isIdInFilter {
continue
}
existing, exists := replaceableEvents[key]
if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() {
replaceableEvents[key] = ev
}
} else if ev.Kind.IsParameterizedReplaceable() {
// For parameterized replaceable events, we need to consider the
// 'd' tag
key := string(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind.K))
// Get the 'd' tag value
dTag := ev.Tags.GetFirst(tag.New([]byte{'d'}))
var dValue string
if dTag != nil && dTag.Len() > 1 {
dValue = string(dTag.Value())
} else {
// If no 'd' tag, use empty string
dValue = ""
}
if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
if deletionMap[dValue] && !isIdInFilter {
continue
}
}
// Initialize the inner map if it doesn't exist
if _, exists := paramReplaceableEvents[key]; !exists {
paramReplaceableEvents[key] = make(map[string]*event.E)
}
// Check if we already have an event with this 'd' tag value
existing, exists := paramReplaceableEvents[key][dValue]
if !exists || ev.CreatedAt.I64() > existing.CreatedAt.I64() {
paramReplaceableEvents[key][dValue] = ev
}
} else {
// Regular events
regularEvents = append(regularEvents, ev)
}
}
// Add all the latest replaceable events to the result
for _, ev := range replaceableEvents {
evs = append(evs, ev)
}
// Add all the latest parameterized replaceable events to the result
for _, innerMap := range paramReplaceableEvents {
for _, ev := range innerMap {
evs = append(evs, ev)
}
}
// Add all regular events to the result
evs = append(evs, regularEvents...)
// Sort all events by timestamp (newest first)
sort.Slice(
evs, func(i, j int) bool {
return evs[i].CreatedAt.I64() > evs[j].CreatedAt.I64()
},
)
}
return
}