mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
add automatic time and soft deleting features: create_at/update_at/delete_at for package gdb; improve schema changing feature for package gdb
This commit is contained in:
@ -11,6 +11,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"time"
|
||||
|
||||
@ -18,7 +19,6 @@ import (
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
)
|
||||
@ -89,6 +89,7 @@ type DB interface {
|
||||
SetSchema(schema string)
|
||||
GetSchema() string
|
||||
GetPrefix() string
|
||||
GetGroup() string
|
||||
SetDryRun(dryrun bool)
|
||||
GetDryRun() bool
|
||||
SetLogger(logger *glog.Logger)
|
||||
@ -169,21 +170,23 @@ type Link interface {
|
||||
Prepare(sql string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// Value is the field value type.
|
||||
type Value = *gvar.Var
|
||||
type (
|
||||
// Value is the field value type.
|
||||
Value = *gvar.Var
|
||||
|
||||
// Record is the row record of the table.
|
||||
type Record map[string]Value
|
||||
// Record is the row record of the table.
|
||||
Record map[string]Value
|
||||
|
||||
// Result is the row record array.
|
||||
type Result []Record
|
||||
// Result is the row record array.
|
||||
Result []Record
|
||||
|
||||
// Map is alias of map[string]interface{},
|
||||
// which is the most common usage map type.
|
||||
type Map = map[string]interface{}
|
||||
// Map is alias of map[string]interface{},
|
||||
// which is the most common usage map type.
|
||||
Map = map[string]interface{}
|
||||
|
||||
// List is type of map array.
|
||||
type List = []Map
|
||||
// List is type of map array.
|
||||
List = []Map
|
||||
)
|
||||
|
||||
const (
|
||||
gINSERT_OPTION_DEFAULT = 0
|
||||
|
||||
@ -157,7 +157,7 @@ func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
|
||||
return c.DB.DoGetAll(nil, sql, args...)
|
||||
}
|
||||
|
||||
// doGetAll queries and returns data records from database.
|
||||
// DoGetAll queries and returns data records from database.
|
||||
func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) {
|
||||
if link == nil {
|
||||
link, err = c.DB.Slave()
|
||||
@ -379,13 +379,15 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
|
||||
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
|
||||
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
|
||||
func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var fields []string
|
||||
var values []string
|
||||
var params []interface{}
|
||||
var dataMap Map
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
reflectValue := reflect.ValueOf(data)
|
||||
reflectKind := reflectValue.Kind()
|
||||
var (
|
||||
fields []string
|
||||
values []string
|
||||
params []interface{}
|
||||
dataMap Map
|
||||
reflectValue = reflect.ValueOf(data)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
@ -401,16 +403,23 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
if len(dataMap) == 0 {
|
||||
return nil, errors.New("data cannot be empty")
|
||||
}
|
||||
charL, charR := c.DB.GetChars()
|
||||
var (
|
||||
charL, charR = c.DB.GetChars()
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, charL+k+charR)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
operation := GetInsertOperationByOption(option)
|
||||
updateStr := ""
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if k == gSOFT_FIELD_NAME_CREATE {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
@ -462,12 +471,15 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
|
||||
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
|
||||
}
|
||||
|
||||
// doBatchInsert batch inserts/replaces/saves data.
|
||||
// DoBatchInsert batch inserts/replaces/saves data.
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
var keys, values []string
|
||||
var params []interface{}
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
listMap := (List)(nil)
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
listMap List
|
||||
)
|
||||
switch v := list.(type) {
|
||||
case Result:
|
||||
listMap = v.List()
|
||||
@ -478,8 +490,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
case Map:
|
||||
listMap = List{v}
|
||||
default:
|
||||
rv := reflect.ValueOf(list)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(list)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -512,15 +526,21 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
holders = append(holders, "?")
|
||||
}
|
||||
// Prepare the batch result pointer.
|
||||
batchResult := new(SqlResult)
|
||||
charL, charR := c.DB.GetChars()
|
||||
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
|
||||
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
|
||||
|
||||
operation := GetInsertOperationByOption(option)
|
||||
updateStr := ""
|
||||
var (
|
||||
charL, charR = c.DB.GetChars()
|
||||
batchResult = new(SqlResult)
|
||||
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
|
||||
valueHolderStr = "(" + strings.Join(holders, ",") + ")"
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
if k == gSOFT_FIELD_NAME_CREATE {
|
||||
continue
|
||||
}
|
||||
if len(updateStr) > 0 {
|
||||
updateStr += ","
|
||||
}
|
||||
@ -599,18 +619,25 @@ func (c *Core) Update(table string, data interface{}, condition interface{}, arg
|
||||
// Also see Update.
|
||||
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
updates := ""
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(data)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
params := []interface{}(nil)
|
||||
var (
|
||||
params []interface{}
|
||||
updates = ""
|
||||
)
|
||||
switch kind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
var fields []string
|
||||
for k, v := range DataToMapDeep(data) {
|
||||
var (
|
||||
fields []string
|
||||
dataMap = DataToMapDeep(data)
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, c.DB.QuoteWord(k)+"=?")
|
||||
params = append(params, v)
|
||||
}
|
||||
@ -656,7 +683,7 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{})
|
||||
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
|
||||
}
|
||||
|
||||
// doDelete does "DELETE FROM ... " statement for the table.
|
||||
// DoDelete does "DELETE FROM ... " statement for the table.
|
||||
// Also see Delete.
|
||||
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
|
||||
if link == nil {
|
||||
|
||||
@ -174,6 +174,11 @@ func (c *Core) GetPrefix() string {
|
||||
return c.prefix
|
||||
}
|
||||
|
||||
// GetGroup returns the group string configured.
|
||||
func (c *Core) GetGroup() string {
|
||||
return c.group
|
||||
}
|
||||
|
||||
// SetDryRun enables/disables the DryRun feature.
|
||||
func (c *Core) SetDryRun(dryrun bool) {
|
||||
c.dryrun.Set(dryrun)
|
||||
|
||||
@ -13,6 +13,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
@ -187,9 +188,10 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
return nil, errors.New("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
|
||||
@ -8,6 +8,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
@ -89,9 +90,10 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
|
||||
//
|
||||
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
|
||||
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
return nil, errors.New("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := d.schema.Val()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
|
||||
@ -148,9 +148,10 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) {
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
return nil, errors.New("function TableFields supports only single table operations")
|
||||
}
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
|
||||
@ -13,6 +13,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
@ -78,7 +79,6 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME"
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0])
|
||||
@ -97,9 +97,10 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
return nil, errors.New("function TableFields supports only single table operations")
|
||||
}
|
||||
table, _ = gregex.ReplaceString("\"", "", table)
|
||||
checkSchema := d.DB.GetSchema()
|
||||
|
||||
@ -12,6 +12,7 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
@ -88,11 +89,11 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
|
||||
|
||||
// TableFields retrieves and returns the fields information of specified table of current schema.
|
||||
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
|
||||
table = gstr.Trim(table)
|
||||
charL, charR := d.GetChars()
|
||||
table = gstr.Trim(table, charL+charR)
|
||||
if gstr.Contains(table, " ") {
|
||||
panic("function TableFields supports only single table operations")
|
||||
return nil, errors.New("function TableFields supports only single table operations")
|
||||
}
|
||||
|
||||
checkSchema := d.DB.GetSchema()
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
|
||||
@ -37,6 +37,7 @@ type Model struct {
|
||||
cacheEnabled bool // Enable sql result cache feature.
|
||||
cacheDuration time.Duration // Cache TTL duration.
|
||||
cacheName string // Cache name for custom operation.
|
||||
force bool // Force select/delete without soft operation features.
|
||||
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
|
||||
}
|
||||
|
||||
|
||||
@ -8,8 +8,21 @@ package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// Force enables/disables the soft deleting feature.
|
||||
func (m *Model) Force(force ...bool) *Model {
|
||||
model := m.getModel()
|
||||
if len(force) > 0 {
|
||||
model.force = force[0]
|
||||
} else {
|
||||
model.force = true
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
@ -22,6 +35,19 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
)
|
||||
// Soft deleting.
|
||||
if !m.force && fieldNameDelete != "" {
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
fmt.Sprintf(`%s='%s'`, m.db.QuoteWord(fieldNameDelete), gtime.Now().String()),
|
||||
conditionWhere+conditionExtra,
|
||||
conditionArgs...,
|
||||
)
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
|
||||
}
|
||||
|
||||
@ -9,8 +9,10 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -94,6 +96,9 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
|
||||
}
|
||||
|
||||
@ -101,45 +106,10 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
// The optional parameter <data> is the same as the parameter of Model.Data function,
|
||||
// see Model.Data.
|
||||
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("inserting into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch insert.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
option,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("inserting into table with invalid data type")
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the model.
|
||||
@ -149,37 +119,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Replace()
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("replacing into table with empty data")
|
||||
}
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch replace.
|
||||
batch := 10
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single insert.
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_REPLACE,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("replacing into table with invalid data type")
|
||||
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
|
||||
@ -192,35 +132,67 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Save()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
m.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if m.data == nil {
|
||||
return nil, errors.New("saving into table with empty data")
|
||||
return nil, errors.New("inserting into table with empty data")
|
||||
}
|
||||
var (
|
||||
nowString = gtime.Now().String()
|
||||
fieldNameCreate = m.getSoftFieldNameCreate()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
// Batch save.
|
||||
batch := gDEFAULT_BATCH_NUM
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.force && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
for k, v := range list {
|
||||
if fieldNameCreate != "" && !gutil.MapContainsPossibleKey(v, fieldNameCreate) {
|
||||
v[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" && !gutil.MapContainsPossibleKey(v, fieldNameUpdate) {
|
||||
v[fieldNameUpdate] = nowString
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
gINSERT_OPTION_SAVE,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
} else if data, ok := m.data.(Map); ok {
|
||||
// Single save.
|
||||
}
|
||||
// Single operation.
|
||||
if data, ok := m.data.(Map); ok {
|
||||
// Automatic handling for creating/updating time.
|
||||
if !m.force && (fieldNameCreate != "" || fieldNameUpdate != "") {
|
||||
if fieldNameCreate != "" && !gutil.MapContainsPossibleKey(data, fieldNameCreate) {
|
||||
data[fieldNameCreate] = nowString
|
||||
}
|
||||
if fieldNameUpdate != "" && !gutil.MapContainsPossibleKey(data, fieldNameUpdate) {
|
||||
data[fieldNameUpdate] = nowString
|
||||
}
|
||||
}
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
gINSERT_OPTION_SAVE,
|
||||
option,
|
||||
)
|
||||
}
|
||||
return nil, errors.New("saving into table with invalid data type")
|
||||
return nil, errors.New("inserting into table with invalid data type")
|
||||
}
|
||||
|
||||
@ -30,9 +30,18 @@ func (m *Model) All(where ...interface{}) (Result, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).All()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDelete()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
)
|
||||
if !m.force && fieldNameDelete != "" {
|
||||
if conditionWhere != "" {
|
||||
conditionWhere += " AND"
|
||||
}
|
||||
conditionWhere += fmt.Sprintf(` %s IS NULL`, m.db.QuoteWord(fieldNameDelete))
|
||||
}
|
||||
return m.getAll(
|
||||
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition),
|
||||
fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, conditionWhere+conditionExtra),
|
||||
conditionArgs...,
|
||||
)
|
||||
}
|
||||
@ -73,8 +82,7 @@ func (m *Model) One(where ...interface{}) (Record, error) {
|
||||
if len(where) > 0 {
|
||||
return m.Where(where[0], where[1:]...).One()
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(true)
|
||||
all, err := m.getAll(fmt.Sprintf("SELECT %s FROM %s%s", m.fields, m.tables, condition), conditionArgs...)
|
||||
all, err := m.All()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -236,8 +244,8 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
if m.fields != "" && m.fields != "*" {
|
||||
countFields = fmt.Sprintf(`COUNT(%s)`, m.fields)
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, condition)
|
||||
conditionWhere, conditionExtra, conditionArgs := m.formatCondition(false)
|
||||
s := fmt.Sprintf("SELECT %s FROM %s %s", countFields, m.tables, conditionWhere+conditionExtra)
|
||||
if len(m.groupBy) > 0 {
|
||||
s = fmt.Sprintf("SELECT COUNT(1) FROM (%s) count_alias", s)
|
||||
}
|
||||
|
||||
57
database/gdb/gdb_model_time.go
Normal file
57
database/gdb/gdb_model_time.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
const (
|
||||
gSOFT_FIELD_NAME_CREATE = "create_at"
|
||||
gSOFT_FIELD_NAME_UPDATE = "update_at"
|
||||
gSOFT_FIELD_NAME_DELETE = "delete_at"
|
||||
)
|
||||
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameCreate() (field string) {
|
||||
fieldsMap, _ := m.db.TableFields(m.tables)
|
||||
if len(fieldsMap) > 0 {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_CREATE,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getSoftFieldNameUpdate checks and returns the field name for record updating time.
|
||||
// If there's no field name for storing updating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameUpdate() (field string) {
|
||||
fieldsMap, _ := m.db.TableFields(m.tables)
|
||||
if len(fieldsMap) > 0 {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_UPDATE,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getSoftFieldNameDelete checks and returns the field name for record deleting time.
|
||||
// If there's no field name for storing deleting time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func (m *Model) getSoftFieldNameDelete() (field string) {
|
||||
fieldsMap, _ := m.db.TableFields(m.tables)
|
||||
if len(fieldsMap) > 0 {
|
||||
field, _ = gutil.MapPossibleItemByKey(
|
||||
gconv.Map(fieldsMap), gSOFT_FIELD_NAME_DELETE,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -9,6 +9,12 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Update does "UPDATE ... " statement for the model.
|
||||
@ -34,12 +40,41 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
if m.data == nil {
|
||||
return nil, errors.New("updating table with empty data")
|
||||
}
|
||||
condition, conditionArgs := m.formatCondition(false)
|
||||
var (
|
||||
updateData = m.data
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdate()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
if !m.force && fieldNameUpdate != "" {
|
||||
var (
|
||||
refValue = reflect.ValueOf(m.data)
|
||||
refKind = refValue.Kind()
|
||||
)
|
||||
if refKind == reflect.Ptr {
|
||||
refValue = refValue.Elem()
|
||||
refKind = refValue.Kind()
|
||||
}
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := DataToMapDeep(m.data)
|
||||
if fieldNameUpdate != "" && !gutil.MapContainsPossibleKey(dataMap, fieldNameUpdate) {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
}
|
||||
updateData = dataMap
|
||||
default:
|
||||
updates := gconv.String(m.data)
|
||||
if fieldNameUpdate != "" && !gstr.Contains(updates, fieldNameUpdate) {
|
||||
updates += fmt.Sprintf(`,%s='%s'`, fieldNameUpdate, gtime.Now().String())
|
||||
}
|
||||
updateData = updates
|
||||
}
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(m.data),
|
||||
condition,
|
||||
m.filterDataForInsertOrUpdate(updateData),
|
||||
conditionWhere+conditionExtra,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
}
|
||||
|
||||
@ -28,15 +28,19 @@ func (m *Model) getModel() *Model {
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
if list, ok := m.data.(List); ok {
|
||||
for k, item := range list {
|
||||
list[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
switch value := data.(type) {
|
||||
case List:
|
||||
for k, item := range value {
|
||||
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
}
|
||||
return list
|
||||
} else if item, ok := m.data.(Map); ok {
|
||||
return m.doFilterDataMapForInsertOrUpdate(item, true)
|
||||
return value
|
||||
|
||||
case Map:
|
||||
return m.doFilterDataMapForInsertOrUpdate(value, true)
|
||||
|
||||
default:
|
||||
return data
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
@ -144,16 +148,15 @@ func (m *Model) checkAndRemoveCache() {
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []interface{}) {
|
||||
var where string
|
||||
func (m *Model) formatCondition(limit bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
case gWHERE_HOLDER_WHERE:
|
||||
if where == "" {
|
||||
if conditionWhere == "" {
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
where = newWhere
|
||||
conditionWhere = newWhere
|
||||
conditionArgs = newArgs
|
||||
}
|
||||
continue
|
||||
@ -163,10 +166,10 @@ func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []i
|
||||
case gWHERE_HOLDER_AND:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s AND (%s)`, where, newWhere)
|
||||
if conditionWhere[0] == '(' {
|
||||
conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) AND (%s)`, where, newWhere)
|
||||
conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
@ -174,39 +177,39 @@ func (m *Model) formatCondition(limit bool) (condition string, conditionArgs []i
|
||||
case gWHERE_HOLDER_OR:
|
||||
newWhere, newArgs := formatWhere(m.db, v.where, v.args, m.option&OPTION_OMITEMPTY > 0)
|
||||
if len(newWhere) > 0 {
|
||||
if where[0] == '(' {
|
||||
where = fmt.Sprintf(`%s OR (%s)`, where, newWhere)
|
||||
if conditionWhere[0] == '(' {
|
||||
conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere)
|
||||
} else {
|
||||
where = fmt.Sprintf(`(%s) OR (%s)`, where, newWhere)
|
||||
conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere)
|
||||
}
|
||||
conditionArgs = append(conditionArgs, newArgs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if where != "" {
|
||||
condition += " WHERE " + where
|
||||
if conditionWhere != "" {
|
||||
conditionWhere = " WHERE " + conditionWhere
|
||||
}
|
||||
if m.groupBy != "" {
|
||||
condition += " GROUP BY " + m.groupBy
|
||||
conditionExtra += " GROUP BY " + m.groupBy
|
||||
}
|
||||
if m.orderBy != "" {
|
||||
condition += " ORDER BY " + m.orderBy
|
||||
conditionExtra += " ORDER BY " + m.orderBy
|
||||
}
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
condition += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
condition += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit {
|
||||
condition += " LIMIT 1"
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
condition += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
if m.lockInfo != "" {
|
||||
condition += " " + m.lockInfo
|
||||
conditionExtra += " " + m.lockInfo
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -40,6 +40,14 @@ func (s *Schema) Table(table string) *Model {
|
||||
} else {
|
||||
m = s.db.Table(table)
|
||||
}
|
||||
// Do not change the schema of the original db,
|
||||
// it here creates a new db and changes its schema.
|
||||
db, err := New(m.db.GetGroup())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db.SetSchema(s.schema)
|
||||
m.db = db
|
||||
m.schema = s.schema
|
||||
return m
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ func CopyMap(data map[string]interface{}) (copy map[string]interface{}) {
|
||||
// cases or chars '-'/'_'/'.'/' '.
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (string, interface{}) {
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
@ -47,3 +47,12 @@ func MapPossibleItemByKey(data map[string]interface{}, key string) (string, inte
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// MapContainsPossibleKey checks if the given <key> is contained in given map <data>.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
|
||||
if k, _ := MapPossibleItemByKey(data, key); k != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user