// Package `tracking` defines a generic track (sort of record) type // and a container type that allows storing of tracks in a SQL database. package tracking import ( sqllib "database/sql" "encoding/json" "time" lib "git.sr.ht/~cco/go-scopes" "git.sr.ht/~cco/go-scopes/logging/log" "git.sr.ht/~cco/go-scopes/storage" sql "git.sr.ht/~cco/go-scopes/storage" ) type ItemFactory func(*TrackTemplate) Track type Track interface { TrackId() lib.Ident Head() lib.StrMap Data() lib.Map TimeStamp() *time.Time Update(*TrackTemplate) } type BaseTrack = track type TrackTemplate struct { TrackId lib.Ident Head lib.StrMap TimeStamp *time.Time Data lib.Map Container *Container } // basic track implementation type track struct { trackId lib.Ident head lib.StrMap timeStamp *time.Time data lib.Map container *Container } func MakeTrack(t *TrackTemplate) Track { return &track{t.TrackId, t.Head, t.TimeStamp, t.Data, t.Container} } func (tr *track) TrackId() lib.Ident { return tr.trackId } func (tr *track) Head() lib.StrMap { return tr.head } func (tr *track) Data() lib.Map { return tr.data } func (tr *track) TimeStamp() *time.Time { return tr.timeStamp } func (tr *track) Update(t *TrackTemplate) { if t.TrackId != 0 { tr.trackId = t.TrackId } if t.Head != nil { tr.head = t.Head // or update map with non-empty values? } if t.TimeStamp != nil { tr.timeStamp = t.TimeStamp } if t.Data != nil { tr.data = t.Data } if t.Container != nil { tr.container = t.Container } } // basic container implementation type Container struct { ItemFactory ItemFactory TableName string HeadFields []string Indexes [][]string Storage *sql.Storage } func Tracks(db *sql.Storage) *Container { return &Container{ ItemFactory: MakeTrack, TableName: "tracks", HeadFields: []string{"taskId", "userName"}, Indexes: [][]string{[]string{"taskId", "userName"}, []string{"userName"}}, Storage: db, } } func (cont *Container) New(headValues lib.StrSlice, data lib.Map) Track { head := lib.StrMap{} for i, k := range cont.HeadFields { if i >= len(headValues) { break } head[k] = headValues[i] } //tr := &track{head: head, data: data} tr := cont.ItemFactory(&TrackTemplate{Head: head, Data: data, Container: cont}) cont.insert(tr) return tr } func (cont *Container) insert(tr Track) lib.Ident { var columns []string var values []any head := tr.Head() for _, k := range cont.HeadFields { columns = append(columns, k) values = append(values, head[k]) } columns = append(columns, "Data") b, _ := json.Marshal(tr.Data()) values = append(values, b) db := cont.Storage data := lib.Map{ "schema": db.Schema, "tablename": cont.TableName, "columns": columns, } sql := storage.BuildSql(SqlInsert, data) var trid lib.Ident var ts *time.Time var tsstr string proc := func(r *sqllib.Rows) error { err := r.Scan(&trid, &ts) if err != nil { err = r.Scan(&trid, &tsstr) ts = ParseDateTime(tsstr) } return err } if err := db.Query(proc, sql, values...); err == nil { tr.Update(&TrackTemplate{TrackId: trid, TimeStamp: ts}) return trid } return 0 } func ParseDateTime(inp string) *time.Time { ts, err := time.Parse("2006-01-02 15:04:05", inp) if err == nil { //fmt.Printf("%+v\n", ts) return &ts } log.Error(err).Msg("storage.tracking.ParseDateTime") return nil } func (cont *Container) CreateTable() { db := cont.Storage data := lib.Map{ "schema": db.Schema, "tablename": cont.TableName, "headFields": cont.HeadFields, "indexes": cont.Indexes, "params": db.Params, } sql := storage.BuildSql(SqlCreate, data) if _, err := db.Exec(sql); err != nil { panic(err) } }