From 1a75d19a89e211b710126ac3e8c1e21e51490685 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 22 Jul 2025 23:39:52 +0800 Subject: [PATCH] fix: memo filter for sqlite --- server/router/api/v1/memo_service.go | 4 +-- store/db/mysql/memo.go | 42 ---------------------------- store/db/postgres/memo.go | 42 ---------------------------- store/db/sqlite/memo.go | 42 ---------------------------- store/db/sqlite/memo_filter.go | 14 +++++----- store/db/sqlite/memo_filter_test.go | 17 +++++++---- store/memo.go | 11 ++------ store/test/memo_test.go | 4 +-- 8 files changed, 23 insertions(+), 153 deletions(-) diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index b27443a37..3ee1d59e8 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -559,7 +559,7 @@ func (s *APIV1Service) RenameMemoTag(ctx context.Context, request *v1pb.RenameMe memoFind := &store.FindMemo{ CreatorID: &user.ID, - PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.OldTag}}, + Filters: []string{fmt.Sprintf("tag in [\"%s\"]", request.OldTag)}, ExcludeComments: true, } if (request.Parent) != "memos/-" { @@ -609,7 +609,7 @@ func (s *APIV1Service) DeleteMemoTag(ctx context.Context, request *v1pb.DeleteMe memoFind := &store.FindMemo{ CreatorID: &user.ID, - PayloadFind: &store.FindMemoPayload{TagSearch: []string{request.Tag}}, + Filters: []string{fmt.Sprintf("tag in [\"%s\"]", request.Tag)}, ExcludeContent: true, ExcludeComments: true, } diff --git a/store/db/mysql/memo.go b/store/db/mysql/memo.go index 3c8684bdd..19196cce9 100644 --- a/store/db/mysql/memo.go +++ b/store/db/mysql/memo.go @@ -80,23 +80,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v := find.RowStatus; v != nil { where, args = append(where, "`memo`.`row_status` = ?"), append(args, *v) } - if v := find.CreatedTsBefore; v != nil { - where, args = append(where, "UNIX_TIMESTAMP(`memo`.`created_ts`) < ?"), append(args, *v) - } - if v := find.CreatedTsAfter; v != nil { - where, args = append(where, "UNIX_TIMESTAMP(`memo`.`created_ts`) > ?"), append(args, *v) - } - if v := find.UpdatedTsBefore; v != nil { - where, args = append(where, "UNIX_TIMESTAMP(`memo`.`updated_ts`) < ?"), append(args, *v) - } - if v := find.UpdatedTsAfter; v != nil { - where, args = append(where, "UNIX_TIMESTAMP(`memo`.`updated_ts`) > ?"), append(args, *v) - } - if v := find.ContentSearch; len(v) != 0 { - for _, s := range v { - where, args = append(where, "`memo`.`content` LIKE ?"), append(args, "%"+s+"%") - } - } if v := find.VisibilityList; len(v) != 0 { placeholder := []string{} for _, visibility := range v { @@ -105,31 +88,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo } where = append(where, fmt.Sprintf("`memo`.`visibility` in (%s)", strings.Join(placeholder, ","))) } - if v := find.Pinned; v != nil { - where, args = append(where, "`memo`.`pinned` = ?"), append(args, *v) - } - if v := find.PayloadFind; v != nil { - if v.Raw != nil { - where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) - } - if len(v.TagSearch) != 0 { - for _, tag := range v.TagSearch { - where, args = append(where, "(JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?) OR JSON_CONTAINS(JSON_EXTRACT(`memo`.`payload`, '$.tags'), ?))"), append(args, fmt.Sprintf(`"%s"`, tag), fmt.Sprintf(`"%s/"`, tag)) - } - } - if v.HasLink { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") - } - if v.HasTaskList { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE") - } - if v.HasCode { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE") - } - if v.HasIncompleteTasks { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE") - } - } if find.ExcludeComments { having = append(having, "`parent_id` IS NULL") } diff --git a/store/db/postgres/memo.go b/store/db/postgres/memo.go index 19323505a..b41d78805 100644 --- a/store/db/postgres/memo.go +++ b/store/db/postgres/memo.go @@ -72,23 +72,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v := find.RowStatus; v != nil { where, args = append(where, "memo.row_status = "+placeholder(len(args)+1)), append(args, *v) } - if v := find.CreatedTsBefore; v != nil { - where, args = append(where, "memo.created_ts < "+placeholder(len(args)+1)), append(args, *v) - } - if v := find.CreatedTsAfter; v != nil { - where, args = append(where, "memo.created_ts > "+placeholder(len(args)+1)), append(args, *v) - } - if v := find.UpdatedTsBefore; v != nil { - where, args = append(where, "memo.updated_ts < "+placeholder(len(args)+1)), append(args, *v) - } - if v := find.UpdatedTsAfter; v != nil { - where, args = append(where, "memo.updated_ts > "+placeholder(len(args)+1)), append(args, *v) - } - if v := find.ContentSearch; len(v) != 0 { - for _, s := range v { - where, args = append(where, "memo.content ILIKE "+placeholder(len(args)+1)), append(args, fmt.Sprintf("%%%s%%", s)) - } - } if v := find.VisibilityList; len(v) != 0 { holders := []string{} for _, visibility := range v { @@ -97,31 +80,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo } where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(holders, ", "))) } - if v := find.Pinned; v != nil { - where, args = append(where, "memo.pinned = "+placeholder(len(args)+1)), append(args, *v) - } - if v := find.PayloadFind; v != nil { - if v.Raw != nil { - where, args = append(where, "memo.payload = "+placeholder(len(args)+1)), append(args, *v.Raw) - } - if len(v.TagSearch) != 0 { - for _, tag := range v.TagSearch { - where, args = append(where, "EXISTS (SELECT 1 FROM jsonb_array_elements(memo.payload->'tags') AS tag WHERE tag::text = "+placeholder(len(args)+1)+" OR tag::text LIKE "+placeholder(len(args)+2)+")"), append(args, fmt.Sprintf(`"%s"`, tag), fmt.Sprintf(`"%s/%%"`, tag)) - } - } - if v.HasLink { - where = append(where, "(memo.payload->'property'->>'hasLink')::BOOLEAN IS TRUE") - } - if v.HasTaskList { - where = append(where, "(memo.payload->'property'->>'hasTaskList')::BOOLEAN IS TRUE") - } - if v.HasCode { - where = append(where, "(memo.payload->'property'->>'hasCode')::BOOLEAN IS TRUE") - } - if v.HasIncompleteTasks { - where = append(where, "(memo.payload->'property'->>'hasIncompleteTasks')::BOOLEAN IS TRUE") - } - } if find.ExcludeComments { where = append(where, "memo_relation.related_memo_id IS NULL") } diff --git a/store/db/sqlite/memo.go b/store/db/sqlite/memo.go index 3460282a4..ace8c20fb 100644 --- a/store/db/sqlite/memo.go +++ b/store/db/sqlite/memo.go @@ -72,23 +72,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo if v := find.RowStatus; v != nil { where, args = append(where, "`memo`.`row_status` = ?"), append(args, *v) } - if v := find.CreatedTsBefore; v != nil { - where, args = append(where, "`memo`.`created_ts` < ?"), append(args, *v) - } - if v := find.CreatedTsAfter; v != nil { - where, args = append(where, "`memo`.`created_ts` > ?"), append(args, *v) - } - if v := find.UpdatedTsBefore; v != nil { - where, args = append(where, "`memo`.`updated_ts` < ?"), append(args, *v) - } - if v := find.UpdatedTsAfter; v != nil { - where, args = append(where, "`memo`.`updated_ts` > ?"), append(args, *v) - } - if v := find.ContentSearch; len(v) != 0 { - for _, s := range v { - where, args = append(where, "`memo`.`content` LIKE ?"), append(args, fmt.Sprintf("%%%s%%", s)) - } - } if v := find.VisibilityList; len(v) != 0 { placeholder := []string{} for _, visibility := range v { @@ -97,31 +80,6 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo } where = append(where, fmt.Sprintf("`memo`.`visibility` IN (%s)", strings.Join(placeholder, ","))) } - if v := find.Pinned; v != nil { - where, args = append(where, "`memo`.`pinned` = ?"), append(args, *v) - } - if v := find.PayloadFind; v != nil { - if v.Raw != nil { - where, args = append(where, "`memo`.`payload` = ?"), append(args, *v.Raw) - } - if len(v.TagSearch) != 0 { - for _, tag := range v.TagSearch { - where, args = append(where, "(JSON_EXTRACT(`memo`.`payload`, '$.tags') LIKE ? OR JSON_EXTRACT(`memo`.`payload`, '$.tags') LIKE ?)"), append(args, fmt.Sprintf(`%%"%s"%%`, tag), fmt.Sprintf(`%%"%s/%%`, tag)) - } - } - if v.HasLink { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE") - } - if v.HasTaskList { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE") - } - if v.HasCode { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE") - } - if v.HasIncompleteTasks { - where = append(where, "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE") - } - } if find.ExcludeComments { where = append(where, "`parent_id` IS NULL") } diff --git a/store/db/sqlite/memo_filter.go b/store/db/sqlite/memo_filter.go index b231f085f..e3e72893c 100644 --- a/store/db/sqlite/memo_filter.go +++ b/store/db/sqlite/memo_filter.go @@ -200,15 +200,15 @@ func (d *DB) convertWithTemplates(ctx *filter.ConvertContext, expr *exprv1.Expr) var sqlTemplate string if operator == "=" { if valueBool { - sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') = JSON('true')", jsonPath) + sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') IS TRUE", jsonPath) } else { - sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') = JSON('false')", jsonPath) + sqlTemplate = fmt.Sprintf("NOT(JSON_EXTRACT(`memo`.`payload`, '%s') IS TRUE)", jsonPath) } } else { // operator == "!=" if valueBool { - sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') != JSON('true')", jsonPath) + sqlTemplate = fmt.Sprintf("NOT(JSON_EXTRACT(`memo`.`payload`, '%s') IS TRUE)", jsonPath) } else { - sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') != JSON('false')", jsonPath) + sqlTemplate = fmt.Sprintf("JSON_EXTRACT(`memo`.`payload`, '%s') IS TRUE", jsonPath) } } if _, err := ctx.Buffer.WriteString(sqlTemplate); err != nil { @@ -319,17 +319,17 @@ func (d *DB) convertWithTemplates(ctx *filter.ConvertContext, expr *exprv1.Expr) } } else if identifier == "has_link" { // Handle has_link as a standalone boolean identifier - if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') = JSON('true')"); err != nil { + if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE"); err != nil { return err } } else if identifier == "has_code" { // Handle has_code as a standalone boolean identifier - if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') = JSON('true')"); err != nil { + if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE"); err != nil { return err } } else if identifier == "has_incomplete_tasks" { // Handle has_incomplete_tasks as a standalone boolean identifier - if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') = JSON('true')"); err != nil { + if _, err := ctx.Buffer.WriteString("JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE"); err != nil { return err } } diff --git a/store/db/sqlite/memo_filter_test.go b/store/db/sqlite/memo_filter_test.go index 143f44774..cff143f82 100644 --- a/store/db/sqlite/memo_filter_test.go +++ b/store/db/sqlite/memo_filter_test.go @@ -60,6 +60,11 @@ func TestConvertExprToSQL(t *testing.T) { want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') IS TRUE", args: []any{}, }, + { + filter: `has_code`, + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE", + args: []any{}, + }, { filter: `has_task_list == true`, want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasTaskList') = 1", @@ -117,32 +122,32 @@ func TestConvertExprToSQL(t *testing.T) { }, { filter: `has_link == true`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') = JSON('true')", + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE", args: []any{}, }, { filter: `has_code == false`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') = JSON('false')", + want: "NOT(JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE)", args: []any{}, }, { filter: `has_incomplete_tasks != false`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') != JSON('false')", + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE", args: []any{}, }, { filter: `has_link`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') = JSON('true')", + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasLink') IS TRUE", args: []any{}, }, { filter: `has_code`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') = JSON('true')", + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasCode') IS TRUE", args: []any{}, }, { filter: `has_incomplete_tasks`, - want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') = JSON('true')", + want: "JSON_EXTRACT(`memo`.`payload`, '$.property.hasIncompleteTasks') IS TRUE", args: []any{}, }, } diff --git a/store/memo.go b/store/memo.go index 5192f881f..a3a10a075 100644 --- a/store/memo.go +++ b/store/memo.go @@ -60,18 +60,11 @@ type FindMemo struct { UID *string // Standard fields - RowStatus *RowStatus - CreatorID *int32 - CreatedTsAfter *int64 - CreatedTsBefore *int64 - UpdatedTsAfter *int64 - UpdatedTsBefore *int64 + RowStatus *RowStatus + CreatorID *int32 // Domain specific fields - ContentSearch []string VisibilityList []Visibility - Pinned *bool - PayloadFind *FindMemoPayload ExcludeContent bool ExcludeComments bool Filters []string diff --git a/store/test/memo_test.go b/store/test/memo_test.go index e40138776..d2590ac03 100644 --- a/store/test/memo_test.go +++ b/store/test/memo_test.go @@ -86,9 +86,7 @@ func TestMemoListByTags(t *testing.T) { require.NotNil(t, memo) memoList, err := ts.ListMemos(ctx, &store.FindMemo{ - PayloadFind: &store.FindMemoPayload{ - TagSearch: []string{"test_tag"}, - }, + Filters: []string{"tag in [\"test_tag\"]"}, }) require.NoError(t, err) require.Equal(t, 1, len(memoList))