Skip to content

Commit

Permalink
up
Browse files Browse the repository at this point in the history
  • Loading branch information
oldme-git committed Feb 25, 2024
1 parent d64a31f commit 44b60cf
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 103 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
64 changes: 59 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,55 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list
}
return d.Core.DoInsert(ctx, link, table, list, option)
}

// DoFormatUpsert returns SQL clause of type upsert for PgSQL.
// For example: ON CONFLICT (id) DO UPDATE SET ...
func (d *Driver) DoFormatUpsert(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),
)
}
}

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

return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + 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
3 changes: 3 additions & 0 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.

DoFormatUpsert(columns []string, option DoInsertOption) (string, error) // See Core.DoFormatUpsert

// ===========================================================================
// Query APIs for convenience purpose.
// ===========================================================================
Expand Down Expand Up @@ -320,6 +322,7 @@ 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.
OnConflict []string // Custom conflict key of upsert clause, if the database needs it.
InsertOption InsertOption // Insert operation in constant value.
BatchCount int // Batch count for batch inserting.
}
Expand Down
54 changes: 7 additions & 47 deletions database/gdb/gdb_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,9 +487,12 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
operation = GetInsertOperationByOption(option.InsertOption)
)
// `ON DUPLICATED...` statement only takes effect on Save operation.
// Upsert clause only takes effect on Save operation.
if option.InsertOption == InsertOptionSave {
onDuplicateStr = c.formatOnDuplicate(keys, option)
onDuplicateStr, err = c.db.DoFormatUpsert(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
45 changes: 45 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 @@ -352,6 +353,50 @@ func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (stmt *Stmt
return out.Stmt, err
}

// DoFormatUpsert returns SQL clause for type upsert.
func (c *Core) DoFormatUpsert(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
}

// RowsToResult converts underlying data record type sql.Rows to Result type.
func (c *Core) RowsToResult(ctx context.Context, rows *sql.Rows) (Result, error) {
if rows == nil {
Expand Down
5 changes: 3 additions & 2 deletions database/gdb/gdb_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ type Model struct {
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.
onDuplicate interface{} // onDuplicate is used for on Upsert clause.
onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns on Upsert clause.
onConflict interface{} // onConflict is used for conflict keys on Upsert clause.
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
Loading

0 comments on commit 44b60cf

Please sign in to comment.