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

This commit is contained in:
John Guo 2024-12-03 15:24:49 +08:00 committed by GitHub
parent 2d0cd7b770
commit 532e665841
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 353 additions and 11 deletions

View File

@ -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)
})
}

View File

@ -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);

View File

@ -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)
}

View File

@ -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)
@ -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
}
@ -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)
@ -881,9 +892,6 @@ func handleSliceAndStructArgsForSql(
}
}
newArgs = append(newArgs, oldArg)
default:
newArgs = append(newArgs, oldArg)
}
}
return

View File

@ -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()
}

View File

@ -8,6 +8,8 @@ package gstr_test
import (
"fmt"
"strconv"
"strings"
"github.com/gogf/gf/v2/text/gstr"
)
@ -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 (

View File

@ -9,6 +9,7 @@
package gstr_test
import (
"strings"
"testing"
"github.com/gogf/gf/v2/frame/g"
@ -88,3 +89,143 @@ func Test_ReplaceI_2(t *testing.T) {
t.Assert(gstr.ReplaceI("aaa", "A", "AA", 4), `AAAAAA`)
})
}
func Test_ReplaceIFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
origin = "hello GF 2018~2020!"
search = "gf"
)
// Simple replacement
result := gstr.ReplaceIFunc(origin, search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "hello GoFrame 2018~2020!")
// Replace with original string
result = gstr.ReplaceIFunc(origin, search, func(s string) string {
return s
})
t.Assert(result, origin)
// Replace with empty string
result = gstr.ReplaceIFunc(origin, search, func(s string) string {
return ""
})
t.Assert(result, "hello 2018~2020!")
// Replace multiple occurrences with different cases
origin = "GF is best, gf is nice, Gf is excellent"
result = gstr.ReplaceIFunc(origin, search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "GoFrame is best, GoFrame is nice, GoFrame is excellent")
// Empty search string
result = gstr.ReplaceIFunc(origin, "", func(s string) string {
return "GoFrame"
})
t.Assert(result, origin)
// Empty origin string
result = gstr.ReplaceIFunc("", search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "")
// Replace with longer string
result = gstr.ReplaceIFunc("GF", search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "GoFrame")
// Replace with shorter string
result = gstr.ReplaceIFunc("GF", search, func(s string) string {
return "g"
})
t.Assert(result, "g")
// Replace with mixed case patterns
origin = "gf GF Gf gF"
result = gstr.ReplaceIFunc(origin, search, func(s string) string {
return strings.ToUpper(s)
})
t.Assert(result, "GF GF GF GF")
})
}
func Test_ReplaceFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
origin = "hello gf 2018~2020!"
search = "gf"
)
// Simple replacement
result := gstr.ReplaceFunc(origin, search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "hello GoFrame 2018~2020!")
// Replace with original string
result = gstr.ReplaceFunc(origin, search, func(s string) string {
return s
})
t.Assert(result, origin)
// Replace with empty string
result = gstr.ReplaceFunc(origin, search, func(s string) string {
return ""
})
t.Assert(result, "hello 2018~2020!")
// Replace multiple occurrences
origin = "gf is best, gf is nice"
result = gstr.ReplaceFunc(origin, search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "GoFrame is best, GoFrame is nice")
// Empty search string
result = gstr.ReplaceFunc(origin, "", func(s string) string {
return "GoFrame"
})
t.Assert(result, origin)
// Empty origin string
result = gstr.ReplaceFunc("", search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "")
// Case sensitive
origin = "GF is best, gf is nice"
result = gstr.ReplaceFunc(origin, search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "GF is best, GoFrame is nice")
// Replace with longer string
result = gstr.ReplaceFunc("gf", search, func(s string) string {
return "GoFrame"
})
t.Assert(result, "GoFrame")
// Replace with shorter string
result = gstr.ReplaceFunc("gf", search, func(s string) string {
return "g"
})
t.Assert(result, "g")
})
gtest.C(t, func(t *gtest.T) {
var (
origin = "gggg"
search = "g"
replace = "gg"
)
// Simple replacement
result := gstr.ReplaceFunc(origin, search, func(s string) string {
return replace
})
t.Assert(result, "gggggggg")
})
}