refactor(assistant): replace JSON LIKE handling with JSON contains logic

- Updated GetAssistants and DeleteAssistants methods to utilize jsonContainsValue for filtering tags and locales, improving query compatibility across database drivers.
- Renamed related test functions to reflect the new JSON handling approach.
- Removed deprecated jsonLikeExpr and whereJsonLike methods to streamline codebase.
This commit is contained in:
Max 2026-04-06 10:56:45 +08:00
parent 4b30e80924
commit 8d44ef31c1
3 changed files with 32 additions and 41 deletions

View file

@ -319,8 +319,12 @@ func (store *Xun) GetAssistants(filter types.AssistantFilter, locale ...string)
if len(filter.Tags) > 0 { if len(filter.Tags) > 0 {
qb.Where(func(qb query.Query) { qb.Where(func(qb query.Query) {
for i, tag := range filter.Tags { for i, tag := range filter.Tags {
pattern := fmt.Sprintf("%%\"%s\"%%", tag) val := store.jsonContainsValue(fmt.Sprintf("%%\"%s\"%%", tag))
store.whereJsonLike(qb, "tags", pattern, i > 0) if i == 0 {
qb.WhereJSONContains("tags", val)
} else {
qb.OrWhereJSONContains("tags", val)
}
} }
}) })
} }
@ -332,7 +336,8 @@ func (store *Xun) GetAssistants(filter types.AssistantFilter, locale ...string)
qb.Where("name", "like", kw). qb.Where("name", "like", kw).
OrWhere("description", "like", kw). OrWhere("description", "like", kw).
OrWhere("capabilities", "like", kw) OrWhere("capabilities", "like", kw)
store.whereJsonLike(qb, "locales", kw, true) localeVal := store.jsonContainsValue(kw)
qb.OrWhereJSONContains("locales", localeVal)
}) })
} }
@ -708,8 +713,12 @@ func (store *Xun) DeleteAssistants(filter types.AssistantFilter) (int64, error)
if len(filter.Tags) > 0 { if len(filter.Tags) > 0 {
qb.Where(func(qb query.Query) { qb.Where(func(qb query.Query) {
for i, tag := range filter.Tags { for i, tag := range filter.Tags {
pattern := fmt.Sprintf("%%\"%s\"%%", tag) val := store.jsonContainsValue(fmt.Sprintf("%%\"%s\"%%", tag))
store.whereJsonLike(qb, "tags", pattern, i > 0) if i == 0 {
qb.WhereJSONContains("tags", val)
} else {
qb.OrWhereJSONContains("tags", val)
}
} }
}) })
} }
@ -900,6 +909,9 @@ func (store *Xun) translate(model *types.AssistantModel, assistantID string, loc
// sandboxRawSQL returns dialect-specific raw SQL fragments for sandbox JSON null detection. // sandboxRawSQL returns dialect-specific raw SQL fragments for sandbox JSON null detection.
// Returns (notNullExpr, isNullExpr) for filtering sandbox field. // Returns (notNullExpr, isNullExpr) for filtering sandbox field.
// WhereRaw is used here because this is a JSON literal `null` comparison, not a JSON array
// contains query. Each dialect requires different casting to compare the JSON value as text.
// No bind parameters are needed (pure string comparison), so no placeholder issues.
func (store *Xun) sandboxRawSQL() (string, string) { func (store *Xun) sandboxRawSQL() (string, string) {
switch store.getDriver() { switch store.getDriver() {
case "postgres": case "postgres":

View file

@ -2,6 +2,7 @@ package xun
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -198,33 +199,13 @@ func (store *Xun) getDriver() string {
return "mysql" return "mysql"
} }
// jsonLikeExpr returns a dialect-specific SQL expression for LIKE on a JSON column. // jsonContainsValue formats a value for WhereJSONContains.
// PostgreSQL requires casting json/jsonb to text before applying LIKE. // PG/MySQL need JSON string (e.g. `"tag"`), SQLite needs LIKE pattern (e.g. `%"tag"%`).
func (store *Xun) jsonLikeExpr(column string) string { func (store *Xun) jsonContainsValue(value string) string {
switch store.getDriver() { if store.getDriver() == "sqlite3" {
case "postgres": return value
return fmt.Sprintf(`"%s"::text LIKE ?`, column)
default:
return column + " LIKE ?"
}
}
// whereJsonLike applies a LIKE condition on a JSON column with proper dialect handling.
func (store *Xun) whereJsonLike(qb query.Query, column, pattern string, or bool) {
if store.getDriver() == "postgres" {
expr := fmt.Sprintf(`"%s"::text LIKE ?`, column)
if or {
qb.OrWhereRaw(expr, pattern)
} else {
qb.WhereRaw(expr, pattern)
}
} else {
if or {
qb.OrWhere(column, "like", pattern)
} else {
qb.Where(column, "like", pattern)
}
} }
return strings.TrimSuffix(strings.TrimPrefix(value, "%"), "%")
} }
// GenerateAssistantID generates a random-looking 6-digit ID // GenerateAssistantID generates a random-looking 6-digit ID

View file

@ -81,7 +81,7 @@ func TestSandboxRawSQLAllDialects(t *testing.T) {
assert.Contains(t, isNull, "=") assert.Contains(t, isNull, "=")
} }
func TestJsonLikeExpr(t *testing.T) { func TestJsonContainsValue(t *testing.T) {
test.Prepare(t, config.Conf) test.Prepare(t, config.Conf)
defer test.Clean() defer test.Clean()
@ -89,22 +89,20 @@ func TestJsonLikeExpr(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
xunStore := store.(*Xun) xunStore := store.(*Xun)
expr := xunStore.jsonLikeExpr("tags")
assert.Contains(t, expr, "LIKE ?")
driver := xunStore.getDriver() driver := xunStore.getDriver()
val := xunStore.jsonContainsValue(`%"admin"%`)
switch driver { switch driver {
case "postgres": case "sqlite3":
assert.Contains(t, expr, `"tags"::text`) assert.Equal(t, `%"admin"%`, val, "SQLite keeps LIKE pattern as-is")
default: default:
assert.Equal(t, "tags LIKE ?", expr) assert.Equal(t, `"admin"`, val, "PG/MySQL strips % wrappers for JSON value")
} }
} }
func TestJsonLikeExprFallback(t *testing.T) { func TestJsonContainsValueFallback(t *testing.T) {
store := &Xun{} store := &Xun{}
expr := store.jsonLikeExpr("tags") val := store.jsonContainsValue(`%"test"%`)
assert.Equal(t, "tags LIKE ?", expr) assert.Equal(t, `"test"`, val, "Default (mysql) strips % wrappers")
} }
func TestToDBTime(t *testing.T) { func TestToDBTime(t *testing.T) {