2024-09-16 22:23:55 -03:00

148 lines
3.7 KiB
Go

package sqlite
import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk/hints"
)
type SQLiteHints struct {
*sqlx.DB
saves [7]*sqlx.Stmt
topN *sqlx.Stmt
}
func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
sh := SQLiteHints{DB: db}
// create table and indexes
cols := strings.Builder{}
cols.Grow(len(hints.KeyBasePoints) * 20)
for i := range hints.KeyBasePoints {
name := hints.HintKey(i).String()
cols.WriteString(name)
cols.WriteString(" integer")
if i == len(hints.KeyBasePoints)-1 {
cols.WriteString(")")
} else {
cols.WriteString(",")
}
}
_, err := sh.Exec(`CREATE TABLE pubkey_relays (pubkey text, relay text, ` + cols.String())
if err != nil {
return SQLiteHints{}, err
}
_, err = sh.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS pkr ON pubkey_relays (pubkey, relay)`)
if err != nil {
return SQLiteHints{}, err
}
_, err = sh.Exec(`CREATE INDEX IF NOT EXISTS bypk ON pubkey_relays (pubkey)`)
if err != nil {
return SQLiteHints{}, err
}
// prepare statements
for i := range hints.KeyBasePoints {
col := hints.HintKey(i).String()
stmt, err := sh.Preparex(
`INSERT INTO pubkey_relays (pubkey, relay, ` + col + `) VALUES (?, ?, ?)
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = max(?, coalesce(` + col + `, 0))`,
)
if err != nil {
return sh, fmt.Errorf("failed to prepare statement for %s: %w", col, err)
}
sh.saves[i] = stmt
}
{
stmt, err := sh.Preparex(
`SELECT relay FROM pubkey_relays WHERE pubkey = ? ORDER BY (` + scorePartialQuery() + `) DESC LIMIT ?`,
)
if err != nil {
return sh, fmt.Errorf("failed to prepare statement for querying: %w", err)
}
sh.topN = stmt
}
return sh, nil
}
func (sh SQLiteHints) TopN(pubkey string, n int) []string {
res := make([]string, 0, n)
err := sh.topN.Select(&res, pubkey, n)
if err != nil && err != sql.ErrNoRows {
nostr.InfoLogger.Printf("[sdk/hints/sqlite] unexpected error on query for %s: %s\n",
pubkey, err)
}
return res
}
func (sh SQLiteHints) Save(pubkey string, relay string, key hints.HintKey, score nostr.Timestamp) {
_, err := sh.saves[key].Exec(pubkey, relay, score, score)
if err != nil {
nostr.InfoLogger.Printf("[sdk/hints/sqlite] unexpected error on insert for %s, %s, %d: %s\n",
pubkey, relay, score, err)
}
}
func (sh SQLiteHints) PrintScores() {
fmt.Println("= print scores")
allpubkeys := make([]string, 0, 50)
if err := sh.Select(&allpubkeys, `SELECT DISTINCT pubkey FROM pubkey_relays`); err != nil {
panic(err)
}
allrelays := make([]struct {
PubKey string `db:"pubkey"`
Relay string `db:"relay"`
Score float64 `db:"score"`
}, 0, 20)
for _, pubkey := range allpubkeys {
fmt.Println("== relay scores for", pubkey)
if err := sh.Select(&allrelays,
`SELECT pubkey, relay, coalesce(`+scorePartialQuery()+`, 0) AS score
FROM pubkey_relays WHERE pubkey = ? ORDER BY score DESC`, pubkey); err != nil {
panic(err)
}
for i, re := range allrelays {
fmt.Printf(" %3d :: %30s ::> %12d\n", i, re.Relay, int(re.Score))
}
}
}
func scorePartialQuery() string {
calc := strings.Builder{}
calc.Grow(len(hints.KeyBasePoints) * (10 + 25 + 51 + 25 + 24 + 4 + 12 + 3))
for i, points := range hints.KeyBasePoints {
col := hints.HintKey(i).String()
multiplier := strconv.FormatInt(points, 10)
calc.WriteString(`(CASE WHEN `)
calc.WriteString(col)
calc.WriteString(` IS NOT NULL THEN 10000000000 * `)
calc.WriteString(multiplier)
calc.WriteString(` / power(max(1, (unixepoch() + 86400) - `)
calc.WriteString(col)
calc.WriteString(`), 1.3) ELSE 0 END)`)
if i != len(hints.KeyBasePoints)-1 {
calc.WriteString(` + `)
}
}
return calc.String()
}