smon/datapoint.go
2024-05-20 19:40:19 +02:00

282 lines
5.8 KiB
Go

package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
"github.com/jmoiron/sqlx"
// Standard
"database/sql"
"errors"
"strings"
"time"
)
type DatapointType string
const (
INT DatapointType = "INT"
STRING = "STRING"
DATETIME = "DATETIME"
)
type Datapoint struct {
ID int
Group string
Name string
Datatype DatapointType
LastValue time.Time `db:"last_value"`
DatapointValueJSON []byte `db:"datapoint_value_json"`
LastDatapointValue DatapointValue
Found bool
}
type DatapointValue struct {
ID int
DatapointID int `db:"datapoint_id"`
Ts time.Time
ValueInt sql.NullInt64 `db:"value_int"`
ValueString sql.NullString `db:"value_string"`
ValueDateTime sql.NullTime `db:"value_datetime"`
TemplateValue any
}
func (dp DatapointValue) Value() any { // {{{
if dp.ValueInt.Valid {
return dp.ValueInt.Int64
}
if dp.ValueString.Valid {
return dp.ValueString.String
}
if dp.ValueDateTime.Valid {
return dp.ValueDateTime.Time
}
return nil
} // }}}
func (dp DatapointValue) FormattedTime() string {// {{{
if dp.ValueDateTime.Valid {
return dp.ValueDateTime.Time.Format("2006-01-02 15:04:05")
}
return "invalid time"
}// }}}
func (dp Datapoint) Update() (err error) {// {{{
name := strings.TrimSpace(dp.Name)
if name == "" {
err = errors.New("Name can't be empty")
return
}
if dp.ID == 0 {
_, err = service.Db.Conn.Exec(
`INSERT INTO datapoint("group", name, datatype) VALUES($1, $2, $3)`,
dp.Group,
name,
dp.Datatype,
)
} else {
_, err = service.Db.Conn.Exec(
`UPDATE datapoint SET "group"=$2, name=$3, datatype=$4 WHERE id=$1`,
dp.ID,
dp.Group,
name,
dp.Datatype,
)
}
return
}// }}}
func DatapointAdd[T any](name string, value T) (err error) { // {{{
row := service.Db.Conn.QueryRow(`SELECT id, datatype FROM datapoint WHERE name=$1`, name)
var dpID int
var dpType DatapointType
err = row.Scan(&dpID, &dpType)
if err != nil {
err = werr.Wrap(err).WithData(struct {
Name string
Value any
}{name, value})
return
}
switch dpType {
case INT:
_, err = service.Db.Conn.Exec(`INSERT INTO datapoint_value(datapoint_id, value_int) VALUES($1, $2)`, dpID, value)
case STRING:
_, err = service.Db.Conn.Exec(`INSERT INTO datapoint_value(datapoint_id, value_string) VALUES($1, $2)`, dpID, value)
case DATETIME:
_, err = service.Db.Conn.Exec(`INSERT INTO datapoint_value(datapoint_id, value_datetime) VALUES($1, $2)`, dpID, value)
}
if err != nil {
err = werr.Wrap(err).WithData(struct {
ID int
value any
}{dpID, value})
return
}
service.Db.Conn.Exec(`UPDATE datapoint SET last_value = NOW() WHERE name=$1`, name)
return
} // }}}
func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{
dps = []Datapoint{}
var rows *sqlx.Rows
rows, err = service.Db.Conn.Queryx(`
SELECT
dp.id,
dp.name,
dp.datatype,
dp.last_value,
dp.group,
dpv.id AS v_id,
dpv.ts,
dpv.value_int,
dpv.value_string,
dpv.value_datetime
FROM public.datapoint dp
LEFT JOIN (
SELECT
*,
row_number() OVER (PARTITION BY "datapoint_id" ORDER BY ts DESC) AS rn
FROM datapoint_value
) dpv ON dpv.datapoint_id = dp.id AND rn = 1
ORDER BY
dp.group ASC,
dp.name ASC
`)
if err != nil {
err = werr.Wrap(err)
}
defer rows.Close()
type DbRes struct {
ID int
Group string
Name string
Datatype DatapointType
LastValue time.Time `db:"last_value"`
VID sql.NullInt64 `db:"v_id"`
Ts sql.NullTime
ValueInt sql.NullInt64 `db:"value_int"`
ValueString sql.NullString `db:"value_string"`
ValueDateTime sql.NullTime `db:"value_datetime"`
}
for rows.Next() {
dp := Datapoint{}
dpv := DatapointValue{}
res := DbRes{}
err = rows.StructScan(&res)
if err != nil {
err = werr.Wrap(err)
return
}
dp.ID = res.ID
dp.Name = res.Name
dp.Group = res.Group
dp.Datatype = res.Datatype
dp.LastValue = res.LastValue
dp.Found = true
if res.VID.Valid {
dpv.ID = int(res.VID.Int64)
dpv.Ts = res.Ts.Time
dpv.ValueInt = res.ValueInt
dpv.ValueString = res.ValueString
dpv.ValueDateTime = res.ValueDateTime
dp.LastDatapointValue = dpv
}
dps = append(dps, dp)
}
return
} // }}}
func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{
var query string
var param any
if id > 0 {
query = `SELECT *, true AS found FROM datapoint WHERE id = $1`
param = id
dp.ID = id
} else {
query = `SELECT *, true AS found FROM datapoint WHERE name = $1`
param = name
}
row := service.Db.Conn.QueryRowx(query, param)
err = row.StructScan(&dp)
if err == sql.ErrNoRows {
dp = Datapoint{
Name: name,
}
err = nil
return
}
if err != nil {
err = werr.Wrap(err).WithData(name)
return
}
row = service.Db.Conn.QueryRowx(`
SELECT *
FROM datapoint_value
WHERE datapoint_id = $1
ORDER BY ts DESC
LIMIT 1
`,
dp.ID,
)
err = row.StructScan(&dp.LastDatapointValue)
if err == sql.ErrNoRows {
err = nil
return
}
if err != nil {
err = werr.Wrap(err).WithData(dp.ID)
return
}
return
} // }}}
func DatapointDelete(id int) (err error) {// {{{
_, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id)
if err != nil {
err = werr.Wrap(err).WithData(id)
}
return
}// }}}
func DatapointValues(id int) (values []DatapointValue, err error) {// {{{
rows, err := service.Db.Conn.Queryx(`SELECT * FROM datapoint_value WHERE datapoint_id=$1 ORDER BY ts DESC LIMIT 500`, id)
if err != nil {
err = werr.Wrap(err).WithData(id)
return
}
defer rows.Close()
for rows.Next() {
dpv := DatapointValue{}
err = rows.StructScan(&dpv)
if err != nil {
err = werr.Wrap(err).WithData(id)
return
}
values = append(values, dpv)
}
return
}// }}}