gotelem/db.go
2023-06-28 19:23:08 -05:00

184 lines
4.8 KiB
Go

package gotelem
// this file implements the database functions to load/store/read from a sql database.
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/kschamplin/gotelem/skylab"
_ "github.com/mattn/go-sqlite3"
)
type TelemDb struct {
db *sqlx.DB
}
type TelemDbOption func(*TelemDb) error
func OpenTelemDb(path string, options ...TelemDbOption) (tdb *TelemDb, err error) {
tdb = &TelemDb{}
tdb.db, err = sqlx.Connect("sqlite3", path)
if err != nil {
return
}
// TODO: add options support.
for _, fn := range options {
err = fn(tdb)
if err != nil {
return
}
}
// execute database up statement (better hope it is idempotent!)
_, err = tdb.db.Exec(sqlDbUp)
if err != nil {
}
return tdb, nil
}
// the sql commands to create the database.
const sqlDbUp = `
CREATE TABLE IF NOT EXISTS "bus_events" (
"ts" REAL NOT NULL, -- timestamp
"id" INTEGER NOT NULL, -- can ID
"name" TEXT NOT NULL, -- name of base packet
"index" INTEGER, -- index of the repeated packet (base_id = id - index)
"packet" TEXT NOT NULL CHECK(json_valid(packet)) -- JSON object describing the data
);
CREATE INDEX IF NOT EXISTS "ids_timestamped" ON "bus_events" (
"id",
"ts" DESC
);
CREATE INDEX IF NOT EXISTS "times" ON "bus_events" (
"ts" DESC
);
-- this table shows when we started/stopped logging.
CREATE TABLE "bus_records" (
"id" INTEGER NOT NULL UNIQUE,
"start_time" INTEGER NOT NULL,
"end_time" INTEGER,
"note" TEXT,
PRIMARY KEY("id" AUTOINCREMENT),
CONSTRAINT "duration_valid" CHECK(end_time is null or start_time < end_time)
);
`
// sql sequence to tear down the database.
// not used often, but good to keep track of what's going on.
// Up() then Down() should result in an empty database.
const sqlDbDown = `
DROP TABLE "bus_events";
DROP INDEX "ids_timestamped";
DROP INDEX "times";
DROP TABLE "bus_records";
`
// sql expression to insert a bus event into the packets database.1
const sqlInsertEvent = `
INSERT INTO "bus_events" (time, can_id, name, index, packet) VALUES ($1, $2, $3, json($4));
`
// AddEvent adds the bus event to the database.
func (tdb *TelemDb) AddEvents(events ...skylab.BusEvent) {
//
tx, err := tdb.db.Begin()
if err != nil {
tx.Rollback()
return
}
for _, b := range events {
j, err := json.Marshal(b.Data)
if err != nil {
tx.Rollback()
return
}
tx.Exec(sqlInsertEvent, b.Timestamp, b.Id, b.Name, j)
}
tx.Commit()
}
// QueryIdString is a string that filters ids from the set. use ID query functions to
// create them.
type QueryIdString string
// QueryIds constructs a CAN Id filter for one or more distinct Ids.
// For a range of ids, use QueryIdRange(start, stop uint32)
func QueryIds(ids ...uint32) QueryIdString {
// FIXME: zero elements case?
var idsString []string
for _, id := range ids {
idsString = append(idsString, strconv.FormatUint(uint64(id), 10))
}
return QueryIdString("id IN (" + strings.Join(idsString, ",") + ")")
}
func QueryIdsInv(ids ...uint32) QueryIdString {
}
// QueryIdRange selects all IDs between start and end, *inclusive*.
// This function is preferred over a generated list of IDs.
func QueryIdRange(start, end uint32) QueryIdString {
startString := strconv.FormatUint(uint64(start), 10)
endString := strconv.FormatUint(uint64(end), 10)
return QueryIdString("id BETWEEN " + startString + " AND " + endString)
}
// QueryIdRangeInv removes all IDs between start and end from the results.
// See QueryIdRange for more details.
func QueryIdRangeInv(start, end uint32) QueryIdString {
return QueryIdString("NOT ") + QueryIdRange(start, end)
}
type QueryTimestampString string
// QueryDuration takes a start and end time and filters where the packets are between that time range.
func QueryDuration(start, end time.Time) QueryTimestampString {
// the time in the database is a float, we have a time.Time so use unixNano() / 1e9 to float it.
startString := strconv.FormatFloat(float64(start.UnixNano())/1e9, 'f', -1, 64)
endString := strconv.FormatFloat(float64(start.UnixNano())/1e9, 'f', -1, 64)
return QueryTimestampString("ts BETWEEN " + startString + " AND " + endString)
}
type QueryNameString string
func QueryNames(names ...string) QueryNameString
func QueryNamesInv(names ...string) QueryNameString
// Describes the parameters for an event query
type EventsQuery struct {
Ids []QueryIdString // Ids contains a list of CAN ID filters that are OR'd together.
Times []QueryTimestampString
Names []QueryNameString
Limit uint // max number of results.
}
// GetEvents is the mechanism to request underlying event data.
// it takes functions (which are defined in db.go) that modify the query,
// and then return the results.
func (tdb *TelemDb) GetEvents(q *EventsQuery) []skylab.BusEvent {
// if function is inverse, AND and OR are switched.
// Demorgan's
// how to know if function is inverted???
return nil
}