mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
feat(contrib/drivers/dm): add Replace/InsertIgnore support for dm
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
English | [简体中文](README.zh_CN.MD)
|
||||
|
||||
# Database drivers
|
||||
|
||||
@ -44,7 +43,7 @@ func main() {
|
||||
|
||||
## Supported Drivers
|
||||
|
||||
### MySQL/MariaDB/TiDB
|
||||
### MySQL/MariaDB/TiDB/OceanBase
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
@ -116,10 +115,6 @@ Note:
|
||||
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
|
||||
```
|
||||
|
||||
Note:
|
||||
|
||||
- It does not support `Replace` features.
|
||||
|
||||
## Custom Drivers
|
||||
|
||||
It's quick and easy, please refer to current driver source.
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
[English](README.MD) | 简体中文
|
||||
|
||||
# 数据库驱动程序
|
||||
|
||||
用于gdb包的数据库驱动程序。
|
||||
|
||||
## 安装
|
||||
|
||||
以 `mysql` 为例。
|
||||
|
||||
```shell
|
||||
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
|
||||
# 方便复制
|
||||
go get github.com/gogf/gf/contrib/drivers/clickhouse/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/dm/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/mssql/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/oracle/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/pgsql/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/sqlite/v2@latest
|
||||
go get github.com/gogf/gf/contrib/drivers/sqlitecgo/v2@latest
|
||||
```
|
||||
|
||||
选择并将驱动程序导入到您的项目中:
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
通常在 `main.go` 的顶部导入:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
|
||||
// 其他导入的包。
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 主要逻辑。
|
||||
}
|
||||
```
|
||||
|
||||
## 支持的驱动程序
|
||||
|
||||
### MySQL/MariaDB/TiDB
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
```
|
||||
|
||||
### SQLite
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
```
|
||||
|
||||
#### cgo 版本
|
||||
|
||||
32位Windows请使用cgo版本
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
|
||||
### SQL Server
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
- 仅支持服务器版本 >= `SQL Server2005`
|
||||
- 仅支持 datetime2 和 datetimeoffset 类型来自动处理 created_at/updated_at/deleted_at 列,因为 datetime 类型在将列值作为字符串传递时不支持微秒精度。
|
||||
|
||||
### Oracle
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
- 不支持 `LastInsertId`。
|
||||
|
||||
### ClickHouse
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `InsertIgnore/InsertGetId` 功能。
|
||||
- 不支持 `Save/Replace` 功能。
|
||||
- 不支持 `Transaction` 功能。
|
||||
- 不支持 `RowsAffected` 功能。
|
||||
|
||||
### DM
|
||||
|
||||
```go
|
||||
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 不支持 `Replace` 功能。
|
||||
|
||||
## 自定义驱动程序
|
||||
|
||||
自定义驱动程序非常快速和简单,您可以参考当前驱动程序的源代码来进行开发。
|
||||
如果您有关于支持新驱动程序的PR(Pull Request),我们将非常感激地接受您的提交到当前仓库。
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// Driver is the driver for dm database.
|
||||
type Driver struct {
|
||||
*gdb.Core
|
||||
}
|
||||
|
||||
@ -28,28 +28,70 @@ func (d *Driver) DoInsert(
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
case gdb.InsertOptionReplace:
|
||||
// TODO:: Should be Supported
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
|
||||
)
|
||||
}
|
||||
// dm does not support REPLACE INTO syntax, use SAVE instead.
|
||||
return d.doSave(ctx, link, table, list, option)
|
||||
|
||||
return d.Core.DoInsert(ctx, link, table, list, option)
|
||||
case gdb.InsertOptionIgnore:
|
||||
// dm does not support INSERT IGNORE syntax, use MERGE instead.
|
||||
return d.doInsertIgnore(ctx, link, table, list, option)
|
||||
|
||||
default:
|
||||
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`,
|
||||
)
|
||||
return d.doMergeInsert(ctx, link, table, list, option, true)
|
||||
}
|
||||
|
||||
// doInsertIgnore implements INSERT IGNORE operation using MERGE statement for DM database.
|
||||
// It only inserts records when there's no conflict on primary/unique keys.
|
||||
func (d *Driver) doInsertIgnore(ctx context.Context,
|
||||
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
|
||||
) (result sql.Result, err error) {
|
||||
return d.doMergeInsert(ctx, link, table, list, option, false)
|
||||
}
|
||||
|
||||
// doMergeInsert implements MERGE-based insert operations for DM database.
|
||||
// When withUpdate is true, it performs upsert (insert or update).
|
||||
// When withUpdate is false, it performs insert ignore (insert only when no conflict).
|
||||
func (d *Driver) doMergeInsert(
|
||||
ctx context.Context,
|
||||
link gdb.Link,
|
||||
table string,
|
||||
list gdb.List,
|
||||
option gdb.DoInsertOption,
|
||||
withUpdate bool,
|
||||
) (result sql.Result, err error) {
|
||||
// If OnConflict is not specified, automatically get the primary key of the table
|
||||
conflictKeys := option.OnConflict
|
||||
if len(conflictKeys) == 0 {
|
||||
conflictKeys, err = d.getPrimaryKeys(ctx, table)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCode(
|
||||
gcode.CodeInternalError,
|
||||
err,
|
||||
`failed to get primary keys for table`,
|
||||
)
|
||||
}
|
||||
if len(conflictKeys) == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeMissingParameter,
|
||||
`Please specify conflict columns or ensure the table has a primary key`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, gerror.NewCode(
|
||||
gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`,
|
||||
opName := "Save"
|
||||
if !withUpdate {
|
||||
opName = "InsertIgnore"
|
||||
}
|
||||
return nil, gerror.NewCodef(
|
||||
gcode.CodeInvalidRequest, `%s operation list is empty by dm driver`, opName,
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,14 +100,13 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
oneLen = len(one)
|
||||
charL, charR = d.GetChars()
|
||||
|
||||
conflictKeys = option.OnConflict
|
||||
conflictKeySet = gset.New(false)
|
||||
|
||||
// queryHolders: Handle data with Holder that need to be upsert
|
||||
// queryValues: Handle data that need to be upsert
|
||||
// queryHolders: Handle data with Holder that need to be merged
|
||||
// queryValues: Handle data that need to be merged
|
||||
// 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
|
||||
// updateValues: Handle values that need to be updated (only when withUpdate=true)
|
||||
queryHolders = make([]string, oneLen)
|
||||
queryValues = make([]any, oneLen)
|
||||
insertKeys = make([]string, oneLen)
|
||||
@ -86,9 +127,9 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
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)) {
|
||||
// Build updateValues only when withUpdate is true
|
||||
// Filter conflict keys and soft created fields from updateValues
|
||||
if withUpdate && !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
|
||||
updateValues = append(
|
||||
updateValues,
|
||||
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
|
||||
@ -97,8 +138,10 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
index++
|
||||
}
|
||||
|
||||
batchResult := new(gdb.SqlResult)
|
||||
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
var (
|
||||
batchResult = new(gdb.SqlResult)
|
||||
sqlStr = parseSqlForMerge(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
|
||||
)
|
||||
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
@ -112,40 +155,59 @@ func (d *Driver) doSave(ctx context.Context,
|
||||
return batchResult, nil
|
||||
}
|
||||
|
||||
// parseSqlForUpsert
|
||||
// MERGE INTO {{table}} T1
|
||||
// USING ( SELECT {{queryHolders}} FROM DUAL T2
|
||||
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
|
||||
// WHEN NOT MATCHED THEN
|
||||
// INSERT {{insertKeys}} VALUES {{insertValues}}
|
||||
// WHEN MATCHED THEN
|
||||
// UPDATE SET {{updateValues}}
|
||||
func parseSqlForUpsert(table string,
|
||||
// getPrimaryKeys retrieves the primary key field list of the table.
|
||||
// This method extracts primary key information from TableFields.
|
||||
func (d *Driver) getPrimaryKeys(ctx context.Context, table string) ([]string, error) {
|
||||
tableFields, err := d.TableFields(ctx, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var primaryKeys []string
|
||||
for _, field := range tableFields {
|
||||
if field.Key == "PRI" {
|
||||
primaryKeys = append(primaryKeys, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return primaryKeys, nil
|
||||
}
|
||||
|
||||
// parseSqlForMerge generates MERGE statement for DM database.
|
||||
// When updateValues is empty, it only inserts (INSERT IGNORE behavior).
|
||||
// When updateValues is provided, it performs upsert (INSERT or UPDATE).
|
||||
// Examples:
|
||||
// - INSERT IGNORE: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...)
|
||||
// - UPSERT: MERGE INTO table T1 USING (...) T2 ON (...) WHEN NOT MATCHED THEN INSERT(...) VALUES (...) WHEN MATCHED THEN UPDATE SET ...
|
||||
func parseSqlForMerge(table string,
|
||||
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
|
||||
) (sqlStr string) {
|
||||
var (
|
||||
queryHolderStr = strings.Join(queryHolders, ",")
|
||||
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;`)
|
||||
)
|
||||
|
||||
// Build ON condition
|
||||
for index, keys := range duplicateKey {
|
||||
if index != 0 {
|
||||
duplicateKeyStr += " AND "
|
||||
}
|
||||
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
duplicateKeyStr += duplicateTmp
|
||||
duplicateKeyStr += fmt.Sprintf("T1.%s = T2.%s", keys, keys)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(pattern,
|
||||
table,
|
||||
queryHolderStr,
|
||||
duplicateKeyStr,
|
||||
insertKeyStr,
|
||||
insertValueStr,
|
||||
updateValueStr,
|
||||
)
|
||||
// Build SQL based on whether UPDATE is needed
|
||||
pattern := gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s)`)
|
||||
if len(updateValues) > 0 {
|
||||
// Upsert: INSERT or UPDATE
|
||||
pattern += gstr.Trim(`WHEN MATCHED THEN UPDATE SET %s`)
|
||||
return fmt.Sprintf(
|
||||
pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr,
|
||||
strings.Join(updateValues, ","),
|
||||
)
|
||||
} else {
|
||||
// Insert Ignore: INSERT only
|
||||
return fmt.Sprintf(pattern, table, queryHolderStr, duplicateKeyStr, insertKeyStr, insertValueStr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func escapeSingleQuote(s string) string {
|
||||
}
|
||||
|
||||
const (
|
||||
tableFieldsSqlTmp = `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'`
|
||||
tableFieldsSqlTmp = `SELECT c.COLUMN_NAME, c.DATA_TYPE, c.DATA_LENGTH, c.DATA_PRECISION, c.DATA_SCALE, c.DATA_DEFAULT, c.NULLABLE, cc.COMMENTS FROM ALL_TAB_COLUMNS c LEFT JOIN ALL_COL_COMMENTS cc ON c.COLUMN_NAME = cc.COLUMN_NAME AND c.TABLE_NAME = cc.TABLE_NAME AND c.OWNER = cc.OWNER WHERE c.TABLE_NAME = '%s' AND c.OWNER = '%s'`
|
||||
tableFieldsPkSqlSchemaTmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM USER_CONSTRAINTS CONS JOIN USER_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
|
||||
tableFieldsPkSqlDBATmp = `SELECT COLS.COLUMN_NAME AS PRIMARY_KEY_COLUMN FROM DBA_CONSTRAINTS CONS JOIN DBA_CONS_COLUMNS COLS ON CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME WHERE CONS.TABLE_NAME = '%s' AND CONS.OWNER = '%s' AND CONS.CONSTRAINT_TYPE = 'P'`
|
||||
)
|
||||
@ -85,10 +85,24 @@ func (d *Driver) TableFields(
|
||||
if m["NULLABLE"].String() != "N" {
|
||||
nullable = true
|
||||
}
|
||||
|
||||
// Build field type with length/precision
|
||||
// For NUMBER(p,s): use DATA_PRECISION and DATA_SCALE
|
||||
// For VARCHAR2/CHAR: use DATA_LENGTH
|
||||
var (
|
||||
fieldType string
|
||||
dataType = m["DATA_TYPE"].String()
|
||||
dataLength = m["DATA_LENGTH"].Int()
|
||||
)
|
||||
if dataLength > 0 {
|
||||
fieldType = fmt.Sprintf("%s(%d)", dataType, dataLength)
|
||||
} else {
|
||||
fieldType = dataType
|
||||
}
|
||||
fields[m["COLUMN_NAME"].String()] = &gdb.TableField{
|
||||
Index: i,
|
||||
Name: m["COLUMN_NAME"].String(),
|
||||
Type: m["DATA_TYPE"].String(),
|
||||
Type: fieldType,
|
||||
Null: nullable,
|
||||
Default: m["DATA_DEFAULT"].Val(),
|
||||
Key: pkFields.Get(m["COLUMN_NAME"].String()),
|
||||
|
||||
@ -80,12 +80,12 @@ func TestTableFields(t *testing.T) {
|
||||
createInitTable(tables)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var expect = map[string][]any{
|
||||
"ID": {"BIGINT", false},
|
||||
"ACCOUNT_NAME": {"VARCHAR", false},
|
||||
"PWD_RESET": {"TINYINT", false},
|
||||
"ATTR_INDEX": {"INT", true},
|
||||
"DELETED": {"INT", false},
|
||||
"CREATED_TIME": {"TIMESTAMP", false},
|
||||
"ID": {"BIGINT(8)", false},
|
||||
"ACCOUNT_NAME": {"VARCHAR(128)", false},
|
||||
"PWD_RESET": {"TINYINT(1)", false},
|
||||
"ATTR_INDEX": {"INT(4)", true},
|
||||
"DELETED": {"INT(4)", false},
|
||||
"CREATED_TIME": {"TIMESTAMP(8)", false},
|
||||
}
|
||||
|
||||
res, err := db.TableFields(ctx, tables)
|
||||
@ -140,109 +140,6 @@ func Test_DB_Query(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelSave(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
AccountName string
|
||||
AttrIndex int
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac1",
|
||||
"attrIndex": 100,
|
||||
}).OnConflict("id").Save()
|
||||
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.AccountName, "ac1")
|
||||
t.Assert(user.AttrIndex, 100)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac2",
|
||||
"attrIndex": 200,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.AccountName, "ac2")
|
||||
t.Assert(user.AttrIndex, 200)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelInsert(t *testing.T) {
|
||||
// g.Model.insert not lost default not null coloumn
|
||||
table := "A_tables"
|
||||
createInitTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 200
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwo`, i),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Insert(&data)
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 201
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
|
||||
PwdReset: 1,
|
||||
CreatedTime: time.Now(),
|
||||
AttrIndex: 98,
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Data(&data).Insert()
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDBInsert(t *testing.T) {
|
||||
table := "A_tables"
|
||||
createInitTable("A_tables")
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 300
|
||||
data := g.Map{
|
||||
"ID": i,
|
||||
"ACCOUNT_NAME": fmt.Sprintf(`A%dthress`, i),
|
||||
"PWD_RESET": 3,
|
||||
"ATTR_INDEX": 98,
|
||||
"CREATED_TIME": gtime.Now(),
|
||||
"UPDATED_TIME": gtime.Now(),
|
||||
}
|
||||
_, err := db.Insert(ctx, table, &data)
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Exec(t *testing.T) {
|
||||
createInitTable("A_tables")
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -612,3 +509,124 @@ func Test_Empty_Slice_Argument(t *testing.T) {
|
||||
t.Assert(len(result), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelSave(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
AccountName string
|
||||
AttrIndex int
|
||||
}
|
||||
var (
|
||||
user User
|
||||
count int
|
||||
result sql.Result
|
||||
err error
|
||||
)
|
||||
|
||||
result, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac1",
|
||||
"attrIndex": 100,
|
||||
}).OnConflict("id").Save()
|
||||
|
||||
t.AssertNil(err)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 1)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.AccountName, "ac1")
|
||||
t.Assert(user.AttrIndex, 100)
|
||||
|
||||
_, err = db.Model(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"accountName": "ac2",
|
||||
"attrIndex": 200,
|
||||
}).OnConflict("id").Save()
|
||||
t.AssertNil(err)
|
||||
|
||||
err = db.Model(table).Scan(&user)
|
||||
t.AssertNil(err)
|
||||
t.Assert(user.AccountName, "ac2")
|
||||
t.Assert(user.AttrIndex, 200)
|
||||
|
||||
count, err = db.Model(table).Count()
|
||||
t.AssertNil(err)
|
||||
t.Assert(count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelInsert(t *testing.T) {
|
||||
// g.Model.insert not lost default not null coloumn
|
||||
table := "A_tables"
|
||||
createInitTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 200
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwo`, i),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Insert(&data)
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i := 201
|
||||
data := User{
|
||||
ID: int64(i),
|
||||
AccountName: fmt.Sprintf(`A%dtwoONE`, i),
|
||||
PwdReset: 1,
|
||||
CreatedTime: time.Now(),
|
||||
AttrIndex: 98,
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
|
||||
_, err := db.Model(table).Data(&data).Insert()
|
||||
gtest.AssertNil(err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_InsertIgnore(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// db.SetDebug(true)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := User{
|
||||
ID: int64(666),
|
||||
AccountName: fmt.Sprintf(`name_%d`, 666),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNil(err)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := User{
|
||||
ID: int64(666),
|
||||
AccountName: fmt.Sprintf(`name_%d`, 777),
|
||||
PwdReset: 0,
|
||||
AttrIndex: 99,
|
||||
CreatedTime: time.Now(),
|
||||
UpdatedTime: time.Now(),
|
||||
}
|
||||
_, err := db.Model(table).Data(data).InsertIgnore()
|
||||
t.AssertNil(err)
|
||||
|
||||
one, err := db.Model(table).Where("id", 666).One()
|
||||
t.AssertNil(err)
|
||||
t.Assert(one["ACCOUNT_NAME"].String(), "name_666")
|
||||
})
|
||||
}
|
||||
|
||||
1400
contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go
Normal file
1400
contrib/drivers/dm/dm_z_unit_feature_soft_time_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -63,8 +63,8 @@ func init() {
|
||||
Weight: 1,
|
||||
MaxIdleConnCount: 10,
|
||||
MaxOpenConnCount: 10,
|
||||
CreatedAt: "created_time",
|
||||
UpdatedAt: "updated_time",
|
||||
// CreatedAt: "created_time",
|
||||
// UpdatedAt: "updated_time",
|
||||
}
|
||||
|
||||
nodeLink := gdb.ConfigNode{
|
||||
|
||||
@ -446,8 +446,10 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
// Group the list by fields. Different fields to different list.
|
||||
// It here uses ListMap to keep sequence for data inserting.
|
||||
// ============================================================================================
|
||||
var keyListMap = gmap.NewListMap()
|
||||
var tmpkeyListMap = make(map[string]List)
|
||||
var (
|
||||
keyListMap = gmap.NewListMap()
|
||||
tmpKeyListMap = make(map[string]List)
|
||||
)
|
||||
for _, item := range list {
|
||||
mapLen := len(item)
|
||||
if mapLen == 0 {
|
||||
@ -463,13 +465,13 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
|
||||
keys = tmpKeys // for fieldsToSequence
|
||||
|
||||
tmpKeysInSequenceStr := gstr.Join(tmpKeys, ",")
|
||||
if tmpkeyListMapItem, ok := tmpkeyListMap[tmpKeysInSequenceStr]; ok {
|
||||
tmpkeyListMap[tmpKeysInSequenceStr] = append(tmpkeyListMapItem, item)
|
||||
if tmpKeyListMapItem, ok := tmpKeyListMap[tmpKeysInSequenceStr]; ok {
|
||||
tmpKeyListMap[tmpKeysInSequenceStr] = append(tmpKeyListMapItem, item)
|
||||
} else {
|
||||
tmpkeyListMap[tmpKeysInSequenceStr] = List{item}
|
||||
tmpKeyListMap[tmpKeysInSequenceStr] = List{item}
|
||||
}
|
||||
}
|
||||
for tmpKeysInSequenceStr, itemList := range tmpkeyListMap {
|
||||
for tmpKeysInSequenceStr, itemList := range tmpKeyListMap {
|
||||
keyListMap.Set(tmpKeysInSequenceStr, itemList)
|
||||
}
|
||||
if keyListMap.Size() > 1 {
|
||||
|
||||
@ -380,6 +380,7 @@ func (m *softTimeMaintainer) GetValueByFieldTypeForCreateOrUpdate(
|
||||
ctx context.Context, fieldType LocalType, isDeletedField bool,
|
||||
) any {
|
||||
var value any
|
||||
// for creat or update procedure, the deleted field is always set to non-deleted value.
|
||||
if isDeletedField {
|
||||
switch fieldType {
|
||||
case LocalTypeDate, LocalTypeTime, LocalTypeDatetime:
|
||||
|
||||
Reference in New Issue
Block a user