212 lines
4.2 KiB
Go
212 lines
4.2 KiB
Go
package lmdbn
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"sync/atomic"
|
|
|
|
"github.com/bmatsuo/lmdb-go/lmdb"
|
|
"github.com/fiatjaf/eventstore"
|
|
"github.com/nbd-wtf/go-nostr"
|
|
)
|
|
|
|
const (
|
|
maxuint16 = 65535
|
|
maxuint32 = 4294967295
|
|
)
|
|
|
|
var _ eventstore.Store = (*LMDBBackend)(nil)
|
|
|
|
type LMDBBackend struct {
|
|
Path string
|
|
MaxLimit int
|
|
|
|
lmdbEnv *lmdb.Env
|
|
|
|
rawEventStore lmdb.DBI
|
|
indexCreatedAt lmdb.DBI
|
|
indexId lmdb.DBI
|
|
indexKind lmdb.DBI
|
|
indexPubkey lmdb.DBI
|
|
indexPubkeyKind lmdb.DBI
|
|
indexTag lmdb.DBI
|
|
|
|
lastId atomic.Uint32
|
|
}
|
|
|
|
func (b *LMDBBackend) Init() error {
|
|
if b.MaxLimit == 0 {
|
|
b.MaxLimit = 500
|
|
}
|
|
|
|
// open lmdb
|
|
env, err := lmdb.NewEnv()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
env.SetMaxDBs(7)
|
|
env.SetMaxReaders(500)
|
|
env.SetMapSize(1 << 38) // ~273GB
|
|
|
|
err = env.Open(b.Path, lmdb.NoTLS, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.lmdbEnv = env
|
|
|
|
// open each db
|
|
if err := b.lmdbEnv.Update(func(txn *lmdb.Txn) error {
|
|
if dbi, err := txn.OpenDBI("raw", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.rawEventStore = dbi
|
|
return nil
|
|
}
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
if err := b.lmdbEnv.Update(func(txn *lmdb.Txn) error {
|
|
if dbi, err := txn.OpenDBI("created_at", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexCreatedAt = dbi
|
|
}
|
|
if dbi, err := txn.OpenDBI("id", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexId = dbi
|
|
}
|
|
if dbi, err := txn.OpenDBI("kind", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexKind = dbi
|
|
}
|
|
if dbi, err := txn.OpenDBI("pubkey", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexPubkey = dbi
|
|
}
|
|
if dbi, err := txn.OpenDBI("pubkeyKind", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexPubkeyKind = dbi
|
|
}
|
|
if dbi, err := txn.OpenDBI("tag", lmdb.Create); err != nil {
|
|
return err
|
|
} else {
|
|
b.indexTag = dbi
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// get lastId
|
|
if err := b.lmdbEnv.View(func(txn *lmdb.Txn) error {
|
|
txn.RawRead = true
|
|
cursor, err := txn.OpenCursor(b.rawEventStore)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cursor.Close()
|
|
k, _, err := cursor.Get(nil, nil, lmdb.Last)
|
|
if operr, ok := err.(*lmdb.OpError); ok && operr.Errno == lmdb.NotFound {
|
|
// nothing found, so we're at zero
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
}
|
|
b.lastId.Store(binary.BigEndian.Uint32(k))
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *LMDBBackend) Close() {
|
|
b.lmdbEnv.Close()
|
|
}
|
|
|
|
func (b *LMDBBackend) Serial() []byte {
|
|
v := b.lastId.Add(1)
|
|
vb := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(vb[:], uint32(v))
|
|
return vb
|
|
}
|
|
|
|
type key struct {
|
|
dbi lmdb.DBI
|
|
key []byte
|
|
}
|
|
|
|
func (b *LMDBBackend) getIndexKeysForEvent(evt *nostr.Event) []key {
|
|
keys := make([]key, 0, 18)
|
|
|
|
// indexes
|
|
{
|
|
// ~ by id
|
|
k, _ := hex.DecodeString(evt.ID)
|
|
keys = append(keys, key{dbi: b.indexId, key: k})
|
|
}
|
|
|
|
{
|
|
// ~ by pubkey+date
|
|
pubkey, _ := hex.DecodeString(evt.PubKey)
|
|
k := make([]byte, 32+4)
|
|
copy(k[:], pubkey)
|
|
binary.BigEndian.PutUint32(k[32:], uint32(evt.CreatedAt))
|
|
keys = append(keys, key{dbi: b.indexPubkey, key: k})
|
|
}
|
|
|
|
{
|
|
// ~ by kind+date
|
|
k := make([]byte, 2+4)
|
|
binary.BigEndian.PutUint16(k[:], uint16(evt.Kind))
|
|
binary.BigEndian.PutUint32(k[2:], uint32(evt.CreatedAt))
|
|
keys = append(keys, key{dbi: b.indexKind, key: k})
|
|
}
|
|
|
|
{
|
|
// ~ by pubkey+kind+date
|
|
pubkey, _ := hex.DecodeString(evt.PubKey)
|
|
k := make([]byte, 32+2+4)
|
|
copy(k[:], pubkey)
|
|
binary.BigEndian.PutUint16(k[32:], uint16(evt.Kind))
|
|
binary.BigEndian.PutUint32(k[32+2:], uint32(evt.CreatedAt))
|
|
keys = append(keys, key{dbi: b.indexPubkeyKind, key: k})
|
|
}
|
|
|
|
// ~ by tagvalue+date
|
|
for _, tag := range evt.Tags {
|
|
if len(tag) < 2 || len(tag[0]) != 1 || len(tag[1]) == 0 || len(tag[1]) > 100 {
|
|
continue
|
|
}
|
|
|
|
var v []byte
|
|
if vb, _ := hex.DecodeString(tag[1]); len(vb) == 32 {
|
|
// store value as bytes
|
|
v = vb
|
|
} else {
|
|
v = []byte(tag[1])
|
|
}
|
|
|
|
k := make([]byte, len(v)+4)
|
|
copy(k[:], v)
|
|
binary.BigEndian.PutUint32(k[len(v):], uint32(evt.CreatedAt))
|
|
keys = append(keys, key{dbi: b.indexTag, key: k})
|
|
}
|
|
|
|
{
|
|
// ~ by date only
|
|
k := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(k[:], uint32(evt.CreatedAt))
|
|
keys = append(keys, key{dbi: b.indexCreatedAt, key: k})
|
|
}
|
|
|
|
return keys
|
|
}
|