202 changed files with 12803 additions and 420 deletions
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
module crypto.orly |
||||
|
||||
go 1.25.0 |
||||
|
||||
require ( |
||||
encoders.orly v0.0.0-00010101000000-000000000000 |
||||
github.com/davecgh/go-spew v1.1.1 |
||||
github.com/klauspost/cpuid/v2 v2.3.0 |
||||
github.com/stretchr/testify v1.11.1 |
||||
interfaces.orly v0.0.0-00010101000000-000000000000 |
||||
lol.mleku.dev v1.0.2 |
||||
utils.orly v0.0.0-00010101000000-000000000000 |
||||
) |
||||
|
||||
require ( |
||||
github.com/fatih/color v1.18.0 // indirect |
||||
github.com/mattn/go-colorable v0.1.14 // indirect |
||||
github.com/mattn/go-isatty v0.0.20 // indirect |
||||
github.com/pmezard/go-difflib v1.0.0 // indirect |
||||
github.com/templexxx/cpu v0.0.1 // indirect |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect |
||||
golang.org/x/sys v0.35.0 // indirect |
||||
gopkg.in/yaml.v3 v3.0.1 // indirect |
||||
) |
||||
|
||||
replace ( |
||||
encoders.orly => ../encoders |
||||
interfaces.orly => ../interfaces |
||||
utils.orly => ../utils |
||||
) |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= |
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= |
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= |
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= |
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= |
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= |
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= |
||||
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= |
||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= |
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
lol.mleku.dev v1.0.2 h1:bSV1hHnkmt1hq+9nSvRwN6wgcI7itbM3XRZ4dMB438c= |
||||
lol.mleku.dev v1.0.2/go.mod h1:DQ0WnmkntA9dPLCXgvtIgYt5G0HSqx3wSTLolHgWeLA= |
||||
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"context" |
||||
"os" |
||||
"path/filepath" |
||||
"time" |
||||
|
||||
"github.com/dgraph-io/badger/v4" |
||||
"github.com/dgraph-io/badger/v4/options" |
||||
"lol.mleku.dev" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
"utils.orly/apputil" |
||||
"utils.orly/units" |
||||
) |
||||
|
||||
type D struct { |
||||
ctx context.Context |
||||
cancel context.CancelFunc |
||||
dataDir string |
||||
Logger *logger |
||||
*badger.DB |
||||
seq *badger.Sequence |
||||
} |
||||
|
||||
func New( |
||||
ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string, |
||||
) ( |
||||
d *D, err error, |
||||
) { |
||||
d = &D{ |
||||
ctx: ctx, |
||||
cancel: cancel, |
||||
dataDir: dataDir, |
||||
Logger: NewLogger(lol.GetLogLevel(logLevel), dataDir), |
||||
DB: nil, |
||||
seq: nil, |
||||
} |
||||
|
||||
// Ensure the data directory exists
|
||||
if err = os.MkdirAll(dataDir, 0755); chk.E(err) { |
||||
return |
||||
} |
||||
|
||||
// Also ensure the directory exists using apputil.EnsureDir for any potential subdirectories
|
||||
dummyFile := filepath.Join(dataDir, "dummy.sst") |
||||
if err = apputil.EnsureDir(dummyFile); chk.E(err) { |
||||
return |
||||
} |
||||
|
||||
opts := badger.DefaultOptions(d.dataDir) |
||||
opts.BlockCacheSize = int64(units.Gb) |
||||
opts.BlockSize = units.Gb |
||||
opts.CompactL0OnClose = true |
||||
opts.LmaxCompaction = true |
||||
opts.Compression = options.None |
||||
opts.Logger = d.Logger |
||||
if d.DB, err = badger.Open(opts); chk.E(err) { |
||||
return |
||||
} |
||||
log.T.Ln("getting event sequence lease", d.dataDir) |
||||
if d.seq, err = d.DB.GetSequence([]byte("EVENTS"), 1000); chk.E(err) { |
||||
return |
||||
} |
||||
// run code that updates indexes when new indexes have been added and bumps
|
||||
// the version so they aren't run again.
|
||||
d.RunMigrations() |
||||
// start up the expiration tag processing and shut down and clean up the
|
||||
// database after the context is canceled.
|
||||
go func() { |
||||
expirationTicker := time.NewTicker(time.Minute * 10) |
||||
select { |
||||
case <-expirationTicker.C: |
||||
d.DeleteExpired() |
||||
return |
||||
case <-d.ctx.Done(): |
||||
} |
||||
d.cancel() |
||||
d.seq.Release() |
||||
d.DB.Close() |
||||
}() |
||||
return |
||||
} |
||||
|
||||
// Path returns the path where the database files are stored.
|
||||
func (d *D) Path() string { return d.dataDir } |
||||
|
||||
func (d *D) Wipe() (err error) { |
||||
// TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
func (d *D) SetLogLevel(level string) { |
||||
d.Logger.SetLogLevel(lol.GetLogLevel(level)) |
||||
} |
||||
|
||||
func (d *D) EventIdsBySerial(start uint64, count int) ( |
||||
evs []uint64, err error, |
||||
) { |
||||
// TODO implement me
|
||||
panic("implement me") |
||||
} |
||||
|
||||
// Init initializes the database with the given path.
|
||||
func (d *D) Init(path string) (err error) { |
||||
// The database is already initialized in the New function,
|
||||
// so we just need to ensure the path is set correctly.
|
||||
d.dataDir = path |
||||
return nil |
||||
} |
||||
|
||||
// Sync flushes the database buffers to disk.
|
||||
func (d *D) Sync() (err error) { |
||||
d.DB.RunValueLogGC(0.5) |
||||
return d.DB.Sync() |
||||
} |
||||
|
||||
// Close releases resources and closes the database.
|
||||
func (d *D) Close() (err error) { |
||||
if d.seq != nil { |
||||
if err = d.seq.Release(); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
if d.DB != nil { |
||||
if err = d.DB.Close(); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
// DeleteEvent removes an event from the database identified by `eid`. If
|
||||
// noTombstone is false or not provided, a tombstone is created for the event.
|
||||
func (d *D) DeleteEvent(c context.Context, eid []byte) (err error) { |
||||
d.Logger.Warningf("deleting event %0x", eid) |
||||
|
||||
// Get the serial number for the event ID
|
||||
var ser *types.Uint40 |
||||
ser, err = d.GetSerialById(eid) |
||||
if chk.E(err) { |
||||
return |
||||
} |
||||
if ser == nil { |
||||
// Event wasn't found, nothing to delete
|
||||
return |
||||
} |
||||
// Fetch the event to get its data
|
||||
var ev *event.E |
||||
ev, err = d.FetchEventBySerial(ser) |
||||
if chk.E(err) { |
||||
return |
||||
} |
||||
if ev == nil { |
||||
// Event wasn't found, nothing to delete. this shouldn't happen.
|
||||
return |
||||
} |
||||
if err = d.DeleteEventBySerial(c, ser, ev); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (d *D) DeleteEventBySerial( |
||||
c context.Context, ser *types.Uint40, ev *event.E, |
||||
) (err error) { |
||||
// Get all indexes for the event
|
||||
var idxs [][]byte |
||||
idxs, err = GetIndexesForEvent(ev, ser.Get()) |
||||
if chk.E(err) { |
||||
return |
||||
} |
||||
// Get the event key
|
||||
eventKey := new(bytes.Buffer) |
||||
if err = indexes.EventEnc(ser).MarshalWrite(eventKey); chk.E(err) { |
||||
return |
||||
} |
||||
// Delete the event and all its indexes in a transaction
|
||||
err = d.Update( |
||||
func(txn *badger.Txn) (err error) { |
||||
// Delete the event
|
||||
if err = txn.Delete(eventKey.Bytes()); chk.E(err) { |
||||
return |
||||
} |
||||
// Delete all indexes
|
||||
for _, key := range idxs { |
||||
if err = txn.Delete(key); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
}, |
||||
) |
||||
return |
||||
} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"time" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
func (d *D) DeleteExpired() { |
||||
var err error |
||||
var expiredSerials types.Uint40s |
||||
// make the operation atomic and save on accesses to the system clock by
|
||||
// setting the boundary at the current second
|
||||
now := time.Now().Unix() |
||||
// search the expiration indexes for expiry timestamps that are now past
|
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
exp, ser := indexes.ExpirationVars() |
||||
expPrf := new(bytes.Buffer) |
||||
if _, err = indexes.ExpirationPrefix.Write(expPrf); chk.E(err) { |
||||
return |
||||
} |
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: expPrf.Bytes()}) |
||||
defer it.Close() |
||||
for it.Rewind(); it.Valid(); it.Next() { |
||||
item := it.Item() |
||||
key := item.Key() |
||||
buf := bytes.NewBuffer(key) |
||||
if err = indexes.ExpirationDec( |
||||
exp, ser, |
||||
).UnmarshalRead(buf); chk.E(err) { |
||||
continue |
||||
} |
||||
if int64(exp.Get()) > now { |
||||
// not expired yet
|
||||
continue |
||||
} |
||||
expiredSerials = append(expiredSerials, ser) |
||||
} |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
} |
||||
// delete the events and their indexes
|
||||
for _, ser := range expiredSerials { |
||||
var ev *event.E |
||||
if ev, err = d.FetchEventBySerial(ser); chk.E(err) { |
||||
continue |
||||
} |
||||
if err = d.DeleteEventBySerial( |
||||
context.Background(), ser, ev, |
||||
); chk.E(err) { |
||||
continue |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"io" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly/units" |
||||
) |
||||
|
||||
// Export the complete database of stored events to an io.Writer in line structured minified
|
||||
// JSON.
|
||||
func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte) { |
||||
var err error |
||||
evB := make([]byte, 0, units.Mb) |
||||
evBuf := bytes.NewBuffer(evB) |
||||
if len(pubkeys) == 0 { |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
buf := new(bytes.Buffer) |
||||
if err = indexes.EventEnc(nil).MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: buf.Bytes()}) |
||||
defer it.Close() |
||||
for it.Rewind(); it.Valid(); it.Next() { |
||||
item := it.Item() |
||||
if err = item.Value( |
||||
func(val []byte) (err error) { |
||||
evBuf.Write(val) |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
continue |
||||
} |
||||
ev := event.New() |
||||
if err = ev.UnmarshalBinary(evBuf); chk.E(err) { |
||||
continue |
||||
} |
||||
// Serialize the event to JSON and write it to the output
|
||||
if _, err = w.Write(ev.Serialize()); chk.E(err) { |
||||
return |
||||
} |
||||
if _, err = w.Write([]byte{'\n'}); chk.E(err) { |
||||
return |
||||
} |
||||
evBuf.Reset() |
||||
} |
||||
return |
||||
}, |
||||
); err != nil { |
||||
return |
||||
} |
||||
} else { |
||||
for _, pubkey := range pubkeys { |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
pkBuf := new(bytes.Buffer) |
||||
ph := &types.PubHash{} |
||||
if err = ph.FromPubkey(pubkey); chk.E(err) { |
||||
return |
||||
} |
||||
if err = indexes.PubkeyEnc( |
||||
ph, nil, nil, |
||||
).MarshalWrite(pkBuf); chk.E(err) { |
||||
return |
||||
} |
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: pkBuf.Bytes()}) |
||||
defer it.Close() |
||||
for it.Rewind(); it.Valid(); it.Next() { |
||||
item := it.Item() |
||||
if err = item.Value( |
||||
func(val []byte) (err error) { |
||||
evBuf.Write(val) |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
continue |
||||
} |
||||
ev := event.New() |
||||
if err = ev.UnmarshalBinary(evBuf); chk.E(err) { |
||||
continue |
||||
} |
||||
// Serialize the event to JSON and write it to the output
|
||||
if _, err = w.Write(ev.Serialize()); chk.E(err) { |
||||
continue |
||||
} |
||||
if _, err = w.Write([]byte{'\n'}); chk.E(err) { |
||||
continue |
||||
} |
||||
evBuf.Reset() |
||||
} |
||||
return |
||||
}, |
||||
); err != nil { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"os" |
||||
"testing" |
||||
|
||||
"encoders.orly/event" |
||||
"encoders.orly/event/examples" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
// TestExport tests the Export function by:
|
||||
// 1. Creating a new database with events from examples.Cache
|
||||
// 2. Checking that all event IDs in the cache are found in the export
|
||||
// 3. Verifying this also works when only a few pubkeys are requested
|
||||
func TestExport(t *testing.T) { |
||||
// Create a temporary directory for the database
|
||||
tempDir, err := os.MkdirTemp("", "test-db-*") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create temporary directory: %v", err) |
||||
} |
||||
defer os.RemoveAll(tempDir) // Clean up after the test
|
||||
|
||||
// Create a context and cancel function for the database
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
// Initialize the database
|
||||
db, err := New(ctx, cancel, tempDir, "info") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create database: %v", err) |
||||
} |
||||
defer db.Close() |
||||
|
||||
// Create a scanner to read events from examples.Cache
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache)) |
||||
scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) |
||||
|
||||
// Maps to store event IDs and their associated pubkeys
|
||||
eventIDs := make(map[string]bool) |
||||
pubkeyToEventIDs := make(map[string][]string) |
||||
|
||||
// Process each event
|
||||
for scanner.Scan() { |
||||
chk.E(scanner.Err()) |
||||
b := scanner.Bytes() |
||||
ev := event.New() |
||||
|
||||
// Unmarshal the event
|
||||
if _, err = ev.Unmarshal(b); chk.E(err) { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Save the event to the database
|
||||
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil { |
||||
t.Fatalf("Failed to save event: %v", err) |
||||
} |
||||
|
||||
// Store the event ID
|
||||
eventID := string(ev.ID) |
||||
eventIDs[eventID] = true |
||||
|
||||
// Store the event ID by pubkey
|
||||
pubkey := string(ev.Pubkey) |
||||
pubkeyToEventIDs[pubkey] = append(pubkeyToEventIDs[pubkey], eventID) |
||||
} |
||||
|
||||
// Check for scanner errors
|
||||
if err = scanner.Err(); err != nil { |
||||
t.Fatalf("Scanner error: %v", err) |
||||
} |
||||
|
||||
t.Logf("Saved %d events to the database", len(eventIDs)) |
||||
|
||||
// Test 1: Export all events and verify all IDs are in the export
|
||||
var exportBuffer bytes.Buffer |
||||
db.Export(ctx, &exportBuffer) |
||||
|
||||
// Parse the exported events and check that all IDs are present
|
||||
exportedIDs := make(map[string]bool) |
||||
exportScanner := bufio.NewScanner(&exportBuffer) |
||||
exportScanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) |
||||
exportCount := 0 |
||||
for exportScanner.Scan() { |
||||
b := exportScanner.Bytes() |
||||
ev := event.New() |
||||
if _, err = ev.Unmarshal(b); chk.E(err) { |
||||
t.Fatal(err) |
||||
} |
||||
exportedIDs[string(ev.ID)] = true |
||||
exportCount++ |
||||
} |
||||
// Check for scanner errors
|
||||
if err = exportScanner.Err(); err != nil { |
||||
t.Fatalf("Scanner error: %v", err) |
||||
} |
||||
|
||||
t.Logf("Found %d events in the export", exportCount) |
||||
|
||||
// Check that all original event IDs are in the export
|
||||
for id := range eventIDs { |
||||
if !exportedIDs[id] { |
||||
t.Errorf("Event ID %s not found in export", id) |
||||
} |
||||
} |
||||
|
||||
t.Logf("All %d event IDs found in export", len(eventIDs)) |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error) { |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
buf := new(bytes.Buffer) |
||||
if err = indexes.EventEnc(ser).MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
var item *badger.Item |
||||
if item, err = txn.Get(buf.Bytes()); err != nil { |
||||
return |
||||
} |
||||
var v []byte |
||||
if v, err = item.ValueCopy(nil); chk.E(err) { |
||||
return |
||||
} |
||||
ev = new(event.E) |
||||
if err = ev.UnmarshalBinary(bytes.NewBuffer(v)); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
}, |
||||
); err != nil { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"os" |
||||
"testing" |
||||
|
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"encoders.orly/event/examples" |
||||
"encoders.orly/filter" |
||||
"encoders.orly/tag" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
) |
||||
|
||||
func TestFetchEventBySerial(t *testing.T) { |
||||
// Create a temporary directory for the database
|
||||
tempDir, err := os.MkdirTemp("", "test-db-*") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create temporary directory: %v", err) |
||||
} |
||||
defer os.RemoveAll(tempDir) // Clean up after the test
|
||||
|
||||
// Create a context and cancel function for the database
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
// Initialize the database
|
||||
db, err := New(ctx, cancel, tempDir, "info") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create database: %v", err) |
||||
} |
||||
defer db.Close() |
||||
|
||||
// Create a scanner to read events from examples.Cache
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache)) |
||||
scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) |
||||
|
||||
// Count the number of events processed
|
||||
eventCount := 0 |
||||
|
||||
var events []*event.E |
||||
|
||||
// Process each event
|
||||
for scanner.Scan() { |
||||
chk.E(scanner.Err()) |
||||
b := scanner.Bytes() |
||||
ev := event.New() |
||||
|
||||
// Unmarshal the event
|
||||
if _, err = ev.Unmarshal(b); chk.E(err) { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
events = append(events, ev) |
||||
|
||||
// Save the event to the database
|
||||
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil { |
||||
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err) |
||||
} |
||||
|
||||
eventCount++ |
||||
} |
||||
|
||||
// Check for scanner errors
|
||||
if err = scanner.Err(); err != nil { |
||||
t.Fatalf("Scanner error: %v", err) |
||||
} |
||||
|
||||
t.Logf("Successfully saved %d events to the database", eventCount) |
||||
|
||||
// Instead of trying to find a valid serial directly, let's use QueryForIds
|
||||
// which is known to work from the other tests
|
||||
testEvent := events[3] // Using the same event as in other tests
|
||||
|
||||
// Use QueryForIds to get the IdPkTs for this event
|
||||
var sers types.Uint40s |
||||
sers, err = db.QueryForSerials( |
||||
ctx, &filter.F{ |
||||
Ids: tag.NewFromBytesSlice(testEvent.ID), |
||||
}, |
||||
) |
||||
if err != nil { |
||||
t.Fatalf("Failed to query for Ids: %v", err) |
||||
} |
||||
|
||||
// Verify we got exactly one result
|
||||
if len(sers) != 1 { |
||||
t.Fatalf("Expected 1 IdPkTs, got %d", len(sers)) |
||||
} |
||||
|
||||
// Fetch the event by serial
|
||||
fetchedEvent, err := db.FetchEventBySerial(sers[0]) |
||||
if err != nil { |
||||
t.Fatalf("Failed to fetch event by serial: %v", err) |
||||
} |
||||
|
||||
// Verify the fetched event is not nil
|
||||
if fetchedEvent == nil { |
||||
t.Fatal("Expected fetched event to be non-nil, but got nil") |
||||
} |
||||
|
||||
// Verify the fetched event has the same ID as the original event
|
||||
if !utils.FastEqual(fetchedEvent.ID, testEvent.ID) { |
||||
t.Fatalf( |
||||
"Fetched event ID doesn't match original event ID. Got %x, expected %x", |
||||
fetchedEvent.ID, testEvent.ID, |
||||
) |
||||
} |
||||
|
||||
// Verify other event properties match
|
||||
if fetchedEvent.Kind != testEvent.Kind { |
||||
t.Fatalf( |
||||
"Fetched event kind doesn't match. Got %d, expected %d", |
||||
fetchedEvent.Kind, testEvent.Kind, |
||||
) |
||||
} |
||||
|
||||
if !utils.FastEqual(fetchedEvent.Pubkey, testEvent.Pubkey) { |
||||
t.Fatalf( |
||||
"Fetched event pubkey doesn't match. Got %x, expected %x", |
||||
fetchedEvent.Pubkey, testEvent.Pubkey, |
||||
) |
||||
} |
||||
|
||||
if fetchedEvent.CreatedAt != testEvent.CreatedAt { |
||||
t.Fatalf( |
||||
"Fetched event created_at doesn't match. Got %d, expected %d", |
||||
fetchedEvent.CreatedAt, testEvent.CreatedAt, |
||||
) |
||||
} |
||||
|
||||
// Test with a non-existent serial
|
||||
nonExistentSerial := new(types.Uint40) |
||||
err = nonExistentSerial.Set(uint64(0xFFFFFFFFFF)) // Max value
|
||||
if err != nil { |
||||
t.Fatalf("Failed to create non-existent serial: %v", err) |
||||
} |
||||
|
||||
// This should return an error since the serial doesn't exist
|
||||
fetchedEvent, err = db.FetchEventBySerial(nonExistentSerial) |
||||
if err == nil { |
||||
t.Fatal("Expected error for non-existent serial, but got nil") |
||||
} |
||||
|
||||
// The fetched event should be nil
|
||||
if fetchedEvent != nil { |
||||
t.Fatalf( |
||||
"Expected nil event for non-existent serial, but got: %v", |
||||
fetchedEvent, |
||||
) |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"interfaces.orly/store" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
func (d *D) GetFullIdPubkeyBySerial(ser *types.Uint40) ( |
||||
fidpk *store.IdPkTs, err error, |
||||
) { |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
buf := new(bytes.Buffer) |
||||
if err = indexes.FullIdPubkeyEnc( |
||||
ser, nil, nil, nil, |
||||
).MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
prf := buf.Bytes() |
||||
it := txn.NewIterator( |
||||
badger.IteratorOptions{ |
||||
Prefix: prf, |
||||
}, |
||||
) |
||||
defer it.Close() |
||||
it.Seek(prf) |
||||
if it.Valid() { |
||||
item := it.Item() |
||||
key := item.Key() |
||||
ser, fid, p, ca := indexes.FullIdPubkeyVars() |
||||
buf2 := bytes.NewBuffer(key) |
||||
if err = indexes.FullIdPubkeyDec( |
||||
ser, fid, p, ca, |
||||
).UnmarshalRead(buf2); chk.E(err) { |
||||
return |
||||
} |
||||
idpkts := store.IdPkTs{ |
||||
Id: fid.Bytes(), |
||||
Pub: p.Bytes(), |
||||
Ts: int64(ca.Get()), |
||||
Ser: ser.Get(), |
||||
} |
||||
fidpk = &idpkts |
||||
} |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"database.orly/indexes" |
||||
"database.orly/indexes/types" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"interfaces.orly/store" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
// GetFullIdPubkeyBySerials seeks directly to each serial's prefix in the
|
||||
// FullIdPubkey index. The input sers slice is expected to be sorted in
|
||||
// ascending order, allowing efficient forward-only iteration via a single
|
||||
// Badger iterator.
|
||||
func (d *D) GetFullIdPubkeyBySerials(sers []*types.Uint40) ( |
||||
fidpks []*store.IdPkTs, err error, |
||||
) { |
||||
if len(sers) == 0 { |
||||
return |
||||
} |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
// Scope the iterator to the FullIdPubkey table using its 3-byte prefix.
|
||||
buf := new(bytes.Buffer) |
||||
if err = indexes.NewPrefix(indexes.FullIdPubkey).MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
tablePrefix := buf.Bytes() |
||||
it := txn.NewIterator(badger.IteratorOptions{Prefix: tablePrefix}) |
||||
defer it.Close() |
||||
|
||||
for _, s := range sers { |
||||
if s == nil { |
||||
continue |
||||
} |
||||
// Build the serial-specific prefix: 3-byte table prefix + 5-byte serial.
|
||||
sbuf := new(bytes.Buffer) |
||||
if err = indexes.FullIdPubkeyEnc( |
||||
s, nil, nil, nil, |
||||
).MarshalWrite(sbuf); chk.E(err) { |
||||
return |
||||
} |
||||
serialPrefix := sbuf.Bytes() |
||||
|
||||
// Seek to the first key for this serial and verify it matches the prefix.
|
||||
it.Seek(serialPrefix) |
||||
if it.ValidForPrefix(serialPrefix) { |
||||
item := it.Item() |
||||
key := item.Key() |
||||
ser, fid, p, ca := indexes.FullIdPubkeyVars() |
||||
if err = indexes.FullIdPubkeyDec( |
||||
ser, fid, p, ca, |
||||
).UnmarshalRead(bytes.NewBuffer(key)); chk.E(err) { |
||||
return |
||||
} |
||||
fidpks = append( |
||||
fidpks, &store.IdPkTs{ |
||||
Id: fid.Bytes(), |
||||
Pub: p.Bytes(), |
||||
Ts: int64(ca.Get()), |
||||
Ser: ser.Get(), |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"database.orly/indexes" |
||||
. "database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
// appendIndexBytes marshals an index to a byte slice and appends it to the idxs slice
|
||||
func appendIndexBytes(idxs *[][]byte, idx *indexes.T) (err error) { |
||||
buf := new(bytes.Buffer) |
||||
// Marshal the index to the buffer
|
||||
if err = idx.MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
// Copy the buffer's bytes to a new byte slice
|
||||
// Append the byte slice to the idxs slice
|
||||
*idxs = append(*idxs, buf.Bytes()) |
||||
return |
||||
} |
||||
|
||||
// GetIndexesForEvent creates all the indexes for an event.E instance as defined
|
||||
// in keys.go. It returns a slice of byte slices that can be used to store the
|
||||
// event in the database.
|
||||
func GetIndexesForEvent(ev *event.E, serial uint64) ( |
||||
idxs [][]byte, err error, |
||||
) { |
||||
defer func() { |
||||
if chk.E(err) { |
||||
idxs = nil |
||||
} |
||||
}() |
||||
// Convert serial to Uint40
|
||||
ser := new(Uint40) |
||||
if err = ser.Set(serial); chk.E(err) { |
||||
return |
||||
} |
||||
// ID index
|
||||
idHash := new(IdHash) |
||||
if err = idHash.FromId(ev.ID); chk.E(err) { |
||||
return |
||||
} |
||||
idIndex := indexes.IdEnc(idHash, ser) |
||||
if err = appendIndexBytes(&idxs, idIndex); chk.E(err) { |
||||
return |
||||
} |
||||
// FullIdPubkey index
|
||||
fullID := new(Id) |
||||
if err = fullID.FromId(ev.ID); chk.E(err) { |
||||
return |
||||
} |
||||
pubHash := new(PubHash) |
||||
if err = pubHash.FromPubkey(ev.Pubkey); chk.E(err) { |
||||
return |
||||
} |
||||
createdAt := new(Uint64) |
||||
createdAt.Set(uint64(ev.CreatedAt)) |
||||
idPubkeyIndex := indexes.FullIdPubkeyEnc( |
||||
ser, fullID, pubHash, createdAt, |
||||
) |
||||
if err = appendIndexBytes(&idxs, idPubkeyIndex); chk.E(err) { |
||||
return |
||||
} |
||||
// CreatedAt index
|
||||
createdAtIndex := indexes.CreatedAtEnc(createdAt, ser) |
||||
if err = appendIndexBytes(&idxs, createdAtIndex); chk.E(err) { |
||||
return |
||||
} |
||||
// PubkeyCreatedAt index
|
||||
pubkeyIndex := indexes.PubkeyEnc(pubHash, createdAt, ser) |
||||
if err = appendIndexBytes(&idxs, pubkeyIndex); chk.E(err) { |
||||
return |
||||
} |
||||
// Process tags for tag-related indexes
|
||||
if ev.Tags != nil && ev.Tags.Len() > 0 { |
||||
for _, tag := range ev.Tags.ToSliceOfTags() { |
||||
// only index tags with a value field and the key is a single character
|
||||
if tag.Len() >= 2 { |
||||
// Get the key and value from the tag
|
||||
keyBytes := tag.Key() |
||||
// require single-letter key
|
||||
if len(keyBytes) != 1 { |
||||
continue |
||||
} |
||||
// if the key is not a-zA-Z skip
|
||||
if (keyBytes[0] < 'a' || keyBytes[0] > 'z') && (keyBytes[0] < 'A' || keyBytes[0] > 'Z') { |
||||
continue |
||||
} |
||||
valueBytes := tag.Value() |
||||
// Create tag key and value
|
||||
key := new(Letter) |
||||
key.Set(keyBytes[0]) |
||||
valueHash := new(Ident) |
||||
valueHash.FromIdent(valueBytes) |
||||
// TagPubkey index
|
||||
pubkeyTagIndex := indexes.TagPubkeyEnc( |
||||
key, valueHash, pubHash, createdAt, ser, |
||||
) |
||||
if err = appendIndexBytes( |
||||
&idxs, pubkeyTagIndex, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
// Tag index
|
||||
tagIndex := indexes.TagEnc( |
||||
key, valueHash, createdAt, ser, |
||||
) |
||||
if err = appendIndexBytes( |
||||
&idxs, tagIndex, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
// Kind-related tag indexes
|
||||
kind := new(Uint16) |
||||
kind.Set(ev.Kind) |
||||
// TagKind index
|
||||
kindTagIndex := indexes.TagKindEnc( |
||||
key, valueHash, kind, createdAt, ser, |
||||
) |
||||
if err = appendIndexBytes( |
||||
&idxs, kindTagIndex, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
// TagKindPubkey index
|
||||
kindPubkeyTagIndex := indexes.TagKindPubkeyEnc( |
||||
key, valueHash, kind, pubHash, createdAt, ser, |
||||
) |
||||
if err = appendIndexBytes( |
||||
&idxs, kindPubkeyTagIndex, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
} |
||||
kind := new(Uint16) |
||||
kind.Set(uint16(ev.Kind)) |
||||
// Kind index
|
||||
kindIndex := indexes.KindEnc(kind, createdAt, ser) |
||||
if err = appendIndexBytes(&idxs, kindIndex); chk.E(err) { |
||||
return |
||||
} |
||||
// KindPubkey index
|
||||
// Using the correct parameters based on the function signature
|
||||
kindPubkeyIndex := indexes.KindPubkeyEnc( |
||||
kind, pubHash, createdAt, ser, |
||||
) |
||||
if err = appendIndexBytes(&idxs, kindPubkeyIndex); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,304 @@
@@ -0,0 +1,304 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"crypto.orly/sha256" |
||||
"database.orly/indexes" |
||||
types2 "database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"encoders.orly/kind" |
||||
"encoders.orly/tag" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
) |
||||
|
||||
func TestGetIndexesForEvent(t *testing.T) { |
||||
t.Run("BasicEvent", testBasicEvent) |
||||
t.Run("EventWithTags", testEventWithTags) |
||||
t.Run("ErrorHandling", testErrorHandling) |
||||
} |
||||
|
||||
// Helper function to verify that a specific index is included in the generated
|
||||
// indexes
|
||||
func verifyIndexIncluded(t *testing.T, idxs [][]byte, expectedIdx *indexes.T) { |
||||
// Marshal the expected index
|
||||
buf := new(bytes.Buffer) |
||||
err := expectedIdx.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to marshal expected index: %v", err) |
||||
} |
||||
|
||||
expectedBytes := buf.Bytes() |
||||
found := false |
||||
|
||||
for _, idx := range idxs { |
||||
if utils.FastEqual(idx, expectedBytes) { |
||||
found = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
if !found { |
||||
t.Errorf("Expected index not found in generated indexes") |
||||
t.Errorf("Expected: %v", expectedBytes) |
||||
t.Errorf("Generated indexes: %d indexes", len(idxs)) |
||||
} |
||||
} |
||||
|
||||
// Test basic event with minimal fields
|
||||
func testBasicEvent(t *testing.T) { |
||||
// Create a basic event
|
||||
ev := event.New() |
||||
|
||||
// Set ID
|
||||
id := make([]byte, sha256.Size) |
||||
for i := range id { |
||||
id[i] = byte(i) |
||||
} |
||||
ev.ID = id |
||||
|
||||
// Set Pubkey
|
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i + 1) |
||||
} |
||||
ev.Pubkey = pubkey |
||||
|
||||
// Set CreatedAt
|
||||
ev.CreatedAt = 12345 |
||||
|
||||
// Set Kind
|
||||
ev.Kind = kind.TextNote.K |
||||
|
||||
// Set Content
|
||||
ev.Content = []byte("Test content") |
||||
|
||||
// Generate indexes
|
||||
serial := uint64(1) |
||||
idxs, err := GetIndexesForEvent(ev, serial) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesForEvent failed: %v", err) |
||||
} |
||||
|
||||
// Verify the number of indexes (should be 6 for a basic event without tags)
|
||||
if len(idxs) != 6 { |
||||
t.Fatalf("Expected 6 indexes, got %d", len(idxs)) |
||||
} |
||||
|
||||
// Create and verify the expected indexes
|
||||
|
||||
// 1. ID index
|
||||
ser := new(types2.Uint40) |
||||
err = ser.Set(serial) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create Uint40: %v", err) |
||||
} |
||||
|
||||
idHash := new(types2.IdHash) |
||||
err = idHash.FromId(ev.ID) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create IdHash: %v", err) |
||||
} |
||||
idIndex := indexes.IdEnc(idHash, ser) |
||||
verifyIndexIncluded(t, idxs, idIndex) |
||||
|
||||
// 2. FullIdPubkey index
|
||||
fullID := new(types2.Id) |
||||
err = fullID.FromId(ev.ID) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create ID: %v", err) |
||||
} |
||||
|
||||
pubHash := new(types2.PubHash) |
||||
err = pubHash.FromPubkey(ev.Pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
createdAt := new(types2.Uint64) |
||||
createdAt.Set(uint64(ev.CreatedAt)) |
||||
|
||||
idPubkeyIndex := indexes.FullIdPubkeyEnc(ser, fullID, pubHash, createdAt) |
||||
verifyIndexIncluded(t, idxs, idPubkeyIndex) |
||||
|
||||
// 3. CreatedAt index
|
||||
createdAtIndex := indexes.CreatedAtEnc(createdAt, ser) |
||||
verifyIndexIncluded(t, idxs, createdAtIndex) |
||||
|
||||
// 4. Pubkey index
|
||||
pubkeyIndex := indexes.PubkeyEnc(pubHash, createdAt, ser) |
||||
verifyIndexIncluded(t, idxs, pubkeyIndex) |
||||
|
||||
// 5. Kind index
|
||||
kind := new(types2.Uint16) |
||||
kind.Set(ev.Kind) |
||||
|
||||
kindIndex := indexes.KindEnc(kind, createdAt, ser) |
||||
verifyIndexIncluded(t, idxs, kindIndex) |
||||
|
||||
// 6. KindPubkey index
|
||||
kindPubkeyIndex := indexes.KindPubkeyEnc(kind, pubHash, createdAt, ser) |
||||
verifyIndexIncluded(t, idxs, kindPubkeyIndex) |
||||
} |
||||
|
||||
// Test event with tags
|
||||
func testEventWithTags(t *testing.T) { |
||||
// Create an event with tags
|
||||
ev := event.New() |
||||
|
||||
// Set ID
|
||||
id := make([]byte, sha256.Size) |
||||
for i := range id { |
||||
id[i] = byte(i) |
||||
} |
||||
ev.ID = id |
||||
|
||||
// Set Pubkey
|
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i + 1) |
||||
} |
||||
ev.Pubkey = pubkey |
||||
|
||||
// Set CreatedAt
|
||||
ev.CreatedAt = 12345 |
||||
|
||||
// Set Kind
|
||||
ev.Kind = kind.TextNote.K // TextNote kind
|
||||
|
||||
// Set Content
|
||||
ev.Content = []byte("Test content with tags") |
||||
|
||||
// Add tags
|
||||
ev.Tags = tag.NewS() |
||||
|
||||
// Add e tag (event reference)
|
||||
eTagKey := "e" |
||||
eTagValue := "abcdef1234567890" |
||||
eTag := tag.NewFromAny(eTagKey, eTagValue) |
||||
*ev.Tags = append(*ev.Tags, eTag) |
||||
|
||||
// Add p tag (pubkey reference)
|
||||
pTagKey := "p" |
||||
pTagValue := "0123456789abcdef" |
||||
pTag := tag.NewFromAny(pTagKey, pTagValue) |
||||
*ev.Tags = append(*ev.Tags, pTag) |
||||
|
||||
// Generate indexes
|
||||
serial := uint64(2) |
||||
idxs, err := GetIndexesForEvent(ev, serial) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesForEvent failed: %v", err) |
||||
} |
||||
|
||||
// Verify the number of indexes (should be 14 for an event with 2 tags)
|
||||
// 6 basic indexes + 4 indexes per tag (TagPubkey, Tag, TagKind, TagKindPubkey)
|
||||
if len(idxs) != 14 { |
||||
t.Fatalf("Expected 14 indexes, got %d", len(idxs)) |
||||
} |
||||
|
||||
// Create and verify the basic indexes (same as in testBasicEvent)
|
||||
ser := new(types2.Uint40) |
||||
err = ser.Set(serial) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create Uint40: %v", err) |
||||
} |
||||
|
||||
idHash := new(types2.IdHash) |
||||
err = idHash.FromId(ev.ID) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create IdHash: %v", err) |
||||
} |
||||
|
||||
// Verify one of the tag-related indexes (e tag)
|
||||
pubHash := new(types2.PubHash) |
||||
err = pubHash.FromPubkey(ev.Pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
createdAt := new(types2.Uint64) |
||||
createdAt.Set(uint64(ev.CreatedAt)) |
||||
|
||||
// Create tag key and value for e tag
|
||||
eKey := new(types2.Letter) |
||||
eKey.Set('e') |
||||
|
||||
eValueHash := new(types2.Ident) |
||||
eValueHash.FromIdent([]byte("abcdef1234567890")) |
||||
|
||||
// Verify TagPubkey index for e tag
|
||||
pubkeyTagIndex := indexes.TagPubkeyEnc( |
||||
eKey, eValueHash, pubHash, createdAt, ser, |
||||
) |
||||
verifyIndexIncluded(t, idxs, pubkeyTagIndex) |
||||
|
||||
// Verify Tag index for e tag
|
||||
tagIndex := indexes.TagEnc( |
||||
eKey, eValueHash, createdAt, ser, |
||||
) |
||||
verifyIndexIncluded(t, idxs, tagIndex) |
||||
|
||||
// Verify TagKind index for e tag
|
||||
kind := new(types2.Uint16) |
||||
kind.Set(ev.Kind) |
||||
|
||||
kindTagIndex := indexes.TagKindEnc(eKey, eValueHash, kind, createdAt, ser) |
||||
verifyIndexIncluded(t, idxs, kindTagIndex) |
||||
|
||||
// Verify TagKindPubkey index for e tag
|
||||
kindPubkeyTagIndex := indexes.TagKindPubkeyEnc( |
||||
eKey, eValueHash, kind, pubHash, createdAt, ser, |
||||
) |
||||
verifyIndexIncluded(t, idxs, kindPubkeyTagIndex) |
||||
} |
||||
|
||||
// Test error handling
|
||||
func testErrorHandling(t *testing.T) { |
||||
// Test with invalid serial number (too large for Uint40)
|
||||
ev := event.New() |
||||
|
||||
// Set ID
|
||||
id := make([]byte, sha256.Size) |
||||
for i := range id { |
||||
id[i] = byte(i) |
||||
} |
||||
ev.ID = id |
||||
|
||||
// Set Pubkey
|
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i + 1) |
||||
} |
||||
ev.Pubkey = pubkey |
||||
|
||||
// Set CreatedAt
|
||||
ev.CreatedAt = 12345 |
||||
|
||||
// Set Kind
|
||||
ev.Kind = kind.TextNote.K |
||||
|
||||
// Set Content
|
||||
ev.Content = []byte("Test content") |
||||
|
||||
// Use an invalid serial number (too large for Uint40)
|
||||
invalidSerial := uint64(1) << 40 // 2^40, which is too large for Uint40
|
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesForEvent(ev, invalidSerial) |
||||
|
||||
// Verify that an error was returned
|
||||
if err == nil { |
||||
t.Fatalf("Expected error for invalid serial number, got nil") |
||||
} |
||||
|
||||
// Verify that idxs is nil when an error occurs
|
||||
if idxs != nil { |
||||
t.Fatalf("Expected nil idxs when error occurs, got %v", idxs) |
||||
} |
||||
|
||||
// Note: We don't test with nil event as it causes a panic
|
||||
// The function doesn't have nil checks, which is a potential improvement
|
||||
} |
||||
@ -0,0 +1,388 @@
@@ -0,0 +1,388 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math" |
||||
"sort" |
||||
|
||||
"database.orly/indexes" |
||||
types2 "database.orly/indexes/types" |
||||
"encoders.orly/filter" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
type Range struct { |
||||
Start, End []byte |
||||
} |
||||
|
||||
// IsHexString checks if the byte slice contains only hex characters
|
||||
func IsHexString(data []byte) (isHex bool) { |
||||
if len(data)%2 != 0 { |
||||
return false |
||||
} |
||||
for _, b := range data { |
||||
if !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'f') || (b >= 'A' && b <= 'F')) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// CreateIdHashFromData creates an IdHash from data that could be hex or binary
|
||||
func CreateIdHashFromData(data []byte) (i *types2.IdHash, err error) { |
||||
i = new(types2.IdHash) |
||||
|
||||
// If data looks like hex string and has the right length for hex-encoded
|
||||
// sha256
|
||||
if len(data) == 64 { |
||||
if err = i.FromIdHex(string(data)); chk.E(err) { |
||||
err = nil |
||||
} else { |
||||
return |
||||
} |
||||
} |
||||
// Assume it's binary data
|
||||
if err = i.FromId(data); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// CreatePubHashFromData creates a PubHash from data that could be hex or binary
|
||||
func CreatePubHashFromData(data []byte) (p *types2.PubHash, err error) { |
||||
p = new(types2.PubHash) |
||||
|
||||
// If data looks like hex string and has the right length for hex-encoded
|
||||
// pubkey
|
||||
if len(data) == 64 { |
||||
if err = p.FromPubkeyHex(string(data)); chk.E(err) { |
||||
err = nil |
||||
} else { |
||||
return |
||||
} |
||||
} else { |
||||
// Assume it's binary data
|
||||
if err = p.FromPubkey(data); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// GetIndexesFromFilter returns encoded indexes based on the given filter.
|
||||
//
|
||||
// An error is returned if any input values are invalid during encoding.
|
||||
//
|
||||
// The indexes are designed so that only one table needs to be iterated, being a
|
||||
// complete set of combinations of all fields in the event, thus there is no
|
||||
// need to decode events until they are to be delivered.
|
||||
func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) { |
||||
// ID eid
|
||||
//
|
||||
// If there is any Ids in the filter, none of the other fields matter. It
|
||||
// should be an error, but convention just ignores it.
|
||||
if f.Ids.Len() > 0 { |
||||
for _, id := range f.Ids.ToSliceOfBytes() { |
||||
if err = func() (err error) { |
||||
var i *types2.IdHash |
||||
if i, err = CreateIdHashFromData(id); chk.E(err) { |
||||
return |
||||
} |
||||
buf := new(bytes.Buffer) |
||||
idx := indexes.IdEnc(i, nil) |
||||
if err = idx.MarshalWrite(buf); chk.E(err) { |
||||
return |
||||
} |
||||
b := buf.Bytes() |
||||
r := Range{b, b} |
||||
idxs = append(idxs, r) |
||||
return |
||||
}(); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
caStart := new(types2.Uint64) |
||||
caEnd := new(types2.Uint64) |
||||
|
||||
// Set the start of range (Since or default to zero)
|
||||
if f.Since != nil && f.Since.V != 0 { |
||||
caStart.Set(uint64(f.Since.V)) |
||||
} else { |
||||
caStart.Set(uint64(0)) |
||||
} |
||||
|
||||
// Set the end of range (Until or default to math.MaxInt64)
|
||||
if f.Until != nil && f.Until.V != 0 { |
||||
caEnd.Set(uint64(f.Until.V)) |
||||
} else { |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
} |
||||
|
||||
if f.Tags != nil && f.Tags.Len() > 0 { |
||||
// sort the tags so they are in iteration order (reverse)
|
||||
tmp := f.Tags.ToSliceOfTags() |
||||
sort.Slice( |
||||
tmp, func(i, j int) bool { |
||||
return bytes.Compare(tmp[i].Key(), tmp[j].Key()) > 0 |
||||
}, |
||||
) |
||||
} |
||||
|
||||
// TagKindPubkey tkp
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Authors != nil && f.Authors.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 { |
||||
for _, k := range f.Kinds.ToUint16() { |
||||
for _, author := range f.Authors.ToSliceOfBytes() { |
||||
for _, tag := range f.Tags.ToSliceOfTags() { |
||||
// accept single-letter keys like "e" or filter-style keys like "#e"
|
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) { |
||||
kind := new(types2.Uint16) |
||||
kind.Set(k) |
||||
var p *types2.PubHash |
||||
if p, err = CreatePubHashFromData(author); chk.E(err) { |
||||
return |
||||
} |
||||
keyBytes := tag.Key() |
||||
key := new(types2.Letter) |
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' { |
||||
key.Set(keyBytes[1]) |
||||
} else { |
||||
key.Set(keyBytes[0]) |
||||
} |
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] { |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent(valueBytes) |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.TagKindPubkeyEnc( |
||||
key, valueHash, kind, p, caStart, nil, |
||||
) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.TagKindPubkeyEnc( |
||||
key, valueHash, kind, p, caEnd, nil, |
||||
) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{ |
||||
start.Bytes(), end.Bytes(), |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// TagKind tkc
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 { |
||||
for _, k := range f.Kinds.ToUint16() { |
||||
for _, tag := range f.Tags.ToSliceOfTags() { |
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) { |
||||
kind := new(types2.Uint16) |
||||
kind.Set(k) |
||||
keyBytes := tag.Key() |
||||
key := new(types2.Letter) |
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' { |
||||
key.Set(keyBytes[1]) |
||||
} else { |
||||
key.Set(keyBytes[0]) |
||||
} |
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] { |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent(valueBytes) |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.TagKindEnc( |
||||
key, valueHash, kind, caStart, nil, |
||||
) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.TagKindEnc( |
||||
key, valueHash, kind, caEnd, nil, |
||||
) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{ |
||||
start.Bytes(), end.Bytes(), |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// TagPubkey tpc
|
||||
if f.Authors != nil && f.Authors.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 { |
||||
for _, author := range f.Authors.ToSliceOfBytes() { |
||||
for _, tag := range f.Tags.ToSliceOfTags() { |
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) { |
||||
var p *types2.PubHash |
||||
if p, err = CreatePubHashFromData(author); chk.E(err) { |
||||
return |
||||
} |
||||
keyBytes := tag.Key() |
||||
key := new(types2.Letter) |
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' { |
||||
key.Set(keyBytes[1]) |
||||
} else { |
||||
key.Set(keyBytes[0]) |
||||
} |
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] { |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent(valueBytes) |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.TagPubkeyEnc( |
||||
key, valueHash, p, caStart, nil, |
||||
) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.TagPubkeyEnc( |
||||
key, valueHash, p, caEnd, nil, |
||||
) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Tag tc-
|
||||
if f.Tags != nil && f.Tags.Len() > 0 && (f.Authors == nil || f.Authors.Len() == 0) && (f.Kinds == nil || f.Kinds.Len() == 0) { |
||||
for _, tag := range f.Tags.ToSliceOfTags() { |
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) { |
||||
keyBytes := tag.Key() |
||||
key := new(types2.Letter) |
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' { |
||||
key.Set(keyBytes[1]) |
||||
} else { |
||||
key.Set(keyBytes[0]) |
||||
} |
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] { |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent(valueBytes) |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.TagEnc(key, valueHash, caStart, nil) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.TagEnc(key, valueHash, caEnd, nil) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// KindPubkey kpc
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Authors != nil && f.Authors.Len() > 0 { |
||||
for _, k := range f.Kinds.ToUint16() { |
||||
for _, author := range f.Authors.ToSliceOfBytes() { |
||||
kind := new(types2.Uint16) |
||||
kind.Set(k) |
||||
p := new(types2.PubHash) |
||||
if err = p.FromPubkey(author); chk.E(err) { |
||||
return |
||||
} |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.KindPubkeyEnc(kind, p, caStart, nil) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.KindPubkeyEnc(kind, p, caEnd, nil) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Kind kc-
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && (f.Authors == nil || f.Authors.Len() == 0) && (f.Tags == nil || f.Tags.Len() == 0) { |
||||
for _, k := range f.Kinds.ToUint16() { |
||||
kind := new(types2.Uint16) |
||||
kind.Set(k) |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.KindEnc(kind, caStart, nil) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.KindEnc(kind, caEnd, nil) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Pubkey pc-
|
||||
if f.Authors != nil && f.Authors.Len() > 0 { |
||||
for _, author := range f.Authors.ToSliceOfBytes() { |
||||
p := new(types2.PubHash) |
||||
if err = p.FromPubkey(author); chk.E(err) { |
||||
return |
||||
} |
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.PubkeyEnc(p, caStart, nil) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.PubkeyEnc(p, caEnd, nil) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// CreatedAt c--
|
||||
start, end := new(bytes.Buffer), new(bytes.Buffer) |
||||
idxS := indexes.CreatedAtEnc(caStart, nil) |
||||
if err = idxS.MarshalWrite(start); chk.E(err) { |
||||
return |
||||
} |
||||
idxE := indexes.CreatedAtEnc(caEnd, nil) |
||||
if err = idxE.MarshalWrite(end); chk.E(err) { |
||||
return |
||||
} |
||||
idxs = append( |
||||
idxs, Range{start.Bytes(), end.Bytes()}, |
||||
) |
||||
return |
||||
} |
||||
@ -0,0 +1,587 @@
@@ -0,0 +1,587 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math" |
||||
"testing" |
||||
|
||||
"crypto.orly/sha256" |
||||
"database.orly/indexes" |
||||
types2 "database.orly/indexes/types" |
||||
"encoders.orly/filter" |
||||
"encoders.orly/kind" |
||||
"encoders.orly/tag" |
||||
"encoders.orly/timestamp" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
) |
||||
|
||||
// TestGetIndexesFromFilter tests the GetIndexesFromFilter function
|
||||
func TestGetIndexesFromFilter(t *testing.T) { |
||||
t.Run("ID", testIdFilter) |
||||
t.Run("Pubkey", testPubkeyFilter) |
||||
t.Run("CreatedAt", testCreatedAtFilter) |
||||
t.Run("CreatedAtUntil", testCreatedAtUntilFilter) |
||||
t.Run("TagPubkey", testPubkeyTagFilter) |
||||
t.Run("Tag", testTagFilter) |
||||
t.Run("Kind", testKindFilter) |
||||
t.Run("KindPubkey", testKindPubkeyFilter) |
||||
t.Run("MultipleKindPubkey", testMultipleKindPubkeyFilter) |
||||
t.Run("TagKind", testKindTagFilter) |
||||
t.Run("TagKindPubkey", testKindPubkeyTagFilter) |
||||
} |
||||
|
||||
// Helper function to verify that the generated index matches the expected indexes
|
||||
func verifyIndex( |
||||
t *testing.T, idxs []Range, expectedStartIdx, expectedEndIdx *indexes.T, |
||||
) { |
||||
if len(idxs) != 1 { |
||||
t.Fatalf("Expected 1 index, got %d", len(idxs)) |
||||
} |
||||
|
||||
// Marshal the expected start index
|
||||
startBuf := new(bytes.Buffer) |
||||
err := expectedStartIdx.MarshalWrite(startBuf) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to marshal expected start index: %v", err) |
||||
} |
||||
|
||||
// Compare the generated start index with the expected start index
|
||||
if !utils.FastEqual(idxs[0].Start, startBuf.Bytes()) { |
||||
t.Errorf("Generated start index does not match expected start index") |
||||
t.Errorf("Generated: %v", idxs[0].Start) |
||||
t.Errorf("Expected: %v", startBuf.Bytes()) |
||||
} |
||||
|
||||
// If expectedEndIdx is nil, use expectedStartIdx
|
||||
endIdx := expectedEndIdx |
||||
if endIdx == nil { |
||||
endIdx = expectedStartIdx |
||||
} |
||||
|
||||
// Marshal the expected end index
|
||||
endBuf := new(bytes.Buffer) |
||||
err = endIdx.MarshalWrite(endBuf) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to marshal expected End index: %v", err) |
||||
} |
||||
|
||||
// Compare the generated end index with the expected end index
|
||||
if !utils.FastEqual(idxs[0].End, endBuf.Bytes()) { |
||||
t.Errorf("Generated End index does not match expected End index") |
||||
t.Errorf("Generated: %v", idxs[0].End) |
||||
t.Errorf("Expected: %v", endBuf.Bytes()) |
||||
} |
||||
} |
||||
|
||||
// Test ID filter
|
||||
func testIdFilter(t *testing.T) { |
||||
// Create a filter with an ID
|
||||
f := filter.New() |
||||
id := make([]byte, sha256.Size) |
||||
for i := range id { |
||||
id[i] = byte(i) |
||||
} |
||||
f.Ids.T = append(f.Ids.T, id) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected index
|
||||
idHash := new(types2.IdHash) |
||||
err = idHash.FromId(id) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create IdHash: %v", err) |
||||
} |
||||
expectedIdx := indexes.IdEnc(idHash, nil) |
||||
|
||||
// Verify the generated index
|
||||
// For ID filter, both start and end indexes are the same
|
||||
verifyIndex(t, idxs, expectedIdx, expectedIdx) |
||||
} |
||||
|
||||
// Test Pubkey filter
|
||||
func testPubkeyFilter(t *testing.T) { |
||||
// Create a filter with an Author, Since, and Until
|
||||
f := filter.New() |
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i) |
||||
} |
||||
f.Authors.T = append(f.Authors.T, pubkey) |
||||
f.Since = timestamp.FromUnix(12345) |
||||
f.Until = timestamp.FromUnix(67890) // Added Until field
|
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
p := new(types2.PubHash) |
||||
err = p.FromPubkey(pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.PubkeyEnc(p, caStart, nil) |
||||
|
||||
// End index uses Until
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(f.Until.V)) |
||||
expectedEndIdx := indexes.PubkeyEnc(p, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test CreatedAt filter
|
||||
func testCreatedAtFilter(t *testing.T) { |
||||
// Create a filter with Since
|
||||
f := filter.New() |
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected start index (using Since)
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.CreatedAtEnc(caStart, nil) |
||||
|
||||
// Create the expected end index (using math.MaxInt64 since Until is not specified)
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.CreatedAtEnc(caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test CreatedAt filter with Until
|
||||
func testCreatedAtUntilFilter(t *testing.T) { |
||||
// Create a filter with Until
|
||||
f := filter.New() |
||||
f.Until = timestamp.FromUnix(67890) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected start index (using 0 since Since is not specified)
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(0)) |
||||
expectedStartIdx := indexes.CreatedAtEnc(caStart, nil) |
||||
|
||||
// Create the expected end index (using Until)
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(f.Until.V)) |
||||
expectedEndIdx := indexes.CreatedAtEnc(caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test TagPubkey filter
|
||||
func testPubkeyTagFilter(t *testing.T) { |
||||
// Create a filter with an Author, a Tag, and Since
|
||||
f := filter.New() |
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i) |
||||
} |
||||
f.Authors.T = append(f.Authors.T, pubkey) |
||||
// Create a tag
|
||||
tagKey := "e" |
||||
tagValue := "test-value" |
||||
tagT := tag.NewFromAny(tagKey, tagValue) |
||||
*f.Tags = append(*f.Tags, tagT) |
||||
|
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
p := new(types2.PubHash) |
||||
err = p.FromPubkey(pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
key := new(types2.Letter) |
||||
key.Set(tagKey[0]) |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent([]byte(tagValue)) |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.TagPubkeyEnc(key, valueHash, p, caStart, nil) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.TagPubkeyEnc(key, valueHash, p, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test Tag filter
|
||||
func testTagFilter(t *testing.T) { |
||||
// Create a filter with a Tag and Since
|
||||
f := filter.New() |
||||
|
||||
// Create a tag
|
||||
tagKey := "e" |
||||
tagValue := "test-value" |
||||
tagT := tag.NewFromAny(tagKey, tagValue) |
||||
*f.Tags = append(*f.Tags, tagT) |
||||
|
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
key := new(types2.Letter) |
||||
key.Set(tagKey[0]) |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent([]byte(tagValue)) |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.TagEnc(key, valueHash, caStart, nil) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.TagEnc(key, valueHash, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test Kind filter
|
||||
func testKindFilter(t *testing.T) { |
||||
// Create a filter with a Kind and Since
|
||||
f := filter.New() |
||||
f.Kinds = kind.NewS(kind.TextNote) |
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
k := new(types2.Uint16) |
||||
k.Set(1) |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.KindEnc(k, caStart, nil) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.KindEnc(k, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test KindPubkey filter
|
||||
func testKindPubkeyFilter(t *testing.T) { |
||||
// Create a filter with a Kind, an Author, and Since
|
||||
f := filter.New() |
||||
f.Kinds = kind.NewS(kind.TextNote) |
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i) |
||||
} |
||||
f.Authors.T = append(f.Authors.T, pubkey) |
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
k := new(types2.Uint16) |
||||
k.Set(1) |
||||
p := new(types2.PubHash) |
||||
err = p.FromPubkey(pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.KindPubkeyEnc(k, p, caStart, nil) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.KindPubkeyEnc(k, p, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test TagKind filter
|
||||
func testKindTagFilter(t *testing.T) { |
||||
// Create a filter with a Kind, a Tag, and Since
|
||||
f := filter.New() |
||||
f.Kinds = kind.NewS(kind.TextNote) |
||||
|
||||
// Create a tag
|
||||
tagKey := "e" |
||||
tagValue := "test-value" |
||||
tagT := tag.NewFromAny(tagKey, tagValue) |
||||
*f.Tags = append(*f.Tags, tagT) |
||||
|
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
k := new(types2.Uint16) |
||||
k.Set(1) |
||||
key := new(types2.Letter) |
||||
key.Set(tagKey[0]) |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent([]byte(tagValue)) |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.TagKindEnc(key, valueHash, k, caStart, nil) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.TagKindEnc(key, valueHash, k, caEnd, nil) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
|
||||
// Test Multiple KindPubkey filter
|
||||
func testMultipleKindPubkeyFilter(t *testing.T) { |
||||
// Create a filter with multiple Kinds and multiple Authors
|
||||
f := filter.New() |
||||
f.Kinds = kind.NewS(kind.New(1), kind.New(2)) |
||||
|
||||
// Create two pubkeys
|
||||
pubkey1 := make([]byte, 32) |
||||
pubkey2 := make([]byte, 32) |
||||
for i := range pubkey1 { |
||||
pubkey1[i] = byte(i) |
||||
pubkey2[i] = byte(i + 100) |
||||
} |
||||
f.Authors.T = append(f.Authors.T, pubkey1) |
||||
f.Authors.T = append(f.Authors.T, pubkey2) |
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// We should have 4 indexes (2 kinds * 2 pubkeys)
|
||||
if len(idxs) != 4 { |
||||
t.Fatalf("Expected 4 indexes, got %d", len(idxs)) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
k1 := new(types2.Uint16) |
||||
k1.Set(1) |
||||
k2 := new(types2.Uint16) |
||||
k2.Set(2) |
||||
|
||||
p1 := new(types2.PubHash) |
||||
err = p1.FromPubkey(pubkey1) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
p2 := new(types2.PubHash) |
||||
err = p2.FromPubkey(pubkey2) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
|
||||
// Create all expected combinations
|
||||
expectedIdxs := make([][]byte, 8) // 4 combinations * 2 (start/end)
|
||||
|
||||
// Kind 1, Pubkey 1
|
||||
startBuf1 := new(bytes.Buffer) |
||||
idxS1 := indexes.KindPubkeyEnc(k1, p1, caStart, nil) |
||||
if err = idxS1.MarshalWrite(startBuf1); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[0] = startBuf1.Bytes() |
||||
|
||||
endBuf1 := new(bytes.Buffer) |
||||
idxE1 := indexes.KindPubkeyEnc(k1, p1, caEnd, nil) |
||||
if err = idxE1.MarshalWrite(endBuf1); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[1] = endBuf1.Bytes() |
||||
|
||||
// Kind 1, Pubkey 2
|
||||
startBuf2 := new(bytes.Buffer) |
||||
idxS2 := indexes.KindPubkeyEnc(k1, p2, caStart, nil) |
||||
if err = idxS2.MarshalWrite(startBuf2); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[2] = startBuf2.Bytes() |
||||
|
||||
endBuf2 := new(bytes.Buffer) |
||||
idxE2 := indexes.KindPubkeyEnc(k1, p2, caEnd, nil) |
||||
if err = idxE2.MarshalWrite(endBuf2); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[3] = endBuf2.Bytes() |
||||
|
||||
// Kind 2, Pubkey 1
|
||||
startBuf3 := new(bytes.Buffer) |
||||
idxS3 := indexes.KindPubkeyEnc(k2, p1, caStart, nil) |
||||
if err = idxS3.MarshalWrite(startBuf3); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[4] = startBuf3.Bytes() |
||||
|
||||
endBuf3 := new(bytes.Buffer) |
||||
idxE3 := indexes.KindPubkeyEnc(k2, p1, caEnd, nil) |
||||
if err = idxE3.MarshalWrite(endBuf3); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[5] = endBuf3.Bytes() |
||||
|
||||
// Kind 2, Pubkey 2
|
||||
startBuf4 := new(bytes.Buffer) |
||||
idxS4 := indexes.KindPubkeyEnc(k2, p2, caStart, nil) |
||||
if err = idxS4.MarshalWrite(startBuf4); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[6] = startBuf4.Bytes() |
||||
|
||||
endBuf4 := new(bytes.Buffer) |
||||
idxE4 := indexes.KindPubkeyEnc(k2, p2, caEnd, nil) |
||||
if err = idxE4.MarshalWrite(endBuf4); chk.E(err) { |
||||
t.Fatalf("Failed to marshal index: %v", err) |
||||
} |
||||
expectedIdxs[7] = endBuf4.Bytes() |
||||
|
||||
// Verify that all expected combinations are present
|
||||
foundCombinations := 0 |
||||
for _, idx := range idxs { |
||||
for i := 0; i < len(expectedIdxs); i += 2 { |
||||
if utils.FastEqual(idx.Start, expectedIdxs[i]) && utils.FastEqual( |
||||
idx.End, expectedIdxs[i+1], |
||||
) { |
||||
foundCombinations++ |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
if foundCombinations != 4 { |
||||
t.Fatalf("Expected to find 4 combinations, found %d", foundCombinations) |
||||
} |
||||
} |
||||
|
||||
// Test TagKindPubkey filter
|
||||
func testKindPubkeyTagFilter(t *testing.T) { |
||||
// Create a filter with a Kind, an Author, a Tag, and Since
|
||||
f := filter.New() |
||||
f.Kinds = kind.NewS(kind.New(1)) |
||||
pubkey := make([]byte, 32) |
||||
for i := range pubkey { |
||||
pubkey[i] = byte(i) |
||||
} |
||||
f.Authors.T = append(f.Authors.T, pubkey) |
||||
|
||||
// Create a tag
|
||||
tagKey := "e" |
||||
tagValue := "test-value" |
||||
tagT := tag.NewFromAny(tagKey, tagValue) |
||||
*f.Tags = append(*f.Tags, tagT) |
||||
|
||||
f.Since = timestamp.FromUnix(12345) |
||||
|
||||
// Generate indexes
|
||||
idxs, err := GetIndexesFromFilter(f) |
||||
if chk.E(err) { |
||||
t.Fatalf("GetIndexesFromFilter failed: %v", err) |
||||
} |
||||
|
||||
// Create the expected indexes
|
||||
k := new(types2.Uint16) |
||||
k.Set(1) |
||||
p := new(types2.PubHash) |
||||
err = p.FromPubkey(pubkey) |
||||
if chk.E(err) { |
||||
t.Fatalf("Failed to create PubHash: %v", err) |
||||
} |
||||
key := new(types2.Letter) |
||||
key.Set(tagKey[0]) |
||||
valueHash := new(types2.Ident) |
||||
valueHash.FromIdent([]byte(tagValue)) |
||||
|
||||
// Start index uses Since
|
||||
caStart := new(types2.Uint64) |
||||
caStart.Set(uint64(f.Since.V)) |
||||
expectedStartIdx := indexes.TagKindPubkeyEnc( |
||||
key, valueHash, k, p, caStart, nil, |
||||
) |
||||
|
||||
// End index uses math.MaxInt64 since Until is not specified
|
||||
caEnd := new(types2.Uint64) |
||||
caEnd.Set(uint64(math.MaxInt64)) |
||||
expectedEndIdx := indexes.TagKindPubkeyEnc( |
||||
key, valueHash, k, p, caEnd, nil, |
||||
) |
||||
|
||||
// Verify the generated index
|
||||
verifyIndex(t, idxs, expectedStartIdx, expectedEndIdx) |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
|
||||
"database.orly/indexes/types" |
||||
"encoders.orly/filter" |
||||
"encoders.orly/tag" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/errorf" |
||||
) |
||||
|
||||
func (d *D) GetSerialById(id []byte) (ser *types.Uint40, err error) { |
||||
var idxs []Range |
||||
if idxs, err = GetIndexesFromFilter(&filter.F{Ids: tag.NewFromBytesSlice(id)}); chk.E(err) { |
||||
return |
||||
} |
||||
if len(idxs) == 0 { |
||||
err = errorf.E("no indexes found for id %0x", id) |
||||
} |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
it := txn.NewIterator(badger.DefaultIteratorOptions) |
||||
var key []byte |
||||
defer it.Close() |
||||
it.Seek(idxs[0].Start) |
||||
if it.ValidForPrefix(idxs[0].Start) { |
||||
item := it.Item() |
||||
key = item.Key() |
||||
ser = new(types.Uint40) |
||||
buf := bytes.NewBuffer(key[len(key)-5:]) |
||||
if err = ser.UnmarshalRead(buf); chk.E(err) { |
||||
return |
||||
} |
||||
} else { |
||||
// just don't return what we don't have? others may be
|
||||
// found tho.
|
||||
} |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
//
|
||||
// func (d *D) GetSerialBytesById(id []byte) (ser []byte, err error) {
|
||||
// var idxs []Range
|
||||
// if idxs, err = GetIndexesFromFilter(&filter.F{Ids: tag.New(id)}); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
// if len(idxs) == 0 {
|
||||
// err = errorf.E("no indexes found for id %0x", id)
|
||||
// }
|
||||
// if err = d.View(
|
||||
// func(txn *badger.Txn) (err error) {
|
||||
// it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
// var key []byte
|
||||
// defer it.Close()
|
||||
// it.Seek(idxs[0].Start)
|
||||
// if it.ValidForPrefix(idxs[0].Start) {
|
||||
// item := it.Item()
|
||||
// key = item.Key()
|
||||
// ser = key[len(key)-5:]
|
||||
// } else {
|
||||
// // just don't return what we don't have? others may be
|
||||
// // found tho.
|
||||
// }
|
||||
// return
|
||||
// },
|
||||
// ); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"os" |
||||
"testing" |
||||
|
||||
"encoders.orly/event" |
||||
"encoders.orly/event/examples" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
func TestGetSerialById(t *testing.T) { |
||||
// Create a temporary directory for the database
|
||||
tempDir, err := os.MkdirTemp("", "test-db-*") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create temporary directory: %v", err) |
||||
} |
||||
defer os.RemoveAll(tempDir) // Clean up after the test
|
||||
|
||||
// Create a context and cancel function for the database
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
// Initialize the database
|
||||
db, err := New(ctx, cancel, tempDir, "info") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create database: %v", err) |
||||
} |
||||
defer db.Close() |
||||
|
||||
// Create a scanner to read events from examples.Cache
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache)) |
||||
scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) |
||||
|
||||
// Count the number of events processed
|
||||
eventCount := 0 |
||||
|
||||
var events []*event.E |
||||
|
||||
// Process each event
|
||||
for scanner.Scan() { |
||||
chk.E(scanner.Err()) |
||||
b := scanner.Bytes() |
||||
ev := event.New() |
||||
|
||||
// Unmarshal the event
|
||||
if _, err = ev.Unmarshal(b); chk.E(err) { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
events = append(events, ev) |
||||
|
||||
// Save the event to the database
|
||||
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil { |
||||
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err) |
||||
} |
||||
|
||||
eventCount++ |
||||
} |
||||
|
||||
// Check for scanner errors
|
||||
if err = scanner.Err(); err != nil { |
||||
t.Fatalf("Scanner error: %v", err) |
||||
} |
||||
|
||||
t.Logf("Successfully saved %d events to the database", eventCount) |
||||
|
||||
// Test GetSerialById with a known event ID
|
||||
testEvent := events[3] // Using the same event as in QueryForIds test
|
||||
|
||||
// Get the serial by ID
|
||||
serial, err := db.GetSerialById(testEvent.ID) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get serial by ID: %v", err) |
||||
} |
||||
|
||||
// Verify the serial is not nil
|
||||
if serial == nil { |
||||
t.Fatal("Expected serial to be non-nil, but got nil") |
||||
} |
||||
|
||||
// Test with a non-existent ID
|
||||
nonExistentId := make([]byte, len(testEvent.ID)) |
||||
// Ensure it's different from any real ID
|
||||
for i := range nonExistentId { |
||||
nonExistentId[i] = ^testEvent.ID[i] |
||||
} |
||||
|
||||
serial, err = db.GetSerialById(nonExistentId) |
||||
if err != nil { |
||||
t.Fatalf("Expected no error for non-existent ID, but got: %v", err) |
||||
} |
||||
|
||||
// For non-existent Ids, the function should return nil serial
|
||||
if serial != nil { |
||||
t.Fatalf("Expected nil serial for non-existent ID, but got: %v", serial) |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bytes" |
||||
"sort" |
||||
|
||||
"database.orly/indexes/types" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
func (d *D) GetSerialsByRange(idx Range) ( |
||||
sers types.Uint40s, err error, |
||||
) { |
||||
if err = d.View( |
||||
func(txn *badger.Txn) (err error) { |
||||
it := txn.NewIterator( |
||||
badger.IteratorOptions{ |
||||
Reverse: true, |
||||
}, |
||||
) |
||||
defer it.Close() |
||||
for it.Seek(idx.End); it.Valid(); it.Next() { |
||||
item := it.Item() |
||||
var key []byte |
||||
key = item.Key() |
||||
if bytes.Compare( |
||||
key[:len(key)-5], idx.Start, |
||||
) < 0 { |
||||
// didn't find it within the timestamp range
|
||||
return |
||||
} |
||||
ser := new(types.Uint40) |
||||
buf := bytes.NewBuffer(key[len(key)-5:]) |
||||
if err = ser.UnmarshalRead(buf); chk.E(err) { |
||||
return |
||||
} |
||||
sers = append(sers, ser) |
||||
} |
||||
return |
||||
}, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
sort.Slice( |
||||
sers, func(i, j int) bool { |
||||
return sers[i].Get() < sers[j].Get() |
||||
}, |
||||
) |
||||
return |
||||
} |
||||
@ -0,0 +1,232 @@
@@ -0,0 +1,232 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"os" |
||||
"testing" |
||||
|
||||
"database.orly/indexes/types" |
||||
"encoders.orly/event" |
||||
"encoders.orly/event/examples" |
||||
"encoders.orly/filter" |
||||
"encoders.orly/kind" |
||||
"encoders.orly/tag" |
||||
"encoders.orly/timestamp" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
) |
||||
|
||||
func TestGetSerialsByRange(t *testing.T) { |
||||
// Create a temporary directory for the database
|
||||
tempDir, err := os.MkdirTemp("", "test-db-*") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create temporary directory: %v", err) |
||||
} |
||||
defer os.RemoveAll(tempDir) // Clean up after the test
|
||||
|
||||
// Create a context and cancel function for the database
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
// Initialize the database
|
||||
db, err := New(ctx, cancel, tempDir, "info") |
||||
if err != nil { |
||||
t.Fatalf("Failed to create database: %v", err) |
||||
} |
||||
defer db.Close() |
||||
|
||||
// Create a scanner to read events from examples.Cache
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache)) |
||||
scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000) |
||||
|
||||
// Count the number of events processed
|
||||
eventCount := 0 |
||||
|
||||
var events []*event.E |
||||
var eventSerials = make(map[string]*types.Uint40) // Map event ID (hex) to serial
|
||||
|
||||
// Process each event
|
||||
for scanner.Scan() { |
||||
chk.E(scanner.Err()) |
||||
b := scanner.Bytes() |
||||
ev := event.New() |
||||
|
||||
// Unmarshal the event
|
||||
if _, err = ev.Unmarshal(b); chk.E(err) { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
events = append(events, ev) |
||||
|
||||
// Save the event to the database
|
||||
if _, _, err = db.SaveEvent(ctx, ev, false, nil); err != nil { |
||||
t.Fatalf("Failed to save event #%d: %v", eventCount+1, err) |
||||
} |
||||
|
||||
// Get the serial for this event
|
||||
serial, err := db.GetSerialById(ev.ID) |
||||
if err != nil { |
||||
t.Fatalf( |
||||
"Failed to get serial for event #%d: %v", eventCount+1, err, |
||||
) |
||||
} |
||||
|
||||
if serial != nil { |
||||
eventSerials[string(ev.ID)] = serial |
||||
} |
||||
|
||||
eventCount++ |
||||
} |
||||
|
||||
// Check for scanner errors
|
||||
if err = scanner.Err(); err != nil { |
||||
t.Fatalf("Scanner error: %v", err) |
||||
} |
||||
|
||||
t.Logf("Successfully saved %d events to the database", eventCount) |
||||
|
||||
// Test GetSerialsByRange with a time range filter
|
||||
// Use the timestamp from the middle event as a reference
|
||||
middleIndex := len(events) / 2 |
||||
middleEvent := events[middleIndex] |
||||
|
||||
// Create a timestamp range that includes events before and after the middle event
|
||||
sinceTime := new(timestamp.T) |
||||
sinceTime.V = middleEvent.CreatedAt - 3600 // 1 hour before middle event
|
||||
|
||||
untilTime := new(timestamp.T) |
||||
untilTime.V = middleEvent.CreatedAt + 3600 // 1 hour after middle event
|
||||
|
||||
// Create a filter with the time range
|
||||
timeFilter := &filter.F{ |
||||
Since: sinceTime, |
||||
Until: untilTime, |
||||
} |
||||
|
||||
// Get the indexes from the filter
|
||||
ranges, err := GetIndexesFromFilter(timeFilter) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get indexes from filter: %v", err) |
||||
} |
||||
|
||||
// Verify we got at least one range
|
||||
if len(ranges) == 0 { |
||||
t.Fatal("Expected at least one range from filter, but got none") |
||||
} |
||||
|
||||
// Test GetSerialsByRange with the first range
|
||||
serials, err := db.GetSerialsByRange(ranges[0]) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get serials by range: %v", err) |
||||
} |
||||
|
||||
// Verify we got results
|
||||
if len(serials) == 0 { |
||||
t.Fatal("Expected serials for events in time range, but got none") |
||||
} |
||||
|
||||
// Verify the serials correspond to events within the time range
|
||||
for i, serial := range serials { |
||||
// Fetch the event using the serial
|
||||
ev, err := db.FetchEventBySerial(serial) |
||||
if err != nil { |
||||
t.Fatalf("Failed to fetch event for serial %d: %v", i, err) |
||||
} |
||||
|
||||
if ev.CreatedAt < sinceTime.V || ev.CreatedAt > untilTime.V { |
||||
t.Fatalf( |
||||
"Event %d is outside the time range. Got %d, expected between %d and %d", |
||||
i, ev.CreatedAt, sinceTime.V, untilTime.V, |
||||
) |
||||
} |
||||
} |
||||
|
||||
// Test GetSerialsByRange with a kind filter
|
||||
testKind := kind.New(1) // Kind 1 is typically text notes
|
||||
kindFilter := &filter.F{ |
||||
Kinds: kind.NewS(testKind), |
||||
} |
||||
|
||||
// Get the indexes from the filter
|
||||
ranges, err = GetIndexesFromFilter(kindFilter) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get indexes from filter: %v", err) |
||||
} |
||||
|
||||
// Verify we got at least one range
|
||||
if len(ranges) == 0 { |
||||
t.Fatal("Expected at least one range from filter, but got none") |
||||
} |
||||
|
||||
// Test GetSerialsByRange with the first range
|
||||
serials, err = db.GetSerialsByRange(ranges[0]) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get serials by range: %v", err) |
||||
} |
||||
|
||||
// Verify we got results
|
||||
if len(serials) == 0 { |
||||
t.Fatal("Expected serials for events with kind 1, but got none") |
||||
} |
||||
|
||||
// Verify the serials correspond to events with the correct kind
|
||||
for i, serial := range serials { |
||||
// Fetch the event using the serial
|
||||
ev, err := db.FetchEventBySerial(serial) |
||||
if err != nil { |
||||
t.Fatalf("Failed to fetch event for serial %d: %v", i, err) |
||||
} |
||||
|
||||
if ev.Kind != testKind.K { |
||||
t.Fatalf( |
||||
"Event %d has incorrect kind. Got %d, expected %d", |
||||
i, ev.Kind, testKind.K, |
||||
) |
||||
} |
||||
} |
||||
|
||||
// Test GetSerialsByRange with an author filter
|
||||
authorFilter := &filter.F{ |
||||
Authors: tag.NewFromBytesSlice(events[1].Pubkey), |
||||
} |
||||
|
||||
// Get the indexes from the filter
|
||||
ranges, err = GetIndexesFromFilter(authorFilter) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get indexes from filter: %v", err) |
||||
} |
||||
|
||||
// Verify we got at least one range
|
||||
if len(ranges) == 0 { |
||||
t.Fatal("Expected at least one range from filter, but got none") |
||||
} |
||||
|
||||
// Test GetSerialsByRange with the first range
|
||||
serials, err = db.GetSerialsByRange(ranges[0]) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get serials by range: %v", err) |
||||
} |
||||
|
||||
// Verify we got results
|
||||
if len(serials) == 0 { |
||||
t.Fatal("Expected serials for events from author, but got none") |
||||
} |
||||
|
||||
// Verify the serials correspond to events with the correct author
|
||||
for i, serial := range serials { |
||||
// Fetch the event using the serial
|
||||
ev, err := db.FetchEventBySerial(serial) |
||||
if err != nil { |
||||
t.Fatalf("Failed to fetch event for serial %d: %v", i, err) |
||||
} |
||||
|
||||
if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) { |
||||
t.Fatalf( |
||||
"Event %d has incorrect author. Got %x, expected %x", |
||||
i, ev.Pubkey, events[1].Pubkey, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
module database.orly |
||||
|
||||
go 1.25.0 |
||||
|
||||
replace ( |
||||
crypto.orly => ../crypto |
||||
encoders.orly => ../encoders |
||||
interfaces.orly => ../interfaces |
||||
next.orly.dev => ../../ |
||||
protocol.orly => ../protocol |
||||
utils.orly => ../utils |
||||
) |
||||
|
||||
require ( |
||||
crypto.orly v0.0.0-00010101000000-000000000000 |
||||
encoders.orly v0.0.0-00010101000000-000000000000 |
||||
github.com/dgraph-io/badger/v4 v4.8.0 |
||||
go.uber.org/atomic v1.11.0 |
||||
interfaces.orly v0.0.0-00010101000000-000000000000 |
||||
lol.mleku.dev v1.0.2 |
||||
lukechampine.com/frand v1.5.1 |
||||
utils.orly v0.0.0-00010101000000-000000000000 |
||||
) |
||||
|
||||
require ( |
||||
github.com/adrg/xdg v0.5.3 // indirect |
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect |
||||
github.com/davecgh/go-spew v1.1.1 // indirect |
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect |
||||
github.com/dustin/go-humanize v1.0.1 // indirect |
||||
github.com/fatih/color v1.18.0 // indirect |
||||
github.com/go-logr/logr v1.4.3 // indirect |
||||
github.com/go-logr/stdr v1.2.2 // indirect |
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect |
||||
github.com/klauspost/compress v1.18.0 // indirect |
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect |
||||
github.com/mattn/go-colorable v0.1.14 // indirect |
||||
github.com/mattn/go-isatty v0.0.20 // indirect |
||||
github.com/templexxx/cpu v0.0.1 // indirect |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect |
||||
go-simpler.org/env v0.12.0 // indirect |
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect |
||||
go.opentelemetry.io/otel v1.37.0 // indirect |
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect |
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect |
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect |
||||
golang.org/x/net v0.41.0 // indirect |
||||
golang.org/x/sys v0.35.0 // indirect |
||||
google.golang.org/protobuf v1.36.6 // indirect |
||||
next.orly.dev v0.0.0-00010101000000-000000000000 // indirect |
||||
) |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= |
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= |
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= |
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= |
||||
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= |
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= |
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= |
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= |
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= |
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= |
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= |
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= |
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= |
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= |
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= |
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= |
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= |
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= |
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= |
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= |
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= |
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= |
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= |
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= |
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= |
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= |
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= |
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= |
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= |
||||
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= |
||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ= |
||||
go-simpler.org/env v0.12.0 h1:kt/lBts0J1kjWJAnB740goNdvwNxt5emhYngL0Fzufs= |
||||
go-simpler.org/env v0.12.0/go.mod h1:cc/5Md9JCUM7LVLtN0HYjPTDcI3Q8TDaPlNTAlDU+WI= |
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= |
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= |
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= |
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= |
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= |
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= |
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= |
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= |
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= |
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= |
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= |
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= |
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= |
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= |
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= |
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= |
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
lol.mleku.dev v1.0.2 h1:bSV1hHnkmt1hq+9nSvRwN6wgcI7itbM3XRZ4dMB438c= |
||||
lol.mleku.dev v1.0.2/go.mod h1:DQ0WnmkntA9dPLCXgvtIgYt5G0HSqx3wSTLolHgWeLA= |
||||
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w= |
||||
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q= |
||||
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"bufio" |
||||
"io" |
||||
"os" |
||||
"runtime/debug" |
||||
|
||||
"encoders.orly/event" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
) |
||||
|
||||
const maxLen = 500000000 |
||||
|
||||
// Import a collection of events in line structured minified JSON format (JSONL).
|
||||
func (d *D) Import(rr io.Reader) { |
||||
// store to disk so we can return fast
|
||||
tmpPath := os.TempDir() + string(os.PathSeparator) + "orly" |
||||
os.MkdirAll(tmpPath, 0700) |
||||
tmp, err := os.CreateTemp(tmpPath, "") |
||||
if chk.E(err) { |
||||
return |
||||
} |
||||
log.I.F("buffering upload to %s", tmp.Name()) |
||||
if _, err = io.Copy(tmp, rr); chk.E(err) { |
||||
return |
||||
} |
||||
if _, err = tmp.Seek(0, 0); chk.E(err) { |
||||
return |
||||
} |
||||
|
||||
go func() { |
||||
var err error |
||||
// Create a scanner to read the buffer line by line
|
||||
scan := bufio.NewScanner(tmp) |
||||
scanBuf := make([]byte, maxLen) |
||||
scan.Buffer(scanBuf, maxLen) |
||||
|
||||
var count, total int |
||||
for scan.Scan() { |
||||
select { |
||||
case <-d.ctx.Done(): |
||||
log.I.F("context closed") |
||||
return |
||||
default: |
||||
} |
||||
|
||||
b := scan.Bytes() |
||||
total += len(b) + 1 |
||||
if len(b) < 1 { |
||||
continue |
||||
} |
||||
|
||||
ev := &event.E{} |
||||
if _, err = ev.Unmarshal(b); err != nil { |
||||
continue |
||||
} |
||||
|
||||
if _, _, err = d.SaveEvent(d.ctx, ev, false, nil); err != nil { |
||||
continue |
||||
} |
||||
|
||||
b = nil |
||||
ev = nil |
||||
count++ |
||||
if count%100 == 0 { |
||||
log.I.F("received %d events", count) |
||||
debug.FreeOSMemory() |
||||
} |
||||
} |
||||
|
||||
log.I.F("read %d bytes and saved %d events", total, count) |
||||
err = scan.Err() |
||||
if chk.E(err) { |
||||
} |
||||
|
||||
// Help garbage collection
|
||||
tmp = nil |
||||
}() |
||||
|
||||
return |
||||
} |
||||
@ -0,0 +1,439 @@
@@ -0,0 +1,439 @@
|
||||
package indexes |
||||
|
||||
import ( |
||||
"io" |
||||
"reflect" |
||||
|
||||
"database.orly/indexes/types" |
||||
"interfaces.orly/codec" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
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") |
||||
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
|
||||
|
||||
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 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 |
||||
} |
||||
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 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 |
||||
} |
||||
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) } |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
@ -0,0 +1,981 @@
@@ -0,0 +1,981 @@
|
||||
package indexes |
||||
|
||||
import ( |
||||
"bytes" |
||||
"io" |
||||
"testing" |
||||
|
||||
"database.orly/indexes/types" |
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
) |
||||
|
||||
// TestNewPrefix tests the NewPrefix function with and without arguments
|
||||
func TestNewPrefix(t *testing.T) { |
||||
// Test with no arguments (default prefix)
|
||||
defaultPrefix := NewPrefix() |
||||
if len(defaultPrefix.Bytes()) != 3 { |
||||
t.Errorf( |
||||
"Default prefix should be 3 bytes, got %d", |
||||
len(defaultPrefix.Bytes()), |
||||
) |
||||
} |
||||
|
||||
// Test with a valid prefix index
|
||||
validPrefix := NewPrefix(Event) |
||||
if string(validPrefix.Bytes()) != string(EventPrefix) { |
||||
t.Errorf("Expected prefix %q, got %q", EventPrefix, validPrefix.Bytes()) |
||||
} |
||||
|
||||
// Test with an invalid prefix index (should panic)
|
||||
defer func() { |
||||
if r := recover(); r == nil { |
||||
t.Errorf("NewPrefix should panic with invalid prefix index") |
||||
} |
||||
}() |
||||
_ = NewPrefix(-1) // This should panic
|
||||
} |
||||
|
||||
// TestPrefixMethods tests the methods of the P struct
|
||||
func TestPrefixMethods(t *testing.T) { |
||||
// Create a prefix
|
||||
prefix := NewPrefix(Event) |
||||
|
||||
// Test Bytes method
|
||||
if !utils.FastEqual(prefix.Bytes(), []byte(EventPrefix)) { |
||||
t.Errorf( |
||||
"Bytes method returned %v, expected %v", prefix.Bytes(), |
||||
[]byte(EventPrefix), |
||||
) |
||||
} |
||||
|
||||
// Test MarshalWrite method
|
||||
buf := new(bytes.Buffer) |
||||
err := prefix.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
if !utils.FastEqual(buf.Bytes(), []byte(EventPrefix)) { |
||||
t.Errorf( |
||||
"MarshalWrite wrote %v, expected %v", buf.Bytes(), |
||||
[]byte(EventPrefix), |
||||
) |
||||
} |
||||
|
||||
// Test UnmarshalRead method
|
||||
newPrefix := &P{} |
||||
err = newPrefix.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
if !utils.FastEqual(newPrefix.Bytes(), []byte(EventPrefix)) { |
||||
t.Errorf( |
||||
"UnmarshalRead read %v, expected %v", newPrefix.Bytes(), |
||||
[]byte(EventPrefix), |
||||
) |
||||
} |
||||
} |
||||
|
||||
// TestPrefixFunction tests the Prefix function
|
||||
func TestPrefixFunction(t *testing.T) { |
||||
testCases := []struct { |
||||
name string |
||||
index int |
||||
expected I |
||||
}{ |
||||
{"Event", Event, EventPrefix}, |
||||
{"ID", Id, IdPrefix}, |
||||
{"FullIdPubkey", FullIdPubkey, FullIdPubkeyPrefix}, |
||||
{"Pubkey", Pubkey, PubkeyPrefix}, |
||||
{"CreatedAt", CreatedAt, CreatedAtPrefix}, |
||||
{"TagPubkey", TagPubkey, TagPubkeyPrefix}, |
||||
{"Tag", Tag, TagPrefix}, |
||||
{"Kind", Kind, KindPrefix}, |
||||
{"KindPubkey", KindPubkey, KindPubkeyPrefix}, |
||||
{"TagKind", TagKind, TagKindPrefix}, |
||||
{ |
||||
"TagKindPubkey", TagKindPubkey, |
||||
TagKindPubkeyPrefix, |
||||
}, |
||||
{"Invalid", -1, ""}, |
||||
} |
||||
|
||||
for _, tc := range testCases { |
||||
t.Run( |
||||
tc.name, func(t *testing.T) { |
||||
result := Prefix(tc.index) |
||||
if result != tc.expected { |
||||
t.Errorf( |
||||
"Prefix(%d) = %q, expected %q", tc.index, result, |
||||
tc.expected, |
||||
) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
|
||||
// TestIdentify tests the Identify function
|
||||
func TestIdentify(t *testing.T) { |
||||
testCases := []struct { |
||||
name string |
||||
prefix I |
||||
expected int |
||||
}{ |
||||
{"Event", EventPrefix, Event}, |
||||
{"ID", IdPrefix, Id}, |
||||
{"FullIdPubkey", FullIdPubkeyPrefix, FullIdPubkey}, |
||||
{"Pubkey", PubkeyPrefix, Pubkey}, |
||||
{"CreatedAt", CreatedAtPrefix, CreatedAt}, |
||||
{"TagPubkey", TagPubkeyPrefix, TagPubkey}, |
||||
{"Tag", TagPrefix, Tag}, |
||||
{"Kind", KindPrefix, Kind}, |
||||
{"KindPubkey", KindPubkeyPrefix, KindPubkey}, |
||||
{"TagKind", TagKindPrefix, TagKind}, |
||||
{ |
||||
"TagKindPubkey", TagKindPubkeyPrefix, |
||||
TagKindPubkey, |
||||
}, |
||||
} |
||||
|
||||
for _, tc := range testCases { |
||||
t.Run( |
||||
tc.name, func(t *testing.T) { |
||||
result, err := Identify(bytes.NewReader([]byte(tc.prefix))) |
||||
if chk.E(err) { |
||||
t.Fatalf("Identify failed: %v", err) |
||||
} |
||||
if result != tc.expected { |
||||
t.Errorf( |
||||
"Identify(%q) = %d, expected %d", tc.prefix, result, |
||||
tc.expected, |
||||
) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
|
||||
// Test with invalid data
|
||||
t.Run( |
||||
"Invalid", func(t *testing.T) { |
||||
result, err := Identify(bytes.NewReader([]byte("xyz"))) |
||||
if chk.E(err) { |
||||
t.Fatalf("Identify failed: %v", err) |
||||
} |
||||
if result != 0 { |
||||
t.Errorf( |
||||
"Identify with invalid prefix should return 0, got %d", |
||||
result, |
||||
) |
||||
} |
||||
}, |
||||
) |
||||
|
||||
// Test with error from reader
|
||||
t.Run( |
||||
"ReaderError", func(t *testing.T) { |
||||
errReader := &errorReader{} |
||||
result, err := Identify(errReader) |
||||
if err == nil { |
||||
t.Errorf("Identify should return error with failing reader") |
||||
} |
||||
if result != -1 { |
||||
t.Errorf( |
||||
"Identify with reader error should return -1, got %d", |
||||
result, |
||||
) |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
|
||||
// errorReader is a mock reader that always returns an error
|
||||
type errorReader struct{} |
||||
|
||||
func (e *errorReader) Read(p []byte) (n int, err error) { |
||||
return 0, io.ErrUnexpectedEOF |
||||
} |
||||
|
||||
// TestTStruct tests the T struct and its methods
|
||||
func TestTStruct(t *testing.T) { |
||||
// Create some test encoders
|
||||
prefix := NewPrefix(Event) |
||||
ser := new(types.Uint40) |
||||
ser.Set(12345) |
||||
|
||||
// Test New function
|
||||
enc := New(prefix, ser) |
||||
if len(enc.Encs) != 2 { |
||||
t.Errorf("New should create T with 2 encoders, got %d", len(enc.Encs)) |
||||
} |
||||
|
||||
// Test MarshalWrite
|
||||
buf := new(bytes.Buffer) |
||||
err := enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Test UnmarshalRead
|
||||
dec := New(NewPrefix(), new(types.Uint40)) |
||||
err = dec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
decodedPrefix := dec.Encs[0].(*P) |
||||
decodedSer := dec.Encs[1].(*types.Uint40) |
||||
if !utils.FastEqual(decodedPrefix.Bytes(), prefix.Bytes()) { |
||||
t.Errorf( |
||||
"Decoded prefix %v, expected %v", decodedPrefix.Bytes(), |
||||
prefix.Bytes(), |
||||
) |
||||
} |
||||
if decodedSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", decodedSer.Get(), ser.Get()) |
||||
} |
||||
|
||||
// Test with nil encoder
|
||||
encWithNil := New(prefix, nil, ser) |
||||
buf.Reset() |
||||
err = encWithNil.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite with nil encoder failed: %v", err) |
||||
} |
||||
} |
||||
|
||||
// TestEventFunctions tests the Event-related functions
|
||||
func TestEventFunctions(t *testing.T) { |
||||
// Test EventVars
|
||||
ser := EventVars() |
||||
if ser == nil { |
||||
t.Fatalf("EventVars should return non-nil *types.Uint40") |
||||
} |
||||
|
||||
// Set a value
|
||||
ser.Set(12345) |
||||
|
||||
// Test EventEnc
|
||||
enc := EventEnc(ser) |
||||
if len(enc.Encs) != 2 { |
||||
t.Errorf( |
||||
"EventEnc should create T with 2 encoders, got %d", len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test EventDec
|
||||
dec := EventDec(ser) |
||||
if len(dec.Encs) != 2 { |
||||
t.Errorf( |
||||
"EventDec should create T with 2 encoders, got %d", len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err := enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newSer := new(types.Uint40) |
||||
newDec := EventDec(newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded value
|
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestIdFunctions tests the Id-related functions
|
||||
func TestIdFunctions(t *testing.T) { |
||||
// Test IdVars
|
||||
id, ser := IdVars() |
||||
if id == nil || ser == nil { |
||||
t.Fatalf("IdVars should return non-nil *types.IdHash and *types.Uint40") |
||||
} |
||||
|
||||
// Set values
|
||||
id.Set([]byte{1, 2, 3, 4, 5, 6, 7, 8}) |
||||
ser.Set(12345) |
||||
|
||||
// Test IdEnc
|
||||
enc := IdEnc(id, ser) |
||||
if len(enc.Encs) != 3 { |
||||
t.Errorf("IdEnc should create T with 3 encoders, got %d", len(enc.Encs)) |
||||
} |
||||
|
||||
// Test IdDec
|
||||
dec := IdDec(id, ser) |
||||
if len(dec.Encs) != 3 { |
||||
t.Errorf("IdDec should create T with 3 encoders, got %d", len(dec.Encs)) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err := enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newId, newSer := IdVars() |
||||
newDec := IdDec(newId, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if !utils.FastEqual(newId.Bytes(), id.Bytes()) { |
||||
t.Errorf("Decoded id %v, expected %v", newId.Bytes(), id.Bytes()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestIdPubkeyFunctions tests the FullIdPubkey-related functions
|
||||
func TestIdPubkeyFunctions(t *testing.T) { |
||||
// Test FullIdPubkeyVars
|
||||
ser, fid, p, ca := FullIdPubkeyVars() |
||||
if ser == nil || fid == nil || p == nil || ca == nil { |
||||
t.Fatalf("FullIdPubkeyVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ser.Set(12345) |
||||
err := fid.FromId( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromId failed: %v", err) |
||||
} |
||||
err = p.FromPubkey( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromPubkey failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
|
||||
// Test FullIdPubkeyEnc
|
||||
enc := FullIdPubkeyEnc(ser, fid, p, ca) |
||||
if len(enc.Encs) != 5 { |
||||
t.Errorf( |
||||
"FullIdPubkeyEnc should create T with 5 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test FullIdPubkeyDec
|
||||
dec := FullIdPubkeyDec(ser, fid, p, ca) |
||||
if len(dec.Encs) != 5 { |
||||
t.Errorf( |
||||
"FullIdPubkeyDec should create T with 5 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newSer, newFid, newP, newCa := FullIdPubkeyVars() |
||||
newDec := FullIdPubkeyDec(newSer, newFid, newP, newCa) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
if !utils.FastEqual(newFid.Bytes(), fid.Bytes()) { |
||||
t.Errorf("Decoded id %v, expected %v", newFid.Bytes(), fid.Bytes()) |
||||
} |
||||
if !utils.FastEqual(newP.Bytes(), p.Bytes()) { |
||||
t.Errorf("Decoded pubkey hash %v, expected %v", newP.Bytes(), p.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestCreatedAtFunctions tests the CreatedAt-related functions
|
||||
func TestCreatedAtFunctions(t *testing.T) { |
||||
// Test CreatedAtVars
|
||||
ca, ser := CreatedAtVars() |
||||
if ca == nil || ser == nil { |
||||
t.Fatalf("CreatedAtVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test CreatedAtEnc
|
||||
enc := CreatedAtEnc(ca, ser) |
||||
if len(enc.Encs) != 3 { |
||||
t.Errorf( |
||||
"CreatedAtEnc should create T with 3 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test CreatedAtDec
|
||||
dec := CreatedAtDec(ca, ser) |
||||
if len(dec.Encs) != 3 { |
||||
t.Errorf( |
||||
"CreatedAtDec should create T with 3 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err := enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newCa, newSer := CreatedAtVars() |
||||
newDec := CreatedAtDec(newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestPubkeyFunctions tests the Pubkey-related functions
|
||||
func TestPubkeyFunctions(t *testing.T) { |
||||
// Test PubkeyVars
|
||||
p, ca, ser := PubkeyVars() |
||||
if p == nil || ca == nil || ser == nil { |
||||
t.Fatalf("PubkeyVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
err := p.FromPubkey( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromPubkey failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test PubkeyEnc
|
||||
enc := PubkeyEnc(p, ca, ser) |
||||
if len(enc.Encs) != 4 { |
||||
t.Errorf( |
||||
"PubkeyEnc should create T with 4 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test PubkeyDec
|
||||
dec := PubkeyDec(p, ca, ser) |
||||
if len(dec.Encs) != 4 { |
||||
t.Errorf( |
||||
"PubkeyDec should create T with 4 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newP, newCa, newSer := PubkeyVars() |
||||
newDec := PubkeyDec(newP, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if !utils.FastEqual(newP.Bytes(), p.Bytes()) { |
||||
t.Errorf("Decoded pubkey hash %v, expected %v", newP.Bytes(), p.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestPubkeyTagFunctions tests the TagPubkey-related functions
|
||||
func TestPubkeyTagFunctions(t *testing.T) { |
||||
// Test TagPubkeyVars
|
||||
k, v, p, ca, ser := TagPubkeyVars() |
||||
if p == nil || k == nil || v == nil || ca == nil || ser == nil { |
||||
t.Fatalf("TagPubkeyVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
err := p.FromPubkey( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromPubkey failed: %v", err) |
||||
} |
||||
k.Set('e') |
||||
v.FromIdent([]byte("test-value")) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromIdent failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test TagPubkeyEnc
|
||||
enc := TagPubkeyEnc(k, v, p, ca, ser) |
||||
if len(enc.Encs) != 6 { |
||||
t.Errorf( |
||||
"TagPubkeyEnc should create T with 6 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test TagPubkeyDec
|
||||
dec := TagPubkeyDec(k, v, p, ca, ser) |
||||
if len(dec.Encs) != 6 { |
||||
t.Errorf( |
||||
"TagPubkeyDec should create T with 6 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newK, newV, newP, newCa, newSer := TagPubkeyVars() |
||||
newDec := TagPubkeyDec(newK, newV, newP, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if !utils.FastEqual(newP.Bytes(), p.Bytes()) { |
||||
t.Errorf("Decoded pubkey hash %v, expected %v", newP.Bytes(), p.Bytes()) |
||||
} |
||||
if newK.Letter() != k.Letter() { |
||||
t.Errorf( |
||||
"Decoded key letter %c, expected %c", newK.Letter(), k.Letter(), |
||||
) |
||||
} |
||||
if !utils.FastEqual(newV.Bytes(), v.Bytes()) { |
||||
t.Errorf("Decoded value hash %v, expected %v", newV.Bytes(), v.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestTagFunctions tests the Tag-related functions
|
||||
func TestTagFunctions(t *testing.T) { |
||||
var err error |
||||
// Test TagVars
|
||||
k, v, ca, ser := TagVars() |
||||
if k == nil || v == nil || ca == nil || ser == nil { |
||||
t.Fatalf("TagVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
k.Set('e') |
||||
v.FromIdent([]byte("test-value")) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromIdent failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test TagEnc
|
||||
enc := TagEnc(k, v, ca, ser) |
||||
if len(enc.Encs) != 5 { |
||||
t.Errorf( |
||||
"TagEnc should create T with 5 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test TagDec
|
||||
dec := TagDec(k, v, ca, ser) |
||||
if len(dec.Encs) != 5 { |
||||
t.Errorf( |
||||
"TagDec should create T with 5 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newK, newV, newCa, newSer := TagVars() |
||||
newDec := TagDec(newK, newV, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newK.Letter() != k.Letter() { |
||||
t.Errorf( |
||||
"Decoded key letter %c, expected %c", newK.Letter(), k.Letter(), |
||||
) |
||||
} |
||||
if !utils.FastEqual(newV.Bytes(), v.Bytes()) { |
||||
t.Errorf("Decoded value hash %v, expected %v", newV.Bytes(), v.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestKindFunctions tests the Kind-related functions
|
||||
func TestKindFunctions(t *testing.T) { |
||||
// Test KindVars
|
||||
ki, ca, ser := KindVars() |
||||
if ki == nil || ca == nil || ser == nil { |
||||
t.Fatalf("KindVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ki.Set(1234) |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test KindEnc
|
||||
enc := KindEnc(ki, ca, ser) |
||||
if len(enc.Encs) != 4 { |
||||
t.Errorf( |
||||
"KindEnc should create T with 4 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test KindDec
|
||||
dec := KindDec(ki, ca, ser) |
||||
if len(dec.Encs) != 4 { |
||||
t.Errorf( |
||||
"KindDec should create T with 4 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err := enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newKi, newCa, newSer := KindVars() |
||||
newDec := KindDec(newKi, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newKi.Get() != ki.Get() { |
||||
t.Errorf("Decoded kind %d, expected %d", newKi.Get(), ki.Get()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestKindTagFunctions tests the TagKind-related functions
|
||||
func TestKindTagFunctions(t *testing.T) { |
||||
var err error |
||||
// Test TagKindVars
|
||||
k, v, ki, ca, ser := TagKindVars() |
||||
if ki == nil || k == nil || v == nil || ca == nil || ser == nil { |
||||
t.Fatalf("TagKindVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ki.Set(1234) |
||||
k.Set('e') |
||||
v.FromIdent([]byte("test-value")) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromIdent failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test TagKindEnc
|
||||
enc := TagKindEnc(k, v, ki, ca, ser) |
||||
if len(enc.Encs) != 6 { |
||||
t.Errorf( |
||||
"TagKindEnc should create T with 6 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test TagKindDec
|
||||
dec := TagKindDec(k, v, ki, ca, ser) |
||||
if len(dec.Encs) != 6 { |
||||
t.Errorf( |
||||
"TagKindDec should create T with 6 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newK, newV, newKi, newCa, newSer := TagKindVars() |
||||
newDec := TagKindDec(newK, newV, newKi, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newKi.Get() != ki.Get() { |
||||
t.Errorf("Decoded kind %d, expected %d", newKi.Get(), ki.Get()) |
||||
} |
||||
if newK.Letter() != k.Letter() { |
||||
t.Errorf( |
||||
"Decoded key letter %c, expected %c", newK.Letter(), k.Letter(), |
||||
) |
||||
} |
||||
if !utils.FastEqual(newV.Bytes(), v.Bytes()) { |
||||
t.Errorf("Decoded value hash %v, expected %v", newV.Bytes(), v.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestKindPubkeyFunctions tests the KindPubkey-related functions
|
||||
func TestKindPubkeyFunctions(t *testing.T) { |
||||
// Test KindPubkeyVars
|
||||
ki, p, ca, ser := KindPubkeyVars() |
||||
if ki == nil || p == nil || ca == nil || ser == nil { |
||||
t.Fatalf("KindPubkeyVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ki.Set(1234) |
||||
err := p.FromPubkey( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromPubkey failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test KindPubkeyEnc
|
||||
enc := KindPubkeyEnc(ki, p, ca, ser) |
||||
if len(enc.Encs) != 5 { |
||||
t.Errorf( |
||||
"KindPubkeyEnc should create T with 5 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test KindPubkeyDec
|
||||
dec := KindPubkeyDec(ki, p, ca, ser) |
||||
if len(dec.Encs) != 5 { |
||||
t.Errorf( |
||||
"KindPubkeyDec should create T with 5 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newKi, newP, newCa, newSer := KindPubkeyVars() |
||||
newDec := KindPubkeyDec(newKi, newP, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newKi.Get() != ki.Get() { |
||||
t.Errorf("Decoded kind %d, expected %d", newKi.Get(), ki.Get()) |
||||
} |
||||
if !utils.FastEqual(newP.Bytes(), p.Bytes()) { |
||||
t.Errorf("Decoded pubkey hash %v, expected %v", newP.Bytes(), p.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
|
||||
// TestKindPubkeyTagFunctions tests the TagKindPubkey-related functions
|
||||
func TestKindPubkeyTagFunctions(t *testing.T) { |
||||
// Test TagKindPubkeyVars
|
||||
k, v, ki, p, ca, ser := TagKindPubkeyVars() |
||||
if ki == nil || p == nil || k == nil || v == nil || ca == nil || ser == nil { |
||||
t.Fatalf("TagKindPubkeyVars should return non-nil values") |
||||
} |
||||
|
||||
// Set values
|
||||
ki.Set(1234) |
||||
err := p.FromPubkey( |
||||
[]byte{ |
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, |
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, |
||||
}, |
||||
) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromPubkey failed: %v", err) |
||||
} |
||||
k.Set('e') |
||||
v.FromIdent([]byte("test-value")) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromIdent failed: %v", err) |
||||
} |
||||
ca.Set(98765) |
||||
ser.Set(12345) |
||||
|
||||
// Test TagKindPubkeyEnc
|
||||
enc := TagKindPubkeyEnc(k, v, ki, p, ca, ser) |
||||
if len(enc.Encs) != 7 { |
||||
t.Errorf( |
||||
"TagKindPubkeyEnc should create T with 7 encoders, got %d", |
||||
len(enc.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test TagKindPubkeyDec
|
||||
dec := TagKindPubkeyDec(k, v, ki, p, ca, ser) |
||||
if len(dec.Encs) != 7 { |
||||
t.Errorf( |
||||
"TagKindPubkeyDec should create T with 7 encoders, got %d", |
||||
len(dec.Encs), |
||||
) |
||||
} |
||||
|
||||
// Test marshaling and unmarshaling
|
||||
buf := new(bytes.Buffer) |
||||
err = enc.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Create new variables for decoding
|
||||
newK, newV, newKi, newP, newCa, newSer := TagKindPubkeyVars() |
||||
newDec := TagKindPubkeyDec(newK, newV, newKi, newP, newCa, newSer) |
||||
|
||||
err = newDec.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the decoded values
|
||||
if newKi.Get() != ki.Get() { |
||||
t.Errorf("Decoded kind %d, expected %d", newKi.Get(), ki.Get()) |
||||
} |
||||
if !utils.FastEqual(newP.Bytes(), p.Bytes()) { |
||||
t.Errorf("Decoded pubkey hash %v, expected %v", newP.Bytes(), p.Bytes()) |
||||
} |
||||
if newK.Letter() != k.Letter() { |
||||
t.Errorf( |
||||
"Decoded key letter %c, expected %c", newK.Letter(), k.Letter(), |
||||
) |
||||
} |
||||
if !utils.FastEqual(newV.Bytes(), v.Bytes()) { |
||||
t.Errorf("Decoded value hash %v, expected %v", newV.Bytes(), v.Bytes()) |
||||
} |
||||
if newCa.Get() != ca.Get() { |
||||
t.Errorf("Decoded created at %d, expected %d", newCa.Get(), ca.Get()) |
||||
} |
||||
if newSer.Get() != ser.Get() { |
||||
t.Errorf("Decoded serial %d, expected %d", newSer.Get(), ser.Get()) |
||||
} |
||||
} |
||||
@ -0,0 +1,419 @@
@@ -0,0 +1,419 @@
|
||||
package types |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/binary" |
||||
"testing" |
||||
) |
||||
|
||||
// TestTypesSortLexicographically tests if the numeric types sort lexicographically
|
||||
// when using bytes.Compare after marshaling.
|
||||
func TestTypesSortLexicographically(t *testing.T) { |
||||
// Test Uint16
|
||||
t.Run("Uint16", func(t *testing.T) { |
||||
testUint16Sorting(t) |
||||
}) |
||||
|
||||
// Test Uint24
|
||||
t.Run("Uint24", func(t *testing.T) { |
||||
testUint24Sorting(t) |
||||
}) |
||||
|
||||
// Test Uint32
|
||||
t.Run("Uint32", func(t *testing.T) { |
||||
testUint32Sorting(t) |
||||
}) |
||||
|
||||
// Test Uint40
|
||||
t.Run("Uint40", func(t *testing.T) { |
||||
testUint40Sorting(t) |
||||
}) |
||||
|
||||
// Test Uint64
|
||||
t.Run("Uint64", func(t *testing.T) { |
||||
testUint64Sorting(t) |
||||
}) |
||||
} |
||||
|
||||
// TestEdgeCases tests sorting with edge cases like zero, max values, and adjacent values
|
||||
func TestEdgeCases(t *testing.T) { |
||||
// Test Uint16 edge cases
|
||||
t.Run("Uint16EdgeCases", func(t *testing.T) { |
||||
testUint16EdgeCases(t) |
||||
}) |
||||
|
||||
// Test Uint24 edge cases
|
||||
t.Run("Uint24EdgeCases", func(t *testing.T) { |
||||
testUint24EdgeCases(t) |
||||
}) |
||||
|
||||
// Test Uint32 edge cases
|
||||
t.Run("Uint32EdgeCases", func(t *testing.T) { |
||||
testUint32EdgeCases(t) |
||||
}) |
||||
|
||||
// Test Uint40 edge cases
|
||||
t.Run("Uint40EdgeCases", func(t *testing.T) { |
||||
testUint40EdgeCases(t) |
||||
}) |
||||
|
||||
// Test Uint64 edge cases
|
||||
t.Run("Uint64EdgeCases", func(t *testing.T) { |
||||
testUint64EdgeCases(t) |
||||
}) |
||||
} |
||||
|
||||
func testUint16Sorting(t *testing.T) { |
||||
values := []uint16{1, 10, 100, 1000, 10000, 65535} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint16) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint16 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint16 values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint24Sorting(t *testing.T) { |
||||
values := []uint32{1, 10, 100, 1000, 10000, 100000, 1000000, 16777215} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint24) |
||||
err := u.Set(val) |
||||
if err != nil { |
||||
t.Fatalf("Failed to set Uint24 %d: %v", val, err) |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err = u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint24 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint24 values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint32Sorting(t *testing.T) { |
||||
values := []uint32{1, 10, 100, 1000, 10000, 100000, 1000000, 4294967295} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint32) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint32 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint32 values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint40Sorting(t *testing.T) { |
||||
values := []uint64{1, 10, 100, 1000, 10000, 100000, 1000000, 1099511627775} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint40) |
||||
err := u.Set(val) |
||||
if err != nil { |
||||
t.Fatalf("Failed to set Uint40 %d: %v", val, err) |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err = u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint40 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint40 values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint64Sorting(t *testing.T) { |
||||
values := []uint64{1, 10, 100, 1000, 10000, 100000, 1000000, 18446744073709551615} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint64) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint64 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint64 values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Edge case test functions
|
||||
|
||||
func testUint16EdgeCases(t *testing.T) { |
||||
// Test edge cases: 0, max value, and adjacent values
|
||||
values := []uint16{0, 1, 2, 65534, 65535} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint16) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint16 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint16 edge case values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint24EdgeCases(t *testing.T) { |
||||
// Test edge cases: 0, max value, and adjacent values
|
||||
values := []uint32{0, 1, 2, 16777214, 16777215} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint24) |
||||
err := u.Set(val) |
||||
if err != nil { |
||||
t.Fatalf("Failed to set Uint24 %d: %v", val, err) |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err = u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint24 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint24 edge case values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint32EdgeCases(t *testing.T) { |
||||
// Test edge cases: 0, max value, and adjacent values
|
||||
values := []uint32{0, 1, 2, 4294967294, 4294967295} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint32) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint32 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint32 edge case values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint40EdgeCases(t *testing.T) { |
||||
// Test edge cases: 0, max value, and adjacent values
|
||||
values := []uint64{0, 1, 2, 1099511627774, 1099511627775} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint40) |
||||
err := u.Set(val) |
||||
if err != nil { |
||||
t.Fatalf("Failed to set Uint40 %d: %v", val, err) |
||||
} |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err = u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint40 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint40 edge case values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func testUint64EdgeCases(t *testing.T) { |
||||
// Test edge cases: 0, max value, and adjacent values
|
||||
values := []uint64{0, 1, 2, 18446744073709551614, 18446744073709551615} |
||||
|
||||
// Marshal each value
|
||||
marshaledValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
u := new(Uint64) |
||||
u.Set(val) |
||||
|
||||
buf := new(bytes.Buffer) |
||||
err := u.MarshalWrite(buf) |
||||
if err != nil { |
||||
t.Fatalf("Failed to marshal Uint64 %d: %v", val, err) |
||||
} |
||||
|
||||
marshaledValues[i] = buf.Bytes() |
||||
} |
||||
|
||||
// Check if they sort correctly with bytes.Compare
|
||||
for i := 0; i < len(marshaledValues)-1; i++ { |
||||
if bytes.Compare(marshaledValues[i], marshaledValues[i+1]) >= 0 { |
||||
t.Errorf("Uint64 edge case values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", marshaledValues[i], marshaledValues[i+1]) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// TestEndianness demonstrates why BigEndian is used instead of LittleEndian
|
||||
// for lexicographical sorting with bytes.Compare
|
||||
func TestEndianness(t *testing.T) { |
||||
// Test with uint32 values
|
||||
values := []uint32{1, 10, 100, 1000, 10000} |
||||
|
||||
// Marshal each value using BigEndian
|
||||
bigEndianValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
buf := make([]byte, 4) |
||||
binary.BigEndian.PutUint32(buf, val) |
||||
bigEndianValues[i] = buf |
||||
} |
||||
|
||||
// Marshal each value using LittleEndian
|
||||
littleEndianValues := make([][]byte, len(values)) |
||||
for i, val := range values { |
||||
buf := make([]byte, 4) |
||||
binary.LittleEndian.PutUint32(buf, val) |
||||
littleEndianValues[i] = buf |
||||
} |
||||
|
||||
// Check if BigEndian values sort correctly with bytes.Compare
|
||||
t.Log("Testing BigEndian sorting:") |
||||
for i := 0; i < len(bigEndianValues)-1; i++ { |
||||
result := bytes.Compare(bigEndianValues[i], bigEndianValues[i+1]) |
||||
t.Logf("Compare %d with %d: result = %d", values[i], values[i+1], result) |
||||
if result >= 0 { |
||||
t.Errorf("BigEndian values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", bigEndianValues[i], bigEndianValues[i+1]) |
||||
} |
||||
} |
||||
|
||||
// Check if LittleEndian values sort correctly with bytes.Compare
|
||||
t.Log("Testing LittleEndian sorting:") |
||||
correctOrder := true |
||||
for i := 0; i < len(littleEndianValues)-1; i++ { |
||||
result := bytes.Compare(littleEndianValues[i], littleEndianValues[i+1]) |
||||
t.Logf("Compare %d with %d: result = %d", values[i], values[i+1], result) |
||||
if result >= 0 { |
||||
correctOrder = false |
||||
t.Logf("LittleEndian values don't sort correctly: %v should be less than %v", |
||||
values[i], values[i+1]) |
||||
t.Logf("Bytes representation: %v vs %v", littleEndianValues[i], littleEndianValues[i+1]) |
||||
} |
||||
} |
||||
|
||||
// We expect LittleEndian to NOT sort correctly
|
||||
if correctOrder { |
||||
t.Error("LittleEndian values unexpectedly sorted correctly") |
||||
} else { |
||||
t.Log("As expected, LittleEndian values don't sort correctly with bytes.Compare") |
||||
} |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
package types |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"crypto.orly/sha256" |
||||
"lol.mleku.dev/errorf" |
||||
) |
||||
|
||||
const IdLen = sha256.Size |
||||
|
||||
type Id struct { |
||||
val [IdLen]byte |
||||
} |
||||
|
||||
func (fi *Id) FromId(id []byte) (err error) { |
||||
if len(id) != IdLen { |
||||
err = errorf.E( |
||||
"fullid.FromId: invalid ID length, got %d require %d", len(id), |
||||
IdLen, |
||||
) |
||||
return |
||||
} |
||||
copy(fi.val[:], id) |
||||
return |
||||
} |
||||
func (fi *Id) Bytes() (b []byte) { return fi.val[:] } |
||||
|
||||
func (fi *Id) MarshalWrite(w io.Writer) (err error) { |
||||
_, err = w.Write(fi.val[:]) |
||||
return |
||||
} |
||||
|
||||
func (fi *Id) UnmarshalRead(r io.Reader) (err error) { |
||||
copy(fi.val[:], fi.val[:IdLen]) |
||||
_, err = r.Read(fi.val[:]) |
||||
return |
||||
} |
||||
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
package types |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"lol.mleku.dev/chk" |
||||
"utils.orly" |
||||
|
||||
"crypto.orly/sha256" |
||||
) |
||||
|
||||
func TestFromId(t *testing.T) { |
||||
// Create a valid ID (32 bytes)
|
||||
validId := make([]byte, sha256.Size) |
||||
for i := 0; i < sha256.Size; i++ { |
||||
validId[i] = byte(i) |
||||
} |
||||
|
||||
// Create an invalid ID (wrong size)
|
||||
invalidId := make([]byte, sha256.Size-1) |
||||
|
||||
// Test with valid ID
|
||||
fi := &Id{} |
||||
err := fi.FromId(validId) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromId failed with valid ID: %v", err) |
||||
} |
||||
|
||||
// Verify the ID was set correctly
|
||||
if !utils.FastEqual(fi.Bytes(), validId) { |
||||
t.Errorf( |
||||
"FromId did not set the ID correctly: got %v, want %v", fi.Bytes(), |
||||
validId, |
||||
) |
||||
} |
||||
|
||||
// Test with invalid ID
|
||||
fi = &Id{} |
||||
err = fi.FromId(invalidId) |
||||
if err == nil { |
||||
t.Errorf("FromId should have failed with invalid ID size") |
||||
} |
||||
} |
||||
|
||||
func TestIdMarshalWriteUnmarshalRead(t *testing.T) { |
||||
// Create a ID with a known value
|
||||
fi1 := &Id{} |
||||
validId := make([]byte, sha256.Size) |
||||
for i := 0; i < sha256.Size; i++ { |
||||
validId[i] = byte(i) |
||||
} |
||||
err := fi1.FromId(validId) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromId failed: %v", err) |
||||
} |
||||
|
||||
// Test MarshalWrite
|
||||
buf := new(bytes.Buffer) |
||||
err = fi1.MarshalWrite(buf) |
||||
if chk.E(err) { |
||||
t.Fatalf("MarshalWrite failed: %v", err) |
||||
} |
||||
|
||||
// Verify the written bytes
|
||||
if !utils.FastEqual(buf.Bytes(), validId) { |
||||
t.Errorf("MarshalWrite wrote %v, want %v", buf.Bytes(), validId) |
||||
} |
||||
|
||||
// Test UnmarshalRead
|
||||
fi2 := &Id{} |
||||
err = fi2.UnmarshalRead(bytes.NewBuffer(buf.Bytes())) |
||||
if chk.E(err) { |
||||
t.Fatalf("UnmarshalRead failed: %v", err) |
||||
} |
||||
|
||||
// Verify the read value
|
||||
if !utils.FastEqual(fi2.Bytes(), validId) { |
||||
t.Errorf("UnmarshalRead read %v, want %v", fi2.Bytes(), validId) |
||||
} |
||||
} |
||||
|
||||
func TestIdUnmarshalReadWithCorruptedData(t *testing.T) { |
||||
// Create a ID with a known value
|
||||
fi1 := &Id{} |
||||
validId := make([]byte, sha256.Size) |
||||
for i := 0; i < sha256.Size; i++ { |
||||
validId[i] = byte(i) |
||||
} |
||||
err := fi1.FromId(validId) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromId failed: %v", err) |
||||
} |
||||
|
||||
// Create a second ID with a different value
|
||||
fi2 := &Id{} |
||||
differentId := make([]byte, sha256.Size) |
||||
for i := 0; i < sha256.Size; i++ { |
||||
differentId[i] = byte(sha256.Size - i - 1) |
||||
} |
||||
err = fi2.FromId(differentId) |
||||
if chk.E(err) { |
||||
t.Fatalf("FromId failed: %v", err) |
||||
} |
||||
|
||||
// Test UnmarshalRead with corrupted data (less than Len bytes)
|
||||
corruptedData := make([]byte, sha256.Size/2) |
||||
fi2.UnmarshalRead(bytes.NewBuffer(corruptedData)) |
||||
|
||||
// The UnmarshalRead method should not have copied the original data to itself
|
||||
// before reading, so the value should be partially overwritten
|
||||
if utils.FastEqual(fi2.Bytes(), differentId) { |
||||
t.Errorf("UnmarshalRead did not modify the value as expected") |
||||
} |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
package types |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"crypto.orly/sha256" |
||||
) |
||||
|
||||
const IdentLen = 8 |
||||
|
||||
type Ident struct{ val [IdentLen]byte } |
||||
|
||||
func (i *Ident) FromIdent(id []byte) { |
||||
idh := sha256.Sum256(id) |
||||
copy(i.val[:], idh[:IdentLen]) |
||||
return |
||||
} |
||||
|
||||
func (i *Ident) Bytes() (b []byte) { return i.val[:] } |
||||
|
||||
func (i *Ident) MarshalWrite(w io.Writer) (err error) { |
||||
_, err = w.Write(i.val[:]) |
||||
return |
||||
} |
||||
|
||||
func (i *Ident) UnmarshalRead(r io.Reader) (err error) { |
||||
|
||||
copy(i.val[:], i.val[:IdentLen]) |
||||
_, err = r.Read(i.val[:]) |
||||
return |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue