From 424f11f227cde6d5a1328729fc5385c7b2faf4c1 Mon Sep 17 00:00:00 2001 From: Steven Date: Wed, 26 Nov 2025 07:40:39 +0800 Subject: [PATCH] fix(store): fix PostgreSQL tag filtering type inference error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves issue where tag filtering in PostgreSQL databases failed with "operator does not exist: jsonb ~~ unknown" error. The hierarchical tag filtering feature introduced in commit 5e47f25b generated SQL with implicit type placeholders that PostgreSQL couldn't infer. The fix explicitly casts the LIKE comparison placeholder to text (::text) in the PostgreSQL dialect, ensuring proper type resolution for the query parameter. Fixes #5275 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- plugin/filter/render.go | 2 +- store/db/postgres/memo_filter_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/filter/render.go b/plugin/filter/render.go index 01fee5e1b..10eeac39e 100644 --- a/plugin/filter/render.go +++ b/plugin/filter/render.go @@ -352,7 +352,7 @@ func (r *renderer) renderTagInList(values []ValueExpr) (renderResult, error) { case DialectPostgres: // Support hierarchical tags: match exact tag OR tags with this prefix exactMatch := fmt.Sprintf("%s @> jsonb_build_array(%s::json)", jsonArrayExpr(r.dialect, field), r.addArg(fmt.Sprintf(`"%s"`, str))) - prefixMatch := fmt.Sprintf("%s::text LIKE %s", jsonArrayExpr(r.dialect, field), r.addArg(fmt.Sprintf(`%%"%s/%%`, str))) + prefixMatch := fmt.Sprintf("%s::text LIKE %s::text", jsonArrayExpr(r.dialect, field), r.addArg(fmt.Sprintf(`%%"%s/%%`, str))) expr := fmt.Sprintf("(%s OR %s)", exactMatch, prefixMatch) conditions = append(conditions, expr) default: diff --git a/store/db/postgres/memo_filter_test.go b/store/db/postgres/memo_filter_test.go index 4f9495299..12045197a 100644 --- a/store/db/postgres/memo_filter_test.go +++ b/store/db/postgres/memo_filter_test.go @@ -18,12 +18,12 @@ func TestConvertExprToSQL(t *testing.T) { }{ { filter: `tag in ["tag1", "tag2"]`, - want: "((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2) OR (memo.payload->'tags' @> jsonb_build_array($3::json) OR memo.payload->'tags'::text LIKE $4))", + want: "((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2::text) OR (memo.payload->'tags' @> jsonb_build_array($3::json) OR memo.payload->'tags'::text LIKE $4::text))", args: []any{`"tag1"`, `%"tag1/%`, `"tag2"`, `%"tag2/%`}, }, { filter: `!(tag in ["tag1", "tag2"])`, - want: "NOT (((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2) OR (memo.payload->'tags' @> jsonb_build_array($3::json) OR memo.payload->'tags'::text LIKE $4)))", + want: "NOT (((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2::text) OR (memo.payload->'tags' @> jsonb_build_array($3::json) OR memo.payload->'tags'::text LIKE $4::text)))", args: []any{`"tag1"`, `%"tag1/%`, `"tag2"`, `%"tag2/%`}, }, { @@ -43,7 +43,7 @@ func TestConvertExprToSQL(t *testing.T) { }, { filter: `tag in ['tag1'] || content.contains('hello')`, - want: "((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2) OR memo.content ILIKE $3)", + want: "((memo.payload->'tags' @> jsonb_build_array($1::json) OR memo.payload->'tags'::text LIKE $2::text) OR memo.content ILIKE $3)", args: []any{`"tag1"`, `%"tag1/%`, "%hello%"}, }, {