Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
63c2bb7
feat(contrib/drivers/dm): add Replace/InsertIgnore support for dm
gqcn Dec 4, 2025
0d122d6
up
gqcn Dec 4, 2025
8723999
up
gqcn Dec 4, 2025
ffe65d9
merge master
gqcn Dec 4, 2025
91e3f1e
Merge branch 'master' of github.com:gogf/gf into feat/gdb-pgsql-replace
gqcn Dec 8, 2025
99536c8
up
gqcn Dec 8, 2025
ba44475
Update contrib/drivers/pgsql/pgsql_do_insert.go
gqcn Dec 8, 2025
111f8b3
Update contrib/drivers/pgsql/pgsql_z_unit_upsert_test.go
gqcn Dec 8, 2025
01cd4a3
up
gqcn Dec 8, 2025
b7cd39a
up
gqcn Dec 8, 2025
d8fa0a7
Update contrib/drivers/pgsql/pgsql_do_insert.go
gqcn Dec 8, 2025
67a8a28
Update contrib/drivers/pgsql/pgsql_do_insert.go
gqcn Dec 8, 2025
4080452
fix(contrib/drivers/pgsql): Fixed table field call issue in primary k…
LanceAdd Dec 8, 2025
5f664f3
up
gqcn Dec 8, 2025
bfe31a4
merge
gqcn Dec 8, 2025
17906e3
up
gqcn Dec 8, 2025
f6a7fd2
fix(gendao): Added support for UUID types (#4548)
LanceAdd Dec 8, 2025
8b48a60
up
gqcn Dec 8, 2025
36d6335
Update contrib/drivers/dm/dm_do_insert.go
gqcn Dec 8, 2025
73b91ed
Update contrib/drivers/mssql/mssql_do_insert.go
gqcn Dec 8, 2025
141b5c1
Update contrib/drivers/mssql/mssql_z_unit_basic_test.go
gqcn Dec 8, 2025
ffb3aac
Update contrib/drivers/mssql/mssql_do_insert.go
gqcn Dec 8, 2025
57e5126
merge
gqcn Dec 8, 2025
3658a09
up
gqcn Dec 8, 2025
ce76575
up
gqcn Dec 8, 2025
dddfe46
up
gqcn Dec 9, 2025
bc3b20e
up
gqcn Dec 9, 2025
34a4db8
up
gqcn Dec 9, 2025
02213ae
up
gqcn Dec 9, 2025
36b8534
up
gqcn Dec 9, 2025
5aaafde
Apply suggestions from code review
gqcn Dec 9, 2025
a5b434e
up
gqcn Dec 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
# Service containers to run with `code-test`
services:
# Etcd service.
# docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
# docker run -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnamilegacy/etcd:3.4.24
etcd:
image: bitnamilegacy/etcd:3.4.24
env:
Expand All @@ -75,7 +75,7 @@ jobs:
- 6379:6379

# MySQL backend server.
# docker run -d --name mysql \
# docker run \
# -p 3306:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
Expand All @@ -89,7 +89,7 @@ jobs:
- 3306:3306

# MariaDb backend server.
# docker run -d --name mariadb \
# docker run \
# -p 3307:3306 \
# -e MYSQL_DATABASE=test \
# -e MYSQL_ROOT_PASSWORD=12345678 \
Expand All @@ -103,7 +103,7 @@ jobs:
- 3307:3306

# PostgreSQL backend server.
# docker run -d --name postgres \
# docker run \
# -p 5432:5432 \
# -e POSTGRES_PASSWORD=12345678 \
# -e POSTGRES_USER=postgres \
Expand Down Expand Up @@ -150,7 +150,7 @@ jobs:
--health-retries 10

# ClickHouse backend server.
# docker run -d --name clickhouse \
# docker run \
# -p 9000:9000 -p 8123:8123 -p 9001:9001 \
# clickhouse/clickhouse-server:24.11.1.2557-alpine
clickhouse-server:
Expand All @@ -161,7 +161,7 @@ jobs:
- 9001:9001

# Polaris backend server.
# docker run -d --name polaris \
# docker run \
# -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \
# polarismesh/polaris-standalone:v1.17.2
polaris:
Expand Down
4 changes: 4 additions & 0 deletions cmd/gf/internal/cmd/gendao/gendao.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ var (
"smallmoney": {
Type: "float64",
},
"uuid": {
Type: "uuid.UUID",
Import: "github.com/google/uuid",
},
}

// tablewriter Options
Expand Down
21 changes: 11 additions & 10 deletions contrib/drivers/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Let's take `mysql` for example.

```shell
go get github.com/gogf/gf/contrib/drivers/mysql/v2@latest
# Easy to copy
# Easy for copying:
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
Expand Down Expand Up @@ -57,7 +57,7 @@ import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"

#### cgo version

When the target is a 32-bit Windows system, the cgo version needs to be used.
When the target is a `32-bit` Windows system, the `cgo` version needs to be used.

```go
import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
Expand All @@ -69,10 +69,6 @@ import _ "github.com/gogf/gf/contrib/drivers/sqlitecgo/v2"
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
```

Note:

- It does not support `Replace` features.

### SQL Server

```go
Expand All @@ -81,9 +77,10 @@ import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"

Note:

- It does not support `Replace` features.
- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.
- It supports server version >= `SQL Server2005`
- It ONLY supports datetime2 and datetimeoffset types for auto handling created_at/updated_at/deleted_at columns, because datetime type does not support microseconds precision when column value is passed as string.
- It ONLY supports `datetime2` and `datetimeoffset` types for auto handling created_at/updated_at/deleted_at columns,
because datetime type does not support microseconds precision when column value is passed as string.

### Oracle

Expand All @@ -93,8 +90,8 @@ import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"

Note:

- It does not support `Replace` features.
- It does not support `LastInsertId`.
- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.

### ClickHouse

Expand All @@ -104,7 +101,7 @@ import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"

Note:

- It does not support `InsertIgnore/InsertGetId` features.
- It does not support `InsertIgnore/InsertAndGetId` features.
- It does not support `Save/Replace` features.
- It does not support `Transaction` feature.
- It does not support `RowsAffected` feature.
Expand All @@ -115,6 +112,10 @@ Note:
import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
```

Note:

- `InsertIgnore` returns error if there is no primary key or unique index submitted with record.

## Custom Drivers

It's quick and easy, please refer to current driver source.
Expand Down
1 change: 1 addition & 0 deletions contrib/drivers/clickhouse/clickhouse_do_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
Expand Down
72 changes: 31 additions & 41 deletions contrib/drivers/dm/dm_do_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
)

// DoInsert inserts or updates data for given table.
// The list parameter must contain at least one record, which was previously validated.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
Expand All @@ -36,6 +37,12 @@ func (d *Driver) DoInsert(
return d.doInsertIgnore(ctx, link, table, list, option)

default:
// DM database supports IDENTITY auto-increment columns natively.
// The driver automatically returns LastInsertId through sql.Result.
//
// Note: DM IDENTITY columns cannot accept explicit ID values unless
// IDENTITY_INSERT is enabled. When using tables with IDENTITY columns,
// avoid providing explicit ID values in the data.
return d.Core.DoInsert(ctx, link, table, list, option)
}
}
Expand All @@ -60,46 +67,47 @@ func (d *Driver) doInsertIgnore(ctx context.Context,
// 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,
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)
primaryKeys, err := d.Core.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(
foundPrimaryKey := false
for _, primaryKey := range primaryKeys {
for dataKey := range list[0] {
if strings.EqualFold(dataKey, primaryKey) {
foundPrimaryKey = true
break
}
}
if foundPrimaryKey {
break
}
}
if !foundPrimaryKey {
return nil, gerror.NewCodef(
gcode.CodeMissingParameter,
`Please specify conflict columns or ensure the table has a primary key`,
`Replace/Save/InsertIgnore operation requires conflict detection: `+
`either specify OnConflict() columns or ensure table '%s' has a primary key in the data`,
table,
)
}
}

if len(list) == 0 {
opName := "Save"
if !withUpdate {
opName = "InsertIgnore"
}
return nil, gerror.NewCodef(
gcode.CodeInvalidRequest, `%s operation list is empty by dm driver`, opName,
)
// TODO consider composite primary keys.
conflictKeys = primaryKeys
}

var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()

one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeySet = gset.New(false)

// queryHolders: Handle data with Holder that need to be merged
Expand Down Expand Up @@ -155,24 +163,6 @@ func (d *Driver) doMergeInsert(
return batchResult, nil
}

// getPrimaryKeys retrieves the primary key field names of the table as a slice of strings.
// 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).
Expand Down
122 changes: 0 additions & 122 deletions contrib/drivers/dm/dm_z_unit_basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package dm_test

import (
"database/sql"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -509,124 +508,3 @@ 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 column
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")
})
}
Loading
Loading