diff --git a/db/commands/base/constants.go b/db/commands/base/constants.go index 7d4b01a2f821e2f99432ec48e7c75f9488a9234a..a5ade644e298871604abb7008bacb49597175b41 100644 --- a/db/commands/base/constants.go +++ b/db/commands/base/constants.go @@ -34,7 +34,7 @@ const ( OptExportShort = 'e' OptModelPath = "model-path" - OptModelPathDefault = "app/models" + OptModelPathDefault = "app/http/models" OptModelPathDesc = "Generated model files location" OptModelPathShort = 'm' diff --git a/db/src/dao.go b/db/src/dao.go new file mode 100644 index 0000000000000000000000000000000000000000..c58717782f12a2a585018c74ddc86f31f52cc08c --- /dev/null +++ b/db/src/dao.go @@ -0,0 +1,411 @@ +// Author: wsfuyibing <682805@qq.com> +// Date: 2025-04-10 + +package src + +import ( + "context" + "gitee.com/go-libs/db-xorm/db" + "xorm.io/xorm" +) + +type ( + // Dao is an alias of Database Access Object, It's read and write data on + // database server such as mysql, postgres. + Dao[T any] struct { + sess *xorm.Session + key string + typ T + } + + DaoFieldNameForPrimaryKey interface { + PrimaryKeyName() string + } +) + +// NewDao creates a dao instance with given model type and model struct. Data +// in database operated depends on model type. +// +// type ModelService struct { +// Dao *db.Dao[models.Model] +// } +// +// func NewModelService(dbs ...*framework.DB) *ModelService { +// o := &ModelService{} +// o.Dao = db.NewDao[models.ErpSales](models.ErpSales{}) +// o.Dao.WithConn(dbs...) +// o.Dao.WithKey("db") +// return o +// } +func NewDao[T any](t T) *Dao[T] { + return &Dao[T]{ + typ: t, + key: DefaultKey, + } +} + +// +---------------------------------------------------------------------------+ +// | Common operations | +// +---------------------------------------------------------------------------+ + +// Master returns a session from master connection. +func (o *Dao[T]) Master(ctx context.Context) (*db.Session, error) { + if o.sess != nil { + return o.sess, nil + } + return Config.GetSlave(ctx, o.key) +} + +// Slaver returns a session from slaver connection. +func (o *Dao[T]) Slaver(ctx context.Context) (*xorm.Session, error) { + if o.sess != nil { + return o.sess, nil + } + return Config.GetSlave(ctx, o.key) +} + +// WithSess bind a session of database connection. +func (o *Dao[T]) WithSess(ss ...*db.Session) *Dao[T] { + if len(ss) > 0 && ss[0] != nil { + o.sess = ss[0] + } + return o +} + +// WithKey bind a connection key for connector. +func (o *Dao[T]) WithKey(k string) *Dao[T] { + if k != "" { + o.key = k + } + return o +} + +// +---------------------------------------------------------------------------+ +// | Read / Get one record | +// +---------------------------------------------------------------------------+ + +// GetBy returns a model specified column and value. +// +// // sql: SELECT * FROM `table` WHERE `id` = 10 LIMIT 1 +// svc.Dao.GetBy(ctx, "id", 10) +func (o *Dao[T]) GetBy(ctx context.Context, column string, value any) (model *T, has bool, err error) { + return o.GetByMap(ctx, map[string]any{ + column: value, + }) +} + +// GetById returns a model by given primary key value. +// +// // sql: SELECT * FROM `table` WHERE `id` = 10 LIMIT 1 +// svc.Dao.GetById(ctx, 10) +func (o *Dao[T]) GetById(ctx context.Context, value any) (model *T, has bool, err error) { + return o.GetByMap(ctx, map[string]any{ + o.parsePrimaryKeyId(): value, + }) +} + +// GetByMap returns a model by given condition with mapper param. +// +// // sql: SELECT * FROM `table` WHERE `id` = 10 LIMIT 1 +// svc.Dao.GetByMap(ctx, map[string]any{ +// "id": 10 +// }) +func (o *Dao[T]) GetByMap(ctx context.Context, condition map[string]any) (model *T, has bool, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Init an empty model. + model = &o.typ + + // Send query on database. + if has, err = sess.Where(condition).Get(&model); err != nil || !has { + model = nil + } + return +} + +// GetByStruct returns a model with model condition. It's ignore default value +// condition such as zero or empty string. +// +// // sql: SELECT * FROM `table` WHERE `id` = 10 LIMIT 1 +// svc.Dao.GetByModel(ctx, &models.Model{ +// Id: 10, +// }) +func (o *Dao[T]) GetByStruct(ctx context.Context, t T) (model *T, has bool, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Init an empty model. + model = &o.typ + + // Send query on database. + if has, err = sess.Where(t).Get(&model); err != nil || !has { + model = nil + } + return +} + +// +---------------------------------------------------------------------------+ +// | Read / List records. | +// +---------------------------------------------------------------------------+ + +// ListByMap returns a list of models by given condition with map param. +// +// sql: SELECT * FROM `table` WHERE `user_id` = 1 +// svc.Dao.GetByModel(ctx, map[string]any{ +// "user_id": 1 +// }) +func (o *Dao[T]) ListByMap(ctx context.Context, m map[string]any) (list []*T, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Init an empty model list. + list = make([]*T, 0) + + // Send query on database. + if err = sess.Where(m).Find(&list); err != nil { + list = nil + } + return +} + +// ListByStruct returns a list of models by given condition with model struct. +// +// // sql: SELECT * FROM `table` WHERE `user_id` = 10 +// svc.Dao.GetByModel(ctx, &models.Model{ +// UserId: 1 +// }) +func (o *Dao[T]) ListByStruct(ctx context.Context, t T) (list []*T, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Init an empty model list. + list = make([]*T, 0) + + // Send query on database. + if err = sess.Where(t).Find(&list); err != nil { + list = nil + } + return +} + +// +---------------------------------------------------------------------------+ +// | Read / List records with paginator. | +// +---------------------------------------------------------------------------+ + +// PagingByMap returns a list of models by given condition with map and calculate +// total items in table. +func (o *Dao[T]) PagingByMap(ctx context.Context, m map[string]any, page, size int) (list []*T, total int64, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Execute matched record count. + if total, err = sess.Table(o.typ).Where(m).Count(); err != nil { + return + } + + // Return if no matched record. + if total == 0 { + return + } + + // Init an empty model list. + list = make([]*T, 0) + + // Send query on database. + if err = sess.Where(m).Limit(size, (page-1)*size).Find(&list); err != nil { + list = nil + } + return +} + +// PagingByStruct returns a list of models by given condition with model struct and +// calculate total items in table. +func (o *Dao[T]) PagingByStruct(ctx context.Context, t T, page, size int) (list []*T, total int64, err error) { + var sess *xorm.Session + + // Open a session from slaver connection. + if sess, err = o.Slaver(ctx); err != nil { + return + } + + // Execute matched record count. + if total, err = sess.Table(o.typ).Where(t).Count(); err != nil { + return + } + + // Return if no matched record. + if total == 0 { + return + } + + // Init an empty model list. + list = make([]*T, 0) + + // Send query on database. + if err = sess.Where(t).Limit(size, (page-1)*size).Find(&list); err != nil { + list = nil + } + return +} + +// +---------------------------------------------------------------------------+ +// | Write / Add new record | +// +---------------------------------------------------------------------------+ + +// AddByStruct adds a model by given model struct. +// +// svc.Dao.AddByModel(ctx, &models.Model{ +// UserId: 1, +// ... +// }) +func (o *Dao[T]) AddByStruct(ctx context.Context, t T) (model *T, err error) { + var sess *xorm.Session + + // Open a session from master connection. + if sess, err = o.Master(ctx); err != nil { + return + } + + // Send query on database. + if _, err = sess.Insert(&t); err != nil { + model = &t + } + return +} + +// +---------------------------------------------------------------------------+ +// | Write / Delete records | +// +---------------------------------------------------------------------------+ + +// DeleteBy delete records by given condition with key value pairs. Param k is +// column name and v is column value. +// +// // sql: DELETE FORM `table` WHERE `id` = 1 +// svc.Dao.DeleteBy(ctx, "id", 1) +func (o *Dao[T]) DeleteBy(ctx context.Context, k string, v any) (affects int64, err error) { + return o.DeleteByMap(ctx, map[string]any{k: v}) +} + +// DeleteById delete records by given condition with primary key value. Param v is +// an integer value of primary key. +// +// // sql: DELETE FORM `table` WHERE `id` = 1 +// svc.Dao.DeleteById(ctx, 1) +func (o *Dao[T]) DeleteById(ctx context.Context, v any) (affects int64, err error) { + return o.DeleteByMap(ctx, map[string]any{o.parsePrimaryKeyId(): v}) +} + +// DeleteByMap delete records by given condition with multiple key value pairs. +// +// // sql: DELETE FORM `table` WHERE `status` = 0 AND `status_type` = "error" +// svc.Dao.DeleteByMap(ctx, map[string]any{ +// "status": 0, +// "status_type": "error", +// }) +func (o *Dao[T]) DeleteByMap(ctx context.Context, m map[string]any) (affects int64, err error) { + var sess *xorm.Session + + // Open a session from master connection. + if sess, err = o.Master(ctx); err != nil { + return + } + + // Send delete request. + affects, err = sess.Where(m).Delete(o.typ) + return +} + +// DeleteByStruct delete records by given condition with struct definition. +// +// // sql: DELETE FORM `table` WHERE `status` = 1 AND `status_type` = "error" +// svc.Dao.DeleteByMap(ctx, models.Model{ +// "status": 1, +// "status_type": "error", +// }) +func (o *Dao[T]) DeleteByStruct(ctx context.Context, t T) (affects int64, err error) { + var sess *xorm.Session + + // Open a session from master connection. + if sess, err = o.Master(ctx); err != nil { + return + } + + // Send delete query on database. + affects, err = sess.Where(t).Delete(o.typ) + return +} + +// +---------------------------------------------------------------------------+ +// | Write | +// +---------------------------------------------------------------------------+ + +// UpdateFieldsById updates a record with key value pairs by primary key. +// +// // sql: UPDATE `table` SET `name` = "test", `age` = 18 WHERE `id` = 10 +// svc.Dao.UpdateMapById(ctx, map[string]any{ +// "name": "test", +// "age": 18, +// }, 10) +func (o *Dao[T]) UpdateFieldsById(ctx context.Context, fields map[string]any, v any) (affects int64, err error) { + return o.UpdateFieldsByMap(ctx, fields, map[string]any{ + o.parsePrimaryKeyId(): v, + }) +} + +// UpdateFieldsByMap updates a record with map condition. +// +// // sql: UPDATE `table` SET `name` = "test", `age` = 18 WHERE `id` = 10 AND `status` = 0 +// svc.Dao.UpdateFieldsByMap(ctx, map[string]any{ +// "name": "test", +// "age": 18, +// }, map[string]any{ +// "id": 10, +// "status": 0, +// }) +func (o *Dao[T]) UpdateFieldsByMap(ctx context.Context, fields, condition map[string]any) (affects int64, err error) { + var sess *xorm.Session + + // Open a session from master connection. + if sess, err = o.Master(ctx); err != nil { + return + } + + // Send update query on database. + affects, err = sess.Table(o.typ).Where(condition).Update(fields) + return +} + +// +---------------------------------------------------------------------------+ +// | Access methods | +// +---------------------------------------------------------------------------+ + +func (o *Dao[T]) parsePrimaryKeyId() (id string) { + var ptr any = &o.typ + if v, ok := ptr.(DaoFieldNameForPrimaryKey); ok { + if id = v.PrimaryKeyName(); id != "" { + return + } + } + return "id" +}