2024-01-30 20:03:58 +08:00
// 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 dm
import (
"context"
"database/sql"
"fmt"
"strings"
2024-03-20 19:18:25 +08:00
"github.com/gogf/gf/v2/container/gset"
2024-01-30 20:03:58 +08:00
"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"
)
2024-03-12 20:40:20 +08:00
// DoInsert inserts or updates data for given table.
2024-01-30 20:03:58 +08:00
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 {
2024-04-01 19:08:26 +08:00
case gdb . InsertOptionSave :
return d . doSave ( ctx , link , table , list , option )
2024-01-30 20:03:58 +08:00
case gdb . InsertOptionReplace :
// TODO:: Should be Supported
return nil , gerror . NewCode (
gcode . CodeNotSupported , ` Replace operation is not supported by dm driver ` ,
)
2024-03-20 19:18:25 +08:00
}
2024-04-01 19:08:26 +08:00
2024-03-20 19:18:25 +08:00
return d . Core . DoInsert ( ctx , link , table , list , option )
}
// doSave support upsert for dm
func ( d * Driver ) doSave ( ctx context . Context ,
link gdb . Link , table string , list gdb . List , option gdb . DoInsertOption ,
) ( result sql . Result , err error ) {
if len ( option . OnConflict ) == 0 {
return nil , gerror . NewCode (
gcode . CodeMissingParameter , ` Please specify conflict columns ` ,
2024-01-30 20:03:58 +08:00
)
2024-03-20 19:18:25 +08:00
}
2024-01-30 20:03:58 +08:00
2024-03-20 19:18:25 +08:00
if len ( list ) == 0 {
return nil , gerror . NewCode (
gcode . CodeInvalidRequest , ` Save operation list is empty by oracle driver ` ,
2024-01-30 20:03:58 +08:00
)
}
2024-03-20 19:18:25 +08:00
var (
2024-04-01 19:08:26 +08:00
one = list [ 0 ]
oneLen = len ( one )
charL , charR = d . GetChars ( )
2024-03-20 19:18:25 +08:00
conflictKeys = option . OnConflict
conflictKeySet = gset . New ( false )
2024-04-01 19:08:26 +08:00
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
queryHolders = make ( [ ] string , oneLen )
queryValues = make ( [ ] interface { } , oneLen )
insertKeys = make ( [ ] string , oneLen )
insertValues = make ( [ ] string , oneLen )
updateValues [ ] string
2024-03-20 19:18:25 +08:00
)
// conflictKeys slice type conv to set type
for _ , conflictKey := range conflictKeys {
conflictKeySet . Add ( gstr . ToUpper ( conflictKey ) )
}
2024-01-30 20:03:58 +08:00
2024-04-01 19:08:26 +08:00
index := 0
2024-03-20 19:18:25 +08:00
for key , value := range one {
2024-04-01 19:08:26 +08:00
keyWithChar := charL + key + charR
queryHolders [ index ] = fmt . Sprintf ( "? AS %s" , keyWithChar )
queryValues [ index ] = value
insertKeys [ index ] = keyWithChar
insertValues [ index ] = fmt . Sprintf ( "T2.%s" , keyWithChar )
// filter conflict keys in updateValues.
// And the key is not a soft created field.
if ! ( conflictKeySet . Contains ( key ) || d . Core . IsSoftCreatedFieldName ( key ) ) {
2024-03-20 19:18:25 +08:00
updateValues = append (
updateValues ,
2024-04-01 19:08:26 +08:00
fmt . Sprintf ( ` T1.%s = T2.%s ` , keyWithChar , keyWithChar ) ,
2024-03-20 19:18:25 +08:00
)
2024-01-30 20:03:58 +08:00
}
2024-04-01 19:08:26 +08:00
index ++
2024-01-30 20:03:58 +08:00
}
2024-03-20 19:18:25 +08:00
batchResult := new ( gdb . SqlResult )
2024-04-01 19:08:26 +08:00
sqlStr := parseSqlForUpsert ( table , queryHolders , insertKeys , insertValues , updateValues , conflictKeys )
r , err := d . DoExec ( ctx , link , sqlStr , queryValues ... )
2024-03-20 19:18:25 +08:00
if err != nil {
return r , err
}
if n , err := r . RowsAffected ( ) ; err != nil {
return r , err
} else {
batchResult . Result = r
batchResult . Affected += n
}
return batchResult , nil
2024-01-30 20:03:58 +08:00
}
2024-03-20 19:18:25 +08:00
// parseSqlForUpsert
// MERGE INTO {{table}} T1
2024-04-01 19:08:26 +08:00
// USING ( SELECT {{queryHolders}} FROM DUAL T2
2024-03-20 19:18:25 +08:00
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
func parseSqlForUpsert ( table string ,
2024-04-01 19:08:26 +08:00
queryHolders , insertKeys , insertValues , updateValues , duplicateKey [ ] string ,
2024-01-30 20:03:58 +08:00
) ( sqlStr string ) {
var (
2024-04-01 19:08:26 +08:00
queryHolderStr = strings . Join ( queryHolders , "," )
2024-03-20 19:18:25 +08:00
insertKeyStr = strings . Join ( insertKeys , "," )
insertValueStr = strings . Join ( insertValues , "," )
updateValueStr = strings . Join ( updateValues , "," )
duplicateKeyStr string
pattern = gstr . Trim ( ` MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s; ` )
2024-01-30 20:03:58 +08:00
)
2024-03-20 19:18:25 +08:00
for index , keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateTmp := fmt . Sprintf ( "T1.%s = T2.%s" , keys , keys )
duplicateKeyStr += duplicateTmp
}
return fmt . Sprintf ( pattern ,
table ,
2024-04-01 19:08:26 +08:00
queryHolderStr ,
2024-03-20 19:18:25 +08:00
duplicateKeyStr ,
insertKeyStr ,
insertValueStr ,
updateValueStr ,
2024-01-30 20:03:58 +08:00
)
}