2023-06-30 16:51:06 +00:00
|
|
|
package db
|
2023-06-23 20:52:52 +00:00
|
|
|
|
|
|
|
// this file implements the database functions to load/store/read from a sql database.
|
2023-06-30 12:40:50 +00:00
|
|
|
|
2023-06-23 20:52:52 +00:00
|
|
|
import (
|
2023-06-30 12:40:50 +00:00
|
|
|
"context"
|
2023-06-23 20:52:52 +00:00
|
|
|
"encoding/json"
|
2023-06-30 12:40:50 +00:00
|
|
|
"fmt"
|
2023-06-23 20:52:52 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
"github.com/kschamplin/gotelem/skylab"
|
2024-02-28 20:01:44 +00:00
|
|
|
_ "github.com/mattn/go-sqlite3"
|
2023-06-23 20:52:52 +00:00
|
|
|
)
|
|
|
|
|
2023-06-30 12:40:50 +00:00
|
|
|
|
2023-06-23 20:52:52 +00:00
|
|
|
type TelemDb struct {
|
|
|
|
db *sqlx.DB
|
|
|
|
}
|
|
|
|
|
2024-02-13 16:03:39 +00:00
|
|
|
// TelemDbOption lets you customize the behavior of the sqlite database
|
2023-06-23 20:52:52 +00:00
|
|
|
type TelemDbOption func(*TelemDb) error
|
|
|
|
|
2024-02-14 16:31:41 +00:00
|
|
|
|
2024-02-28 07:07:28 +00:00
|
|
|
// this function is internal use. It actually opens the database, but uses
|
|
|
|
// a raw path string instead of formatting one like the exported functions.
|
|
|
|
func openRawDb(rawpath string, options ...TelemDbOption) (tdb *TelemDb, err error) {
|
2023-06-23 20:52:52 +00:00
|
|
|
tdb = &TelemDb{}
|
2024-02-28 07:07:28 +00:00
|
|
|
tdb.db, err = sqlx.Connect("sqlite3", rawpath)
|
2023-06-23 20:52:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, fn := range options {
|
|
|
|
err = fn(tdb)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-14 16:31:41 +00:00
|
|
|
// perform any database migrations
|
|
|
|
version, err := tdb.GetVersion()
|
2023-07-06 02:16:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-02-14 16:31:41 +00:00
|
|
|
// TODO: use logging instead of printf
|
2023-07-06 16:26:00 +00:00
|
|
|
fmt.Printf("starting version %d\n", version)
|
2023-07-06 02:16:12 +00:00
|
|
|
|
2023-07-06 18:46:02 +00:00
|
|
|
version, err = RunMigrations(tdb)
|
2023-07-06 16:26:00 +00:00
|
|
|
fmt.Printf("ending version %d\n", version)
|
2023-06-23 20:52:52 +00:00
|
|
|
|
2023-06-30 12:40:50 +00:00
|
|
|
return tdb, err
|
2023-06-23 20:52:52 +00:00
|
|
|
}
|
|
|
|
|
2024-02-28 07:07:28 +00:00
|
|
|
|
|
|
|
// this string is used to open the read-write db.
|
|
|
|
// the extra options improve performance significantly.
|
|
|
|
const rwDbPathFmt = "file:%s?_journal_mode=wal&mode=rwc&_txlock=immediate&_timeout=10000"
|
|
|
|
|
|
|
|
// OpenTelemDb opens a new telemetry database at the given path.
|
|
|
|
func OpenTelemDb(path string, options ...TelemDbOption) (*TelemDb, error) {
|
|
|
|
dbStr := fmt.Sprintf(rwDbPathFmt, path)
|
|
|
|
return openRawDb(dbStr, options...)
|
|
|
|
}
|
|
|
|
|
2023-07-06 18:46:02 +00:00
|
|
|
func (tdb *TelemDb) GetVersion() (int, error) {
|
|
|
|
var version int
|
|
|
|
err := tdb.db.Get(&version, "PRAGMA user_version")
|
|
|
|
return version, err
|
|
|
|
}
|
2023-06-29 00:23:08 +00:00
|
|
|
|
2023-07-06 18:46:02 +00:00
|
|
|
func (tdb *TelemDb) SetVersion(version int) error {
|
2023-07-06 20:21:41 +00:00
|
|
|
stmt := fmt.Sprintf("PRAGMA user_version = %d", version)
|
2023-07-06 18:46:02 +00:00
|
|
|
_, err := tdb.db.Exec(stmt)
|
|
|
|
return err
|
|
|
|
}
|
2023-06-23 20:52:52 +00:00
|
|
|
|
|
|
|
// sql expression to insert a bus event into the packets database.1
|
2024-02-13 19:41:32 +00:00
|
|
|
const sqlInsertEvent =`INSERT INTO "bus_events" (ts, name, data) VALUES `
|
2023-06-23 20:52:52 +00:00
|
|
|
|
|
|
|
// AddEvent adds the bus event to the database.
|
2024-02-13 16:03:39 +00:00
|
|
|
func (tdb *TelemDb) AddEventsCtx(ctx context.Context, events ...skylab.BusEvent) (n int64, err error) {
|
2024-02-28 07:07:28 +00:00
|
|
|
// edge case - zero events.
|
|
|
|
if len(events) == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
2024-02-13 16:03:39 +00:00
|
|
|
n = 0
|
2023-06-30 12:40:50 +00:00
|
|
|
tx, err := tdb.db.BeginTx(ctx, nil)
|
2024-02-14 08:15:21 +00:00
|
|
|
defer tx.Rollback()
|
2023-06-23 20:52:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-12 15:45:23 +00:00
|
|
|
sqlStmt := sqlInsertEvent
|
|
|
|
const rowSql = "(?, ?, json(?))"
|
|
|
|
inserts := make([]string, len(events))
|
|
|
|
vals := []interface{}{}
|
2024-02-13 16:03:39 +00:00
|
|
|
idx := 0 // we have to manually increment, because sometimes we don't insert.
|
|
|
|
for _, b := range events {
|
2024-02-12 15:45:23 +00:00
|
|
|
inserts[idx] = rowSql
|
2023-06-30 12:40:50 +00:00
|
|
|
var j []byte
|
|
|
|
j, err = json.Marshal(b.Data)
|
2023-06-23 20:52:52 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2024-02-13 16:03:39 +00:00
|
|
|
// we had some error turning the packet into json.
|
|
|
|
continue // we silently skip.
|
2023-06-23 20:52:52 +00:00
|
|
|
}
|
2024-02-13 16:03:39 +00:00
|
|
|
|
2024-02-12 15:45:23 +00:00
|
|
|
vals = append(vals, b.Timestamp.UnixMilli(), b.Data.String(), j)
|
2024-02-13 16:03:39 +00:00
|
|
|
idx++
|
2024-02-12 15:45:23 +00:00
|
|
|
}
|
2023-07-06 02:16:12 +00:00
|
|
|
|
2024-02-12 15:45:23 +00:00
|
|
|
// construct the full statement now
|
2024-02-13 16:03:39 +00:00
|
|
|
sqlStmt = sqlStmt + strings.Join(inserts[:idx], ",")
|
2024-02-12 15:45:23 +00:00
|
|
|
stmt, err := tx.PrepareContext(ctx, sqlStmt)
|
2024-02-28 07:07:28 +00:00
|
|
|
// defer stmt.Close()
|
2024-02-12 15:45:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
2023-06-23 20:52:52 +00:00
|
|
|
}
|
2024-02-13 16:03:39 +00:00
|
|
|
res, err := stmt.ExecContext(ctx, vals...)
|
2024-02-12 15:45:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-02-13 16:03:39 +00:00
|
|
|
n, err = res.RowsAffected()
|
2024-02-12 15:45:23 +00:00
|
|
|
|
2023-06-23 20:52:52 +00:00
|
|
|
tx.Commit()
|
2023-06-30 12:40:50 +00:00
|
|
|
return
|
2023-06-23 20:52:52 +00:00
|
|
|
}
|
|
|
|
|
2024-02-13 16:03:39 +00:00
|
|
|
func (tdb *TelemDb) AddEvents(events ...skylab.BusEvent) (int64, error) {
|
2023-06-23 20:52:52 +00:00
|
|
|
|
2023-06-30 12:40:50 +00:00
|
|
|
return tdb.AddEventsCtx(context.Background(), events...)
|
2023-06-23 20:52:52 +00:00
|
|
|
}
|
|
|
|
|
2023-07-06 02:16:12 +00:00
|
|
|
|
|
|
|
// GetActiveDrive finds the non-null drive and returns it, if any.
|
|
|
|
func (tdb *TelemDb) GetActiveDrive() (res int, err error) {
|
|
|
|
err = tdb.db.Get(&res, "SELECT id FROM drive_records WHERE end_time IS NULL LIMIT 1")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tdb *TelemDb) NewDrive(start time.Time, note string) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tdb *TelemDb) EndDrive() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tdb *TelemDb) UpdateDrive(id int, note string) {
|
|
|
|
|
|
|
|
}
|