mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-18 22:49:57 +02:00
sdk/hints: adapt sqlite to also support postgres.
This commit is contained in:
parent
9aac901c03
commit
3b3d5cce7b
@ -1,4 +1,4 @@
|
|||||||
package memory
|
package memoryh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package sqlite
|
package sqlh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
@ -11,15 +11,27 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SQLiteHints struct {
|
type SQLHints struct {
|
||||||
*sqlx.DB
|
*sqlx.DB
|
||||||
|
|
||||||
saves [7]*sqlx.Stmt
|
interop interop
|
||||||
topN *sqlx.Stmt
|
saves [7]*sqlx.Stmt
|
||||||
|
topN *sqlx.Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
|
// NewSQLHints takes an sqlx.DB connection (db) and a database type name (driverName ).
|
||||||
sh := SQLiteHints{DB: db}
|
// driverName must be either "postgres" or "sqlite3" -- this is so we can slightly change the queries.
|
||||||
|
func NewSQLHints(db *sql.DB, driverName string) (SQLHints, error) {
|
||||||
|
sh := SQLHints{DB: sqlx.NewDb(db, driverName)}
|
||||||
|
|
||||||
|
switch driverName {
|
||||||
|
case "sqlite3":
|
||||||
|
sh.interop = sqliteInterop
|
||||||
|
case "postgres":
|
||||||
|
sh.interop = postgresInterop
|
||||||
|
default:
|
||||||
|
return sh, fmt.Errorf("unknown database driver '%s'", driverName)
|
||||||
|
}
|
||||||
|
|
||||||
// create table and indexes
|
// create table and indexes
|
||||||
cols := strings.Builder{}
|
cols := strings.Builder{}
|
||||||
@ -37,17 +49,17 @@ func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
|
|||||||
|
|
||||||
_, err := sh.Exec(`CREATE TABLE IF NOT EXISTS nostr_sdk_pubkey_relays (pubkey text, relay text, ` + cols.String())
|
_, err := sh.Exec(`CREATE TABLE IF NOT EXISTS nostr_sdk_pubkey_relays (pubkey text, relay text, ` + cols.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SQLiteHints{}, err
|
return SQLHints{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = sh.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS pkr ON nostr_sdk_pubkey_relays (pubkey, relay)`)
|
_, err = sh.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS pkr ON nostr_sdk_pubkey_relays (pubkey, relay)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SQLiteHints{}, err
|
return SQLHints{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = sh.Exec(`CREATE INDEX IF NOT EXISTS bypk ON nostr_sdk_pubkey_relays (pubkey)`)
|
_, err = sh.Exec(`CREATE INDEX IF NOT EXISTS bypk ON nostr_sdk_pubkey_relays (pubkey)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return SQLiteHints{}, err
|
return SQLHints{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare statements
|
// prepare statements
|
||||||
@ -55,10 +67,14 @@ func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
|
|||||||
col := hints.HintKey(i).String()
|
col := hints.HintKey(i).String()
|
||||||
|
|
||||||
stmt, err := sh.Preparex(
|
stmt, err := sh.Preparex(
|
||||||
`INSERT INTO nostr_sdk_pubkey_relays (pubkey, relay, ` + col + `) VALUES (?, ?, ?)
|
`INSERT INTO nostr_sdk_pubkey_relays (pubkey, relay, ` + col + `) VALUES (` + sh.interop.generateBindingSpots(0, 3) + `)
|
||||||
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = max(?, coalesce(` + col + `, 0))`,
|
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = ` + sh.interop.maxFunc + `(` + sh.interop.generateBindingSpots(3, 1) + `, coalesce(excluded.` + col + `, 0))`,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(
|
||||||
|
`INSERT INTO nostr_sdk_pubkey_relays (pubkey, relay, ` + col + `) VALUES (` + sh.interop.generateBindingSpots(0, 3) + `)
|
||||||
|
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = ` + sh.interop.maxFunc + `(` + sh.interop.generateBindingSpots(3, 1) + `, coalesce(excluded.` + col + `, 0))`,
|
||||||
|
)
|
||||||
return sh, fmt.Errorf("failed to prepare statement for %s: %w", col, err)
|
return sh, fmt.Errorf("failed to prepare statement for %s: %w", col, err)
|
||||||
}
|
}
|
||||||
sh.saves[i] = stmt
|
sh.saves[i] = stmt
|
||||||
@ -66,7 +82,7 @@ func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
stmt, err := sh.Preparex(
|
stmt, err := sh.Preparex(
|
||||||
`SELECT relay FROM nostr_sdk_pubkey_relays WHERE pubkey = ? ORDER BY (` + scorePartialQuery() + `) DESC LIMIT ?`,
|
`SELECT relay FROM nostr_sdk_pubkey_relays WHERE pubkey = ` + sh.interop.generateBindingSpots(0, 1) + ` ORDER BY (` + sh.scorePartialQuery() + `) DESC LIMIT ` + sh.interop.generateBindingSpots(1, 1),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sh, fmt.Errorf("failed to prepare statement for querying: %w", err)
|
return sh, fmt.Errorf("failed to prepare statement for querying: %w", err)
|
||||||
@ -77,29 +93,29 @@ func NewSQLiteHints(db *sqlx.DB) (SQLiteHints, error) {
|
|||||||
return sh, nil
|
return sh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sh SQLiteHints) TopN(pubkey string, n int) []string {
|
func (sh SQLHints) TopN(pubkey string, n int) []string {
|
||||||
res := make([]string, 0, n)
|
res := make([]string, 0, n)
|
||||||
err := sh.topN.Select(&res, pubkey, n)
|
err := sh.topN.Select(&res, pubkey, n)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
nostr.InfoLogger.Printf("[sdk/hints/sqlite] unexpected error on query for %s: %s\n",
|
nostr.InfoLogger.Printf("[sdk/hints/sql] unexpected error on query for %s: %s\n",
|
||||||
pubkey, err)
|
pubkey, err)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sh SQLiteHints) Save(pubkey string, relay string, key hints.HintKey, ts nostr.Timestamp) {
|
func (sh SQLHints) Save(pubkey string, relay string, key hints.HintKey, ts nostr.Timestamp) {
|
||||||
if now := nostr.Now(); ts > now {
|
if now := nostr.Now(); ts > now {
|
||||||
ts = now
|
ts = now
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := sh.saves[key].Exec(pubkey, relay, ts, ts)
|
_, err := sh.saves[key].Exec(pubkey, relay, ts, ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
nostr.InfoLogger.Printf("[sdk/hints/sqlite] unexpected error on insert for %s, %s, %d: %s\n",
|
nostr.InfoLogger.Printf("[sdk/hints/sql] unexpected error on insert for %s, %s, %d: %s\n",
|
||||||
pubkey, relay, ts, err)
|
pubkey, relay, ts, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sh SQLiteHints) PrintScores() {
|
func (sh SQLHints) PrintScores() {
|
||||||
fmt.Println("= print scores")
|
fmt.Println("= print scores")
|
||||||
|
|
||||||
allpubkeys := make([]string, 0, 50)
|
allpubkeys := make([]string, 0, 50)
|
||||||
@ -115,8 +131,8 @@ func (sh SQLiteHints) PrintScores() {
|
|||||||
for _, pubkey := range allpubkeys {
|
for _, pubkey := range allpubkeys {
|
||||||
fmt.Println("== relay scores for", pubkey)
|
fmt.Println("== relay scores for", pubkey)
|
||||||
if err := sh.Select(&allrelays,
|
if err := sh.Select(&allrelays,
|
||||||
`SELECT pubkey, relay, coalesce(`+scorePartialQuery()+`, 0) AS score
|
`SELECT pubkey, relay, coalesce(`+sh.scorePartialQuery()+`, 0) AS score
|
||||||
FROM nostr_sdk_pubkey_relays WHERE pubkey = ? ORDER BY score DESC`, pubkey); err != nil {
|
FROM nostr_sdk_pubkey_relays WHERE pubkey = `+sh.interop.generateBindingSpots(0, 1)+` ORDER BY score DESC`, pubkey); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,9 +142,9 @@ func (sh SQLiteHints) PrintScores() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scorePartialQuery() string {
|
func (sh SQLHints) scorePartialQuery() string {
|
||||||
calc := strings.Builder{}
|
calc := strings.Builder{}
|
||||||
calc.Grow(len(hints.KeyBasePoints) * (10 + 25 + 51 + 25 + 24 + 4 + 12 + 3))
|
calc.Grow(len(hints.KeyBasePoints) * (11 + 25 + 32 + 4 + 4 + 9 + 12 + 25 + 12 + 25 + 19 + 3))
|
||||||
|
|
||||||
for i, points := range hints.KeyBasePoints {
|
for i, points := range hints.KeyBasePoints {
|
||||||
col := hints.HintKey(i).String()
|
col := hints.HintKey(i).String()
|
||||||
@ -138,7 +154,11 @@ func scorePartialQuery() string {
|
|||||||
calc.WriteString(col)
|
calc.WriteString(col)
|
||||||
calc.WriteString(` IS NOT NULL THEN 10000000000 * `)
|
calc.WriteString(` IS NOT NULL THEN 10000000000 * `)
|
||||||
calc.WriteString(multiplier)
|
calc.WriteString(multiplier)
|
||||||
calc.WriteString(` / power(max(1, (unixepoch() + 86400) - `)
|
calc.WriteString(` / power(`)
|
||||||
|
calc.WriteString(sh.interop.maxFunc)
|
||||||
|
calc.WriteString(`(1, (`)
|
||||||
|
calc.WriteString(sh.interop.getUnixEpochFunc)
|
||||||
|
calc.WriteString(` + 86400) - `)
|
||||||
calc.WriteString(col)
|
calc.WriteString(col)
|
||||||
calc.WriteString(`), 1.3) ELSE 0 END)`)
|
calc.WriteString(`), 1.3) ELSE 0 END)`)
|
||||||
|
|
48
sdk/hints/sqlh/interop.go
Normal file
48
sdk/hints/sqlh/interop.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package sqlh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type interop struct {
|
||||||
|
maxFunc string
|
||||||
|
getUnixEpochFunc string
|
||||||
|
generateBindingSpots func(start, n int) string
|
||||||
|
}
|
||||||
|
|
||||||
|
var sqliteInterop = interop{
|
||||||
|
maxFunc: "max",
|
||||||
|
getUnixEpochFunc: "unixepoch()",
|
||||||
|
generateBindingSpots: func(_, n int) string {
|
||||||
|
b := strings.Builder{}
|
||||||
|
b.Grow(n * 2)
|
||||||
|
for i := range n {
|
||||||
|
if i == n-1 {
|
||||||
|
b.WriteString("?")
|
||||||
|
} else {
|
||||||
|
b.WriteString("?,")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var postgresInterop = interop{
|
||||||
|
maxFunc: "greatest",
|
||||||
|
getUnixEpochFunc: "extract(epoch from now())",
|
||||||
|
generateBindingSpots: func(start, n int) string {
|
||||||
|
b := strings.Builder{}
|
||||||
|
b.Grow(n * 2)
|
||||||
|
end := start + n
|
||||||
|
for i := start; i < end; i++ {
|
||||||
|
v := i + 1
|
||||||
|
b.WriteRune('$')
|
||||||
|
b.WriteString(strconv.Itoa(v))
|
||||||
|
if i != end-1 {
|
||||||
|
b.WriteRune(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
},
|
||||||
|
}
|
@ -3,11 +3,11 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlh"
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlite"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
_ "github.com/tursodatabase/go-libsql"
|
_ "github.com/tursodatabase/go-libsql"
|
||||||
)
|
)
|
||||||
@ -16,12 +16,12 @@ func TestSQLiteHintsLibsql(t *testing.T) {
|
|||||||
path := "/tmp/tmpsdkhintssqlite"
|
path := "/tmp/tmpsdkhintssqlite"
|
||||||
os.RemoveAll(path)
|
os.RemoveAll(path)
|
||||||
|
|
||||||
db, err := sqlx.Connect("libsql", "file://"+path)
|
db, err := sql.Open("libsql", "file://"+path)
|
||||||
|
|
||||||
require.NoError(t, err, "failed to create sqlitehints db")
|
require.NoError(t, err, "failed to create sqlitehints db")
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
sh, err := sqlite.NewSQLiteHints(db)
|
sh, err := sqlh.NewSQLHints(db, "sqlite3")
|
||||||
require.NoError(t, err, "failed to setup sqlitehints db")
|
require.NoError(t, err, "failed to setup sqlitehints db")
|
||||||
|
|
||||||
runTestWith(t, sh)
|
runTestWith(t, sh)
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlite"
|
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlh"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,12 +16,12 @@ func TestSQLiteHintsMattn(t *testing.T) {
|
|||||||
path := "/tmp/tmpsdkhintssqlite"
|
path := "/tmp/tmpsdkhintssqlite"
|
||||||
os.RemoveAll(path)
|
os.RemoveAll(path)
|
||||||
|
|
||||||
db, err := sqlx.Connect("sqlite3", path)
|
db, err := sql.Open("sqlite3", path)
|
||||||
|
|
||||||
require.NoError(t, err, "failed to create sqlitehints db")
|
require.NoError(t, err, "failed to create sqlitehints db")
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
sh, err := sqlite.NewSQLiteHints(db)
|
sh, err := sqlh.NewSQLHints(db, "sqlite3")
|
||||||
require.NoError(t, err, "failed to setup sqlitehints db")
|
require.NoError(t, err, "failed to setup sqlitehints db")
|
||||||
|
|
||||||
runTestWith(t, sh)
|
runTestWith(t, sh)
|
||||||
|
@ -3,9 +3,9 @@ package test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints/memory"
|
"github.com/nbd-wtf/go-nostr/sdk/hints/memoryh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMemoryHints(t *testing.T) {
|
func TestMemoryHints(t *testing.T) {
|
||||||
runTestWith(t, memory.NewHintDB())
|
runTestWith(t, memoryh.NewHintDB())
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlh"
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlite"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
@ -14,12 +14,12 @@ func TestSQLiteHintsModernC(t *testing.T) {
|
|||||||
path := "/tmp/tmpsdkhintssqlite"
|
path := "/tmp/tmpsdkhintssqlite"
|
||||||
os.RemoveAll(path)
|
os.RemoveAll(path)
|
||||||
|
|
||||||
db, err := sqlx.Connect("sqlite", path)
|
db, err := sql.Open("sqlite", path)
|
||||||
|
|
||||||
require.NoError(t, err, "failed to create sqlitehints db")
|
require.NoError(t, err, "failed to create sqlitehints db")
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
sh, err := sqlite.NewSQLiteHints(db)
|
sh, err := sqlh.NewSQLHints(db, "sqlite3")
|
||||||
require.NoError(t, err, "failed to setup sqlitehints db")
|
require.NoError(t, err, "failed to setup sqlitehints db")
|
||||||
|
|
||||||
runTestWith(t, sh)
|
runTestWith(t, sh)
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlh"
|
||||||
"github.com/nbd-wtf/go-nostr/sdk/hints/sqlite"
|
|
||||||
_ "github.com/ncruces/go-sqlite3/driver"
|
_ "github.com/ncruces/go-sqlite3/driver"
|
||||||
_ "github.com/ncruces/go-sqlite3/embed"
|
_ "github.com/ncruces/go-sqlite3/embed"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -17,12 +17,12 @@ func TestSQLiteHintsNcruces(t *testing.T) {
|
|||||||
path := "/tmp/tmpsdkhintssqlite"
|
path := "/tmp/tmpsdkhintssqlite"
|
||||||
os.RemoveAll(path)
|
os.RemoveAll(path)
|
||||||
|
|
||||||
db, err := sqlx.Connect("sqlite3", path)
|
db, err := sql.Open("sqlite3", path)
|
||||||
|
|
||||||
require.NoError(t, err, "failed to create sqlitehints db")
|
require.NoError(t, err, "failed to create sqlitehints db")
|
||||||
db.SetMaxOpenConns(1)
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
sh, err := sqlite.NewSQLiteHints(db)
|
sh, err := sqlh.NewSQLHints(db, "sqlite3")
|
||||||
require.NoError(t, err, "failed to setup sqlitehints db")
|
require.NoError(t, err, "failed to setup sqlitehints db")
|
||||||
|
|
||||||
runTestWith(t, sh)
|
runTestWith(t, sh)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user