Migrate package imports from next.orly.dev to new orly domain structure; add new varint and binary encoders with comprehensive tests; enhance existing tag and envelope implementations with additional methods, validations, and test coverage; introduce shared test.sh script for streamlined testing across modules.
This commit is contained in:
369
pkg/database/query-events.go
Normal file
369
pkg/database/query-events.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"crypto.orly/sha256"
|
||||
"database.orly/indexes/types"
|
||||
"encoders.orly/event"
|
||||
"encoders.orly/filter"
|
||||
"encoders.orly/hex"
|
||||
"encoders.orly/ints"
|
||||
"encoders.orly/kind"
|
||||
"encoders.orly/tag"
|
||||
"interfaces.orly/store"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"utils.orly"
|
||||
)
|
||||
|
||||
func CheckExpiration(ev *event.E) (expired bool) {
|
||||
var err error
|
||||
expTag := ev.Tags.GetFirst([]byte("expiration"))
|
||||
if expTag != nil {
|
||||
expTS := ints.New(0)
|
||||
if _, err = expTS.Unmarshal(expTag.Value()); !chk.E(err) {
|
||||
if int64(expTS.N) < time.Now().Unix() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *D) QueryEvents(c context.Context, f *filter.F) (
|
||||
evs event.S, err error,
|
||||
) {
|
||||
// if there is Ids in the query, this overrides anything else
|
||||
var expDeletes types.Uint40s
|
||||
var expEvs event.S
|
||||
if f.Ids != nil && f.Ids.Len() > 0 {
|
||||
for _, idx := range f.Ids.ToSliceOfBytes() {
|
||||
// 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); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
// fetch the events
|
||||
var ev *event.E
|
||||
if ev, err = d.FetchEventBySerial(ser); err != nil {
|
||||
continue
|
||||
}
|
||||
// check for an expiration tag and delete after returning the result
|
||||
if CheckExpiration(ev) {
|
||||
expDeletes = append(expDeletes, ser)
|
||||
expEvs = append(expEvs, ev)
|
||||
continue
|
||||
}
|
||||
evs = append(evs, ev)
|
||||
}
|
||||
// sort the events by timestamp
|
||||
sort.Slice(
|
||||
evs, func(i, j int) bool {
|
||||
return evs[i].CreatedAt > evs[j].CreatedAt
|
||||
},
|
||||
)
|
||||
} 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)
|
||||
// Map to track specific event IDs that have been deleted
|
||||
deletedEventIds := make(map[string]bool)
|
||||
// Query for deletion events separately if we have authors in the filter
|
||||
if f.Authors != nil && f.Authors.Len() > 0 {
|
||||
// Create a filter for deletion events with the same authors
|
||||
deletionFilter := &filter.F{
|
||||
Kinds: kind.NewS(kind.New(5)), // Kind 5 is deletion
|
||||
Authors: f.Authors,
|
||||
}
|
||||
|
||||
var deletionIdPkTs []*store.IdPkTs
|
||||
if deletionIdPkTs, err = d.QueryForIds(
|
||||
c, deletionFilter,
|
||||
); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add deletion events to the list of events to process
|
||||
idPkTs = append(idPkTs, deletionIdPkTs...)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// check for an expiration tag and delete after returning the result
|
||||
if CheckExpiration(ev) {
|
||||
expDeletes = append(expDeletes, ser)
|
||||
expEvs = append(expEvs, ev)
|
||||
continue
|
||||
}
|
||||
// Process deletion events to build our deletion maps
|
||||
if ev.Kind == kind.Deletion.K {
|
||||
// Check for 'e' tags that directly reference event IDs
|
||||
eTags := ev.Tags.GetAll([]byte("e"))
|
||||
for _, eTag := range eTags {
|
||||
if eTag.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
// We don't need to do anything with direct event ID
|
||||
// references as we will filter those out in the second pass
|
||||
}
|
||||
// Check for 'a' tags that reference parameterized replaceable
|
||||
// events
|
||||
aTags := ev.Tags.GetAll([]byte("a"))
|
||||
for _, aTag := range aTags {
|
||||
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
|
||||
kindStr := string(split[0])
|
||||
kindInt, err := strconv.Atoi(kindStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
kk := kind.New(uint16(kindInt))
|
||||
// Only process parameterized replaceable events
|
||||
if !kind.IsParameterizedReplaceable(kk.K) {
|
||||
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 !utils.FastEqual(pk, ev.Pubkey) {
|
||||
continue
|
||||
}
|
||||
// Create the key for the deletion map using hex
|
||||
// representation of pubkey
|
||||
key := hex.Enc(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
|
||||
// Debug logging
|
||||
}
|
||||
// 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 {
|
||||
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.NewFromBytesSlice(evId)},
|
||||
)
|
||||
if err != nil || len(targetEvs) == 0 {
|
||||
continue
|
||||
}
|
||||
targetEv := targetEvs[0]
|
||||
// Only allow users to delete their own events
|
||||
if !utils.FastEqual(targetEv.Pubkey, ev.Pubkey) {
|
||||
continue
|
||||
}
|
||||
// Mark the specific event ID as deleted
|
||||
deletedEventIds[hex.Enc(targetEv.ID)] = true
|
||||
// If the event is replaceable, mark it as deleted, but only
|
||||
// for events older than this one
|
||||
if kind.IsReplaceable(targetEv.Kind) {
|
||||
key := hex.Enc(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind))
|
||||
// We will still use deletionsByKindPubkey, but we'll
|
||||
// check timestamps in the second pass
|
||||
deletionsByKindPubkey[key] = true
|
||||
} else if kind.IsParameterizedReplaceable(targetEv.Kind) {
|
||||
// For parameterized replaceable events, we need to
|
||||
// consider the 'd' tag
|
||||
key := hex.Enc(targetEv.Pubkey) + ":" + strconv.Itoa(int(targetEv.Kind))
|
||||
|
||||
// Get the 'd' tag value
|
||||
dTag := targetEv.Tags.GetFirst([]byte("d"))
|
||||
var dValue string
|
||||
if dTag != nil && dTag.Len() > 1 {
|
||||
dValue = string(dTag.Value())
|
||||
} else {
|
||||
// If no 'd' tag, use empty string
|
||||
dValue = ""
|
||||
}
|
||||
// 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
|
||||
deletionsByKindPubkeyDTag[key][dValue] = 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 == kind.Deletion.K {
|
||||
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 utils.FastEqual(ev.ID, (*f.Ids).T[i]) {
|
||||
isIdInFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if this specific event has been deleted
|
||||
eventIdHex := hex.Enc(ev.ID)
|
||||
if deletedEventIds[eventIdHex] && !isIdInFilter {
|
||||
// Skip this event if it has been specifically deleted and is
|
||||
// not in the filter
|
||||
continue
|
||||
}
|
||||
if kind.IsReplaceable(ev.Kind) {
|
||||
// For replaceable events, we only keep the latest version for
|
||||
// each pubkey and kind, and only if it hasn't been deleted
|
||||
key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
|
||||
// For replaceable events, we need to be more careful with
|
||||
// deletion Only skip this event if it has been deleted by
|
||||
// kind/pubkey and is not in the filter AND there isn't a newer
|
||||
// event with the same kind/pubkey
|
||||
if deletionsByKindPubkey[key] && !isIdInFilter {
|
||||
// Check if there's a newer event with the same kind/pubkey
|
||||
// that hasn't been specifically deleted
|
||||
existing, exists := replaceableEvents[key]
|
||||
if !exists || ev.CreatedAt > existing.CreatedAt {
|
||||
// This is the newest event so far, keep it
|
||||
replaceableEvents[key] = ev
|
||||
} else {
|
||||
// There's a newer event, skip this one
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Normal replaceable event handling
|
||||
existing, exists := replaceableEvents[key]
|
||||
if !exists || ev.CreatedAt > existing.CreatedAt {
|
||||
replaceableEvents[key] = ev
|
||||
}
|
||||
}
|
||||
} else if kind.IsParameterizedReplaceable(ev.Kind) {
|
||||
// For parameterized replaceable events, we need to consider the
|
||||
// 'd' tag
|
||||
key := hex.Enc(ev.Pubkey) + ":" + strconv.Itoa(int(ev.Kind))
|
||||
|
||||
// Get the 'd' tag value
|
||||
dTag := ev.Tags.GetFirst([]byte("d"))
|
||||
var dValue string
|
||||
if dTag != nil && dTag.Len() > 1 {
|
||||
dValue = string(dTag.Value())
|
||||
} else {
|
||||
// If no 'd' tag, use empty string
|
||||
dValue = ""
|
||||
}
|
||||
|
||||
// Check if this event has been deleted via an a-tag
|
||||
if deletionMap, exists := deletionsByKindPubkeyDTag[key]; exists {
|
||||
// If the d-tag value is in the deletion map and this event
|
||||
// is not specifically requested by ID, skip it
|
||||
if deletionMap[dValue] && !isIdInFilter {
|
||||
log.T.F("Debug: Event deleted - skipping")
|
||||
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]
|
||||
// Only keep the newer event, regardless of processing order
|
||||
if !exists {
|
||||
// No existing event, add this one
|
||||
paramReplaceableEvents[key][dValue] = ev
|
||||
} else if ev.CreatedAt > existing.CreatedAt {
|
||||
// This event is newer than the existing one, replace it
|
||||
paramReplaceableEvents[key][dValue] = ev
|
||||
}
|
||||
// If this event is older than the existing one, ignore it
|
||||
} 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 > evs[j].CreatedAt
|
||||
},
|
||||
)
|
||||
// delete the expired events in a background thread
|
||||
go func() {
|
||||
for i, ser := range expDeletes {
|
||||
if err = d.DeleteEventBySerial(c, ser, expEvs[i]); chk.E(err) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user