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