get-serial-by-id.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "bytes"
7 "fmt"
8
9 "github.com/dgraph-io/badger/v4"
10 "next.orly.dev/pkg/lol/chk"
11 "next.orly.dev/pkg/lol/errorf"
12 "next.orly.dev/pkg/lol/log"
13 "next.orly.dev/pkg/database/indexes/types"
14 "next.orly.dev/pkg/nostr/encoders/event"
15 "next.orly.dev/pkg/nostr/encoders/filter"
16
17 // "next.orly.dev/pkg/nostr/encoders/hex"
18 "next.orly.dev/pkg/nostr/encoders/tag"
19 )
20
21 func (d *D) GetSerialById(id []byte) (ser *types.Uint40, err error) {
22 // log.T.F("GetSerialById: input id=%s", hex.Enc(id))
23 if len(id) == 0 {
24 // Return error without logging - caller should validate ID before calling
25 err = errorf.E("empty event ID")
26 return
27 }
28 var idxs []Range
29 if idxs, err = GetIndexesFromFilter(&filter.F{Ids: tag.NewFromBytesSlice(id)}); chk.E(err) {
30 return
31 }
32 // for i, idx := range idxs {
33 // log.T.F(
34 // "GetSerialById: searching range %d: start=%x, end=%x", i, idx.Start,
35 // idx.End,
36 // )
37 // }
38 if len(idxs) == 0 {
39 err = errorf.E("no indexes found for id %0x", id)
40 return
41 }
42 idFound := false
43 if err = d.View(
44 func(txn *badger.Txn) (err error) {
45 it := txn.NewIterator(badger.DefaultIteratorOptions)
46 var key []byte
47 defer it.Close()
48 it.Seek(idxs[0].Start)
49 if it.ValidForPrefix(idxs[0].Start) {
50 item := it.Item()
51 key = item.Key()
52 ser = new(types.Uint40)
53 buf := bytes.NewBuffer(key[len(key)-5:])
54 if err = ser.UnmarshalRead(buf); chk.E(err) {
55 return
56 }
57 idFound = true
58 } else {
59 // Item not found in database
60 // log.T.F(
61 // "GetSerialById: ID not found in database: %s", hex.Enc(id),
62 // )
63 }
64 return
65 },
66 ); chk.E(err) {
67 return
68 }
69 if !idFound {
70 err = fmt.Errorf("id not found in database")
71 return
72 }
73
74 return
75 }
76
77 // GetSerialsByIds takes a tag.T containing multiple IDs and returns a map of IDs to their
78 // corresponding serial numbers. It directly queries the IdPrefix index for matching IDs,
79 // which is more efficient than using GetIndexesFromFilter.
80 func (d *D) GetSerialsByIds(ids *tag.T) (
81 serials map[string]*types.Uint40, err error,
82 ) {
83 return d.GetSerialsByIdsWithFilter(ids, nil)
84 }
85
86 // GetSerialsByIdsWithFilter takes a tag.T containing multiple IDs and returns a
87 // map of IDs to their corresponding serial numbers, applying a filter function
88 // to each event. The function directly creates ID index prefixes for efficient querying.
89 func (d *D) GetSerialsByIdsWithFilter(
90 ids *tag.T, fn func(ev *event.E, ser *types.Uint40) bool,
91 ) (serials map[string]*types.Uint40, err error) {
92 // log.T.F("GetSerialsByIdsWithFilter: input ids count=%d", ids.Len())
93
94 // Initialize the result map with estimated capacity to reduce reallocations
95 serials = make(map[string]*types.Uint40, ids.Len())
96
97 // Return early if no IDs are provided
98 if ids.Len() == 0 {
99 return
100 }
101
102 // Process all IDs in a single transaction
103 if err = d.View(
104 func(txn *badger.Txn) (err error) {
105 it := txn.NewIterator(badger.DefaultIteratorOptions)
106 defer it.Close()
107
108 // Process each ID sequentially
109 for _, id := range ids.T {
110 // Skip empty IDs
111 if len(id) == 0 {
112 continue
113 }
114 // idHex := hex.Enc(id)
115
116 // Get the index prefix for this ID
117 var idxs []Range
118 if idxs, err = GetIndexesFromFilter(&filter.F{Ids: tag.NewFromBytesSlice(id)}); chk.E(err) {
119 // Skip this ID if we can't create its index
120 continue
121 }
122
123 // Skip if no index was created
124 if len(idxs) == 0 {
125 continue
126 }
127
128 // Seek to the start of this ID's range in the database
129 it.Seek(idxs[0].Start)
130 if it.ValidForPrefix(idxs[0].Start) {
131 // Found an entry for this ID
132 item := it.Item()
133 key := item.Key()
134
135 // Extract the serial number from the key
136 ser := new(types.Uint40)
137 buf := bytes.NewBuffer(key[len(key)-5:])
138 if err = ser.UnmarshalRead(buf); chk.E(err) {
139 continue
140 }
141
142 // If a filter function is provided, fetch the event and apply the filter
143 if fn != nil {
144 var ev *event.E
145 if ev, err = d.FetchEventBySerial(ser); err != nil {
146 // Skip this event if we can't fetch it
147 continue
148 }
149
150 // Apply the filter
151 if !fn(ev, ser) {
152 // Skip this event if it doesn't pass the filter
153 continue
154 }
155 }
156
157 // Store the serial in the result map using the hex-encoded ID as the key
158 serials[string(id)] = ser
159 }
160 }
161 return
162 },
163 ); chk.E(err) {
164 return
165 }
166
167 log.T.F(
168 "GetSerialsByIdsWithFilter: found %d serials out of %d requested ids",
169 len(serials), ids.Len(),
170 )
171 return
172 }
173
174 // func (d *D) GetSerialBytesById(id []byte) (ser []byte, err error) {
175 // var idxs []Range
176 // if idxs, err = GetIndexesFromFilter(&filter.F{Ids: tag.New(id)}); chk.E(err) {
177 // return
178 // }
179 // if len(idxs) == 0 {
180 // err = errorf.E("no indexes found for id %0x", id)
181 // }
182 // if err = d.View(
183 // func(txn *badger.Txn) (err error) {
184 // it := txn.NewIterator(badger.DefaultIteratorOptions)
185 // var key []byte
186 // defer it.Close()
187 // it.Seek(idxs[0].Start)
188 // if it.ValidForPrefix(idxs[0].Start) {
189 // item := it.Item()
190 // key = item.Key()
191 // ser = key[len(key)-5:]
192 // } else {
193 // // just don't return what we don't have? others may be
194 // // found tho.
195 // }
196 // return
197 // },
198 // ); chk.E(err) {
199 // return
200 // }
201 // return
202 // }
203