Add prefix to event store

This commit is contained in:
Jon Staab
2025-09-24 16:52:02 -07:00
parent 307dcda4a7
commit 3c3eefc378
9 changed files with 67 additions and 47 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
func (s *SqliteBackend) DeleteEvent(id nostr.ID) error {
_, err := squirrel.Delete("events").Where(squirrel.Eq{"id": id.Hex()}).RunWith(s.db).Exec()
_, err := squirrel.Delete(s.tmpl("{{.Prefix}}events")).Where(squirrel.Eq{"id": id.Hex()}).RunWith(s.db).Exec()
return err
}
+38 -22
View File
@@ -1,8 +1,10 @@
package sqlite
import (
"bytes"
"database/sql"
"fmt"
"text/template"
"fiatjaf.com/nostr/eventstore"
_ "github.com/mattn/go-sqlite3"
@@ -13,6 +15,7 @@ var _ eventstore.Store = (*SqliteBackend)(nil)
type SqliteBackend struct {
db *sql.DB
Path string
Prefix string
FTSAvailable bool
}
@@ -41,10 +44,20 @@ func (s *SqliteBackend) Init() error {
return nil
}
func (s *SqliteBackend) tmpl(t string) string {
var buf bytes.Buffer
err := template.Must(template.New("schema").Parse(t)).Execute(&buf, s)
if err != nil {
panic(err)
}
return buf.String()
}
func (s *SqliteBackend) createSchema() error {
// Create basic schema first
basicSchema := `
CREATE TABLE IF NOT EXISTS events (
basicSchema := s.tmpl(`
CREATE TABLE IF NOT EXISTS {{.Prefix}}events (
id TEXT PRIMARY KEY,
created_at INTEGER NOT NULL,
kind INTEGER NOT NULL,
@@ -54,23 +67,23 @@ func (s *SqliteBackend) createSchema() error {
sig TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);
CREATE INDEX IF NOT EXISTS idx_events_pubkey ON events(pubkey);
CREATE INDEX IF NOT EXISTS idx_events_kind_pubkey ON events(kind, pubkey);
CREATE INDEX IF NOT EXISTS idx_events_kind_pubkey_created_at ON events(kind, pubkey, created_at DESC);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_events_created_at ON {{.Prefix}}events(created_at);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_events_kind ON {{.Prefix}}events(kind);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_events_pubkey ON {{.Prefix}}events(pubkey);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_events_kind_pubkey ON {{.Prefix}}events(kind, pubkey);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_events_kind_pubkey_created_at ON {{.Prefix}}events(kind, pubkey, created_at DESC);
CREATE TABLE IF NOT EXISTS event_tags (
CREATE TABLE IF NOT EXISTS {{.Prefix}}event_tags (
event_id TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
FOREIGN KEY (event_id) REFERENCES {{.Prefix}}events(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_event_tags_event_id ON event_tags(event_id);
CREATE INDEX IF NOT EXISTS idx_event_tags_key ON event_tags(key);
CREATE INDEX IF NOT EXISTS idx_event_tags_key_value ON event_tags(key, value);
`
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_event_tags_event_id ON {{.Prefix}}event_tags(event_id);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_event_tags_key ON {{.Prefix}}event_tags(key);
CREATE INDEX IF NOT EXISTS {{.Prefix}}idx_event_tags_key_value ON {{.Prefix}}event_tags(key, value);
`)
if _, err := s.db.Exec(basicSchema); err != nil {
return fmt.Errorf("failed to create schema: %w", err)
@@ -78,23 +91,26 @@ func (s *SqliteBackend) createSchema() error {
// Try to create FTS5 schema - if it fails, continue without it
ftsSchema := `
CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
CREATE VIRTUAL TABLE IF NOT EXISTS {{.Prefix}}events_fts USING fts5(
content,
content='events',
content='{{.Prefix}}events',
content_rowid='rowid'
);
CREATE TRIGGER IF NOT EXISTS events_ai AFTER INSERT ON events BEGIN
INSERT INTO events_fts(rowid, content) VALUES (new.rowid, new.content);
CREATE TRIGGER IF NOT EXISTS {{.Prefix}}events_ai AFTER INSERT ON {{.Prefix}}events BEGIN
INSERT INTO {{.Prefix}}events_fts(rowid, content) VALUES (new.rowid, new.content);
END;
CREATE TRIGGER IF NOT EXISTS events_ad AFTER DELETE ON events BEGIN
INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', old.rowid, old.content);
CREATE TRIGGER IF NOT EXISTS {{.Prefix}}events_ad AFTER DELETE ON {{.Prefix}}events BEGIN
INSERT INTO {{.Prefix}}events_fts({{.Prefix}}events_fts, rowid, content)
VALUES('delete', old.rowid, old.content);
END;
CREATE TRIGGER IF NOT EXISTS events_au AFTER UPDATE ON events BEGIN
INSERT INTO events_fts(events_fts, rowid, content) VALUES('delete', old.rowid, old.content);
INSERT INTO events_fts(rowid, content) VALUES (new.rowid, new.content);
CREATE TRIGGER IF NOT EXISTS {{.Prefix}}events_au AFTER UPDATE ON {{.Prefix}}events BEGIN
INSERT INTO {{.Prefix}}events_fts({{.Prefix}}events_fts, rowid, content)
VALUES('delete', old.rowid, old.content);
INSERT INTO {{.Prefix}}events_fts(rowid, content)
VALUES (new.rowid, new.content);
END;
`
+3 -3
View File
@@ -76,12 +76,12 @@ func (s *SqliteBackend) QueryEvents(filter nostr.Filter, maxLimit int) iter.Seq[
func (s *SqliteBackend) buildSelectQuery(filter nostr.Filter, limit int) squirrel.SelectBuilder {
qb := squirrel.Select("id", "created_at", "kind", "pubkey", "content", "tags", "sig").
From("events").
From(s.tmpl("{{.Prefix}}events")).
OrderBy("created_at DESC")
// Handle search with FTS (if available)
if filter.Search != "" && s.FTSAvailable {
qb = qb.Join("events_fts ON events.rowid = events_fts.rowid").
qb = qb.Join(s.tmpl("{{.Prefix}}events_fts ON {{.Prefix}}events.rowid = {{.Prefix}}events_fts.rowid")).
Where(squirrel.Eq{"events_fts": filter.Search})
} else if filter.Search != "" {
// Fallback to LIKE search if FTS not available
@@ -128,7 +128,7 @@ func (s *SqliteBackend) buildSelectQuery(filter nostr.Filter, limit int) squirre
}
subQuery := squirrel.Select("event_id").
From("event_tags").
From(s.tmpl("{{.Prefix}}event_tags")).
Where(squirrel.Eq{"key": tagKey}).
Where(squirrel.Eq{"value": tagValueInterfaces})
+3 -3
View File
@@ -13,7 +13,7 @@ import (
func (s *SqliteBackend) SaveEvent(evt nostr.Event) error {
// Check if event already exists
var existingID string
qb := squirrel.Select("id").From("events").Where(squirrel.Eq{"id": evt.ID.Hex()})
qb := squirrel.Select("id").From(s.tmpl("{{.Prefix}}events")).Where(squirrel.Eq{"id": evt.ID.Hex()})
err := qb.RunWith(s.db).QueryRow().Scan(&existingID)
if err == nil {
// Event already exists
@@ -27,7 +27,7 @@ func (s *SqliteBackend) SaveEvent(evt nostr.Event) error {
}
// Insert the event
insertQb := squirrel.Insert("events").
insertQb := squirrel.Insert(s.tmpl("{{.Prefix}}events")).
Columns("id", "created_at", "kind", "pubkey", "content", "tags", "sig").
Values(
evt.ID.Hex(),
@@ -48,7 +48,7 @@ func (s *SqliteBackend) SaveEvent(evt nostr.Event) error {
// Insert single-letter tags into event_tags table
for _, tag := range evt.Tags {
if len(tag) >= 2 && len(tag[0]) == 1 {
tagQb := squirrel.Insert("event_tags").
tagQb := squirrel.Insert(s.tmpl("{{.Prefix}}event_tags")).
Columns("event_id", "key", "value").
Values(evt.ID.Hex(), tag[0], tag[1])
+2 -1
View File
@@ -12,7 +12,8 @@ func TestSqliteFlow(t *testing.T) {
os.RemoveAll("/tmp/sqlitetest.db")
sb := &SqliteBackend{
Path: "/tmp/sqlitetest.db",
Path: "/tmp/sqlitetest.db",
Prefix: "prefix",
}
err := sb.Init()
assert.NoError(t, err)