get-indexes-for-event.go raw

   1  package database
   2  
   3  import (
   4  	"bytes"
   5  
   6  	"next.orly.dev/pkg/lol/chk"
   7  	"next.orly.dev/pkg/database/bufpool"
   8  	"next.orly.dev/pkg/database/indexes"
   9  	. "next.orly.dev/pkg/database/indexes/types"
  10  	"next.orly.dev/pkg/nostr/encoders/event"
  11  )
  12  
  13  // appendIndexBytes marshals an index to a byte slice and appends it to the idxs slice.
  14  // It reuses the provided buffer (resetting it first) to avoid allocations.
  15  func appendIndexBytes(idxs *[][]byte, idx *indexes.T, buf *bytes.Buffer) (err error) {
  16  	buf.Reset()
  17  	// Marshal the index to the buffer
  18  	if err = idx.MarshalWrite(buf); chk.E(err) {
  19  		return
  20  	}
  21  	// Copy the buffer's bytes to a new byte slice and append
  22  	*idxs = append(*idxs, bufpool.CopyBytes(buf))
  23  	return
  24  }
  25  
  26  // GetIndexesForEvent creates all the indexes for an event.E instance as defined
  27  // in keys.go. It returns a slice of byte slices that can be used to store the
  28  // event in the database.
  29  func GetIndexesForEvent(ev *event.E, serial uint64) (
  30  	idxs [][]byte, err error,
  31  ) {
  32  	// Get a reusable buffer for all index serializations
  33  	buf := bufpool.GetSmall()
  34  	defer bufpool.PutSmall(buf)
  35  
  36  	defer func() {
  37  		if chk.E(err) {
  38  			idxs = nil
  39  		}
  40  	}()
  41  	// Convert serial to Uint40
  42  	ser := new(Uint40)
  43  	if err = ser.Set(serial); chk.E(err) {
  44  		return
  45  	}
  46  	// ID index
  47  	idHash := new(IdHash)
  48  	if err = idHash.FromId(ev.ID); chk.E(err) {
  49  		return
  50  	}
  51  	idIndex := indexes.IdEnc(idHash, ser)
  52  	if err = appendIndexBytes(&idxs, idIndex, buf); chk.E(err) {
  53  		return
  54  	}
  55  	// FullIdPubkey index
  56  	fullID := new(Id)
  57  	if err = fullID.FromId(ev.ID); chk.E(err) {
  58  		return
  59  	}
  60  	pubHash := new(PubHash)
  61  	if err = pubHash.FromPubkey(ev.Pubkey); chk.E(err) {
  62  		return
  63  	}
  64  	createdAt := new(Uint64)
  65  	createdAt.Set(uint64(ev.CreatedAt))
  66  	idPubkeyIndex := indexes.FullIdPubkeyEnc(
  67  		ser, fullID, pubHash, createdAt,
  68  	)
  69  	if err = appendIndexBytes(&idxs, idPubkeyIndex, buf); chk.E(err) {
  70  		return
  71  	}
  72  	// CreatedAt index
  73  	createdAtIndex := indexes.CreatedAtEnc(createdAt, ser)
  74  	if err = appendIndexBytes(&idxs, createdAtIndex, buf); chk.E(err) {
  75  		return
  76  	}
  77  	// PubkeyCreatedAt index
  78  	pubkeyIndex := indexes.PubkeyEnc(pubHash, createdAt, ser)
  79  	if err = appendIndexBytes(&idxs, pubkeyIndex, buf); chk.E(err) {
  80  		return
  81  	}
  82  	// Process tags for tag-related indexes
  83  	if ev.Tags != nil && ev.Tags.Len() > 0 {
  84  		for _, t := range *ev.Tags {
  85  			// only index tags with a value field and the key is a single character
  86  			if t.Len() >= 2 {
  87  				// Get the key and value from the tag
  88  				keyBytes := t.Key()
  89  				// require single-letter key
  90  				if len(keyBytes) != 1 {
  91  					continue
  92  				}
  93  				// if the key is not a-zA-Z skip
  94  				if (keyBytes[0] < 'a' || keyBytes[0] > 'z') &&
  95  					(keyBytes[0] < 'A' || keyBytes[0] > 'Z') {
  96  					continue
  97  				}
  98  				valueBytes := t.Value()
  99  				// Create tag key and value
 100  				key := new(Letter)
 101  				key.Set(keyBytes[0])
 102  				valueHash := new(Ident)
 103  				valueHash.FromIdent(valueBytes)
 104  				// TagPubkey index
 105  				pubkeyTagIndex := indexes.TagPubkeyEnc(
 106  					key, valueHash, pubHash, createdAt, ser,
 107  				)
 108  				if err = appendIndexBytes(
 109  					&idxs, pubkeyTagIndex, buf,
 110  				); chk.E(err) {
 111  					return
 112  				}
 113  				// Tag index
 114  				tagIndex := indexes.TagEnc(
 115  					key, valueHash, createdAt, ser,
 116  				)
 117  				if err = appendIndexBytes(
 118  					&idxs, tagIndex, buf,
 119  				); chk.E(err) {
 120  					return
 121  				}
 122  				// Kind-related tag indexes
 123  				kind := new(Uint16)
 124  				kind.Set(ev.Kind)
 125  				// TagKind index
 126  				kindTagIndex := indexes.TagKindEnc(
 127  					key, valueHash, kind, createdAt, ser,
 128  				)
 129  				if err = appendIndexBytes(
 130  					&idxs, kindTagIndex, buf,
 131  				); chk.E(err) {
 132  					return
 133  				}
 134  				// TagKindPubkey index
 135  				kindPubkeyTagIndex := indexes.TagKindPubkeyEnc(
 136  					key, valueHash, kind, pubHash, createdAt, ser,
 137  				)
 138  				if err = appendIndexBytes(
 139  					&idxs, kindPubkeyTagIndex, buf,
 140  				); chk.E(err) {
 141  					return
 142  				}
 143  			}
 144  		}
 145  	}
 146  	kindType := new(Uint16)
 147  	kindType.Set(uint16(ev.Kind))
 148  	// Kind index
 149  	kindIndex := indexes.KindEnc(kindType, createdAt, ser)
 150  	if err = appendIndexBytes(&idxs, kindIndex, buf); chk.E(err) {
 151  		return
 152  	}
 153  	// KindPubkey index
 154  	// Using the correct parameters based on the function signature
 155  	kindPubkeyIndex := indexes.KindPubkeyEnc(
 156  		kindType, pubHash, createdAt, ser,
 157  	)
 158  	if err = appendIndexBytes(&idxs, kindPubkeyIndex, buf); chk.E(err) {
 159  		return
 160  	}
 161  
 162  	// NOTE: AddressableEvent index for parameterized replaceable events (kinds 30000-39999)
 163  	// is written separately in save-event.go with the serial as value for O(1) direct lookup.
 164  	// This enables fast NIP-33 queries by pubkey + kind + d-tag.
 165  
 166  	// Word token indexes (from content)
 167  	if len(ev.Content) > 0 {
 168  		for _, h := range TokenHashes(ev.Content) {
 169  			w := new(Word)
 170  			w.FromWord(h) // 8-byte truncated hash
 171  			wIdx := indexes.WordEnc(w, ser)
 172  			if err = appendIndexBytes(&idxs, wIdx, buf); chk.E(err) {
 173  				return
 174  			}
 175  		}
 176  	}
 177  	// Extend full-text search to include all fields of all tags
 178  	if ev.Tags != nil && ev.Tags.Len() > 0 {
 179  		for _, t := range *ev.Tags {
 180  			for _, field := range t.T { // include key and all values
 181  				if len(field) == 0 {
 182  					continue
 183  				}
 184  				for _, h := range TokenHashes(field) {
 185  					w := new(Word)
 186  					w.FromWord(h)
 187  					wIdx := indexes.WordEnc(w, ser)
 188  					if err = appendIndexBytes(&idxs, wIdx, buf); chk.E(err) {
 189  						return
 190  					}
 191  				}
 192  			}
 193  		}
 194  	}
 195  	return
 196  }
 197