You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

287 lines
6.9 KiB

//go:build !(js && wasm)
package bbolt
import (
"bytes"
bolt "go.etcd.io/bbolt"
"next.orly.dev/pkg/database/indexes/types"
)
// EdgeExists checks if an edge exists between two serials.
// Uses bloom filter for fast negative lookups.
func (b *B) EdgeExists(srcSerial, dstSerial uint64, edgeType byte) (bool, error) {
// Fast path: check bloom filter first
if !b.edgeBloom.MayExist(srcSerial, dstSerial, edgeType) {
return false, nil // Definitely doesn't exist
}
// Bloom says maybe - need to verify in adjacency list
return b.verifyEdgeInAdjacencyList(srcSerial, dstSerial, edgeType)
}
// verifyEdgeInAdjacencyList checks the adjacency list for edge existence.
func (b *B) verifyEdgeInAdjacencyList(srcSerial, dstSerial uint64, edgeType byte) (bool, error) {
var exists bool
err := b.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketEv)
if bucket == nil {
return nil
}
key := makeSerialKey(srcSerial)
data := bucket.Get(key)
if data == nil {
return nil
}
vertex := &EventVertex{}
if err := vertex.Decode(data); err != nil {
return err
}
switch edgeType {
case EdgeTypeAuthor:
exists = vertex.AuthorSerial == dstSerial
case EdgeTypePTag:
for _, s := range vertex.PTagSerials {
if s == dstSerial {
exists = true
break
}
}
case EdgeTypeETag:
for _, s := range vertex.ETagSerials {
if s == dstSerial {
exists = true
break
}
}
}
return nil
})
return exists, err
}
// GetEventVertex retrieves the adjacency list for an event.
func (b *B) GetEventVertex(eventSerial uint64) (*EventVertex, error) {
var vertex *EventVertex
err := b.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketEv)
if bucket == nil {
return nil
}
key := makeSerialKey(eventSerial)
data := bucket.Get(key)
if data == nil {
return nil
}
vertex = &EventVertex{}
return vertex.Decode(data)
})
return vertex, err
}
// GetPubkeyVertex retrieves the adjacency list for a pubkey.
func (b *B) GetPubkeyVertex(pubkeySerial uint64) (*PubkeyVertex, error) {
var vertex *PubkeyVertex
err := b.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketPv)
if bucket == nil {
return nil
}
key := makeSerialKey(pubkeySerial)
data := bucket.Get(key)
if data == nil {
return nil
}
vertex = &PubkeyVertex{}
return vertex.Decode(data)
})
return vertex, err
}
// GetEventsAuthoredBy returns event serials authored by a pubkey.
func (b *B) GetEventsAuthoredBy(pubkeySerial uint64) ([]uint64, error) {
vertex, err := b.GetPubkeyVertex(pubkeySerial)
if err != nil || vertex == nil {
return nil, err
}
return vertex.AuthoredEvents, nil
}
// GetEventsMentioning returns event serials that mention a pubkey.
func (b *B) GetEventsMentioning(pubkeySerial uint64) ([]uint64, error) {
vertex, err := b.GetPubkeyVertex(pubkeySerial)
if err != nil || vertex == nil {
return nil, err
}
return vertex.MentionedIn, nil
}
// GetPTagsFromEvent returns pubkey serials tagged in an event.
func (b *B) GetPTagsFromEvent(eventSerial uint64) ([]uint64, error) {
vertex, err := b.GetEventVertex(eventSerial)
if err != nil || vertex == nil {
return nil, err
}
return vertex.PTagSerials, nil
}
// GetETagsFromEvent returns event serials referenced by an event.
func (b *B) GetETagsFromEvent(eventSerial uint64) ([]uint64, error) {
vertex, err := b.GetEventVertex(eventSerial)
if err != nil || vertex == nil {
return nil, err
}
return vertex.ETagSerials, nil
}
// GetFollowsFromPubkeySerial returns the pubkey serials that a user follows.
// This extracts p-tags from the user's kind-3 contact list event.
func (b *B) GetFollowsFromPubkeySerial(pubkeySerial *types.Uint40) ([]*types.Uint40, error) {
if pubkeySerial == nil {
return nil, nil
}
// Find the kind-3 event for this pubkey
contactEventSerial, err := b.FindEventByAuthorAndKind(pubkeySerial.Get(), 3)
if err != nil {
return nil, nil // No kind-3 event found is not an error
}
if contactEventSerial == 0 {
return nil, nil
}
// Get the p-tags from the event vertex
pTagSerials, err := b.GetPTagsFromEvent(contactEventSerial)
if err != nil {
return nil, err
}
// Convert to types.Uint40
result := make([]*types.Uint40, 0, len(pTagSerials))
for _, s := range pTagSerials {
ser := new(types.Uint40)
ser.Set(s)
result = append(result, ser)
}
return result, nil
}
// FindEventByAuthorAndKind finds an event serial by author and kind.
// For replaceable events like kind-3, returns the most recent one.
func (b *B) FindEventByAuthorAndKind(authorSerial uint64, kindNum uint16) (uint64, error) {
var resultSerial uint64
err := b.db.View(func(tx *bolt.Tx) error {
// First, get events authored by this pubkey
pvBucket := tx.Bucket(bucketPv)
if pvBucket == nil {
return nil
}
pvKey := makeSerialKey(authorSerial)
pvData := pvBucket.Get(pvKey)
if pvData == nil {
return nil
}
vertex := &PubkeyVertex{}
if err := vertex.Decode(pvData); err != nil {
return err
}
// Search through authored events for matching kind
evBucket := tx.Bucket(bucketEv)
if evBucket == nil {
return nil
}
var latestTs int64
for _, eventSerial := range vertex.AuthoredEvents {
evKey := makeSerialKey(eventSerial)
evData := evBucket.Get(evKey)
if evData == nil {
continue
}
evVertex := &EventVertex{}
if err := evVertex.Decode(evData); err != nil {
continue
}
if evVertex.Kind == kindNum {
// For replaceable events, we need to check timestamp
// Get event to compare timestamps
fpcBucket := tx.Bucket(bucketFpc)
if fpcBucket != nil {
// Scan for matching serial prefix in fpc bucket
c := fpcBucket.Cursor()
prefix := makeSerialKey(eventSerial)
for k, _ := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, _ = c.Next() {
// Key format: serial(5) | id(32) | pubkey_hash(8) | created_at(8)
if len(k) >= 53 {
ts := int64(decodeUint64(k[45:53]))
if ts > latestTs {
latestTs = ts
resultSerial = eventSerial
}
}
break
}
} else {
// If no fpc bucket, just take the first match
resultSerial = eventSerial
}
}
}
return nil
})
return resultSerial, err
}
// GetReferencingEvents returns event serials that reference a target event via e-tag.
func (b *B) GetReferencingEvents(targetSerial uint64) ([]uint64, error) {
var result []uint64
err := b.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(bucketEv)
if bucket == nil {
return nil
}
// Scan all event vertices looking for e-tag references
// Note: This is O(n) - for production, consider a reverse index
c := bucket.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
vertex := &EventVertex{}
if err := vertex.Decode(v); err != nil {
continue
}
for _, eTagSerial := range vertex.ETagSerials {
if eTagSerial == targetSerial {
eventSerial := decodeUint40(k)
result = append(result, eventSerial)
break
}
}
}
return nil
})
return result, err
}