Skip to content

Commit

Permalink
fix(database/gdb): move Raw parameter from args to sql statement be…
Browse files Browse the repository at this point in the history
…fore committed to db driver (#3997)
  • Loading branch information
gqcn authored Dec 3, 2024
1 parent 2d0cd7b commit 532e665
Show file tree
Hide file tree
Showing 7 changed files with 353 additions and 11 deletions.
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

0 comments on commit 532e665

Please sign in to comment.