171 lines
3.5 KiB
Go
171 lines
3.5 KiB
Go
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"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
|
|
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("sql.Open")
|
|
return nil
|
|
}
|
|
storage := Storage{DB: db, Cfg: cfg}
|
|
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 := "sql.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 {
|
|
_, 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, "sql.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, "sql.Storage.ParseTemplate", q)
|
|
return nil
|
|
}
|
|
return t
|
|
}
|
|
|
|
func (db *Storage) BuildQuery(qname, tname string) string {
|
|
info := "sql.Storage.SetTable"
|
|
t := db.Sql.Lookup(qname)
|
|
if t == nil {
|
|
db.logErr(fmt.Errorf("template not found"), info, qname)
|
|
return ""
|
|
}
|
|
if db.Cfg.Schema != "" {
|
|
tname = db.Cfg.Schema + "." + tname
|
|
}
|
|
var out strings.Builder
|
|
data := map[string]interface{}{"table": tname}
|
|
err := t.Execute(&out, data)
|
|
if err != nil {
|
|
db.logErr(err, info, fmt.Sprintf("%+v", t))
|
|
return ""
|
|
}
|
|
return out.String()
|
|
}
|
|
|
|
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`
|
|
)
|