Skip to content

Commit

Permalink
spoken: get message traces
Browse files Browse the repository at this point in the history
For #44.
  • Loading branch information
zephyrtronium committed Aug 17, 2024
1 parent 4173dc7 commit 730797c
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 0 deletions.
40 changes: 40 additions & 0 deletions spoken/spoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,46 @@ func Record[DB *sqlitex.Pool | *sqlite.Conn](ctx context.Context, db DB, tag, me
return nil
}

// Trace obtains the trace and time of the most recent instance of a message.
// If the message has not been recorded, the results are empty with a nil error.
func Trace[DB *sqlitex.Pool | *sqlite.Conn](ctx context.Context, db DB, tag, msg string) ([]string, time.Time, error) {
var conn *sqlite.Conn
switch db := any(db).(type) {
case *sqlite.Conn:
conn = db
case *sqlitex.Pool:
var err error
conn, err = db.Take(ctx)
defer db.Put(conn)
if err != nil {
return nil, time.Time{}, fmt.Errorf("couldn't get conn to find trace: %w", err)
}
}
const sel = `SELECT JSON(trace), time FROM spoken WHERE tag=:tag AND msg=:msg ORDER BY time DESC LIMIT 1`
st, err := conn.Prepare(sel)
if err != nil {
return nil, time.Time{}, fmt.Errorf("couldn't prepare statement to find trace: %w", err)
}
st.SetText(":tag", tag)
st.SetText(":msg", msg)
ok, err := st.Step()
if err != nil {
return nil, time.Time{}, fmt.Errorf("couldn't find trace: %w", err)
}
if !ok {
return nil, time.Time{}, nil
}
tr := st.ColumnText(0)
tm := st.ColumnInt64(1)
var trace []string
if err := json.Unmarshal([]byte(tr), &trace); err != nil {
return nil, time.Time{}, fmt.Errorf("couldn't decode trace: %w", err)
}
// Clean up the statement.
st.Step()
return trace, time.Unix(0, tm), nil
}

//go:embed schema.sql
var schemaSQL string

Expand Down
96 changes: 96 additions & 0 deletions spoken/spoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,99 @@ func TestRecord(t *testing.T) {
t.Errorf("failed to scan: %v", err)
}
}

func TestTrace(t *testing.T) {
// Create test fixture first.
ctx := context.Background()
db := testDB(ctx)
insert := []struct {
tag string
msg string
trace string
time int64
}{
{"kessoku", "bocchi", `["1"]`, 1},
{"kessoku", "ryo", `["2"]`, 2},
{"sickhack", "bocchi", `["3"]`, 3},
{"kessoku", "ryo", `["4"]`, 4},
}
{
conn, err := db.Take(ctx)
if err != nil {
t.Fatalf("couldn't get conn: %v", err)
}
st, err := conn.Prepare("INSERT INTO spoken (tag, msg, trace, time, meta) VALUES (:tag, :msg, JSONB(:trace), :time, JSONB('{}'))")
if err != nil {
t.Fatalf("couldn't prep insert: %v", err)
}
for _, r := range insert {
st.SetText(":tag", r.tag)
st.SetText(":msg", r.msg)
st.SetText(":trace", r.trace)
st.SetInt64(":time", r.time)
_, err := st.Step()
if err != nil {
t.Errorf("failed to insert %v: %v", r, err)
}
if err := st.Reset(); err != nil {
t.Errorf("couldn't reset: %v", err)
}
}
if err := st.Finalize(); err != nil {
t.Fatalf("couldn't finalize insert: %v", err)
}
db.Put(conn)
}

cases := []struct {
name string
tag string
msg string
want []string
time time.Time
}{
{
name: "none",
tag: "kessoku",
msg: "nijika",
want: nil,
time: time.Time{},
},
{
name: "single",
tag: "kessoku",
msg: "bocchi",
want: []string{"1"},
time: time.Unix(0, 1),
},
{
name: "latest",
tag: "kessoku",
msg: "ryo",
want: []string{"4"},
time: time.Unix(0, 4),
},
{
name: "tagged",
tag: "sickhack",
msg: "bocchi",
want: []string{"3"},
time: time.Unix(0, 3),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
trace, tm, err := spoken.Trace(context.Background(), db, c.tag, c.msg)
if err != nil {
t.Errorf("couldn't get trace: %v", err)
}
if !slices.Equal(trace, c.want) {
t.Errorf("wrong trace: want %q, got %q", c.want, trace)
}
if !tm.Equal(c.time) {
t.Errorf("wrong time: want %v, got %v", c.time, tm.UnixNano())
}
})
}
}

0 comments on commit 730797c

Please sign in to comment.