Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
80 changes: 80 additions & 0 deletions contrib/drivers/mysql/mysql_z_unit_model_where_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,86 @@ func Test_Model_WherePrefixLike(t *testing.T) {
})
}

func Test_Model_WhereOrPrefixLike(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)

gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
LeftJoinOnField(table2, "id").
WhereOrPrefix(table1, g.Map{
"id": g.Slice{1, 2},
}).
WhereOrPrefixLike(table2, "nickname", "name_3%").
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 3)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
t.Assert(r[2]["id"], "3")
})
}

func Test_Model_WhereOrPrefixLikeLiteral(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)

gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
LeftJoinOnField(table2, "id").
WhereOrPrefix(table1, g.Map{
"id": g.Slice{1, 2},
}).
WhereOrPrefixLikeLiteral(table2, "nickname", "name_3").
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 3)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
t.Assert(r[2]["id"], "3")
})
}

func Test_Model_WhereOrPrefixNotLikeLiteral(t *testing.T) {
var (
table1 = gtime.TimestampNanoStr() + "_table1"
table2 = gtime.TimestampNanoStr() + "_table2"
)
createInitTable(table1)
defer dropTable(table1)
createInitTable(table2)
defer dropTable(table2)

gtest.C(t, func(t *gtest.T) {
r, err := db.Model(table1).
FieldsPrefix(table1, "*").
LeftJoinOnField(table2, "id").
WhereOrPrefix(table1, g.Map{
"id": g.Slice{1, 2},
}).
WhereOrPrefixNotLikeLiteral(table2, "nickname", "name_1").
Order("id asc").All()
t.AssertNil(err)
t.Assert(len(r), 10)
t.Assert(r[0]["id"], "1")
t.Assert(r[1]["id"], "2")
})
}

func Test_Model_WhereExists(t *testing.T) {
table := createInitTable()
defer dropTable(table)
Expand Down
37 changes: 37 additions & 0 deletions database/gdb/gdb_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -1015,3 +1015,40 @@ func genTableNamesCacheKey(group string) string {
func genSoftTimeFieldNameTypeCacheKey(schema, table string, candidateFields []string) string {
return fmt.Sprintf(`getSoftFieldNameAndType:%s#%s#%s`, schema, table, strings.Join(candidateFields, "_"))
}

// EscapeLikeString escapes special characters in a string for LIKE operations.
// It escapes '\', '%', and '_' characters to prevent them from being interpreted
// as wildcard characters in SQL LIKE statements.
//
// This function is useful when you need to search for literal characters that would
// otherwise be treated as wildcards in LIKE patterns. Use this when accepting user
// input for LIKE operations to prevent unintended wildcard matching.
//
// The function follows standard SQL escaping rules:
// - '\' becomes '\\'
// - '%' becomes '\%'
// - '_' becomes '\_'
//
// Usage examples:
//
// // Search for exact text containing special characters
// userInput := "user%name_test"
// escaped := gdb.EscapeLikeString(userInput) // "user\\%name\\_test"
// db.Model("users").WhereLike("username", escaped)
//
// // Search for text containing special characters with wildcards
// userInput := "user%name"
// escaped := gdb.EscapeLikeString(userInput) // "user\\%name"
// db.Model("users").WhereLike("username", "%"+escaped+"%") // LIKE '%user\%name%'
//
// // Normal wildcard usage (do NOT escape)
// db.Model("users").WhereLike("username", "user%") // LIKE 'user%' - matches userABC, user123, etc.
func escapeLikeString(s string) string {
// Escape backslashes first to prevent double escaping
s = strings.ReplaceAll(s, "\\", "\\\\")
// Escape percent signs
s = strings.ReplaceAll(s, "%", "\\%")
// Escape underscores
s = strings.ReplaceAll(s, "_", "\\_")
return s
}
19 changes: 19 additions & 0 deletions database/gdb/gdb_model_builder_where.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"

"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)

// doWhereType sets the condition statement for the model. The parameter `where` can be type of
Expand Down Expand Up @@ -117,6 +118,18 @@ func (b *WhereBuilder) WhereLike(column string, like string) *WhereBuilder {
return b.Wheref(`%s LIKE ?`, b.model.QuoteWord(column), like)
}

// WhereLikeLiteral builds `column LIKE like` statement with automatic escaping of special characters.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
// Use this method when you want to search for exact text that may contain LIKE special characters.
//
// Example:
//
// db.Model("user").WhereLikeLiteral("name", "john%doe_123") // Will search for exact string "john%doe_123"
// db.Model("user").WhereLike("name", "john%") // Will search using % as wildcard
func (b *WhereBuilder) WhereLikeLiteral(column string, like string) *WhereBuilder {
return b.Wheref(`%s LIKE ?`, b.model.QuoteWord(column), escapeLikeString(like))
}

// WhereIn builds `column IN (in)` statement.
func (b *WhereBuilder) WhereIn(column string, in any) *WhereBuilder {
return b.doWherefType(whereHolderTypeIn, `%s IN (?)`, b.model.QuoteWord(column), in)
Expand All @@ -141,6 +154,12 @@ func (b *WhereBuilder) WhereNotLike(column string, like any) *WhereBuilder {
return b.Wheref(`%s NOT LIKE ?`, b.model.QuoteWord(column), like)
}

// WhereNotLikeLiteral builds `column NOT LIKE like` statement with automatic escaping of special characters.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WhereNotLikeLiteral(column string, like any) *WhereBuilder {
return b.Wheref(`%s NOT LIKE ?`, b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WhereNot builds `column != value` statement.
func (b *WhereBuilder) WhereNot(column string, value any) *WhereBuilder {
return b.Wheref(`%s != ?`, b.model.QuoteWord(column), value)
Expand Down
14 changes: 14 additions & 0 deletions database/gdb/gdb_model_builder_where_prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package gdb

import "github.com/gogf/gf/v2/util/gconv"

// WherePrefix performs as Where, but it adds prefix to each field in where statement.
// Eg:
// WherePrefix("order", "status", "paid") => WHERE `order`.`status`='paid'
Expand Down Expand Up @@ -57,6 +59,12 @@ func (b *WhereBuilder) WherePrefixLike(prefix string, column string, like any) *
return b.Wheref(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like)
}

// WherePrefixLikeLiteral builds `prefix.column LIKE like` statement with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WherePrefixLikeLiteral(prefix string, column string, like any) *WhereBuilder {
return b.Wheref(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WherePrefixIn builds `prefix.column IN (in)` statement.
func (b *WhereBuilder) WherePrefixIn(prefix string, column string, in any) *WhereBuilder {
return b.doWherefType(whereHolderTypeIn, `%s.%s IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in)
Expand All @@ -81,6 +89,12 @@ func (b *WhereBuilder) WherePrefixNotLike(prefix string, column string, like any
return b.Wheref(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like)
}

// WherePrefixNotLikeLiteral builds `prefix.column NOT LIKE like` statement with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WherePrefixNotLikeLiteral(prefix string, column string, like any) *WhereBuilder {
return b.Wheref(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WherePrefixNot builds `prefix.column != value` statement.
func (b *WhereBuilder) WherePrefixNot(prefix string, column string, value any) *WhereBuilder {
return b.Wheref(`%s.%s != ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value)
Expand Down
13 changes: 13 additions & 0 deletions database/gdb/gdb_model_builder_whereor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"

"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)

// WhereOr adds "OR" condition to the where statement.
Expand Down Expand Up @@ -86,6 +87,12 @@ func (b *WhereBuilder) WhereOrLike(column string, like any) *WhereBuilder {
return b.WhereOrf(`%s LIKE ?`, b.model.QuoteWord(column), like)
}

// WhereOrLikeLiteral builds `column LIKE 'like'` statement in `OR` conditions with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WhereOrLikeLiteral(column string, like any) *WhereBuilder {
return b.WhereOrf(`%s LIKE ?`, b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WhereOrIn builds `column IN (in)` statement in `OR` conditions.
func (b *WhereBuilder) WhereOrIn(column string, in any) *WhereBuilder {
return b.doWhereOrfType(whereHolderTypeIn, `%s IN (?)`, b.model.QuoteWord(column), in)
Expand All @@ -110,6 +117,12 @@ func (b *WhereBuilder) WhereOrNotLike(column string, like any) *WhereBuilder {
return b.WhereOrf(`%s NOT LIKE ?`, b.model.QuoteWord(column), like)
}

// WhereOrNotLikeLiteral builds `column NOT LIKE like` statement in `OR` conditions with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WhereOrNotLikeLiteral(column string, like any) *WhereBuilder {
return b.WhereOrf(`%s NOT LIKE ?`, b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WhereOrNotIn builds `column NOT IN (in)` statement.
func (b *WhereBuilder) WhereOrNotIn(column string, in any) *WhereBuilder {
return b.doWhereOrfType(whereHolderTypeIn, `%s NOT IN (?)`, b.model.QuoteWord(column), in)
Expand Down
14 changes: 14 additions & 0 deletions database/gdb/gdb_model_builder_whereor_prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package gdb

import "github.com/gogf/gf/v2/util/gconv"

// WhereOrPrefix performs as WhereOr, but it adds prefix to each field in where statement.
// Eg:
// WhereOrPrefix("order", "status", "paid") => WHERE xxx OR (`order`.`status`='paid')
Expand Down Expand Up @@ -59,6 +61,12 @@ func (b *WhereBuilder) WhereOrPrefixLike(prefix string, column string, like any)
return b.WhereOrf(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like)
}

// WhereOrPrefixLikeLiteral builds `prefix.column LIKE 'like'` statement in `OR` conditions with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WhereOrPrefixLikeLiteral(prefix string, column string, like any) *WhereBuilder {
return b.WhereOrf(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WhereOrPrefixIn builds `prefix.column IN (in)` statement in `OR` conditions.
func (b *WhereBuilder) WhereOrPrefixIn(prefix string, column string, in any) *WhereBuilder {
return b.doWhereOrfType(whereHolderTypeIn, `%s.%s IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in)
Expand All @@ -83,6 +91,12 @@ func (b *WhereBuilder) WhereOrPrefixNotLike(prefix string, column string, like a
return b.WhereOrf(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like)
}

// WhereOrPrefixNotLikeLiteral builds `prefix.column NOT LIKE 'like'` statement in `OR` conditions with automatic escaping.
// This method automatically escapes '%', '_', and '\' characters in the like parameter to treat them as literal characters.
func (b *WhereBuilder) WhereOrPrefixNotLikeLiteral(prefix string, column string, like any) *WhereBuilder {
return b.WhereOrf(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), escapeLikeString(gconv.String(like)))
}

// WhereOrPrefixNotIn builds `prefix.column NOT IN (in)` statement.
func (b *WhereBuilder) WhereOrPrefixNotIn(prefix string, column string, in any) *WhereBuilder {
return b.doWhereOrfType(whereHolderTypeIn, `%s.%s NOT IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in)
Expand Down
12 changes: 12 additions & 0 deletions database/gdb/gdb_model_whereor_prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ func (m *Model) WhereOrPrefixLike(prefix string, column string, like any) *Model
return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLike(prefix, column, like))
}

// WhereOrPrefixLikeLiteral builds `prefix.column LIKE 'like'` statement in `OR` conditions with automatic escaping.
// See WhereBuilder.WhereOrPrefixLikeLiteral.
func (m *Model) WhereOrPrefixLikeLiteral(prefix string, column string, like any) *Model {
return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLikeLiteral(prefix, column, like))
}

// WhereOrPrefixIn builds `prefix.column IN (in)` statement in `OR` conditions.
// See WhereBuilder.WhereOrPrefixIn.
func (m *Model) WhereOrPrefixIn(prefix string, column string, in any) *Model {
Expand All @@ -72,6 +78,12 @@ func (m *Model) WhereOrPrefixNotLike(prefix string, column string, like any) *Mo
return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotLike(prefix, column, like))
}

// WhereOrPrefixNotLikeLiteral builds `prefix.column NOT LIKE 'like'` statement in `OR` conditions with automatic escaping.
// See WhereBuilder.WhereOrPrefixNotLikeLiteral.
func (m *Model) WhereOrPrefixNotLikeLiteral(prefix string, column string, like any) *Model {
return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotLikeLiteral(prefix, column, like))
}

// WhereOrPrefixNotIn builds `prefix.column NOT IN (in)` statement.
// See WhereBuilder.WhereOrPrefixNotIn.
func (m *Model) WhereOrPrefixNotIn(prefix string, column string, in any) *Model {
Expand Down
Loading