Skip to content

Commit

Permalink
enhance: add Save operation support for SQLite #2764 (#3315)
Browse files Browse the repository at this point in the history
  • Loading branch information
oldme-git authored Mar 6, 2024
1 parent 240dadf commit fbd266f
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 52 deletions.
3 changes: 0 additions & 3 deletions contrib/drivers/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
// You can obtain one at https://github.com/gogf/gf.

// Package sqlite implements gdb.Driver, which supports operations for database SQLite.
//
// Note:
// 1. It does not support Save features.
package sqlite

import (
Expand Down
10 changes: 0 additions & 10 deletions contrib/drivers/sqlite/sqlite_do_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"context"

"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"
)

Expand All @@ -24,14 +22,6 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [

case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]

default:
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
return sql, args, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by sqlite driver`,
)
}
}
return d.Core.DoFilter(ctx, link, sql, args)
}
68 changes: 68 additions & 0 deletions contrib/drivers/sqlite/sqlite_format_upsert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 sqlite

import (
"fmt"

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

// FormatUpsert returns SQL clause of type upsert for SQLite.
// For example: ON CONFLICT (id) DO UPDATE SET ...
func (d *Driver) FormatUpsert(columns []string, list gdb.List, 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
}
25 changes: 14 additions & 11 deletions contrib/drivers/sqlite/sqlite_z_unit_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
}

func Test_DB_Save(t *testing.T) {
table := createInitTable()
table := createTable()
defer dropTable(table)

gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
_, err := db.Save(ctx, table, g.Map{
"id": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": timeStr,
})
t.Assert(err, ErrorSave)
createTable("t_user")
defer dropTable("t_user")

i := 10
data := g.Map{
"id": i,
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
}
_, err := db.Save(ctx, "t_user", data, 10)
gtest.AssertNE(err, nil)
})
}

Expand Down
5 changes: 0 additions & 5 deletions contrib/drivers/sqlite/sqlite_z_unit_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (

"github.com/gogf/gf/v2/container/garray"
"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/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
Expand All @@ -27,9 +25,6 @@ var (
configNode gdb.ConfigNode
dbDir = gfile.Temp("sqlite")
ctx = gctx.New()

// Error
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
)

const (
Expand Down
56 changes: 50 additions & 6 deletions contrib/drivers/sqlite/sqlite_z_unit_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)

result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"passport": "CN",
"password": "12345678",
"nickname": "oldme",
"create_time": CreateTime,
}).Save()
t.Assert(err, ErrorSave)
}).OnConflict("id").Save()
t.AssertNil(nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)

err = db.Model(table).Scan(&user)
t.Assert(err, nil)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "12345678")
t.Assert(user.NickName, "oldme")
t.Assert(user.CreateTime.String(), CreateTime)

_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "CN",
"password": "abc123456",
"nickname": "to be not to be",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)

err = db.Model(table).Scan(&user)
t.Assert(err, nil)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "abc123456")
t.Assert(user.NickName, "to be not to be")
t.Assert(user.CreateTime.String(), CreateTime)

count, err = db.Model(table).Count()
t.Assert(err, nil)
t.Assert(count, 1)
})
}

Expand Down
25 changes: 14 additions & 11 deletions contrib/drivers/sqlitecgo/sqlitecgo_z_unit_core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
}

func Test_DB_Save(t *testing.T) {
table := createInitTable()
table := createTable()
defer dropTable(table)

gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
_, err := db.Save(ctx, table, g.Map{
"id": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": timeStr,
})
t.Assert(err, ErrorSave)
createTable("t_user")
defer dropTable("t_user")

i := 10
data := g.Map{
"id": i,
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
}
_, err := db.Save(ctx, "t_user", data, 10)
gtest.AssertNE(err, nil)
})
}

Expand Down
56 changes: 50 additions & 6 deletions contrib/drivers/sqlitecgo/sqlitecgo_z_unit_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)

result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"passport": "CN",
"password": "12345678",
"nickname": "oldme",
"create_time": CreateTime,
}).Save()
t.Assert(err, ErrorSave)
}).OnConflict("id").Save()
t.AssertNil(nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)

err = db.Model(table).Scan(&user)
t.Assert(err, nil)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "12345678")
t.Assert(user.NickName, "oldme")
t.Assert(user.CreateTime.String(), CreateTime)

_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "CN",
"password": "abc123456",
"nickname": "to be not to be",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)

err = db.Model(table).Scan(&user)
t.Assert(err, nil)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "abc123456")
t.Assert(user.NickName, "to be not to be")
t.Assert(user.CreateTime.String(), CreateTime)

count, err = db.Model(table).Count()
t.Assert(err, nil)
t.Assert(count, 1)
})
}

Expand Down

0 comments on commit fbd266f

Please sign in to comment.