From 891f765cfb4766fa9ed7faa195f93452591f493f Mon Sep 17 00:00:00 2001 From: Liang Ding Date: Tue, 29 Nov 2022 23:21:19 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20=E6=90=9C=E7=B4=A2=20`=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E8=B7=AF=E5=BE=84`=20=E6=94=AF=E6=8C=81=E5=A4=9A?= =?UTF-8?q?=E9=80=89=20https://github.com/siyuan-note/siyuan/issues/6743?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kernel/api/search.go | 23 ++++++----- kernel/model/search.go | 92 +++++++++++++++++++++++++----------------- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/kernel/api/search.go b/kernel/api/search.go index d6b3f6201..c5b342ac7 100644 --- a/kernel/api/search.go +++ b/kernel/api/search.go @@ -194,15 +194,18 @@ func fullTextSearchBlock(c *gin.Context) { } query := arg["query"].(string) - pathArg := arg["path"] - var path string - if nil != pathArg { - path = pathArg.(string) - } - var box string - if "" != path { - box = strings.Split(path, "/")[0] - path = strings.TrimPrefix(path, box) + pathsArg := arg["paths"] + var paths, boxes []string + if nil != pathsArg { + for _, p := range pathsArg.([]interface{}) { + path := p.(string) + box := strings.Split(path, "/")[0] + boxes = append(boxes, box) + path = strings.TrimPrefix(path, box) + paths = append(paths, path) + } + paths = gulu.Str.RemoveDuplicatedElem(paths) + boxes = gulu.Str.RemoveDuplicatedElem(boxes) } var types map[string]bool if nil != arg["types"] { @@ -222,7 +225,7 @@ func fullTextSearchBlock(c *gin.Context) { if nil != groupByArg { groupBy = int(groupByArg.(float64)) } - blocks, matchedBlockCount, matchedRootCount := model.FullTextSearchBlock(query, box, path, types, method, groupBy) + blocks, matchedBlockCount, matchedRootCount := model.FullTextSearchBlock(query, boxes, paths, types, method, groupBy) ret.Data = map[string]interface{}{ "blocks": blocks, "matchedBlockCount": matchedBlockCount, diff --git a/kernel/model/search.go b/kernel/model/search.go index 0d5293bbb..33a3ee1b6 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -19,6 +19,7 @@ package model import ( "bytes" "errors" + "fmt" "path" "regexp" "strconv" @@ -310,7 +311,7 @@ func FindReplace(keyword, replacement string, ids []string, method int) (err err return } -func FullTextSearchBlock(query, box, path string, types map[string]bool, method int, groupBy int) (ret []*Block, matchedBlockCount, matchedRootCount int) { +func FullTextSearchBlock(query string, boxes, paths []string, types map[string]bool, method int, groupBy int) (ret []*Block, matchedBlockCount, matchedRootCount int) { // method:0:文本,1:查询语法,2:SQL,3:正则表达式 // groupBy:0:不分组,1:按文档分组 query = strings.TrimSpace(query) @@ -318,20 +319,23 @@ func FullTextSearchBlock(query, box, path string, types map[string]bool, method var blocks []*Block switch method { - case 0: // 文本 - typeFilter := buildTypeFilter(types) - blocks, matchedBlockCount, matchedRootCount = fullTextSearch(query, box, path, typeFilter, beforeLen, false) case 1: // 查询语法 filter := buildTypeFilter(types) - blocks, matchedBlockCount, matchedRootCount = fullTextSearch(query, box, path, filter, beforeLen, true) + boxFilter := buildBoxesFilter(boxes) + pathFilter := buildPathsFilter(paths) + blocks, matchedBlockCount, matchedRootCount = fullTextSearch(query, boxFilter, pathFilter, filter, beforeLen, true) case 2: // SQL blocks, matchedBlockCount, matchedRootCount = searchBySQL(query, beforeLen) case 3: // 正则表达式 typeFilter := buildTypeFilter(types) - blocks, matchedBlockCount, matchedRootCount = fullTextSearchByRegexp(query, box, path, typeFilter, beforeLen) - default: + boxFilter := buildBoxesFilter(boxes) + pathFilter := buildPathsFilter(paths) + blocks, matchedBlockCount, matchedRootCount = fullTextSearchByRegexp(query, boxFilter, pathFilter, typeFilter, beforeLen) + default: // 文本 filter := buildTypeFilter(types) - blocks, matchedBlockCount, matchedRootCount = fullTextSearch(query, box, path, filter, beforeLen, false) + boxFilter := buildBoxesFilter(boxes) + pathFilter := buildPathsFilter(paths) + blocks, matchedBlockCount, matchedRootCount = fullTextSearch(query, boxFilter, pathFilter, filter, beforeLen, false) } switch groupBy { @@ -365,6 +369,38 @@ func FullTextSearchBlock(query, box, path string, types map[string]bool, method return } +func buildBoxesFilter(boxes []string) string { + if 0 == len(boxes) { + return "" + } + builder := bytes.Buffer{} + builder.WriteString(" AND (") + for i, box := range boxes { + builder.WriteString(fmt.Sprintf("box = '%s'", box)) + if i < len(boxes)-1 { + builder.WriteString(" OR ") + } + } + builder.WriteString(")") + return builder.String() +} + +func buildPathsFilter(paths []string) string { + if 0 == len(paths) { + return "" + } + builder := bytes.Buffer{} + builder.WriteString(" AND (") + for i, path := range paths { + builder.WriteString(fmt.Sprintf("path LIKE '%s%%'", path)) + if i < len(paths)-1 { + builder.WriteString(" OR ") + } + } + builder.WriteString(")") + return builder.String() +} + func buildTypeFilter(types map[string]bool) string { s := conf.NewSearch() if err := copier.Copy(s, Conf.Search); nil != err { @@ -467,7 +503,7 @@ func fullTextSearchRefBlock(keyword string, beforeLen int) (ret []*Block) { return } -func fullTextSearchCount(query, box, path, typeFilter string) (matchedBlockCount, matchedRootCount int) { +func fullTextSearchCount(query, boxFilter, pathFilter, typeFilter string) (matchedBlockCount, matchedRootCount int) { query = gulu.Str.RemoveInvisible(query) if util.IsIDPattern(query) { ret, _ := sql.Query("SELECT COUNT(id) AS `matches`, COUNT(DISTINCT(root_id)) AS `docs` FROM `blocks` WHERE `id` = '" + query + "'") @@ -485,12 +521,7 @@ func fullTextSearchCount(query, box, path, typeFilter string) (matchedBlockCount } stmt := "SELECT COUNT(id) AS `matches`, COUNT(DISTINCT(root_id)) AS `docs` FROM `" + table + "` WHERE `" + table + "` MATCH '" + columnFilter() + ":(" + query + ")' AND type IN " + typeFilter - if "" != box { - stmt += " AND box = '" + box + "'" - } - if "" != path { - stmt += " AND path LIKE '" + path + "%'" - } + stmt += boxFilter + pathFilter result, _ := sql.Query(stmt) if 1 > len(result) { return @@ -500,7 +531,7 @@ func fullTextSearchCount(query, box, path, typeFilter string) (matchedBlockCount return } -func fullTextSearch(query, box, path, typeFilter string, beforeLen int, querySyntax bool) (ret []*Block, matchedBlockCount, matchedRootCount int) { +func fullTextSearch(query, boxFilter, pathFilter, typeFilter string, beforeLen int, querySyntax bool) (ret []*Block, matchedBlockCount, matchedRootCount int) { query = gulu.Str.RemoveInvisible(query) if util.IsIDPattern(query) { ret, matchedBlockCount, matchedRootCount = searchBySQL("SELECT * FROM `blocks` WHERE `id` = '"+query+"'", beforeLen) @@ -524,12 +555,7 @@ func fullTextSearch(query, box, path, typeFilter string, beforeLen int, querySyn "highlight(" + table + ", 11, '" + search.SearchMarkLeft + "', '" + search.SearchMarkRight + "') AS content, " + "fcontent, markdown, length, type, subtype, ial, sort, created, updated" stmt := "SELECT " + projections + " FROM " + table + " WHERE " + table + " MATCH '" + columnFilter() + ":(" + query + ")' AND type IN " + typeFilter - if "" != box { - stmt += " AND box = '" + box + "'" - } - if "" != path { - stmt += " AND path LIKE '" + path + "%'" - } + stmt += boxFilter + pathFilter stmt += " ORDER BY sort ASC, rank ASC LIMIT " + strconv.Itoa(Conf.Search.Limit) blocks := sql.SelectBlocksRawStmt(stmt, Conf.Search.Limit) ret = fromSQLBlocks(&blocks, "", beforeLen) @@ -537,22 +563,17 @@ func fullTextSearch(query, box, path, typeFilter string, beforeLen int, querySyn ret = []*Block{} } - matchedBlockCount, matchedRootCount = fullTextSearchCount(query, box, path, typeFilter) + matchedBlockCount, matchedRootCount = fullTextSearchCount(query, boxFilter, pathFilter, typeFilter) return } -func fullTextSearchByRegexp(exp, box, path, typeFilter string, beforeLen int) (ret []*Block, matchedBlockCount, matchedRootCount int) { +func fullTextSearchByRegexp(exp, boxFilter, pathFilter, typeFilter string, beforeLen int) (ret []*Block, matchedBlockCount, matchedRootCount int) { exp = gulu.Str.RemoveInvisible(exp) exp = regexp.QuoteMeta(exp) fieldFilter := fieldRegexp(exp) stmt := "SELECT * FROM `blocks` WHERE (" + fieldFilter + ") AND type IN " + typeFilter - if "" != box { - stmt += " AND box = '" + box + "'" - } - if "" != path { - stmt += " AND path LIKE '" + path + "%'" - } + stmt += boxFilter + pathFilter stmt += " ORDER BY sort ASC LIMIT " + strconv.Itoa(Conf.Search.Limit) blocks := sql.SelectBlocksRawStmt(stmt, Conf.Search.Limit) ret = fromSQLBlocks(&blocks, "", beforeLen) @@ -560,19 +581,14 @@ func fullTextSearchByRegexp(exp, box, path, typeFilter string, beforeLen int) (r ret = []*Block{} } - matchedBlockCount, matchedRootCount = fullTextSearchCountByRegexp(exp, box, path, typeFilter) + matchedBlockCount, matchedRootCount = fullTextSearchCountByRegexp(exp, boxFilter, pathFilter, typeFilter) return } -func fullTextSearchCountByRegexp(exp, box, path, typeFilter string) (matchedBlockCount, matchedRootCount int) { +func fullTextSearchCountByRegexp(exp, boxFilter, pathFilter, typeFilter string) (matchedBlockCount, matchedRootCount int) { fieldFilter := fieldRegexp(exp) stmt := "SELECT COUNT(id) AS `matches`, COUNT(DISTINCT(root_id)) AS `docs` FROM `blocks` WHERE " + fieldFilter + " AND type IN " + typeFilter - if "" != box { - stmt += " AND box = '" + box + "'" - } - if "" != path { - stmt += " AND path LIKE '" + path + "%'" - } + stmt += boxFilter + pathFilter stmt += " LIMIT " + strconv.Itoa(Conf.Search.Limit) result, _ := sql.Query(stmt) if 1 > len(result) {