package main import ( // External werr "git.gibonuddevalla.se/go/wrappederror" "github.com/expr-lang/expr" "github.com/expr-lang/expr/ast" "github.com/expr-lang/expr/parser" "github.com/lib/pq" // Standard "database/sql" "encoding/json" "fmt" "strings" ) type Trigger struct { ID int Name string SectionID int `db:"section_id"` Expression string Datapoints []string DatapointValues map[string]any } type ExprRenamePatcher struct { OldName string NewName string } func (p ExprRenamePatcher) Visit(node *ast.Node) { if n, ok := (*node).(*ast.IdentifierNode); ok && n.Value == p.OldName { ast.Patch(node, &ast.IdentifierNode{Value: p.NewName}) } } func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{ t.SectionID = sectionID t.Name = name t.Expression = "false" err = t.Update() return } // }}} func TriggersRetrieve() (areas []Area, err error) { // {{{ areas = []Area{} row := service.Db.Conn.QueryRow(` WITH section_triggers AS ( SELECT s.id AS id, s.area_id, s.name AS name, jsonb_agg( to_jsonb(t.*) ) AS triggers FROM section s LEFT JOIN "trigger" t ON t.section_id = s.id GROUP BY s.id, s.name) SELECT jsonb_agg(jsonsections) FROM ( SELECT a.id, a.name, jsonb_agg( to_jsonb( s.* ) ) AS sections FROM area a LEFT JOIN section_triggers s ON s.area_id = a.id GROUP BY a.id, a.name ) jsonsections `, ) var jsonData []byte err = row.Scan(&jsonData) if err != nil { err = werr.Wrap(err) return } if jsonData == nil { return } err = json.Unmarshal(jsonData, &areas) if err != nil { err = werr.Wrap(err) return } return } // }}} func TriggersRetrieveByDatapoint(datapointName string) (triggers []Trigger, err error) { // {{{ triggers = []Trigger{} row := service.Db.Conn.QueryRow(` SELECT jsonb_agg(t.*) FROM public."trigger" t WHERE datapoints @> $1 `, fmt.Sprintf(`["%s"]`, datapointName), ) var data []byte err = row.Scan(&data) if err != nil { err = werr.Wrap(err).WithData(datapointName) return } // no triggers found for this datapoint. if data == nil { return } err = json.Unmarshal(data, &triggers) if err != nil { err = werr.Wrap(err).WithData(datapointName) return } return } // }}} func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{ row := service.Db.Conn.QueryRow(`SELECT to_jsonb(t.*) FROM "trigger" t WHERE id=$1`, id) var jsonData []byte err = row.Scan(&jsonData) if err != nil { err = werr.Wrap(err) return } err = json.Unmarshal(jsonData, &trigger) return } // }}} func TriggerDelete(id int) (err error) { // {{{ _, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE id=$1`, id) if err != nil { return werr.Wrap(err).WithData(id) } return } // }}} func (t *Trigger) Validate() (ok bool, err error) { // {{{ if strings.TrimSpace(t.Name) == "" { err = fmt.Errorf("Name can't be empty") return } if strings.TrimSpace(t.Expression) == "" { err = fmt.Errorf("Expression can't be empty") return } return true, nil } // }}} func (t *Trigger) Update() (err error) { // {{{ var ok bool if ok, err = t.Validate(); !ok { return } if t.Datapoints == nil { t.Datapoints = []string{} } jsonDatapoints, _ := json.Marshal(t.Datapoints) if t.ID == 0 { var row *sql.Row row = service.Db.Conn.QueryRow(` INSERT INTO "trigger"(name, section_id, expression, datapoints) VALUES($1, $2, $3, $4) RETURNING id `, t.Name, t.SectionID, t.Expression, jsonDatapoints, ) err = row.Scan(&t.ID) if err != nil { err = werr.Wrap(err).WithData( struct { SectionID int Name string Expression string JsonDataPoints []byte }{ t.SectionID, t.Name, t.Expression, jsonDatapoints, }, ) return } } else { _, err = service.Db.Conn.Exec(` UPDATE "trigger" SET name=$2, expression=$3, datapoints=$4 WHERE id=$1 `, t.ID, t.Name, t.Expression, jsonDatapoints, ) } if pqErr, ok := err.(*pq.Error); ok { err = werr.Wrap(err).WithData( struct { Trigger *Trigger PostgresCode pq.ErrorCode PostgresMsg string }{ t, pqErr.Code, pqErr.Code.Name(), }) } else if err != nil { err = werr.Wrap(err).WithData(t) } return } // }}} func (t *Trigger) Run() (output any, err error) { // {{{ datapoints := make(map[string]Datapoint) for _, dpname := range t.Datapoints { var dp Datapoint dp, err = DatapointRetrieve(0, dpname) if err != nil { err = werr.Wrap(err) return } datapoints[dpname] = dp } t.DatapointValues = make(map[string]any) for dpName, dp := range datapoints { t.DatapointValues[dpName] = dp.LastDatapointValue.Value() } program, err := expr.Compile(t.Expression) if err != nil { return } output, err = expr.Run(program, t.DatapointValues) if err != nil { return } return } // }}} func (t *Trigger) RenameDatapoint(from, to string) error { // {{{ tree, err := parser.Parse(t.Expression) if err != nil { return werr.Wrap(err).WithData(t.Expression) } ast.Walk(&tree.Node, ExprRenamePatcher{from, to}) t.Expression = tree.Node.String() return nil } // }}}