Files
next.orly.dev/pkg/database/indexes/keys.go
mleku 29e175efb0
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
implement event table subtyping for small events in value log
2025-11-14 12:15:52 +00:00

522 lines
14 KiB
Go

package indexes
import (
"io"
"reflect"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/database/indexes/types"
"next.orly.dev/pkg/interfaces/codec"
)
var counter int
func init() {
// Initialize the counter to ensure it starts from 0
counter = 0
}
func next() int { counter++; return counter - 1 }
type P struct {
val []byte
}
func NewPrefix(prf ...int) (p *P) {
if len(prf) > 0 {
prefix := Prefix(prf[0])
if prefix == "" {
panic("unknown prefix")
}
return &P{[]byte(prefix)}
} else {
return &P{[]byte{0, 0, 0}}
}
}
func (p *P) Bytes() (b []byte) { return p.val }
func (p *P) MarshalWrite(w io.Writer) (err error) {
_, err = w.Write(p.val)
return
}
func (p *P) UnmarshalRead(r io.Reader) (err error) {
// Allocate a buffer for val if it's nil or empty
if p.val == nil || len(p.val) == 0 {
p.val = make([]byte, 3) // Prefixes are 3 bytes
}
_, err = r.Read(p.val)
return
}
type I string
func (i I) Write(w io.Writer) (n int, err error) { return w.Write([]byte(i)) }
const (
EventPrefix = I("evt")
SmallEventPrefix = I("sev") // small event with inline data (<=384 bytes)
ReplaceableEventPrefix = I("rev") // replaceable event (kinds 0,3,10000-19999) with inline data
AddressableEventPrefix = I("aev") // addressable event (kinds 30000-39999) with inline data
IdPrefix = I("eid")
FullIdPubkeyPrefix = I("fpc") // full id, pubkey, created at
CreatedAtPrefix = I("c--") // created at
KindPrefix = I("kc-") // kind, created at
PubkeyPrefix = I("pc-") // pubkey, created at
KindPubkeyPrefix = I("kpc") // kind, pubkey, created at
TagPrefix = I("tc-") // tag, created at
TagKindPrefix = I("tkc") // tag, kind, created at
TagPubkeyPrefix = I("tpc") // tag, pubkey, created at
TagKindPubkeyPrefix = I("tkp") // tag, kind, pubkey, created at
WordPrefix = I("wrd") // word hash, serial
ExpirationPrefix = I("exp") // timestamp of expiration
VersionPrefix = I("ver") // database version number, for triggering reindexes when new keys are added (policy is add-only).
)
// Prefix returns the three byte human-readable prefixes that go in front of
// database indexes.
func Prefix(prf int) (i I) {
switch prf {
case Event:
return EventPrefix
case SmallEvent:
return SmallEventPrefix
case ReplaceableEvent:
return ReplaceableEventPrefix
case AddressableEvent:
return AddressableEventPrefix
case Id:
return IdPrefix
case FullIdPubkey:
return FullIdPubkeyPrefix
case CreatedAt:
return CreatedAtPrefix
case Kind:
return KindPrefix
case Pubkey:
return PubkeyPrefix
case KindPubkey:
return KindPubkeyPrefix
case Tag:
return TagPrefix
case TagKind:
return TagKindPrefix
case TagPubkey:
return TagPubkeyPrefix
case TagKindPubkey:
return TagKindPubkeyPrefix
case Expiration:
return ExpirationPrefix
case Version:
return VersionPrefix
case Word:
return WordPrefix
}
return
}
func Identify(r io.Reader) (i int, err error) {
// this is here for completeness; however, searches don't need to identify
// this as they work via generated prefixes made using Prefix.
var b [3]byte
_, err = r.Read(b[:])
if err != nil {
i = -1
return
}
switch I(b[:]) {
case EventPrefix:
i = Event
case SmallEventPrefix:
i = SmallEvent
case ReplaceableEventPrefix:
i = ReplaceableEvent
case AddressableEventPrefix:
i = AddressableEvent
case IdPrefix:
i = Id
case FullIdPubkeyPrefix:
i = FullIdPubkey
case CreatedAtPrefix:
i = CreatedAt
case KindPrefix:
i = Kind
case PubkeyPrefix:
i = Pubkey
case KindPubkeyPrefix:
i = KindPubkey
case TagPrefix:
i = Tag
case TagKindPrefix:
i = TagKind
case TagPubkeyPrefix:
i = TagPubkey
case TagKindPubkeyPrefix:
i = TagKindPubkey
case ExpirationPrefix:
i = Expiration
case WordPrefix:
i = Word
}
return
}
type Encs []codec.I
// T is a wrapper around an array of codec.I. The caller provides the Encs so
// they can then call the accessor methods of the codec.I implementation.
type T struct{ Encs }
// New creates a new indexes.T. The helper functions below have an encode and
// decode variant, the decode variant doesn't add the prefix encoder because it
// has been read by Identify or just is being read, and found because it was
// written for the prefix in the iteration.
func New(encoders ...codec.I) (i *T) { return &T{encoders} }
func (t *T) MarshalWrite(w io.Writer) (err error) {
for _, e := range t.Encs {
if e == nil || reflect.ValueOf(e).IsNil() {
// Skip nil encoders instead of returning early. This enables
// generating search prefixes.
continue
}
if err = e.MarshalWrite(w); chk.E(err) {
return
}
}
return
}
func (t *T) UnmarshalRead(r io.Reader) (err error) {
for _, e := range t.Encs {
if err = e.UnmarshalRead(r); chk.E(err) {
return
}
}
return
}
// Event is the whole event stored in binary format
//
// prefix|5 serial - event in binary format
var Event = next()
func EventVars() (ser *types.Uint40) { return new(types.Uint40) }
func EventEnc(ser *types.Uint40) (enc *T) {
return New(NewPrefix(Event), ser)
}
func EventDec(ser *types.Uint40) (enc *T) { return New(NewPrefix(), ser) }
// SmallEvent stores events <=384 bytes with inline data to avoid double lookup.
// This is a Reiser4-inspired optimization for small event packing.
// 384 bytes covers: ID(32) + Pubkey(32) + Sig(64) + basic fields + small content
//
// prefix|5 serial|2 size_uint16|data (variable length, max 384 bytes)
var SmallEvent = next()
func SmallEventVars() (ser *types.Uint40) { return new(types.Uint40) }
func SmallEventEnc(ser *types.Uint40) (enc *T) {
return New(NewPrefix(SmallEvent), ser)
}
func SmallEventDec(ser *types.Uint40) (enc *T) { return New(NewPrefix(), ser) }
// ReplaceableEvent stores replaceable events (kinds 0,3,10000-19999) with inline data.
// Optimized storage for metadata events that are frequently replaced.
// Key format enables direct lookup by pubkey+kind without additional index traversal.
//
// prefix|8 pubkey_hash|2 kind|2 size_uint16|data (variable length, max 384 bytes)
var ReplaceableEvent = next()
func ReplaceableEventVars() (p *types.PubHash, ki *types.Uint16) {
return new(types.PubHash), new(types.Uint16)
}
func ReplaceableEventEnc(p *types.PubHash, ki *types.Uint16) (enc *T) {
return New(NewPrefix(ReplaceableEvent), p, ki)
}
func ReplaceableEventDec(p *types.PubHash, ki *types.Uint16) (enc *T) {
return New(NewPrefix(), p, ki)
}
// AddressableEvent stores parameterized replaceable events (kinds 30000-39999) with inline data.
// Optimized storage for addressable events identified by pubkey+kind+d-tag.
// Key format enables direct lookup without additional index traversal.
//
// prefix|8 pubkey_hash|2 kind|8 dtag_hash|2 size_uint16|data (variable length, max 384 bytes)
var AddressableEvent = next()
func AddressableEventVars() (p *types.PubHash, ki *types.Uint16, d *types.Ident) {
return new(types.PubHash), new(types.Uint16), new(types.Ident)
}
func AddressableEventEnc(p *types.PubHash, ki *types.Uint16, d *types.Ident) (enc *T) {
return New(NewPrefix(AddressableEvent), p, ki, d)
}
func AddressableEventDec(p *types.PubHash, ki *types.Uint16, d *types.Ident) (enc *T) {
return New(NewPrefix(), p, ki, d)
}
// Id contains a truncated 8-byte hash of an event index. This is the secondary
// key of an event, the primary key is the serial found in the Event.
//
// 3 prefix|8 ID hash|5 serial
var Id = next()
func IdVars() (id *types.IdHash, ser *types.Uint40) {
return new(types.IdHash), new(types.Uint40)
}
func IdEnc(id *types.IdHash, ser *types.Uint40) (enc *T) {
return New(NewPrefix(Id), id, ser)
}
func IdDec(id *types.IdHash, ser *types.Uint40) (enc *T) {
return New(NewPrefix(), id, ser)
}
// FullIdPubkey is an index designed to enable sorting and filtering of
// results found via other indexes, without having to decode the event.
//
// 3 prefix|5 serial|32 ID|8 pubkey hash|8 timestamp
var FullIdPubkey = next()
func FullIdPubkeyVars() (
ser *types.Uint40, fid *types.Id, p *types.PubHash, ca *types.Uint64,
) {
return new(types.Uint40), new(types.Id), new(types.PubHash), new(types.Uint64)
}
func FullIdPubkeyEnc(
ser *types.Uint40, fid *types.Id, p *types.PubHash, ca *types.Uint64,
) (enc *T) {
return New(NewPrefix(FullIdPubkey), ser, fid, p, ca)
}
func FullIdPubkeyDec(
ser *types.Uint40, fid *types.Id, p *types.PubHash, ca *types.Uint64,
) (enc *T) {
return New(NewPrefix(), ser, fid, p, ca)
}
// Word index for tokenized search terms
//
// 3 prefix|8 word-hash|5 serial
var Word = next()
func WordVars() (w *types.Word, ser *types.Uint40) {
return new(types.Word), new(types.Uint40)
}
func WordEnc(w *types.Word, ser *types.Uint40) (enc *T) {
return New(NewPrefix(Word), w, ser)
}
func WordDec(w *types.Word, ser *types.Uint40) (enc *T) {
return New(NewPrefix(), w, ser)
}
// CreatedAt is an index that allows search for the timestamp on the event.
//
// 3 prefix|8 timestamp|5 serial
var CreatedAt = next()
func CreatedAtVars() (ca *types.Uint64, ser *types.Uint40) {
return new(types.Uint64), new(types.Uint40)
}
func CreatedAtEnc(ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(CreatedAt), ca, ser)
}
func CreatedAtDec(ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(), ca, ser)
}
// Kind
//
// 3 prefix|2 kind|8 timestamp|5 serial
var Kind = next()
func KindVars() (ki *types.Uint16, ca *types.Uint64, ser *types.Uint40) {
return new(types.Uint16), new(types.Uint64), new(types.Uint40)
}
func KindEnc(ki *types.Uint16, ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(Kind), ki, ca, ser)
}
func KindDec(ki *types.Uint16, ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(), ki, ca, ser)
}
// Pubkey is a composite index that allows search by pubkey
// filtered by timestamp.
//
// 3 prefix|8 pubkey hash|8 timestamp|5 serial
var Pubkey = next()
func PubkeyVars() (p *types.PubHash, ca *types.Uint64, ser *types.Uint40) {
return new(types.PubHash), new(types.Uint64), new(types.Uint40)
}
func PubkeyEnc(p *types.PubHash, ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(Pubkey), p, ca, ser)
}
func PubkeyDec(p *types.PubHash, ca *types.Uint64, ser *types.Uint40) (enc *T) {
return New(NewPrefix(), p, ca, ser)
}
// KindPubkey
//
// 3 prefix|2 kind|8 pubkey hash|8 timestamp|5 serial
var KindPubkey = next()
func KindPubkeyVars() (
ki *types.Uint16, p *types.PubHash, ca *types.Uint64, ser *types.Uint40,
) {
return new(types.Uint16), new(types.PubHash), new(types.Uint64), new(types.Uint40)
}
func KindPubkeyEnc(
ki *types.Uint16, p *types.PubHash, ca *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(KindPubkey), ki, p, ca, ser)
}
func KindPubkeyDec(
ki *types.Uint16, p *types.PubHash, ca *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), ki, p, ca, ser)
}
// Tag allows searching for a tag and filter by timestamp.
//
// 3 prefix|1 key letter|8 value hash|8 timestamp|5 serial
var Tag = next()
func TagVars() (
k *types.Letter, v *types.Ident, ca *types.Uint64, ser *types.Uint40,
) {
return new(types.Letter), new(types.Ident), new(types.Uint64), new(types.Uint40)
}
func TagEnc(
k *types.Letter, v *types.Ident, ca *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(Tag), k, v, ca, ser)
}
func TagDec(
k *types.Letter, v *types.Ident, ca *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), k, v, ca, ser)
}
// TagKind
//
// 3 prefix|1 key letter|8 value hash|2 kind|8 timestamp|5 serial
var TagKind = next()
func TagKindVars() (
k *types.Letter, v *types.Ident, ki *types.Uint16, ca *types.Uint64,
ser *types.Uint40,
) {
return new(types.Letter), new(types.Ident), new(types.Uint16), new(types.Uint64), new(types.Uint40)
}
func TagKindEnc(
k *types.Letter, v *types.Ident, ki *types.Uint16, ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(TagKind), ki, k, v, ca, ser)
}
func TagKindDec(
k *types.Letter, v *types.Ident, ki *types.Uint16, ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), ki, k, v, ca, ser)
}
// TagPubkey allows searching for a pubkey, tag and timestamp.
//
// 3 prefix|1 key letter|8 value hash|8 pubkey hash|8 timestamp|5 serial
var TagPubkey = next()
func TagPubkeyVars() (
k *types.Letter, v *types.Ident, p *types.PubHash, ca *types.Uint64,
ser *types.Uint40,
) {
return new(types.Letter), new(types.Ident), new(types.PubHash), new(types.Uint64), new(types.Uint40)
}
func TagPubkeyEnc(
k *types.Letter, v *types.Ident, p *types.PubHash, ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(TagPubkey), p, k, v, ca, ser)
}
func TagPubkeyDec(
k *types.Letter, v *types.Ident, p *types.PubHash, ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), p, k, v, ca, ser)
}
// TagKindPubkey
//
// 3 prefix|1 key letter|8 value hash|2 kind|8 pubkey hash|8 bytes timestamp|5 serial
var TagKindPubkey = next()
func TagKindPubkeyVars() (
k *types.Letter, v *types.Ident, ki *types.Uint16, p *types.PubHash,
ca *types.Uint64,
ser *types.Uint40,
) {
return new(types.Letter), new(types.Ident), new(types.Uint16), new(types.PubHash), new(types.Uint64), new(types.Uint40)
}
func TagKindPubkeyEnc(
k *types.Letter, v *types.Ident, ki *types.Uint16, p *types.PubHash,
ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(TagKindPubkey), ki, p, k, v, ca, ser)
}
func TagKindPubkeyDec(
k *types.Letter, v *types.Ident, ki *types.Uint16, p *types.PubHash,
ca *types.Uint64,
ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), ki, p, k, v, ca, ser)
}
// Expiration
//
// 3 prefix|8 timestamp|5 serial
var Expiration = next()
func ExpirationVars() (
exp *types.Uint64, ser *types.Uint40,
) {
return new(types.Uint64), new(types.Uint40)
}
func ExpirationEnc(
exp *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(Expiration), exp, ser)
}
func ExpirationDec(
exp *types.Uint64, ser *types.Uint40,
) (enc *T) {
return New(NewPrefix(), exp, ser)
}
// Version
//
// 3 prefix|4 version
var Version = next()
func VersionVars() (
ver *types.Uint32,
) {
return new(types.Uint32)
}
func VersionEnc(
ver *types.Uint32,
) (enc *T) {
return New(NewPrefix(Version), ver)
}
func VersionDec(
ver *types.Uint32,
) (enc *T) {
return New(NewPrefix(), ver)
}