Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(database/gdb): move Raw parameter from args to sql statement before committed to db driver #3997

Merged
merged 4 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions contrib/drivers/mysql/mysql_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1409,3 +1409,51 @@ func Test_Issue3968(t *testing.T) {
t.Assert(len(result), 10)
})
}

// https://github.com/gogf/gf/issues/3915
func Test_Issue3915(t *testing.T) {
table := "issue3915"
array := gstr.SplitAndTrim(gtest.DataContent(`issue3915.sql`), ";")
for _, v := range array {
if _, err := db.Exec(ctx, v); err != nil {
gtest.Error(err)
}
}
defer dropTable(table)

gtest.C(t, func(t *gtest.T) {
//db.SetDebug(true)
all, err := db.Model(table).Where("a < b").All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)

all, err = db.Model(table).Where(gdb.Raw("a < b")).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)

all, err = db.Model(table).WhereLT("a", gdb.Raw("`b`")).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 1)
})

gtest.C(t, func(t *gtest.T) {
//db.SetDebug(true)
all, err := db.Model(table).Where("a > b").All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 2)

all, err = db.Model(table).Where(gdb.Raw("a > b")).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 2)

all, err = db.Model(table).WhereGT("a", gdb.Raw("`b`")).All()
t.AssertNil(err)
t.Assert(len(all), 1)
t.Assert(all[0]["id"], 2)
})
}
9 changes: 9 additions & 0 deletions contrib/drivers/mysql/testdata/issue3915.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE `issue3915` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id',
`a` float DEFAULT NULL COMMENT 'user name',
`b` float DEFAULT NULL COMMENT 'user status',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (1,1,2);
INSERT INTO `issue3915` (`id`,`a`,`b`) VALUES (2,5,4);
4 changes: 0 additions & 4 deletions database/gdb/gdb_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,9 +789,5 @@ func (c *Core) IsSoftCreatedFieldName(fieldName string) bool {
// The internal handleArguments function might be called twice during the SQL procedure,
// but do not worry about it, it's safe and efficient.
func (c *Core) FormatSqlBeforeExecuting(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
// DO NOT do this as there may be multiple lines and comments in the sql.
// sql = gstr.Trim(sql)
// sql = gstr.Replace(sql, "\n", " ")
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
return handleSliceAndStructArgsForSql(sql, args)
}
22 changes: 15 additions & 7 deletions database/gdb/gdb_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ func formatWhereHolder(ctx context.Context, db DB, in formatWhereHolderInput) (n
// ===============================================================
if subModel, ok := in.Args[i].(*Model); ok {
index := -1
whereStr, _ = gregex.ReplaceStringFunc(`(\?)`, whereStr, func(s string) string {
whereStr = gstr.ReplaceFunc(whereStr, `?`, func(s string) string {
index++
if i+len(newArgs) == index {
sqlWithHolder, holderArgs := subModel.getHolderAndArgsAsSubModel(ctx)
Expand Down Expand Up @@ -843,7 +843,7 @@ func handleSliceAndStructArgsForSql(
counter = 0
replaced = false
)
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
newSql = gstr.ReplaceFunc(newSql, `?`, func(s string) string {
if replaced {
return s
}
Expand All @@ -856,9 +856,20 @@ func handleSliceAndStructArgsForSql(
return s
})

// Special struct handling.
case reflect.Struct:
default:
switch oldArg.(type) {
// Do not append Raw arg to args but directly into the sql.
case Raw, *Raw:
var counter = 0
newSql = gstr.ReplaceFunc(newSql, `?`, func(s string) string {
counter++
if counter == index+insertHolderCount+1 {
return gconv.String(oldArg)
}
return s
})
continue

// The underlying driver supports time.Time/*time.Time types.
case time.Time, *time.Time:
newArgs = append(newArgs, oldArg)
Expand All @@ -881,9 +892,6 @@ func handleSliceAndStructArgsForSql(
}
}
newArgs = append(newArgs, oldArg)

default:
newArgs = append(newArgs, oldArg)
}
}
return
Expand Down
93 changes: 93 additions & 0 deletions text/gstr/gstr_replace.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,96 @@ func ReplaceIByMap(origin string, replaces map[string]string) string {
}
return origin
}

// ReplaceFunc returns a copy of the string `origin` in which each non-overlapping substring
// that matches the given search string is replaced by the result of function `f` applied to that substring.
// The function `f` is called with each matching substring as its argument and must return a string to be used
// as the replacement value.
func ReplaceFunc(origin string, search string, f func(string) string) string {
if search == "" {
return origin
}
var (
searchLen = len(search)
originLen = len(origin)
)
// If search string is longer than origin string, no match is possible
if searchLen > originLen {
return origin
}
var (
result strings.Builder
lastMatch int
currentPos int
)
// Pre-allocate the builder capacity to avoid reallocations
result.Grow(originLen)

for currentPos < originLen {
pos := Pos(origin[currentPos:], search)
if pos == -1 {
break
}
pos += currentPos
// Append unmatched portion
result.WriteString(origin[lastMatch:pos])
// Apply replacement function and append result
match := origin[pos : pos+searchLen]
result.WriteString(f(match))
// Update positions
lastMatch = pos + searchLen
currentPos = lastMatch
}
// Append remaining unmatched portion
if lastMatch < originLen {
result.WriteString(origin[lastMatch:])
}
return result.String()
}

// ReplaceIFunc returns a copy of the string `origin` in which each non-overlapping substring
// that matches the given search string is replaced by the result of function `f` applied to that substring.
// The match is done case-insensitively.
// The function `f` is called with each matching substring as its argument and must return a string to be used
// as the replacement value.
func ReplaceIFunc(origin string, search string, f func(string) string) string {
if search == "" {
return origin
}
var (
searchLen = len(search)
originLen = len(origin)
)
// If search string is longer than origin string, no match is possible
if searchLen > originLen {
return origin
}
var (
result strings.Builder
lastMatch int
currentPos int
)
// Pre-allocate the builder capacity to avoid reallocations
result.Grow(originLen)

for currentPos < originLen {
pos := PosI(origin[currentPos:], search)
if pos == -1 {
break
}
pos += currentPos
// Append unmatched portion
result.WriteString(origin[lastMatch:pos])
// Apply replacement function and append result
match := origin[pos : pos+searchLen]
result.WriteString(f(match))
// Update positions
lastMatch = pos + searchLen
currentPos = lastMatch
}
// Append remaining unmatched portion
if lastMatch < originLen {
result.WriteString(origin[lastMatch:])
}
return result.String()
}
47 changes: 47 additions & 0 deletions text/gstr/gstr_z_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package gstr_test

import (
"fmt"
"strconv"
"strings"

"github.com/gogf/gf/v2/text/gstr"
)
Expand Down Expand Up @@ -1018,6 +1020,51 @@ func ExampleReplaceIByMap() {
// goframe is very nice
}

func ExampleReplaceFunc() {
str := "hello gf 2018~2020!"
// Replace "gf" with a custom function that returns "GoFrame"
result := gstr.ReplaceFunc(str, "gf", func(s string) string {
return "GoFrame"
})
fmt.Println(result)

// Replace numbers with their doubled values
result = gstr.ReplaceFunc("1 2 3", "2", func(s string) string {
n, _ := strconv.Atoi(s)
return strconv.Itoa(n * 2)
})
fmt.Println(result)

// Output:
// hello GoFrame 2018~2020!
// 1 4 3
}

func ExampleReplaceIFunc() {
str := "Hello GF, hello gf, HELLO Gf!"
// Replace any case variation of "gf" with "GoFrame"
result := gstr.ReplaceIFunc(str, "gf", func(s string) string {
return "GoFrame"
})
fmt.Println(result)

// Preserve the original case of each match
result = gstr.ReplaceIFunc(str, "gf", func(s string) string {
if s == strings.ToUpper(s) {
return "GOFRAME"
}
if s == strings.ToLower(s) {
return "goframe"
}
return "GoFrame"
})
fmt.Println(result)

// Output:
// Hello GoFrame, hello GoFrame, HELLO GoFrame!
// Hello GOFRAME, hello goframe, HELLO GoFrame!
}

// similartext
func ExampleSimilarText() {
var (
Expand Down
Loading
Loading