Files
nostrlib/eventstore/boltdb/query_planner.go
2025-08-05 16:25:55 -03:00

145 lines
4.1 KiB
Go

package boltdb
import (
"encoding/binary"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore/internal"
)
type query struct {
i int
bucket []byte
prefix []byte
startingPoint []byte
}
func (b *BoltBackend) prepareQueries(filter nostr.Filter) (
queries []query,
extraAuthors []nostr.PubKey,
extraKinds []nostr.Kind,
extraTagKey string,
extraTagValues []string,
since uint32,
err error,
) {
// we will apply this to every query we return
defer func() {
if queries == nil {
return
}
var until uint32 = 4294967295
if filter.Until != 0 {
if fu := uint32(filter.Until); fu < until {
until = fu + 1
}
}
for i, q := range queries {
sp := make([]byte, len(q.prefix))
sp = sp[0:len(q.prefix)]
copy(sp, q.prefix)
queries[i].startingPoint = binary.BigEndian.AppendUint32(sp, uint32(until))
}
}()
// this is where we'll end the iteration
if filter.Since != 0 {
if fs := uint32(filter.Since); fs > since {
since = fs
}
}
if len(filter.Tags) > 0 {
// we will select ONE tag to query for and ONE extra tag to do further narrowing, if available
tagKey, tagValues, goodness := internal.ChooseNarrowestTag(filter)
// we won't use a tag index for this as long as we have something else to match with
if goodness < 2 && (len(filter.Authors) > 0 || len(filter.Kinds) > 0) {
goto pubkeyMatching
}
// otherwise we will use a plain tag index
queries = make([]query, len(tagValues))
for i, value := range tagValues {
// get key prefix (with full length) and offset where to write the created_at
dbi, k := b.getTagIndexPrefix(tagKey, value)
// remove the last parts part to get just the prefix we want here
prefix := k[0 : len(k)-8-4]
queries[i] = query{i: i, bucket: dbi, prefix: prefix}
}
// add an extra kind filter if available (only do this on plain tag index, not on ptag-kind index)
if filter.Kinds != nil {
extraKinds = make([]nostr.Kind, len(filter.Kinds))
for i, kind := range filter.Kinds {
extraKinds[i] = kind
}
}
// add an extra author search if possible
if filter.Authors != nil {
extraAuthors = make([]nostr.PubKey, len(filter.Authors))
for i, pk := range filter.Authors {
extraAuthors[i] = pk
}
}
// add an extra useless tag if available
filter.Tags = internal.CopyMapWithoutKey(filter.Tags, tagKey)
if len(filter.Tags) > 0 {
extraTagKey, extraTagValues, _ = internal.ChooseNarrowestTag(filter)
}
return queries, extraAuthors, extraKinds, extraTagKey, extraTagValues, since, nil
}
pubkeyMatching:
if len(filter.Authors) > 0 {
if len(filter.Kinds) == 0 {
// will use pubkey index
queries = make([]query, len(filter.Authors))
for i, pk := range filter.Authors {
queries[i] = query{i: i, bucket: indexPubkey, prefix: pk[0:8]}
}
} else {
// will use pubkeyKind index
queries = make([]query, len(filter.Authors)*len(filter.Kinds))
i := 0
for _, pk := range filter.Authors {
for _, kind := range filter.Kinds {
prefix := make([]byte, 8+2)
copy(prefix[0:8], pk[0:8])
binary.BigEndian.PutUint16(prefix[8:8+2], uint16(kind))
queries[i] = query{i: i, bucket: indexPubkeyKind, prefix: prefix[0 : 8+2]}
i++
}
}
}
// potentially with an extra useless tag filtering
extraTagKey, extraTagValues, _ = internal.ChooseNarrowestTag(filter)
return queries, nil, nil, extraTagKey, extraTagValues, since, nil
}
if len(filter.Kinds) > 0 {
// will use a kind index
queries = make([]query, len(filter.Kinds))
for i, kind := range filter.Kinds {
prefix := make([]byte, 2)
binary.BigEndian.PutUint16(prefix[0:2], uint16(kind))
queries[i] = query{i: i, bucket: indexKind, prefix: prefix[0:2]}
}
// potentially with an extra useless tag filtering
tagKey, tagValues, _ := internal.ChooseNarrowestTag(filter)
return queries, nil, nil, tagKey, tagValues, since, nil
}
// if we got here our query will have nothing to filter with
queries = make([]query, 1)
prefix := make([]byte, 0)
queries[0] = query{i: 0, bucket: indexCreatedAt, prefix: prefix}
return queries, nil, nil, "", nil, since, nil
}