From 8d44ef31c123d0040e18c0618cf22da2dfd3e77f Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 6 Apr 2026 10:56:45 +0800 Subject: [PATCH] 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. --- agent/store/xun/assistant.go | 22 ++++++++++++++----- agent/store/xun/xun.go | 33 ++++++---------------------- agent/store/xun/xun_internal_test.go | 18 +++++++-------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/agent/store/xun/assistant.go b/agent/store/xun/assistant.go index af1fdc4b..7279c6da 100644 --- a/agent/store/xun/assistant.go +++ b/agent/store/xun/assistant.go @@ -319,8 +319,12 @@ func (store *Xun) GetAssistants(filter types.AssistantFilter, locale ...string) if len(filter.Tags) > 0 { qb.Where(func(qb query.Query) { for i, tag := range filter.Tags { - pattern := fmt.Sprintf("%%\"%s\"%%", tag) - store.whereJsonLike(qb, "tags", pattern, i > 0) + val := store.jsonContainsValue(fmt.Sprintf("%%\"%s\"%%", tag)) + 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). OrWhere("description", "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 { qb.Where(func(qb query.Query) { for i, tag := range filter.Tags { - pattern := fmt.Sprintf("%%\"%s\"%%", tag) - store.whereJsonLike(qb, "tags", pattern, i > 0) + val := store.jsonContainsValue(fmt.Sprintf("%%\"%s\"%%", tag)) + 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. // 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) { switch store.getDriver() { case "postgres": diff --git a/agent/store/xun/xun.go b/agent/store/xun/xun.go index 46d54230..389f3e33 100644 --- a/agent/store/xun/xun.go +++ b/agent/store/xun/xun.go @@ -2,6 +2,7 @@ package xun import ( "fmt" + "strings" "time" jsoniter "github.com/json-iterator/go" @@ -198,33 +199,13 @@ func (store *Xun) getDriver() string { return "mysql" } -// jsonLikeExpr returns a dialect-specific SQL expression for LIKE on a JSON column. -// PostgreSQL requires casting json/jsonb to text before applying LIKE. -func (store *Xun) jsonLikeExpr(column string) string { - switch store.getDriver() { - case "postgres": - 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) - } +// jsonContainsValue formats a value for WhereJSONContains. +// PG/MySQL need JSON string (e.g. `"tag"`), SQLite needs LIKE pattern (e.g. `%"tag"%`). +func (store *Xun) jsonContainsValue(value string) string { + if store.getDriver() == "sqlite3" { + return value } + return strings.TrimSuffix(strings.TrimPrefix(value, "%"), "%") } // GenerateAssistantID generates a random-looking 6-digit ID diff --git a/agent/store/xun/xun_internal_test.go b/agent/store/xun/xun_internal_test.go index 5f437d7b..85ffea70 100644 --- a/agent/store/xun/xun_internal_test.go +++ b/agent/store/xun/xun_internal_test.go @@ -81,7 +81,7 @@ func TestSandboxRawSQLAllDialects(t *testing.T) { assert.Contains(t, isNull, "=") } -func TestJsonLikeExpr(t *testing.T) { +func TestJsonContainsValue(t *testing.T) { test.Prepare(t, config.Conf) defer test.Clean() @@ -89,22 +89,20 @@ func TestJsonLikeExpr(t *testing.T) { assert.NoError(t, err) xunStore := store.(*Xun) - expr := xunStore.jsonLikeExpr("tags") - assert.Contains(t, expr, "LIKE ?") - driver := xunStore.getDriver() + val := xunStore.jsonContainsValue(`%"admin"%`) switch driver { - case "postgres": - assert.Contains(t, expr, `"tags"::text`) + case "sqlite3": + assert.Equal(t, `%"admin"%`, val, "SQLite keeps LIKE pattern as-is") 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{} - expr := store.jsonLikeExpr("tags") - assert.Equal(t, "tags LIKE ?", expr) + val := store.jsonContainsValue(`%"test"%`) + assert.Equal(t, `"test"`, val, "Default (mysql) strips % wrappers") } func TestToDBTime(t *testing.T) {