package modelbase import ( "errors" "fmt" "lone-services/pkg/utils" jsoniter "github.com/json-iterator/go" "gorm.io/gorm" ) const ( DefaultPage = 1 DefaultSize = 20 ) var ( db *gorm.DB prefix string debug bool ) // Config for Init. type Config struct { Prefix string Debug bool } // Init binds the process DB used by Base helpers. Call once after mysql.New. func Init(conn *gorm.DB, c Config) { db = conn prefix = c.Prefix debug = c.Debug } // DB returns the package-level connection (nil before Init). func DB() *gorm.DB { return db } // Prefix returns the configured table prefix (e.g. lone_). func Prefix() string { return prefix } type GroupCountResult struct { Item string `gorm:"column:item"` Count int `gorm:"column:count"` } type GroupSumCountIntResult struct { Item string `gorm:"column:item"` Count int `gorm:"column:count"` Sum int `gorm:"column:sum"` } type GroupSumCountFloat32Result struct { Item string `gorm:"column:item"` Count int `gorm:"column:count"` Sum float32 `gorm:"column:sum"` } type Params struct { Eq map[string]string Not map[string]string Or []map[string]string In map[string][]string Other map[string]string Like map[string]string Between map[string][2]string Join []string Order string Page int Size int } type PageResult struct { Count int64 Items interface{} } // Base is embedded by per-table models in each service. type Base struct { Table string tx *gorm.DB inTx bool } // WithTX runs subsequent ops on an outer transaction (e.g. gorm.Transaction). func (m Base) WithTX(tx *gorm.DB) Base { m.tx = tx m.inTx = true return m } func (m *Base) Begin() *gorm.DB { m.tx = db.Begin() m.inTx = true return m.tx } func (m *Base) Rollback() *gorm.DB { if m.tx != nil { m.tx.Rollback() } m.inTx = false return m.tx } func (m *Base) Commit() *gorm.DB { if m.tx != nil { m.tx.Commit() } m.inTx = false return m.tx } func (m Base) session() *gorm.DB { var o *gorm.DB if m.inTx && m.tx != nil { o = m.tx.Table(m.Table) } else { o = db.Table(m.Table) } if debug { o = o.Debug() } return o } // Session returns the DB session for this model (honors WithTX). func (m Base) Session() *gorm.DB { return m.session() } // Create inserts a row. func (m Base) Create(data interface{}) error { return m.session().Create(data).Error } // Edit updates rows matching Params. func (m Base) Edit(w Params, data interface{}) (int64, error) { o := m.setWhere(w) update := make(map[string]interface{}) jsonBytes, _ := jsoniter.Marshal(data) _ = jsoniter.Unmarshal(jsonBytes, &update) for k, v := range update { if nested, ok := v.(map[string]interface{}); ok { if b, err := jsoniter.Marshal(nested); err == nil { update[k] = string(b) } } } ret := o.Updates(update) return ret.RowsAffected, ignoreNotFound(ret.Error) } // Del deletes rows matching Params. func (m Base) Del(w Params, data interface{}) (int64, error) { o := m.setWhere(w) ret := o.Delete(data) return ret.RowsAffected, ignoreNotFound(ret.Error) } // Sql runs a raw query. func (m Base) Sql(sql string, retData interface{}) error { err := m.session().Raw(sql).Scan(retData).Error return ignoreNotFound(err) } // GetById loads by primary key fields already set on retData. func (m Base) GetById(retData interface{}) error { err := m.session().First(retData).Error return ignoreNotFound(err) } // GetOne loads one row matching Params. func (m Base) GetOne(w Params, retData interface{}) error { o := m.setWhere(w) err := o.Order(w.Order).First(retData).Error return ignoreNotFound(err) } // GetIDsByKeyword plucks a column into []string. func (m Base) GetIDsByKeyword(w Params, column string) ([]string, error) { var results []string o := m.setWhere(w) err := o.Pluck(column, &results).Error if err != nil { return nil, err } return results, nil } // Count finds into retData then returns count (legacy behavior). func (m Base) Count(w Params, retData interface{}) (int64, error) { o := m.setWhere(w) var count int64 err := o.Find(retData).Count(&count).Error return count, ignoreNotFound(err) } func (m Base) CountNumber(w Params) (int64, error) { o := m.setWhere(w) var count int64 err := o.Count(&count).Error return count, ignoreNotFound(err) } func (m Base) Page(w Params, retData interface{}) (PageResult, error) { var items PageResult o := m.setWhere(w) var count int64 err := o.Count(&count).Error if err != nil { return items, ignoreNotFound(err) } if w.Page < DefaultPage { w.Page = DefaultPage } if w.Size < utils.NumberOne { w.Size = DefaultSize } err = o.Offset((w.Page - 1) * w.Size).Limit(w.Size).Order(w.Order).Find(retData).Error items.Count = count items.Items = retData return items, ignoreNotFound(err) } func (m Base) CountSumNumber(w Params) (int64, error) { return m.CountNumber(w) } func (m Base) CountSumFloat32(w Params, key string) (float32, error) { o := m.setWhere(w) var count float32 fs := fmt.Sprintf("IFNULL(SUM(%s), 0) AS %s", key, key) err := o.Select(fs).Find(&count).Error return count, ignoreNotFound(err) } func (m Base) CountGroupNumber(w Params, key string) ([]GroupCountResult, error) { o := m.setWhere(w) var count []GroupCountResult fs := fmt.Sprintf("%s AS item, COUNT(*) AS count", key) err := o.Select(fs).Group(key).Find(&count).Error return count, ignoreNotFound(err) } func (m Base) GroupSumNumber(w Params, group, key string) (GroupSumCountIntResult, error) { o := m.setWhere(w) var count GroupSumCountIntResult fs := fmt.Sprintf("%s AS item, SUM(%s) AS sum", group, key) err := o.Select(fs).Group(key).Scan(&count).Error return count, ignoreNotFound(err) } func (m Base) CountIntNumber(w Params, key string) (GroupSumCountIntResult, error) { o := m.setWhere(w) var count GroupSumCountIntResult fs := fmt.Sprintf("COUNT(*) AS count, SUM(%s) AS sum", key) err := o.Select(fs).Find(&count).Error return count, ignoreNotFound(err) } func (m Base) CountFloat32Number(w Params, key string) (GroupSumCountFloat32Result, error) { o := m.setWhere(w) var count GroupSumCountFloat32Result fs := fmt.Sprintf("COUNT(*) AS count, IFNULL(SUM(%s), 0) AS sum", key) err := o.Select(fs).Find(&count).Error return count, ignoreNotFound(err) } func (m Base) SumFloat32(w Params, key string) (float32, error) { o := m.setWhere(w) var sum float32 key = fmt.Sprintf("IFNULL(SUM(%s), 0)", key) err := o.Select(key).Scan(&sum).Error return sum, ignoreNotFound(err) } func (m Base) SumInt(w Params, key string) (int, error) { o := m.setWhere(w) var sum int key = fmt.Sprintf("IFNULL(SUM(%s), 0)", key) err := o.Select(key).Scan(&sum).Error return sum, ignoreNotFound(err) } // Items loads all matching rows into retData. func (m Base) Items(w Params, retData interface{}) error { o := m.setWhere(w) err := o.Order(w.Order).Find(retData).Error return ignoreNotFound(err) } func (m Base) setWhere(w Params) *gorm.DB { o := m.session().Where(w.Eq) if w.Or != nil { for i := 0; i < len(w.Or); i++ { o = o.Or(w.Or[i]) } } if w.In != nil { for k, v := range w.In { o = o.Where(k, v) } } if w.Like != nil { for k, v := range w.Like { o = o.Where(k, v) } } if w.Join != nil { for i := 0; i < len(w.Join); i++ { o = o.Joins(w.Join[i]) } } if w.Other != nil { for k, v := range w.Other { o = o.Where(k, v) } } if w.Between != nil { for k, v := range w.Between { if len(v) == 2 { o = o.Where(k+" BETWEEN ? AND ?", v[0], v[1]) } } } if w.Not != nil { o = o.Not(w.Not) } return o } func ignoreNotFound(err error) error { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err }