Skip to content

Commit

Permalink
up
Browse files Browse the repository at this point in the history
  • Loading branch information
oldme-git committed Feb 24, 2024
1 parent d64a31f commit ffe280d
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 135 deletions.
3 changes: 2 additions & 1 deletion contrib/drivers/mysql/mysql_z_unit_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,14 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
result, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"create_time": "2018-10-24 10:00:00",
}).Save()
}).OnDuplicate("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
Expand Down
62 changes: 57 additions & 5 deletions contrib/drivers/pgsql/pgsql_do_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ package pgsql
import (
"context"
"database/sql"

"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)

// DoInsert inserts or updates data forF given table.
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case gdb.InsertOptionSave:
return nil, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by pgsql driver`,
)
//return nil, gerror.NewCode(
// gcode.CodeNotSupported,
// `Save operation is not supported by pgsql driver`,
//)

case gdb.InsertOptionReplace:
return nil, gerror.NewCode(
Expand Down Expand Up @@ -50,3 +52,53 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list
}
return d.Core.DoInsert(ctx, link, table, list, option)
}

func (d *Driver) FormatUpsert(columns []string, option gdb.DoInsertOption) (string, error) {
if len(option.OnConflict) == 0 {
return "", gerror.New("Please specify conflict columns")
}

var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case gdb.Raw, *gdb.Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
d.Core.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(k),
d.Core.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation, do not automatically update the creating time.
if d.Core.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(column),
d.Core.QuoteWord(column),
)
}
}

f := gstr.Join(option.OnConflict, ",")

return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", f) + onDuplicateStr, nil
}
14 changes: 14 additions & 0 deletions contrib/drivers/pgsql/pgsql_z_unit_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package pgsql_test

import (
"fmt"
"testing"

"github.com/gogf/gf/v2/frame/g"
Expand Down Expand Up @@ -269,6 +270,19 @@ func Test_Model_Save(t *testing.T) {
})
}

func Test_Model_Save2(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
db.SetDebug(true)
table := "user"
_, err := db.Model(table).Data(g.Map{
"id": 10,
"name": "T11133313",
"age": 2,
}).OnConflict(g.Array{"id"}).Save()
fmt.Println(err)
})
}

func Test_Model_Replace(t *testing.T) {
table := createTable()
defer dropTable(table)
Expand Down
7 changes: 5 additions & 2 deletions database/gdb/gdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ type DB interface {

DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) // See Core.DoPrepare.

FormatUpsert(columns []string, option DoInsertOption) (string, error)

// ===========================================================================
// Query APIs for convenience purpose.
// ===========================================================================
Expand Down Expand Up @@ -320,8 +322,9 @@ type Sql struct {
type DoInsertOption struct {
OnDuplicateStr string // Custom string for `on duplicated` statement.
OnDuplicateMap map[string]interface{} // Custom key-value map from `OnDuplicateEx` function for `on duplicated` statement.
InsertOption InsertOption // Insert operation in constant value.
BatchCount int // Batch count for batch inserting.
OnConflict []string
InsertOption InsertOption // Insert operation in constant value.
BatchCount int // Batch count for batch inserting.
}

// TableField is the struct for table field.
Expand Down
52 changes: 6 additions & 46 deletions database/gdb/gdb_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,10 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
)
// `ON DUPLICATED...` statement only takes effect on Save operation.
if option.InsertOption == InsertOptionSave {
onDuplicateStr = c.formatOnDuplicate(keys, option)
onDuplicateStr, err = c.db.FormatUpsert(keys, option)
if err != nil {
return nil, err
}
}
var (
listLength = len(list)
Expand Down Expand Up @@ -537,49 +540,6 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
return batchResult, nil
}

func (c *Core) formatOnDuplicate(columns []string, option DoInsertOption) string {
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case Raw, *Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
c.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(k),
c.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation, do not automatically update the creating time.
if c.isSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(column),
c.QuoteWord(column),
)
}
}
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr
}

// Update does "UPDATE ... " statement for the table.
//
// The parameter `data` can be type of string/map/gmap/struct/*struct, etc.
Expand Down Expand Up @@ -798,8 +758,8 @@ func (c *Core) GetTablesWithCache() ([]string, error) {
return result.Strings(), nil
}

// isSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time.
func (c *Core) isSoftCreatedFieldName(fieldName string) bool {
// IsSoftCreatedFieldName checks and returns whether given field name is an automatic-filled created time.
func (c *Core) IsSoftCreatedFieldName(fieldName string) bool {
if fieldName == "" {
return false
}
Expand Down
44 changes: 44 additions & 0 deletions database/gdb/gdb_core_underlying.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package gdb
import (
"context"
"database/sql"
"fmt"
"reflect"

"go.opentelemetry.io/otel"
Expand Down Expand Up @@ -315,6 +316,49 @@ func (c *Core) Prepare(ctx context.Context, sql string, execOnMaster ...bool) (*
return c.db.DoPrepare(ctx, link, sql)
}

func (c *Core) FormatUpsert(columns []string, option DoInsertOption) (string, error) {
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case Raw, *Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
c.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(k),
c.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation, do not automatically update the creating time.
if c.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=VALUES(%s)",
c.QuoteWord(column),
c.QuoteWord(column),
)
}
}
return InsertOnDuplicateKeyUpdate + " " + onDuplicateStr, nil
}

// DoPrepare calls prepare function on given link object and returns the statement object.
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt, err error) {
// Transaction checks.
Expand Down
67 changes: 34 additions & 33 deletions database/gdb/gdb_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,40 @@ import (

// Model is core struct implementing the DAO for ORM.
type Model struct {
db DB // Underlying DB interface.
tx TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereBuilder *WhereBuilder // Condition builder for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
partition string // Partition table partition name.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
cacheOption CacheOption // Cache option for query statement.
hookHandler HookHandler // Hook functions for model hook feature.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
db DB // Underlying DB interface.
tx TX // Underlying TX interface.
rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model.
schema string // Custom database schema.
linkType int // Mark for operation on master or slave.
tablesInit string // Table names when model initialization.
tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud".
fields string // Operation fields, multiple fields joined using char ','.
fieldsEx string // Excluded operation fields, multiple fields joined using char ','.
withArray []interface{} // Arguments for With feature.
withAll bool // Enable model association operations on all objects that have "with" tag in the struct.
extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver.
whereBuilder *WhereBuilder // Condition builder for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
offset int // Offset statement for some databases grammar.
partition string // Partition table partition name.
data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc.
batch int // Batch number for batch Insert/Replace/Save operations.
filter bool // Filter data and where key-value pairs according to the fields of the table.
distinct string // Force the query to only return distinct results.
lockInfo string // Lock for update or in shared lock.
cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage.
cacheOption CacheOption // Cache option for query statement.
hookHandler HookHandler // Hook functions for model hook feature.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement.
onConflict interface{}
tableAliasMap map[string]string // Table alias to true table name, usually used in join statements.
softTimeOption SoftTimeOption // SoftTimeOption is the option to customize soft time feature for Model.
}
Expand Down

0 comments on commit ffe280d

Please sign in to comment.