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