言完整指南:從基礎(chǔ)語(yǔ)法到源碼級(jí)實(shí)現(xiàn)解析)
Karakeep 搜索查詢語(yǔ)言完整指南從基礎(chǔ)語(yǔ)法到源碼級(jí)實(shí)現(xiàn)解析【免費(fèi)下載鏈接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ho/hoarderKarakeepHoarder是一款可自托管的收藏一切應(yīng)用支持鏈接、筆記與圖片書(shū)簽并提供基于 AI 的自動(dòng)打標(biāo)簽與全文搜索能力。本文以倉(cāng)庫(kù)中 version-v0.28.0 版本文檔 為骨架系統(tǒng)講解其搜索查詢語(yǔ)言Search Query Language的全部限定符Qualifier、布爾組合語(yǔ)法與全文搜索用法并深入對(duì)應(yīng)解析器與查詢執(zhí)行源碼幫助你精確檢索書(shū)簽庫(kù)甚至構(gòu)建動(dòng)態(tài)智能列表Smart List。一、搜索查詢語(yǔ)言概覽Karakeep 提供了一套專用的搜索查詢語(yǔ)言用于過(guò)濾和查找書(shū)簽。與單純的關(guān)鍵詞搜索不同它允許你通過(guò)結(jié)構(gòu)化限定符如is:fav、#tag、after:2023-01-01組合出精確的檢索條件同時(shí)保留普通文本的全文搜索能力。整套語(yǔ)言由前端與后端共享的解析器實(shí)現(xiàn)同一套語(yǔ)法同時(shí)服務(wù)于搜索框與智能列表保證行為一致。二、基礎(chǔ)語(yǔ)法搜索查詢語(yǔ)言遵循一套簡(jiǎn)單而一致的語(yǔ)法規(guī)則空格分隔多個(gè)條件多個(gè)條件之間用空格隔開(kāi)隱含邏輯 AND與關(guān)系顯式布爾邏輯使用and/or關(guān)鍵字顯式表達(dá)與 / 或邏輯取反限定符在限定符前加-前綴表示取反negate例如-is:archived表示未歸檔的書(shū)簽括號(hào)分組使用圓括號(hào)()對(duì)條件進(jìn)行分組以控制優(yōu)先級(jí)注意分組本身不能被取反即-(...)不合法。補(bǔ)充在更新版本的文檔與當(dāng)前倉(cāng)庫(kù)源碼中取反符號(hào)除-外還支持!作為等價(jià)別名如!is:archived標(biāo)簽限定符也支持tag:作為#的等價(jià)寫(xiě)法詳見(jiàn)后文源碼解析。三、完整限定符參考表以下是 v0.28.0 文檔中給出的全部限定符及其說(shuō)明限定符說(shuō)明用法示例is:fav已收藏的書(shū)簽is:favis:archived已歸檔的書(shū)簽-is:archivedis:tagged帶有一個(gè)或多個(gè)標(biāo)簽的書(shū)簽is:taggedis:inlist位于一個(gè)或多個(gè)列表中的書(shū)簽is:inlistis:link、is:text、is:media類型為鏈接、文本或媒體的書(shū)簽is:linkurl:value匹配 URL 包含指定子串的書(shū)簽url:example.comtitle:value匹配標(biāo)題包含指定子串的書(shū)簽title:example支持用引號(hào)包裹帶空格的標(biāo)題title:my title#tag匹配帶有指定標(biāo)簽的書(shū)簽#important支持用引號(hào)包裹帶空格的標(biāo)簽#work in progresslist:name匹配位于指定列表中的書(shū)簽list:reading支持用引號(hào)包裹帶空格的列表名list:to reviewafter:date創(chuàng)建日期在指定日期YYYY-MM-DD當(dāng)天或之后的書(shū)簽after:2023-01-01before:date創(chuàng)建日期在指定日期YYYY-MM-DD當(dāng)天或之前的書(shū)簽before:2023-12-31feed:name從特定 RSS 訂閱源導(dǎo)入的書(shū)簽feed:Hackernewsage:time-range按書(shū)簽創(chuàng)建距今的時(shí)間范圍匹配。用/表示書(shū)簽的最大 / 最小年齡。支持單位d天、w周、m月、y年age:1d、age:2w、age:6m、age:3y限定符詳解與注意事項(xiàng)is:*系列用于按狀態(tài)或類型過(guò)濾。is:archived與is:fav常與-配合使用如-is:archived找出所有未歸檔書(shū)簽is:tagged/is:inlist判斷書(shū)簽是否至少關(guān)聯(lián)了一個(gè)標(biāo)簽或列表is:link/is:text/is:media對(duì)應(yīng)書(shū)簽的三種存儲(chǔ)類型。url:與title:執(zhí)行的是子串匹配而非精確匹配因此url:example.com能命中所有 URL 中包含該片段的書(shū)簽當(dāng)值中包含空格時(shí)必須使用雙引號(hào)。#tag標(biāo)簽匹配。標(biāo)簽名默認(rèn)支持連字符等字符如#my-tag含空格時(shí)用引號(hào)包裹#work in progress。after:/before:日期格式嚴(yán)格為YYYY-MM-DD語(yǔ)義為閉區(qū)間當(dāng)天或之后/之前。age:相對(duì)時(shí)間過(guò)濾age:1d表示最近 1 天之內(nèi)創(chuàng)建age:3y表示創(chuàng)建超過(guò) 3 年。注意這里/描述的是書(shū)簽?zāi)挲g的大小關(guān)系與after/before的絕對(duì)日期形成互補(bǔ)。官方示例# 查找 2023 年收藏且打了 important 標(biāo)簽的書(shū)簽 is:fav after:2023-01-01 before:2023-12-31 #important # 查找已歸檔、且要么在 reading 列表中、要么打了 work 標(biāo)簽的書(shū)簽 is:archived and (list:reading or #work) # 查找沒(méi)有標(biāo)簽或不在任何列表中的書(shū)簽 -is:tagged or -is:inlist # 查找標(biāo)題中包含 React 的書(shū)簽 title:React四、組合條件布爾邏輯與分組多個(gè)條件可以自由組合。語(yǔ)法層面的核心規(guī)則是空格分隔 隱式 ANDand/or關(guān)鍵字 顯式布爾運(yùn)算圓括號(hào)可改變求值優(yōu)先級(jí)限定符前加-實(shí)現(xiàn)取反。# 查找 2023 年收藏且打了 important 標(biāo)簽的書(shū)簽 is:fav after:2023-01-01 before:2023-12-31 #important # 查找已歸檔、且要么在 reading 列表中、要么打了 work 標(biāo)簽的書(shū)簽 is:archived and (list:reading or #work) # 查找既未收藏也未歸檔的書(shū)簽 -is:fav -is:archived從實(shí)際解析結(jié)果看見(jiàn) searchQueryParser.test.ts 中的復(fù)雜查詢用例(is:fav is:archived) or (#my-tag)會(huì)被解析為or節(jié)點(diǎn)下掛一個(gè)and節(jié)點(diǎn)與一個(gè)標(biāo)簽匹配節(jié)點(diǎn)而(is:fav or is:archived) and #my-tag則相反證明括號(hào)確實(shí)參與構(gòu)造了嵌套的匹配樹(shù)而非簡(jiǎn)單的線性拼接。五、文本搜索全文搜索任何不屬于限定符的文本都會(huì)被當(dāng)作全文搜索內(nèi)容處理# 在書(shū)簽內(nèi)容中搜索 machine learning machine learning # 文本搜索與限定符組合 machine learning is:fav文本與限定符可以交錯(cuò)出現(xiàn)。從 searchQueryParser.test.ts 的用例可見(jiàn)查詢hello is:fav world is:archived mixed world #my-tag test會(huì)被拆分為純文本hello world mixed world test與三個(gè) matcherfavourited、archived、tagName的組合兩者互不干擾。這意味著你可以在一次搜索中同時(shí)享受結(jié)構(gòu)化過(guò)濾與全文檢索。六、源碼級(jí)解析解析器如何工作搜索查詢語(yǔ)言并非簡(jiǎn)單的字符串匹配而是一套由typescript-parsec實(shí)現(xiàn)的詞法 語(yǔ)法解析器位于 packages/shared/searchQueryParser.ts并被 Web 端、移動(dòng)端與智能列表三方共用。詞法分析Lexer解析器首先按優(yōu)先級(jí)順序?qū)⑤斎胱址蟹譃?token見(jiàn) searchQueryParser.tsconst lexerRules: [RegExp, TokenType][] [ [/^\sand/i, TokenType.And], [/^\sor/i, TokenType.Or], [/^#/, TokenType.Hash], [/^(is|url|list|after|before|age|feed|title|tag|source):/, TokenType.Qualifier], [/^([^])/, TokenType.StringLiteral], [/^\(/, TokenType.LParen], [/^\)/, TokenType.RParen], [/^\s/, TokenType.Space], [/^-/, TokenType.Minus], [/^!/, TokenType.Exclamation], [/^[^ )(]/, TokenType.Ident], // 兜底規(guī)則匹配大量普通字符 ] as const;值得注意的細(xì)節(jié)and/or匹配不區(qū)分大小寫(xiě)/i標(biāo)志且要求前面帶空白限定符白名單包含is、url、list、after、before、age、feed、title、tag、source十個(gè)前綴——其中tag:是#的等價(jià)寫(xiě)法source:是 v0.28.0 文檔未列出、但當(dāng)前源碼已實(shí)現(xiàn)的限定符雙引號(hào)字符串被單獨(dú)識(shí)別為StringLiteral解析時(shí)會(huì)剝?nèi)ヒ?hào)-與!都被視為取反符號(hào)Minus/Exclamation因此兩種寫(xiě)法行為完全一致。語(yǔ)法分析與 Matcher 樹(shù)詞法 token 隨后被送入遞歸下降文法EXP/MATCHERsearchQueryParser.ts每個(gè)匹配條件被編譯成一個(gè)Matcher對(duì)象。is:*與各冒號(hào)限定符分別映射為不同類型的 matcheris:fav→{ type: favourited, favourited: true }is:archived→{ type: archived, archived: true }is:tagged→{ type: tagged, tagged: true }is:inlist→{ type: inlist, inList: true }is:link/is:text/is:media→{ type: type, typeName: LINK | TEXT | ASSET }url:→{ type: url, url }title:→{ type: title, title }#/tag:→{ type: tagName, tagName }list:→{ type: listName, listName }feed:→{ type: rssFeedName, feedName }source:→{ type: source, source }after:/before:→{ type: dateAfter | dateBefore, date }age:→{ type: age, relativeDate: { direction, amount, unit } }所有 matcher 的類型定義集中在 packages/shared/types/search.ts其中and/or會(huì)遞歸地組合子 matcher形成一棵匹配樹(shù)export type Matcher | NonRecursiveMatcher | { type: and; matchers: Matcher[] } | { type: or; matchers: Matcher[] };解析完成后還會(huì)調(diào)用flattenAndsAndOrs對(duì)同類型節(jié)點(diǎn)做扁平化合并searchQueryParser.ts例如(is:fav is:archived) #my-tag會(huì)被合并為單個(gè)and節(jié)點(diǎn)下掛三個(gè) matcher。三種解析結(jié)果狀態(tài)parseSearchQuery返回result字段取值full | partial | invalidsearchQueryParser.tsfull整個(gè)查詢被完整解析partial解析器無(wú)法消費(fèi)全部輸入例如用戶正在輸入、查詢尚未寫(xiě)完此時(shí)返回已解析的 matcher 與剩余文本invalid語(yǔ)法不合法整個(gè)查詢降級(jí)為純文本處理。這一設(shè)計(jì)讓搜索框可以在輸入過(guò)程中實(shí)時(shí)給出部分匹配結(jié)果體驗(yàn)流暢而未知的限定符不會(huì)報(bào)錯(cuò)會(huì)被當(dāng)作普通文本回退處理測(cè)試用例is:fav is:helloworld驗(yàn)證了這一點(diǎn)見(jiàn) searchQueryParser.test.ts。相對(duì)時(shí)間解析age:限定符由 packages/shared/utils/relativeDateUtils.ts 負(fù)責(zé)。正則^([])(\d)([dwmy])$嚴(yán)格限定格式表示更新newer年齡小于表示更舊older年齡大于單位映射為d→day、w→week、m→month、y→year。toAbsoluteDate再將相對(duì)時(shí)間換算為絕對(duì)日期用于數(shù)據(jù)庫(kù)查詢。七、源碼級(jí)執(zhí)行Matcher 如何變成 SQL解析出的 Matcher 樹(shù)并不會(huì)直接用于前端過(guò)濾而是被轉(zhuǎn)換為 SQL 查詢。核心實(shí)現(xiàn)在 packages/trpc/lib/search.ts 的getBookmarkIdsFromMatcher中其內(nèi)部通過(guò)getIds對(duì)每種 matcher 類型生成對(duì)應(yīng)的 drizzle-orm 查詢tagName使用EXISTS/NOT EXISTS子查詢關(guān)聯(lián)tagsOnBookmarks與bookmarkTags表按標(biāo)簽名精確匹配search.tstagged用exists/notExists判斷書(shū)簽是否關(guān)聯(lián)了任意標(biāo)簽and/or節(jié)點(diǎn)分別通過(guò)intersect求 ID 交集對(duì)應(yīng) AND與union求 ID 并集對(duì)應(yīng) OR在內(nèi)存中合并各子查詢結(jié)果search.ts。搜索接口bookmarks.searchBookmarks在 packages/trpc/routers/bookmarks.ts 中定義前端通過(guò) apps/web/lib/hooks/bookmark-search.ts 調(diào)用并支持三種搜索模式fts全文檢索、semantic語(yǔ)義搜索與hybrid混合。值得注意的是當(dāng)查詢完全由限定符組成如is:fav沒(méi)有可嵌入的文本時(shí)前端會(huì)自動(dòng)回退到全文檢索模式見(jiàn) bookmark-search.ts。八、進(jìn)階實(shí)戰(zhàn)用查詢語(yǔ)言驅(qū)動(dòng)智能列表搜索查詢語(yǔ)言并不只用于搜索框——Karakeep 的**智能列表Smart List**直接復(fù)用同一套語(yǔ)法。在 packages/trpc/models/lists.ts 中SmartList將用戶保存的查詢字符串通過(guò)parseSearchQuery解析要求結(jié)果為full否則報(bào) Invalid smart list query再調(diào)用getBookmarkIdsFromMatcher實(shí)時(shí)計(jì)算列表內(nèi)容lists.ts。這意味著你可以把常用搜索保存為持久化的智能列表例如# 一個(gè)自動(dòng)匯總最近一周收藏內(nèi)容的智能列表查詢 is:fav age:1w # 一個(gè)聚合所有未歸檔、帶 todo 標(biāo)簽鏈接的智能列表查詢 -is:archived #todo is:link由于智能列表與搜索框共享解析器與執(zhí)行鏈路兩者的行為完全一致查詢語(yǔ)法可以無(wú)縫遷移。九、常見(jiàn)組合速查需求查詢最近一個(gè)月收藏且未讀未歸檔-is:archived age:1m2023 年收藏的已歸檔書(shū)簽is:archived after:2023-01-01 before:2023-12-31在 reading 列表或打了 work 標(biāo)簽list:reading or #work既沒(méi)打標(biāo)簽也不在任何列表-is:tagged -is:inlist標(biāo)題含 React 的鏈接類型書(shū)簽title:React is:link來(lái)自 Hackernews 訂閱源的書(shū)簽feed:Hackernews十、測(cè)試驗(yàn)證與進(jìn)一步閱讀該查詢語(yǔ)言的行為有完整的單元測(cè)試保障覆蓋簡(jiǎn)單is:*查詢、字符串限定符、!取反別名、tag:別名、日期/年齡查詢、復(fù)雜布爾組合、純文本與限定符混排、未知限定符回退、部分解析等場(chǎng)景見(jiàn) packages/shared/searchQueryParser.test.ts。如需深入了解執(zhí)行層的 SQL 生成可閱讀 packages/trpc/lib/search.ts 及其測(cè)試 packages/trpc/lib/tests/search.test.ts。若想進(jìn)一步掌握相關(guān)概念可參閱倉(cāng)庫(kù)內(nèi)的 書(shū)簽使用指南、標(biāo)簽說(shuō)明 與 列表說(shuō)明它們與本搜索語(yǔ)言共同構(gòu)成 Karakeep 的檢索與組織體系?!久赓M(fèi)下載鏈接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ho/hoarder創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考