package storage import ( "database/sql" "fmt" "os" "text/template" lib "git.sr.ht/~cco/go-scopes" "git.sr.ht/~cco/go-scopes/logging/log" ) type Cfg struct { Driver, Connstr, Schema string } type Storage struct { *sql.DB *Cfg Schema string Params map[string]string Sql *template.Template Errors []error } func Start(cfg *Cfg) lib.Proc { return func(ctx lib.Context) { ctx.WithState(Open(cfg)) } } type Rows = sql.Rows type rowsProc = func(*sql.Rows) error type scn[T any] interface{ Scan(*Rows) (T, error) } type scp interface{ ScanP(*Rows) error } type scpcon[T any] interface { *T scp } var SqlSources = "" func Open(cfg *Cfg) *Storage { db, err := sql.Open(cfg.Driver, cfg.Connstr) if err != nil { log.Error(err).Msg("storage.Open") return nil } storage := Storage{ DB: db, Cfg: cfg, Schema: cfg.Schema, Params: map[string]string{}, } storage.Sql = storage.ParseTemplate(SqlSources) return &storage } func QueryData[T scn[T]](db *Storage, q string, args ...interface{}) []T { var data []T var rec T proc := func(r *sql.Rows) error { rec, err := rec.Scan(r) data = append(data, rec) return err } db.Query(proc, q, args...) // ?? check error return data } func QueryDataP[P scpcon[T], T any](db *Storage, q string, args ...interface{}) []T { var data []T rp := P(new(T)) proc := func(r *sql.Rows) error { err := rp.ScanP(r) data = append(data, *rp) return err } db.Query(proc, q, args...) // ?? check error return data } func QueryCol[T any](db *Storage, q string, args ...interface{}) []T { var data []T var field T proc := func(r *sql.Rows) error { err := r.Scan(&field) data = append(data, field) return err } db.Query(proc, q, args...) // ?? check error return data } func (db *Storage) Query(process rowsProc, q string, args ...interface{}) error { info := "storage.Query" //log.Debug().Str("query", q).Msg(info) rows, err := db.DB.Query(q, args...) if err != nil { return db.LogErr(err, info, q) } for rows.Next() { if err := process(rows); err != nil { return db.LogErr(err, info, q) } } if err := rows.Err(); err != nil { return db.LogErr(err, info, q) } return nil } func (db *Storage) Exec(q string, args ...interface{}) (int64, error) { info := "sql.Storage.Exec" //log.Debug().Str("query", q).Msg(info) res, err := db.DB.Exec(q, args...) if err != nil { db.LogErr(err, info, q) return 0, err } nrows, _ := res.RowsAffected() return nrows, nil } func (db *Storage) DropTable(tn string) error { //if db.Schema != "" { // tn = fmt.Sprintf("%s.%s", db.Schema, tn) //} _, err := db.Exec(fmt.Sprintf(sql_drop, tn)) return err } func (db *Storage) RunScript(path string) error { b, err := os.ReadFile(path) if err != nil { return db.LogErr(err, "storage.RunScript", path) } _, err = db.Exec(string(b)) return err } func (db *Storage) ParseTemplate(q string) *template.Template { t, err := template.New("sql").Parse(q) if err != nil { db.LogErr(err, "storage.ParseTemplate", q) return nil } return t } func (db *Storage) LogErr(err error, info, inp string) error { log.Error(err).Str("input", inp).Msg(info) db.Errors = append(db.Errors, err) return err } const ( sql_drop = `drop table if exists %s` )