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