Files
zooid/sqlite/query.go
T
2025-09-24 11:10:06 -07:00

172 lines
4.1 KiB
Go

package sqlite
import (
"encoding/hex"
"encoding/json"
"fmt"
"iter"
"strings"
"fiatjaf.com/nostr"
)
func (s *SqliteBackend) QueryEvents(filter nostr.Filter, maxLimit int) iter.Seq[nostr.Event] {
return func(yield func(nostr.Event) bool) {
s.RLock()
defer s.RUnlock()
if filter.LimitZero {
return
}
limit := maxLimit
if filter.Limit > 0 && filter.Limit < limit {
limit = filter.Limit
}
query, args := s.buildSelectQuery(filter, limit)
rows, err := s.db.Query(query, args...)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var evt nostr.Event
var idStr, pubkeyStr, sigStr, tagsStr string
var createdAt int64
var kind int
err := rows.Scan(&idStr, &createdAt, &kind, &pubkeyStr, &evt.Content, &tagsStr, &sigStr)
if err != nil {
continue
}
// Parse ID
if id, err := nostr.IDFromHex(idStr); err == nil {
evt.ID = id
} else {
continue
}
// Parse PubKey
if pubkey, err := nostr.PubKeyFromHex(pubkeyStr); err == nil {
evt.PubKey = pubkey
} else {
continue
}
// Parse Signature
if sigBytes, err := hex.DecodeString(sigStr); err == nil && len(sigBytes) == 64 {
copy(evt.Sig[:], sigBytes)
} else {
continue
}
// Set other fields
evt.CreatedAt = nostr.Timestamp(createdAt)
evt.Kind = nostr.Kind(kind)
// Parse Tags
if err := json.Unmarshal([]byte(tagsStr), &evt.Tags); err != nil {
continue
}
if !yield(evt) {
return
}
}
}
}
func (s *SqliteBackend) buildSelectQuery(filter nostr.Filter, limit int) (string, []interface{}) {
var conditions []string
var args []interface{}
var joins []string
baseQuery := "SELECT id, created_at, kind, pubkey, content, tags, sig FROM events"
// Handle search with FTS (if available)
if filter.Search != "" && s.FTSAvailable {
joins = append(joins, "JOIN events_fts ON events.rowid = events_fts.rowid")
conditions = append(conditions, "events_fts MATCH ?")
args = append(args, filter.Search)
} else if filter.Search != "" {
// Fallback to LIKE search if FTS not available
conditions = append(conditions, "content LIKE ?")
args = append(args, "%"+filter.Search+"%")
}
// Add WHERE clause conditions
if len(filter.IDs) > 0 {
placeholders := make([]string, len(filter.IDs))
for i, id := range filter.IDs {
placeholders[i] = "?"
args = append(args, id.Hex())
}
conditions = append(conditions, fmt.Sprintf("id IN (%s)", strings.Join(placeholders, ",")))
}
if len(filter.Authors) > 0 {
placeholders := make([]string, len(filter.Authors))
for i, author := range filter.Authors {
placeholders[i] = "?"
args = append(args, author.Hex())
}
conditions = append(conditions, fmt.Sprintf("pubkey IN (%s)", strings.Join(placeholders, ",")))
}
if len(filter.Kinds) > 0 {
placeholders := make([]string, len(filter.Kinds))
for i, kind := range filter.Kinds {
placeholders[i] = "?"
args = append(args, int(kind))
}
conditions = append(conditions, fmt.Sprintf("kind IN (%s)", strings.Join(placeholders, ",")))
}
if filter.Since != 0 {
conditions = append(conditions, "created_at >= ?")
args = append(args, filter.Since)
}
if filter.Until != 0 {
conditions = append(conditions, "created_at <= ?")
args = append(args, filter.Until)
}
// Handle tags - only filter single-letter tags using event_tags table
for tagKey, tagValues := range filter.Tags {
if len(tagValues) > 0 && len(tagKey) == 1 {
placeholders := make([]string, len(tagValues))
for i, tagValue := range tagValues {
placeholders[i] = "?"
args = append(args, tagValue)
}
conditions = append(conditions, fmt.Sprintf("id IN (SELECT event_id FROM event_tags WHERE key = ? AND value IN (%s))", strings.Join(placeholders, ",")))
args = append(args, tagKey)
}
}
// Build the complete query
if len(joins) > 0 {
baseQuery += " " + strings.Join(joins, " ")
}
if len(conditions) > 0 {
baseQuery += " WHERE " + strings.Join(conditions, " AND ")
}
// Order by created_at DESC for most recent first
baseQuery += " ORDER BY created_at DESC"
// Add limit
if limit > 0 {
baseQuery += " LIMIT ?"
args = append(args, limit)
}
return baseQuery, args
}