Files
gf/database/gdb/gdb_model_insert.go
hailaz ee24da4e72 refactor: interface{} to any and reflect.Ptr to reflect.Pointer (#4395)
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.

**Type alias migration to `any`:**

* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)

**Generated code and templates:**

* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)

**Container and utility updates:**

* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)

These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
2025-08-28 16:53:19 +08:00

470 lines
13 KiB
Go

// Copyright GoFrame Author(https://goframe.org). 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 (
"context"
"database/sql"
"reflect"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/reflection"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
// Batch sets the batch operation number for the model.
func (m *Model) Batch(batch int) *Model {
model := m.getModel()
model.batch = batch
return model
}
// Data sets the operation data for the model.
// The parameter `data` can be type of string/map/gmap/slice/struct/*struct, etc.
// Note that, it uses shallow value copying for `data` if `data` is type of map/slice
// to avoid changing it inside function.
// Eg:
// Data("uid=10000")
// Data("uid", 10000)
// Data("uid=? AND name=?", 10000, "john")
// Data(g.Map{"uid": 10000, "name":"john"})
// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"}).
func (m *Model) Data(data ...any) *Model {
var model = m.getModel()
if len(data) > 1 {
if s := gconv.String(data[0]); gstr.Contains(s, "?") {
model.data = s
model.extraArgs = data[1:]
} else {
newData := make(map[string]any)
for i := 0; i < len(data); i += 2 {
newData[gconv.String(data[i])] = data[i+1]
}
model.data = newData
}
} else if len(data) == 1 {
switch value := data[0].(type) {
case Result:
model.data = value.List()
case Record:
model.data = value.Map()
case List:
list := make(List, len(value))
for k, v := range value {
list[k] = gutil.MapCopy(v)
}
model.data = list
case Map:
model.data = gutil.MapCopy(value)
default:
reflectInfo := reflection.OriginValueAndKind(value)
switch reflectInfo.OriginKind {
case reflect.Slice, reflect.Array:
if reflectInfo.OriginValue.Len() > 0 {
// If the `data` parameter is a DO struct,
// it then adds `OmitNilData` option for this condition,
// which will filter all nil parameters in `data`.
if isDoStruct(reflectInfo.OriginValue.Index(0).Interface()) {
model = model.OmitNilData()
model.option |= optionOmitNilDataInternal
}
}
list := make(List, reflectInfo.OriginValue.Len())
for i := 0; i < reflectInfo.OriginValue.Len(); i++ {
list[i] = anyValueToMapBeforeToRecord(reflectInfo.OriginValue.Index(i).Interface())
}
model.data = list
case reflect.Struct:
// If the `data` parameter is a DO struct,
// it then adds `OmitNilData` option for this condition,
// which will filter all nil parameters in `data`.
if isDoStruct(value) {
model = model.OmitNilData()
}
if v, ok := data[0].(iInterfaces); ok {
var (
array = v.Interfaces()
list = make(List, len(array))
)
for i := 0; i < len(array); i++ {
list[i] = anyValueToMapBeforeToRecord(array[i])
}
model.data = list
} else {
model.data = anyValueToMapBeforeToRecord(data[0])
}
case reflect.Map:
model.data = anyValueToMapBeforeToRecord(data[0])
default:
model.data = data[0]
}
}
}
return model
}
// OnConflict sets the primary key or index when columns conflicts occurs.
// It's not necessary for MySQL driver.
func (m *Model) OnConflict(onConflict ...any) *Model {
if len(onConflict) == 0 {
return m
}
model := m.getModel()
if len(onConflict) > 1 {
model.onConflict = onConflict
} else if len(onConflict) == 1 {
model.onConflict = onConflict[0]
}
return model
}
// OnDuplicate sets the operations when columns conflicts occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// In PgSQL, this is used for "ON CONFLICT (id) DO UPDATE SET" statement.
// The parameter `onDuplicate` can be type of string/Raw/*Raw/map/slice.
// Example:
//
// OnDuplicate("nickname, age")
// OnDuplicate("nickname", "age")
//
// OnDuplicate(g.Map{
// "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"),
// })
//
// OnDuplicate(g.Map{
// "nickname": "passport",
// }).
func (m *Model) OnDuplicate(onDuplicate ...any) *Model {
if len(onDuplicate) == 0 {
return m
}
model := m.getModel()
if len(onDuplicate) > 1 {
model.onDuplicate = onDuplicate
} else if len(onDuplicate) == 1 {
model.onDuplicate = onDuplicate[0]
}
return model
}
// OnDuplicateEx sets the excluding columns for operations when columns conflict occurs.
// In MySQL, this is used for "ON DUPLICATE KEY UPDATE" statement.
// In PgSQL, this is used for "ON CONFLICT (id) DO UPDATE SET" statement.
// The parameter `onDuplicateEx` can be type of string/map/slice.
// Example:
//
// OnDuplicateEx("passport, password")
// OnDuplicateEx("passport", "password")
//
// OnDuplicateEx(g.Map{
// "passport": "",
// "password": "",
// }).
func (m *Model) OnDuplicateEx(onDuplicateEx ...any) *Model {
if len(onDuplicateEx) == 0 {
return m
}
model := m.getModel()
if len(onDuplicateEx) > 1 {
model.onDuplicateEx = onDuplicateEx
} else if len(onDuplicateEx) == 1 {
model.onDuplicateEx = onDuplicateEx[0]
}
return model
}
// Insert does "INSERT INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Insert(data ...any) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Insert()
}
return m.doInsertWithOption(ctx, InsertOptionDefault)
}
// InsertAndGetId performs action Insert and returns the last insert id that automatically generated.
func (m *Model) InsertAndGetId(data ...any) (lastInsertId int64, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).InsertAndGetId()
}
result, err := m.doInsertWithOption(ctx, InsertOptionDefault)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) InsertIgnore(data ...any) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).InsertIgnore()
}
return m.doInsertWithOption(ctx, InsertOptionIgnore)
}
// Replace does "REPLACE INTO ..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Replace(data ...any) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Replace()
}
return m.doInsertWithOption(ctx, InsertOptionReplace)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
// The optional parameter `data` is the same as the parameter of Model.Data function,
// see Model.Data.
//
// It updates the record if there's primary or unique index in the saving data,
// or else it inserts a new record into the table.
func (m *Model) Save(data ...any) (result sql.Result, err error) {
var ctx = m.GetCtx()
if len(data) > 0 {
return m.Data(data...).Save()
}
return m.doInsertWithOption(ctx, InsertOptionSave)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(ctx context.Context, insertOption InsertOption) (result sql.Result, err error) {
defer func() {
if err == nil {
m.checkAndRemoveSelectCache(ctx)
}
}()
if m.data == nil {
return nil, gerror.NewCode(gcode.CodeMissingParameter, "inserting into table with empty data")
}
var (
list List
stm = m.softTimeMaintainer()
fieldNameCreate, fieldTypeCreate = stm.GetFieldNameAndTypeForCreate(ctx, "", m.tablesInit)
fieldNameUpdate, fieldTypeUpdate = stm.GetFieldNameAndTypeForUpdate(ctx, "", m.tablesInit)
fieldNameDelete, fieldTypeDelete = stm.GetFieldNameAndTypeForDelete(ctx, "", m.tablesInit)
)
// m.data was already converted to type List/Map by function Data
newData, err := m.filterDataForInsertOrUpdate(m.data)
if err != nil {
return nil, err
}
// It converts any data to List type for inserting.
switch value := newData.(type) {
case List:
list = value
case Map:
list = List{value}
}
if len(list) < 1 {
return result, gerror.NewCode(gcode.CodeMissingParameter, "data list cannot be empty")
}
// Automatic handling for creating/updating time.
if fieldNameCreate != "" && m.isFieldInFieldsEx(fieldNameCreate) {
fieldNameCreate = ""
}
if fieldNameUpdate != "" && m.isFieldInFieldsEx(fieldNameUpdate) {
fieldNameUpdate = ""
}
var isSoftTimeFeatureEnabled = fieldNameCreate != "" || fieldNameUpdate != ""
if !m.unscoped && isSoftTimeFeatureEnabled {
for k, v := range list {
if fieldNameCreate != "" && empty.IsNil(v[fieldNameCreate]) {
fieldCreateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeCreate, false)
if fieldCreateValue != nil {
v[fieldNameCreate] = fieldCreateValue
}
}
if fieldNameUpdate != "" && empty.IsNil(v[fieldNameUpdate]) {
fieldUpdateValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeUpdate, false)
if fieldUpdateValue != nil {
v[fieldNameUpdate] = fieldUpdateValue
}
}
// for timestamp field that should initialize the delete_at field with value, for example 0.
if fieldNameDelete != "" && empty.IsNil(v[fieldNameDelete]) {
fieldDeleteValue := stm.GetValueByFieldTypeForCreateOrUpdate(ctx, fieldTypeDelete, true)
if fieldDeleteValue != nil {
v[fieldNameDelete] = fieldDeleteValue
}
}
list[k] = v
}
}
// Format DoInsertOption, especially for "ON DUPLICATE KEY UPDATE" statement.
columnNames := make([]string, 0, len(list[0]))
for k := range list[0] {
columnNames = append(columnNames, k)
}
doInsertOption, err := m.formatDoInsertOption(insertOption, columnNames)
if err != nil {
return result, err
}
in := &HookInsertInput{
internalParamHookInsert: internalParamHookInsert{
internalParamHook: internalParamHook{
link: m.getLink(true),
},
handler: m.hookHandler.Insert,
},
Model: m,
Table: m.tables,
Schema: m.schema,
Data: list,
Option: doInsertOption,
}
return in.Next(ctx)
}
func (m *Model) formatDoInsertOption(insertOption InsertOption, columnNames []string) (option DoInsertOption, err error) {
option = DoInsertOption{
InsertOption: insertOption,
BatchCount: m.getBatch(),
}
if insertOption != InsertOptionSave {
return
}
onConflictKeys, err := m.formatOnConflictKeys(m.onConflict)
if err != nil {
return option, err
}
option.OnConflict = onConflictKeys
onDuplicateExKeys, err := m.formatOnDuplicateExKeys(m.onDuplicateEx)
if err != nil {
return option, err
}
onDuplicateExKeySet := gset.NewStrSetFrom(onDuplicateExKeys)
if m.onDuplicate != nil {
switch m.onDuplicate.(type) {
case Raw, *Raw:
option.OnDuplicateStr = gconv.String(m.onDuplicate)
default:
reflectInfo := reflection.OriginValueAndKind(m.onDuplicate)
switch reflectInfo.OriginKind {
case reflect.String:
option.OnDuplicateMap = make(map[string]any)
for _, v := range gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ",") {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
case reflect.Map:
option.OnDuplicateMap = make(map[string]any)
for k, v := range gconv.Map(m.onDuplicate) {
if onDuplicateExKeySet.Contains(k) {
continue
}
option.OnDuplicateMap[k] = v
}
case reflect.Slice, reflect.Array:
option.OnDuplicateMap = make(map[string]any)
for _, v := range gconv.Strings(m.onDuplicate) {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
default:
return option, gerror.NewCodef(
gcode.CodeInvalidParameter,
`unsupported OnDuplicate parameter type "%s"`,
reflect.TypeOf(m.onDuplicate),
)
}
}
} else if onDuplicateExKeySet.Size() > 0 {
option.OnDuplicateMap = make(map[string]any)
for _, v := range columnNames {
if onDuplicateExKeySet.Contains(v) {
continue
}
option.OnDuplicateMap[v] = v
}
}
return
}
func (m *Model) formatOnDuplicateExKeys(onDuplicateEx any) ([]string, error) {
if onDuplicateEx == nil {
return nil, nil
}
reflectInfo := reflection.OriginValueAndKind(onDuplicateEx)
switch reflectInfo.OriginKind {
case reflect.String:
return gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ","), nil
case reflect.Map:
return gutil.Keys(onDuplicateEx), nil
case reflect.Slice, reflect.Array:
return gconv.Strings(onDuplicateEx), nil
default:
return nil, gerror.NewCodef(
gcode.CodeInvalidParameter,
`unsupported OnDuplicateEx parameter type "%s"`,
reflect.TypeOf(onDuplicateEx),
)
}
}
func (m *Model) formatOnConflictKeys(onConflict any) ([]string, error) {
if onConflict == nil {
return nil, nil
}
reflectInfo := reflection.OriginValueAndKind(onConflict)
switch reflectInfo.OriginKind {
case reflect.String:
return gstr.SplitAndTrim(reflectInfo.OriginValue.String(), ","), nil
case reflect.Slice, reflect.Array:
return gconv.Strings(onConflict), nil
default:
return nil, gerror.NewCodef(
gcode.CodeInvalidParameter,
`unsupported onConflict parameter type "%s"`,
reflect.TypeOf(onConflict),
)
}
}
func (m *Model) getBatch() int {
return m.batch
}