FTS5 是一個 SQLite 虛擬資料表模組,可為資料庫應用程式提供全文檢索功能。在其最基本的形式中,全文檢索引擎允許使用者有效地搜尋大量文件中包含一個或多個搜尋詞彙的子集。例如,Google 向全球網路使用者提供的搜尋功能就是全文檢索引擎,因為它允許使用者搜尋網路上的所有包含例如「fts5」的文件。
要使用 FTS5,使用者需要建立一個具有一個或多個欄位的 FTS5 虛擬資料表。例如:
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
在用於建立 FTS5 表格的 CREATE VIRTUAL TABLE 陳述式中,加入類型、限制或 PRIMARY KEY 宣告是錯誤的。一旦建立後,FTS5 表格可以使用 INSERT、UPDATE 或 DELETE 陳述式進行資料填充,就像其他表格一樣。如同其他沒有 PRIMARY KEY 宣告的表格,FTS5 表格有一個名為 rowid 的隱含 INTEGER PRIMARY KEY 欄位。
上述範例中未顯示的是,在 CREATE VIRTUAL TABLE 陳述式中,也可以提供 各種選項 給 FTS5 來設定新表格的各個方面。這些選項可以用於修改 FTS5 表格從文件和查詢中提取詞彙的方式、在磁碟上建立額外的索引以加速前綴查詢,或建立一個作為其他位置儲存內容之索引的 FTS5 表格。
資料填充完成後,有三種方法可以對 FTS5 表格的內容執行全文檢索查詢:
如果使用 MATCH 或 = 運算子,運算子左邊的表達式通常是 FTS5 表格的名稱(例外情況是 指定欄位過濾器 時)。右邊的表達式必須是一個指定要搜尋詞彙的文字值。對於表格值函式語法,要搜尋的詞彙被指定為第一個表格引數。例如:
-- Query for all rows that contain at least once instance of the term -- "fts5" (in any column). The following three queries are equivalent. SELECT * FROM email WHERE email MATCH 'fts5'; SELECT * FROM email WHERE email = 'fts5'; SELECT * FROM email('fts5');
預設情況下,FTS5 全文檢索搜尋不區分大小寫。與其他不包含 ORDER BY 子句的 SQL 查詢一樣,上述範例以任意順序返回結果。要按相關性(從最相關到最不相關)排序結果,可以將 ORDER BY 加入全文檢索查詢,如下所示:
-- Query for all rows that contain at least once instance of the term -- "fts5" (in any column). Return results in order from best to worst -- match. SELECT * FROM email WHERE email MATCH 'fts5' ORDER BY rank;
除了符合列的欄位值和 rowid 之外,應用程式還可以利用 FTS5 輔助函式 來擷取有關符合列的額外資訊。例如,輔助函式可用於擷取符合列的欄位值副本,並將所有符合詞彙的實例以 html <b></b> 標籤括起來。輔助函式的呼叫方式與 SQLite 純量函式 相同,只是 FTS5 表格的名稱被指定為第一個引數。例如:
-- Query for rows that match "fts5". Return a copy of the "body" column -- of each row with the matches surrounded by <b></b> tags. SELECT highlight(email, 2, '<b>', '</b>') FROM email('fts5');
可用輔助函式的說明,以及有關特殊「rank」欄位設定的更多詳細資訊,請參見下方。自訂輔助函式 也可以用 C 語言實作並向 FTS5 註冊,就像自訂 SQL 函式可以向 SQLite 核心註冊一樣。
除了搜尋包含某個詞彙的所有列之外,FTS5 還允許使用者搜尋包含以下內容的列:
此類進階搜尋是透過在 MATCH 運算子(或 = 運算子,或作為表格值函式語法的第一個引數)的右側提供更複雜的 FTS5 查詢字串來請求的。完整的查詢語法在此說明。
從 3.9.0 版(2015-10-14)開始,FTS5 作為 SQLite 合併程式碼 的一部分包含在內。如果使用兩個 autoconf 建置系統之一,則可以在執行 configure 指令碼時指定「--enable-fts5」選項來啟用 FTS5。(FTS5 目前在原始碼樹 configure 指令碼中預設為停用,在合併程式碼 configure 指令碼中預設為啟用,但這些預設值未來可能會更改。)
或者,如果 sqlite3.c 是使用其他建置系統編譯的,則需安排定義 SQLITE_ENABLE_FTS5 預處理器符號。
或者,FTS5 可以建置為可載入的擴充功能。
標準的 FTS5 原始程式碼包含 SQLite 原始碼樹狀結構 "ext/fts5" 目錄中的一系列 *.c 和其他檔案。建置過程會將其簡化為兩個檔案 - "fts5.c" 和 "fts5.h" - 可用於建置 SQLite 可載入擴充功能。
$ wget -c https://www.sqlite.org/src/tarball/SQLite-trunk.tgz?uuid=trunk -O SQLite-trunk.tgz .... output ... $ tar -xzf SQLite-trunk.tgz $ cd SQLite-trunk $ ./configure && make fts5.c ... lots of output ... $ ls fts5.[ch] fts5.c fts5.h
然後,可以將 "fts5.c" 中的程式碼編譯成可載入的擴充功能,或如 編譯可載入擴充功能 中所述,靜態連結到應用程式中。定義了兩個進入點,兩者都執行相同的操作
另一個檔案 "fts5.h" 並非編譯 FTS5 擴充功能所必需的。它由實作 自訂 FTS5 分詞器或輔助函式 的應用程式使用。
以下區塊包含以 BNF 形式表示的 FTS 查詢語法摘要。後面接著詳細說明。
<phrase> := string [*] <phrase> := <phrase> + <phrase> <neargroup> := NEAR ( <phrase> <phrase> ... [, N] ) <query> := [ [-] <colspec> :] [^] <phrase> <query> := [ [-] <colspec> :] <neargroup> <query> := [ [-] <colspec> :] ( <query> ) <query> := <query> AND <query> <query> := <query> OR <query> <query> := <query> NOT <query> <colspec> := colname <colspec> := { colname1 colname2 ... }
在 FTS 運算式中,可以使用以下兩種方式之一指定字串
將其括在雙引號 (") 中。在字串中,任何嵌入的雙引號字元都可以使用 SQL 樣式進行跳脫 - 新增第二個雙引號字元。
作為非 "AND"、"OR" 或 "NOT"(區分大小寫)的 FTS5 關鍵字。FTS5 關鍵字是由一個或多個連續字元組成的字串,這些字元全部是:
fts5 查詢中的每個字串都會由分詞器進行剖析(「分詞」),並提取零個或多個詞彙或術語的清單。例如,預設分詞器會將字串 "alpha beta gamma" 分詞為三個獨立的詞彙 - "alpha"、"beta" 和 "gamma" - 按照這個順序。
FTS 查詢由片語組成。片語是由一個或多個詞彙組成的有序清單。查詢中每個字串的詞彙都組成單個片語。可以使用 "+" 運算子將兩個片語串連成一個大片語。例如,假設使用的分詞器模組將輸入 "one.two.three" 分詞為三個獨立的詞彙,則以下四個查詢都指定相同的片語
... MATCH '"one two three"' ... MATCH 'one + two + three' ... MATCH '"one two" + three' ... MATCH 'one.two.three'
如果文件包含至少一個與組成片語的詞彙序列相符的詞彙子序列,則片語會與文件相符。
如果 FTS 運算式中字串後接著 "*" 字元,則從字串中提取的最後一個詞彙會標記為字首詞彙。顧名思義,字首詞彙會與其為字首的任何文件詞彙相符。例如,以下區塊中的前兩個查詢將與包含詞彙 "one"(緊接著是詞彙 "two",然後是任何以 "thr" 開頭的詞彙)的任何文件相符。
... MATCH '"one two thr" * ' ... MATCH 'one + two + thr*' ... MATCH '"one two thr*"' -- May not work as expected!
上方區塊中的最後一個查詢可能無法如預期般運作。因為 "*" 字元位於雙引號內,它會被傳遞給分詞器,分詞器可能會將其捨棄(或者,根據所使用的特定分詞器,將其包含在最後一個詞彙中),而不是將其識別為特殊的 FTS 字元。
如果 "^" 字元緊接在一個非 NEAR 查詢的片語之前,則該片語僅在它出現在欄位的第一個詞彙時才會匹配文件。"^" 語法可以與欄位篩選器組合使用,但不能插入片語的中間。
... MATCH '^one' -- first token in any column must be "one" ... MATCH '^ one + two' -- phrase "one two" must appear at start of a column ... MATCH '^ "one two"' -- same as previous ... MATCH 'a : ^two' -- first token of column "a" must be "two" ... MATCH 'NEAR(^one, two)' -- syntax error! ... MATCH 'one + ^two' -- syntax error! ... MATCH '"^one two"' -- May not work as expected!
兩個或多個片語可以組合成一個NEAR 群組。NEAR 群組的指定方式為:詞彙 "NEAR"(區分大小寫),後跟一個左括號,再後跟兩個或多個以空白分隔的片語,最後可選擇性地加上一個逗號和數值參數 N,以及一個右括號。例如:
... MATCH 'NEAR("one two" "three four", 10)' ... MATCH 'NEAR("one two" thr* + four)'
如果沒有提供 N 參數,則預設值為 10。NEAR 群組會匹配符合以下條件的文件:
例如:
CREATE VIRTUAL TABLE f USING fts5(x); INSERT INTO f(rowid, x) VALUES(1, 'A B C D x x x E F x'); ... MATCH 'NEAR(e d, 4)'; -- Matches! ... MATCH 'NEAR(e d, 3)'; -- Matches! ... MATCH 'NEAR(e d, 2)'; -- Does not match! ... MATCH 'NEAR("c d" "e f", 3)'; -- Matches! ... MATCH 'NEAR("c" "e f", 3)'; -- Does not match! ... MATCH 'NEAR(a d e, 6)'; -- Matches! ... MATCH 'NEAR(a d e, 5)'; -- Does not match! ... MATCH 'NEAR("a b c d" "b c" "e f", 4)'; -- Matches! ... MATCH 'NEAR("a b c d" "b c" "e f", 3)'; -- Does not match!
可以透過在單一片語或 NEAR 群組前面加上欄位名稱和一個冒號,來限制其僅匹配 FTS 表格中指定欄位內的文字。或者,也可以透過在前面加上以空白分隔的欄位名稱列表(用括號「大括號」括起來)和一個冒號,來限制其匹配一組欄位。欄位名稱可以使用上述兩種字串形式的任一種來指定。與片語中的字串不同,欄位名稱不會傳遞給分詞器模組。欄位名稱的英文大小寫,如同 SQLite 欄位名稱的慣例,不區分大小寫 — 僅 ASCII 範圍內的字元才區分大小寫。
... MATCH 'colname : NEAR("one two" "three four", 10)' ... MATCH '"colname" : one + two + three' ... MATCH '{col1 col2} : NEAR("one two" "three four", 10)' ... MATCH '{col2 col1 col3} : one + two + three'
如果欄位篩選器規格前面有一個 "-" 字元,則它會被解釋為不匹配的欄位列表。例如:
-- Search for matches in all columns except "colname" ... MATCH '- colname : NEAR("one two" "three four", 10)' -- Search for matches in all columns except "col1", "col2" and "col3" ... MATCH '- {col2 col1 col3} : one + two + three'
欄位篩選器規格也可以應用於括號內的任意表達式。在這種情況下,欄位篩選器會套用至表達式中的所有片語。巢狀欄位篩選器操作只能進一步限制匹配的欄位子集,它們不能用於重新啟用已篩選的欄位。例如:
-- The following are equivalent: ... MATCH '{a b} : ( {b c} : "hello" AND "world" )' ... MATCH '(b : "hello") AND ({a b} : "world")'
最後,可以使用欄位名稱作為 MATCH 運算子的左側(而不是通常的表格名稱)來指定單一欄位的欄位篩選器。例如:
-- Given the following table CREATE VIRTUAL TABLE ft USING fts5(a, b, c); -- The following are equivalent SELECT * FROM ft WHERE b MATCH 'uvw AND xyz'; SELECT * FROM ft WHERE ft MATCH 'b : (uvw AND xyz)'; -- This query cannot match any rows (since all columns are filtered out): SELECT * FROM ft WHERE b MATCH 'a : xyz';
片語和 NEAR 群組可以使用布林運算子組成表達式。運算子的優先順序從最高(最緊密的組合)到最低(最鬆散的組合)如下:
運算子 | 功能 |
---|---|
<query1> NOT <query2>
| 如果 query1 匹配且 query2 不匹配,則匹配。 |
<query1> AND <query2>
| 如果 query1 和 query2 都匹配,則匹配。 |
<query1> OR <query2>
| 如果 query1 或 query2 匹配,則匹配。 |
括號可以用於對表達式進行分組,以便以常用的方式修改運算子優先順序。例如:
-- Because NOT groups more tightly than OR, either of the following may -- be used to match all documents that contain the token "two" but not -- "three", or contain the token "one". ... MATCH 'one OR two NOT three' ... MATCH 'one OR (two NOT three)' -- Matches documents that contain at least one instance of either "one" -- or "two", but do not contain any instances of token "three". ... MATCH '(one OR two) NOT three'
片語和 NEAR 群組也可以透過隱含的 AND 運算子連接。為了簡潔起見,上述的 BNF 語法中並未顯示這些運算子。實際上,任何僅以空白分隔的片語或 NEAR 群組序列(包括那些限制在指定欄位中匹配的序列)都會被視為在每對片語或 NEAR 群組之間存在一個隱含的 AND 運算子。隱含的 AND 運算子絕不會插入在括號括住的表達式之後或之前。隱含的 AND 運算子的優先順序高於所有其他運算子,包括 NOT。例如:
... MATCH 'one two three' -- 'one AND two AND three' ... MATCH 'three "one two"' -- 'three AND "one two"' ... MATCH 'NEAR(one two) three' -- 'NEAR(one two) AND three' ... MATCH 'one OR two three' -- 'one OR two AND three' ... MATCH 'one NOT two three' -- 'one NOT (two AND three)' ... MATCH '(one OR two) three' -- Syntax error! ... MATCH 'func(one two)' -- Syntax error!
在 "CREATE VIRTUAL TABLE ... USING fts5 ..." 陳述式中指定的每個參數,不是欄位宣告就是設定選項。欄位宣告由一個或多個以空白分隔的 FTS5 簡單詞或以任何 SQLite 可接受的方式引用的字串常值組成。
欄位宣告中的第一個字串或簡單詞是欄位名稱。嘗試將 fts5 資料表欄位命名為 "rowid" 或 "rank",或者將與資料表本身相同的名稱賦予欄位,都是錯誤的。這是不支援的。
欄位宣告中後續的每個字串或簡單詞都是修改該欄位行為的欄位選項。欄位選項不區分大小寫。與 SQLite 核心不同,FTS5 將無法辨識的欄位選項視為錯誤。目前唯一辨識的選項是"UNINDEXED"(見下文)。
設定選項由一個 FTS5 簡單詞(選項名稱)後接一個 "=" 字元,再後接選項值組成。選項值可以使用單個 FTS5 簡單詞或字串常值指定,同樣以任何 SQLite 核心可接受的方式引用。例如:
CREATE VIRTUAL TABLE mail USING fts5(sender, title, body, tokenize = 'porter ascii');
目前有以下設定選項:
具有 UNINDEXED 欄位選項的欄位內容不會新增至 FTS 索引。這表示就 MATCH 查詢和FTS5 輔助函式而言,該欄位不包含任何可匹配的字詞。
例如,要避免將 "uuid" 欄位的內容新增至 FTS 索引:
CREATE VIRTUAL TABLE customers USING fts5(name, addr, uuid UNINDEXED);
預設情況下,FTS5 維護單一索引,記錄文件中每個字詞例項的位置。這表示查詢完整字詞的速度很快,因為只需要一次查找,但查詢前綴字詞的速度可能會很慢,因為需要範圍掃描。例如,查詢前綴字詞 "abc*" 需要對所有大於或等於 "abc" 且小於 "abd" 的字詞進行範圍掃描。
前綴索引是一個獨立的索引,記錄特定字元長度之所有前綴字詞例項的位置,用於加速前綴字詞的查詢。例如,最佳化前綴字詞 "abc*" 的查詢需要一個三字元前綴的前綴索引。
若要將前綴索引添加到 FTS5 表格,請將 "prefix" 選項設定為單個正整數或包含一個或多個以空格分隔的正整數值的文字值。系統會為指定的每個整數建立前綴索引。如果在單個 CREATE VIRTUAL TABLE 陳述式中指定了多個 "prefix" 選項,則所有選項都會套用。
-- Two ways to create an FTS5 table that maintains prefix indexes for -- two and three character prefix tokens. CREATE VIRTUAL TABLE ft USING fts5(a, b, prefix='2 3'); CREATE VIRTUAL TABLE ft USING fts5(a, b, prefix=2, prefix=3);
CREATE VIRTUAL TABLE 的 "tokenize" 選項用於設定 FTS5 表格使用的特定分詞器。選項參數必須是 FTS5 Bareword 或 SQL 文字字面值。參數的文字本身會被視為以空格分隔的一個或多個 FTS5 Bareword 或 SQL 文字字面值的序列。第一個是所使用的分詞器名稱。第二個和後續的列表元素(如果存在)會作為參數傳遞給分詞器實作。
與選項值和欄位名稱不同,作為分詞器的 SQL 文字字面值必須使用單引號括起來。例如:
-- The following are all equivalent CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter ascii'); CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = "porter ascii"); CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = "'porter' 'ascii'"); CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = '''porter'' ''ascii'''); -- But this will fail: CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = '"porter" "ascii"'); -- This will fail too: CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter' 'ascii');
FTS5 具有四個內建分詞器模組,將在後續章節中說明。
也可以為 FTS5 建立自訂分詞器。相關 API 在此處說明。
Unicode 分詞器將所有 Unicode 字元分類為「分隔符號」或「詞彙」字元。根據 Unicode 6.1 的定義,預設情況下,所有空格和標點符號字元都被視為分隔符號,所有其他字元都被視為詞彙字元。更具體地說,所有被分配到以「L」或「N」開頭的通用類別(特別是字母和數字)或「Co」類別(「其他,私人使用」)的 Unicode 字元都被視為詞彙。所有其他字元都是分隔符號。
每個由一個或多個詞彙字元組成的連續序列都被視為一個詞彙。分詞器根據 Unicode 6.1 定義的規則不區分大小寫。
預設情況下,所有拉丁字母的變音符號都會被移除。這意味著,例如「A」、「a」、「À」、「à」、「Â」和「â」都被視為等效。
詞彙規範中「unicode61」之後的任何參數都將被視為選項名稱和值的交替列表。Unicode61 支援以下選項:
選項 | 用法 |
---|---|
remove_diacritics | 此選項應設定為「0」、「1」或「2」。預設值為「1」。如果設定為「1」或「2」,則如上所述,拉丁字母的變音符號將被移除。但是,如果設定為「1」,則在單個 Unicode 字碼點用於表示具有多個變音符號的字元的相當少見的情況下,變音符號不會被移除。例如,字碼點 0x1ED9(「帶揚抑符和下點的拉丁文小寫字母 O」)的變音符號不會被移除。這在技術上是一個錯誤,但如果沒有造成向後相容性問題,則無法修復。如果此選項設定為「2」,則所有拉丁字母的變音符號都會被正確移除。 |
categories | 此選項可用於修改被視為對應於詞彙字元的 Unicode 通用類別集合。參數必須包含以空格分隔的兩個字元通用類別縮寫列表(例如「Lu」或「Nd」),或第二個字元替換為星號(「*」)的相同列表,解讀為萬用字元模式。預設值為「L* N* Co」。 |
tokenchars | 這個選項用來指定額外的 Unicode 字元,即使根據 Unicode 6.1 它們是空白或標點符號,也應被視為詞彙字元。此選項所設定字串中的所有字元都被視為詞彙字元。 |
分隔符號 | 這個選項用來指定額外的 Unicode 字元,即使根據 Unicode 6.1 它們是詞彙字元,也應被視為分隔符號。此選項所設定字串中的所有字元都被視為分隔符號。 |
例如:
-- Create an FTS5 table that does not remove diacritics from Latin -- script characters, and that considers hyphens and underscore characters -- to be part of tokens. CREATE VIRTUAL TABLE ft USING fts5(a, b, tokenize = "unicode61 remove_diacritics 0 tokenchars '-_'" );
或
-- Create an FTS5 table that, as well as the default token character classes, -- considers characters in class "Mn" to be token characters. CREATE VIRTUAL TABLE ft USING fts5(a, b, tokenize = "unicode61 categories 'L* N* Co Mn'" );
fts5 unicode61 分詞器與 fts3/4 unicode61 分詞器位元組相容。
Ascii 分詞器與 Unicode61 分詞器類似,除了:
例如:
-- Create an FTS5 table that uses the ascii tokenizer, but does not -- consider numeric characters to be part of tokens. CREATE VIRTUAL TABLE ft USING fts5(a, b, tokenize = "ascii separators '0123456789'" );
Porter 分詞器是一個包裝分詞器。它接收其他分詞器的輸出,並在將每個詞彙返回給 FTS5 之前,對其應用 Porter 詞幹提取演算法。這允許像「correction」這樣的搜尋詞彙匹配類似的詞彙,例如「corrected」或「correcting」。Porter 詞幹提取演算法僅設計用於英語詞彙 — 將其用於其他語言可能會也可能不會提高搜尋效用。
預設情況下,Porter 分詞器作為預設分詞器 (unicode61) 的包裝器運作。或者,如果在「tokenize」選項中「porter」之後添加一個或多個額外參數,它們將被視為 Porter 詞幹提取器所使用的底層分詞器的規格。例如:
-- Two ways to create an FTS5 table that uses the porter tokenizer to -- stem the output of the default tokenizer (unicode61). CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = porter); CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter unicode61'); -- A porter tokenizer used to stem the output of the unicode61 tokenizer, -- with diacritics removed before stemming. CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'porter unicode61 remove_diacritics 1');
Trigram 分詞器擴展了 FTS5,以支援一般的子字串匹配,而不是通常的詞彙匹配。使用 Trigram 分詞器時,查詢或片語詞彙可以匹配一行中的任何字元序列,而不僅僅是完整的詞彙。例如:
CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram"); INSERT INTO tri VALUES('abcdefghij KLMNOPQRST uvwxyz'); -- The following queries all match the single row in the table SELECT * FROM tri('cdefg'); SELECT * FROM tri('cdefg AND pqr'); SELECT * FROM tri('"hij klm" NOT stuv');
Trigram 分詞器支援以下選項:
選項 | 用法 |
---|---|
case_sensitive | 此值可以設定為 1 或 0(預設值)。如果設定為 1,則匹配區分大小寫。否則,如果此選項設定為 0,則匹配不區分大小寫。 |
remove_diacritics | 此值也可以設定為 1 或 0(預設值)。它只能在 case_sensitive 選項設定為 0 時設定為 1 — 將兩個選項都設定為 1 是一個錯誤。如果設定了此選項,則在匹配之前會從文字中移除變音符號(例如,使「á」匹配「a」)。 |
-- A case-sensitive trigram index CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram case_sensitive 1");
除非設定了 remove_diacritics 選項,否則使用 Trigram 分詞器的 FTS5 表格也支援索引 GLOB 和 LIKE 模式匹配。例如:
SELECT * FROM tri WHERE a LIKE '%cdefg%'; SELECT * FROM tri WHERE a GLOB '*ij klm*xyz';
如果使用 case_sensitive 選項設定為 1 建立 FTS5 Trigram 分詞器,則它只能索引 GLOB 查詢,而不能索引 LIKE 查詢。
注意事項
通常,當一個資料列插入 FTS5 表格時,除了建立索引之外,FTS5 還會複製原始資料列內容。當使用者或輔助函式實作從 FTS5 表格請求欄位值時,這些值會從該私有內容副本中讀取。「content」選項可用於建立僅儲存 FTS 全文索引項目的 FTS5 表格。因為欄位值本身通常比相關聯的全文索引項目大得多,所以這可以節省大量的資料庫空間。
使用「content」選項有兩種方法
透過將「content」選項設定為空字串來建立無內容的 FTS5 表格。例如
CREATE VIRTUAL TABLE f1 USING fts5(a, b, c, content='');
無內容 FTS5 表格不支援 UPDATE 或 DELETE 陳述式,或是不提供資料列 ID 欄位非 NULL 值的 INSERT 陳述式。無內容表格不支援 REPLACE 衝突處理。REPLACE 和 INSERT OR REPLACE 陳述式會被視為一般的 INSERT 陳述式。可以使用 FTS5 刪除指令 從無內容表格中刪除資料列。
嘗試從無內容 FTS5 表格讀取除資料列 ID 以外的任何欄位值都會傳回 SQL NULL 值。
從 3.43.0 版開始,也提供無內容刪除表格。無內容刪除表格是透過將 content 選項設定為空字串,同時將 contentless_delete 選項設定為 1 來建立的。例如
CREATE VIRTUAL TABLE f1 USING fts5(a, b, c, content='', contentless_delete=1);
無內容刪除表格與無內容表格的不同之處在於
-- Supported UPDATE statement: UPDATE f1 SET a=?, b=?, c=? WHERE rowid=?; -- This UPDATE is not supported, as it does not supply a new value -- for column "c". UPDATE f1 SET a=?, b=? WHERE rowid=?;
除非需要向後相容性,否則新程式碼應優先使用無內容刪除表格而不是無內容表格。
外部內容 FTS5 表格是透過將 content 選項設定為同一個資料庫內的表格、虛擬表格或檢視表(以下簡稱「內容表格」)的名稱來建立的。每當 FTS5 需要欄位值時,它會以下列方式查詢內容表格,並將需要值的資料列的資料列 ID 繫結到 SQL 變數
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
在上述內容中,<content> 會被內容表格的名稱取代。預設情況下,<content_rowid> 會被字面文字「rowid」取代。或者,如果在 CREATE VIRTUAL TABLE 陳述式中設定了「content_rowid」選項,則會被該選項的值取代。<cols> 會被以逗號分隔的 FTS5 表格欄位名稱清單取代。例如
-- If the database schema is: CREATE TABLE tbl (a, b, c, d INTEGER PRIMARY KEY); CREATE VIRTUAL TABLE fts USING fts5(a, c, content=tbl, content_rowid=d); -- Fts5 may issue queries such as: SELECT d, a, c FROM tbl WHERE d = ?;
也可以以下列方式查詢內容表格
SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> ASC; SELECT <content_rowid>, <cols> FROM <content> ORDER BY <content_rowid> DESC;
使用者仍有責任確保外部內容 FTS5 表格的內容與內容表格保持同步。一種方法是使用觸發器。例如:
-- Create a table. And an external content fts5 table to index it. CREATE TABLE tbl(a INTEGER PRIMARY KEY, b, c); CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='tbl', content_rowid='a'); -- Triggers to keep the FTS index up to date. CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c); END; CREATE TRIGGER tbl_ad AFTER DELETE ON tbl BEGIN INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c); END; CREATE TRIGGER tbl_au AFTER UPDATE ON tbl BEGIN INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c); INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c); END;
與無內容表格類似,外部內容表格不支援 REPLACE 衝突處理。任何指定 REPLACE 衝突處理的操作都會使用 ABORT 處理。
使用者有責任確保 FTS5 外部內容表格(具有非空 content= 選項的表格)與內容表格本身(由 content= 選項指定的表格)保持一致。如果允許它們不一致,則針對 FTS5 表格的查詢結果可能會變得不直觀且看起來不一致。
在這些情況下,針對 FTS5 外部內容表格的查詢產生的明顯不一致的結果可以理解如下:
如果查詢不使用全文索引(不包含 MATCH 運算子或等效的表值函數語法),則查詢實際上會傳遞到外部內容表格。在這種情況下,FTS 索引的內容不會影響查詢結果。
如果查詢確實使用全文索引,則 FTS5 模組會在索引中查詢與查詢匹配的文件對應的 rowid 值集合。對於每個這樣的 rowid,它會執行類似以下的查詢來檢索任何所需的欄位值,其中「?」會被 rowid 值替換,<content> 和 <content_rowid> 會被 content= 和 content_rowid= 選項指定的值替換。
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
例如,如果使用以下指令碼建立資料庫:
-- Create and populate a table. CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT); INSERT INTO tbl VALUES(1, 'all that glitters'); INSERT INTO tbl VALUES(2, 'is not gold'); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a');
則內容表格包含兩列,但 FTS 索引不包含對應的項目。在這種情況下,以下查詢將返回不一致的結果,如下所示:
-- Returns 2 rows. Because the query does not use the FTS index, it is -- effectively executed against table 'tbl' directly, and so returns -- both rows. SELECT * FROM t1; -- Returns 0 rows. This query does use the FTS index, which currently -- contains no entries. So it returns 0 rows. SELECT rowid, t FROM t1('gold')
或者,如果資料庫的建立和填充方式如下:
-- Create and populate a table. CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a'); INSERT INTO ft(rowid, t) VALUES(1, 'all that glitters'); INSERT INTO ft(rowid, t) VALUES(2, 'is not gold');
則內容表格為空,但 FTS 索引包含 6 個不同詞彙的項目。在這種情況下,以下查詢將返回不一致的結果,如下所示:
-- Returns 0 rows. Since it does not use the FTS index, the query is -- passed directly through to table 'tbl', which contains no data. SELECT * FROM t1; -- Returns 1 row. The "rowid" field of the returned row is 2, and -- the "t" field set to NULL. "t" is set to NULL because when the external -- content table "tbl" was queried for the data associated with the row -- with a=2 ("a" is the content_rowid column), none could be found. SELECT rowid, t FROM t1('gold')
如上一節所述,內容表格上的觸發器是確保 FTS5 外部內容表格保持一致的好方法。但是,僅當在內容表格中插入、更新或刪除列時才會觸發觸發器。這表示,例如,如果資料庫的建立方式如下:
-- Create and populate a table. CREATE TABLE tbl(a INTEGER PRIMARY KEY, t TEXT); INSERT INTO tbl VALUES(1, 'all that glitters'); INSERT INTO tbl VALUES(2, 'is not gold'); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='tbl', content_rowid='a'); -- Create triggers to keep the FTS5 table up to date CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN INSERT INTO ft(rowid, t) VALUES (new.a, new.t); END; <similar triggers for update + delete>
則內容表格和外部內容 FTS5 表格不一致,因為建立觸發器不會將現有列從內容表格複製到 FTS 索引。觸發器只能確保在建立觸發器後對內容表格所做的更新反映在 FTS 索引中。
在這種情況下,以及任何其他 FTS 索引及其內容表格不一致的情況下,可以使用 「重建」 命令完全捨棄 FTS 索引的內容,並根據內容表格的當前內容重建它。
通常,FTS5 會在資料庫中維護一個特殊的後備表格,該表格將插入主 FTS5 表格的每個欄位值的詞彙數量儲存在一個單獨的表格中。此後備表格由 xColumnSize API 函數使用,而該函數又由內建的 bm25 排名函數 使用(並且可能對其他排名函數也很有用)。
為了節省空間,可以透過將 columnsize 選項設定為零來省略此後備表格。例如:
-- A table without the xColumnSize() values stored on disk: CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=0); -- Three equivalent ways of creating a table that does store the -- xColumnSize() values on disk: CREATE VIRTUAL TABLE ft USING fts5(a, b, c); CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=1); CREATE VIRTUAL TABLE ft USING fts5(a, b, columnsize='1', c);
將 columnsize 選項設定為 0 或 1 以外的任何值都是錯誤的。
如果一個 FTS5 資料表設定為 columnsize=0 但不是無內容資料表,xColumnSize API 函式仍然可以運作,但執行速度會慢很多。在這種情況下,它不是直接從資料庫讀取要返回的值,而是讀取文字值本身並根據需要計算其中的詞彙數。
或者,如果資料表同時也是無內容資料表,則適用以下情況:
xColumnSize API 一律返回 -1。無法確定儲存在設定為 columnsize=0 的無內容 FTS5 資料表中值的詞彙數。
插入的每一列都必須伴隨一個明確指定的 rowid 值。如果無內容資料表設定為 columnsize=0,則嘗試在 rowid 中插入 NULL 值會導致 SQLITE_MISMATCH 錯誤。
所有對資料表的查詢都必須是全文檢索查詢。換句話說,它們必須使用 MATCH 或 = 運算子,並以資料表名稱欄位作為左側運算元,或者使用資料表值函式語法。任何非全文檢索查詢都會導致錯誤。
儲存 xColumnSize 值的資料表名稱(除非指定 columnsize=0)是「<name>_docsize」,其中 <name> 是 FTS5 資料表本身的名稱。可以使用 sqlite3_analyzer 工具分析現有資料庫,以確定使用 columnsize=0 重新建立 FTS5 資料表可以節省多少空間。
對於文件中的每個詞彙,FTS5 維護的 FTS 索引會儲存文件的 rowid、包含該詞彙的欄位號以及該詞彙在欄位值中的偏移量。「detail」選項可用於省略部分資訊。這可以減少索引在資料庫檔案中佔用的空間,但也會降低系統的功能和效率。
detail 選項可以設定為「full」(預設值)、「column」或「none」。例如:
-- The following two lines are equivalent (because the default value -- of "detail" is "full". CREATE VIRTUAL TABLE ft1 USING fts5(a, b, c); CREATE VIRTUAL TABLE ft1 USING fts5(a, b, c, detail=full); CREATE VIRTUAL TABLE ft2 USING fts5(a, b, c, detail=column); CREATE VIRTUAL TABLE ft3 USING fts5(a, b, c, detail=none);
如果 detail 選項設定為 column,則對於每個詞彙,FTS 索引只記錄 rowid 和欄位號,省略詞彙偏移量資訊。這會導致以下限制:
如果 detail 選項設定為 none,則對於每個詞彙,FTS 索引只記錄 rowid。欄位和偏移量資訊都會被省略。除了 detail=column 模式下的限制之外,這還會施加以下額外限制:
在一個索引大量電子郵件(磁碟上 1636 MiB)的測試中,detail=full 時 FTS 索引在磁碟上佔用 743 MiB,detail=column 時佔用 340 MiB,detail=none 時佔用 134 MiB。
此選項僅對實現自訂分詞器的應用程式有用。通常,分詞器可能會返回由任何位元組序列組成的詞彙,包括 0x00 位元組。但是,如果表格指定了 tokendata=1 選項,則 fts5 會忽略第一個 0x00 位元組以及詞彙中用於比對的任何尾隨數據。它仍然會儲存分詞器返回的整個詞彙,但 fts5 核心會忽略它。
完整的詞彙版本,包括任何 0x00 位元組和尾隨數據,可透過 xQueryToken 和 xInstToken API 提供給自訂輔助函式。
這可能對排序函式很有用。自訂分詞器可以將額外數據添加到某些文件詞彙中,允許排序函式對某些詞彙(例如文件標題中的詞彙)的匹配賦予更高的權重。
或者,可以結合使用自訂分詞器和自訂輔助函式來實現非對稱搜尋。例如,分詞器可以針對每個文件詞彙返回詞彙的大小寫正規化和未標記版本,後跟一個 0x00 位元組,再後跟文件中詞彙的完整文本。查詢時,fts5 會提供結果,如同查詢中的所有字元都經過大小寫正規化且未標記。然後,可以在查詢的 WHERE 子句中使用自訂輔助函式,以根據文件或查詢詞彙中的次要或三級標記篩選出不匹配的任何列。
輔助函式類似於SQL 純量函式,不同之處在於它們只能在 FTS5 表格的全文查詢(使用 MATCH 運算子或帶有三元分詞器的 LIKE/GLOB 的查詢)中使用。它們的結果不僅基於傳遞給它們的引數,還基於目前的匹配和匹配列。例如,輔助函式可能會返回一個數值,指示匹配的準確性(請參閱 bm25() 函式),或者返回匹配列中包含一個或多個搜尋詞彙實例的文本片段(請參閱 snippet() 函式)。
要呼叫輔助函式,應將 FTS5 表格的名稱指定為第一個引數。其他引數可以跟在第一個引數之後,具體取決於所呼叫的特定輔助函式。例如,要呼叫「highlight」函式
SELECT highlight(email, 2, '<b>', '</b>') FROM email WHERE email MATCH 'fts5'
FTS5 提供的內建輔助函式將在下一節中介紹。應用程式也可以使用 C 語言實現自訂輔助函式。
FTS5 提供三個內建輔助函式
內建的輔助函數 bm25() 會傳回一個實數值,表示目前列與全文檢索查詢的匹配程度。匹配程度越高,傳回的數值越小。可以使用如下查詢,按照匹配程度由最佳到最差的順序傳回匹配結果:
SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts)
為了計算文件的得分,全文檢索查詢會被拆分成其組成片語。文件 D 和查詢 Q 的 bm25 分數計算如下:
在上述公式中,nPhrase 是查詢中片語的數量。|D| 是目前文件中詞元的數量,而 avgdl 是 FTS5 表中所有文件的平均詞元數量。k1 和 b 都是常數,分別硬編碼為 1.2 和 0.75。
公式開頭的「-1」項在大部分 BM25 演算法的實作中並不存在。如果沒有它,匹配程度越高,BM25 分數數值就越高。由於預設的排序順序是「升序」,這表示在查詢後面加上「ORDER BY bm25(fts)」會導致結果以匹配程度由最差到最佳的順序傳回。為了傳回最佳匹配,需要使用「DESC」關鍵字。為了避免這個陷阱,FTS5 實作的 BM25 在傳回結果之前將其乘以 -1,確保匹配程度越高,分數數值越低。
IDF(qi) 是查詢片語 i 的逆向文件頻率。其計算方式如下,其中 N 是 FTS5 表中的總列數,而 n(qi) 是包含至少一個片語 i 範例的總列數:
最後,f(qi,D) 是片語 i 的片語頻率。預設情況下,這就是目前列中片語出現的次數。然而,透過傳遞額外的實數值參數給 bm25() SQL 函數,可以為表格的每一欄指定不同的權重,並計算片語頻率,如下所示:
其中 wc 是指定給欄 c 的權重,而 n(qi,c) 是片語 i 在目前列的欄 c 中出現的次數。傳遞給 bm25() 的第一個參數(表格名稱之後)是指定給 FTS5 表最左邊欄的權重。第二個參數是指定給第二個最左邊欄的權重,依此類推。如果參數數量不足以涵蓋所有表格欄,則剩餘的欄會被指定權重 1.0。如果尾端參數過多,則多餘的參數會被忽略。例如:
-- Assuming the following schema: CREATE VIRTUAL TABLE email USING fts5(sender, title, body); -- Return results in bm25 order, with each phrase hit in the "sender" -- column considered the equal of 10 hits in the "body" column, and -- each hit in the "title" column considered as valuable as 5 hits in -- the "body" column. SELECT * FROM email WHERE email MATCH ? ORDER BY bm25(email, 10.0, 5.0);
有關 BM25 及其變體的更多資訊,請參閱維基百科 BM25 資訊。
highlight() 函數會傳回目前列指定欄位文字的副本,並插入額外的標記文字,以標記片語匹配的開始和結束。
呼叫 highlight() 時,表格名稱之後必須加上三個參數,其解釋如下:
例如:
-- Return a copy of the text from the leftmost column of the current -- row, with phrase matches marked using html "b" tags. SELECT highlight(fts, 0, '<b>', '</b>') FROM fts WHERE fts MATCH ?
如果兩個或多個片語範例重疊(共用一個或多個詞元),則會為每組重疊的片語插入一個開始和結束標記。例如:
-- Assuming this: CREATE VIRTUAL TABLE ft USING fts5(a); INSERT INTO ft VALUES('a b c x c d e'); INSERT INTO ft VALUES('a b c c d e'); INSERT INTO ft VALUES('a b c d e'); -- The following SELECT statement returns these three rows: -- '[a b c] x [c d e]' -- '[a b c] [c d e]' -- '[a b c d e]' SELECT highlight(ft, 0, '[', ']') FROM ft WHERE ft MATCH 'a+b+c AND c+d+e';
snippet() 函數與 highlight() 類似,不同之處在於它不會傳回整個欄位值,而是自動選擇並擷取一小段文件文字進行處理並傳回。在表格名稱參數之後,必須傳遞五個參數給 snippet() 函數:
所有 FTS5 表格都具有一個名為「rank」的特殊隱藏欄位。如果目前的查詢不是全文檢索查詢(即,如果不包含 MATCH 運算子),則「rank」欄位的值始終為 NULL。否則,在全文檢索查詢中,rank 欄位預設包含與執行不帶尾隨引數的 bm25() 輔助函式所返回的值相同的值。
從 rank 欄位讀取與直接在查詢中使用 bm25() 函式之間的差異僅在按回傳值排序時才顯著。在這種情況下,使用「rank」比使用 bm25() 更快。
-- The following queries are logically equivalent. But the second may -- be faster, particularly if the caller abandons the query before -- all rows have been returned (or if the queries were modified to -- include LIMIT clauses). SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts); SELECT * FROM fts WHERE fts MATCH ? ORDER BY rank;
可以針對每個查詢設定對應到 rank 欄位的特定輔助函式,或者為 FTS 表格設定不同的持久預設值,而不是使用不帶尾隨引數的 bm25()。
為了更改單個查詢的 rank 欄位的對應,會將類似於以下任一項的術語添加到查詢的 WHERE 子句中
rank MATCH 'auxiliary-function-name(arg1, arg2, ...)' rank = 'auxiliary-function-name(arg1, arg2, ...)'
MATCH 或 = 運算子的右側必須是常數表達式,其計算結果為一個字串,該字串由要呼叫的輔助函式組成,後跟括號中的零個或多個以逗號分隔的引數。引數必須是 SQL 字面值。例如
-- The following queries are logically equivalent. But the second may -- be faster. See above. SELECT * FROM fts WHERE fts MATCH ? ORDER BY bm25(fts, 10.0, 5.0); SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank;
也可以使用表值函式語法來指定替代排名函式。在這種情況下,應將描述排名函式的文字指定為第二個表值函式引數。以下三個查詢是等效的
SELECT * FROM fts WHERE fts MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM fts WHERE fts = ? AND rank = 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM fts WHERE fts(?, 'bm25(10.0, 5.0)') ORDER BY rank;
可以使用 FTS5 rank 設定選項 修改表格的 rank 欄位的預設對應。
FTS5 使用一系列 B 樹,而不是使用磁碟上的單個資料結構來儲存全文檢索索引。每次提交新的交易時,都會將包含已提交交易內容的新 B 樹寫入資料庫檔案。查詢全文檢索索引時,必須單獨查詢每個 B 樹,並在將結果返回給使用者之前合併結果。
為了防止資料庫中 B 樹的數量變得太大(減慢查詢速度),會定期將較小的 B 樹合併成包含相同資料的單個較大 B 樹。預設情況下,這會在修改全文檢索索引的 INSERT、UPDATE 或 DELETE 陳述式中自動發生。「automerge」參數決定一次合併多少個較小的 B 樹。將其設定為較小的值可以加快查詢速度(因為它們必須查詢和合併來自較少 B 樹的結果),但也可能會減慢寫入資料庫的速度(因為每個 INSERT、UPDATE 或 DELETE 陳述式都必須作為自動合併過程的一部分執行更多工作)。
構成全文檢索索引的每個 B 樹都根據其大小分配到一個「級別」。級別 0 的 B 樹最小,因為它們包含單個交易的內容。更高級別的 B 樹是將兩個或多個級別 0 的 B 樹合併在一起的結果,因此它們更大。一旦存在 *M* 個或更多具有相同級別的 B 樹,FTS5 就會開始將 B 樹合併在一起,其中 *M* 是「automerge」參數的值。
「automerge」參數的最大允許值為 16。預設值為 4。將「automerge」參數設定為 0 會完全停用 b 樹的自動增量合併。
INSERT INTO ft(ft, rank) VALUES('automerge', 8);
「crisismerge」選項與「automerge」類似,它決定組成全文索引的元件 b 樹如何以及多久合併一次。一旦全文索引中單一層級存在 C 個或更多 b 樹,其中 C 是「crisismerge」選項的值,則該層級上的所有 b 樹將立即合併成單個 b 樹。
此選項與「automerge」選項的區別在於,當達到「automerge」限制時,FTS5 只會開始合併 b 樹。大部分的工作是在後續的 INSERT、UPDATE 或 DELETE 操作中執行的。而當達到「crisismerge」限制時,違規的 b 樹會立即全部合併。這意味著觸發危機合併的 INSERT、UPDATE 或 DELETE 操作可能需要很長時間才能完成。
預設的「crisismerge」值為 16。沒有最大限制。嘗試將「crisismerge」參數設定為 0 或 1 等同於將其設定為預設值 (16)。嘗試將「crisismerge」選項設定為負值是錯誤的。
INSERT INTO ft(ft, rank) VALUES('crisismerge', 16);
此命令僅適用於外部內容和無內容表格。它用於從全文索引中刪除與單一行關聯的索引條目。此命令和delete-all命令是從無內容表格的全文索引中移除條目的唯一方法。
為了使用此命令刪除一行,文字值「delete」必須插入到與表格同名的特殊欄位中。要刪除的行的 rowid 會插入到 rowid 欄位中。插入到其他欄位中的值必須與目前儲存在表格中的值相符。例如
-- Insert a row with rowid=14 into the fts5 table. INSERT INTO ft(rowid, a, b, c) VALUES(14, $a, $b, $c); -- Remove the same row from the fts5 table. INSERT INTO ft(ft, rowid, a, b, c) VALUES('delete', 14, $a, $b, $c);
如果作為「delete」命令的一部分「插入」到文字欄位中的值與目前儲存在表格中的值不同,則結果可能無法預測。
其原因很容易理解:當文件插入到 FTS5 表格中時,會在全文索引中新增一個條目,以記錄每個詞彙在新文件中出現的位置。刪除文件時,需要原始資料來決定需要從全文索引中移除的條目集合。因此,如果使用此命令刪除一行時提供給 FTS5 的資料與插入時用於決定詞彙實例集合的資料不同,則某些全文索引條目可能無法正確刪除,或者 FTS5 可能嘗試移除不存在的索引條目。這可能會使全文索引處於不可預測的狀態,從而使未來的查詢結果不可靠。
此命令僅適用於外部內容和無內容表格(包括無內容刪除表格)。它會刪除全文索引中的所有條目。
INSERT INTO ft(ft) VALUES('delete-all');
「deletemerge」選項僅由無內容刪除表格使用。
從無內容刪除表格中刪除一行時,與其詞彙關聯的條目不會立即從 FTS 索引中移除。相反地,包含已刪除行的 rowid 的「墓碑」標記會附加到包含該行 FTS 索引條目的 b 樹。查詢 b 樹時,存在墓碑標記的任何查詢結果行都會從結果中省略。當 b 樹與其他 b 樹合併時,已刪除的行及其墓碑標記都會被捨棄。
此選項指定 B 樹中必須具有刪除標記的行數的最小百分比,才能使 B 樹符合合併條件 - 無論是透過自動合併還是明確的使用者「合併」命令 - 即使它不符合由「automerge」和「usermerge」選項決定的通常條件。
例如,要指定 FTS5 在其 15% 的行具有關聯的刪除標記後才考慮合併元件 B 樹
INSERT INTO ft(ft, rank) VALUES('deletemerge', 15);
此選項的預設值為 10。嘗試將其設定為小於零會恢復預設值。將此選項設定為 0 或大於 100 可確保 B 樹永遠不會因為刪除標記而符合合併條件。
此命令用於驗證全文索引內部一致,以及(可選)與任何外部內容表格一致。
透過將文字值「integrity-check」插入與 FTS5 表格同名的特殊欄位來呼叫 integrity-check 命令。如果為「rank」欄位提供值,則該值必須為 0 或 1。例如
INSERT INTO ft(ft) VALUES('integrity-check'); INSERT INTO ft(ft, rank) VALUES('integrity-check', 0); INSERT INTO ft(ft, rank) VALUES('integrity-check', 1);
以上三種形式對於所有非外部內容表格的 FTS 表格都是等效的。它們會檢查索引資料結構是否損壞,以及如果 FTS 表格不是無內容表格,則索引的內容是否與表格本身的內容相符。
對於外部內容表格,僅當為 rank 欄位指定的值為 1 時,才會將索引的內容與外部內容表格的內容進行比較。
在所有情況下,如果發現任何差異,命令將會失敗並顯示SQLITE_CORRUPT_VTAB錯誤。
INSERT INTO ft(ft, rank) VALUES('merge', 500);
此命令會將 B 樹結構合併在一起,直到大約 N 頁的合併資料寫入資料庫,其中 N 是作為「合併」命令的一部分指定的參數的絕對值。每個頁面的大小由FTS5 pgsz 選項配置。
如果參數為正值,則只有在滿足以下條件之一時,B 樹結構才符合合併條件:
可以透過檢查在執行命令前後sqlite3_total_changes() API 返回的值來判斷「合併」命令是否找到任何要合併的 B 樹。如果兩個值之間的差值為 2 或更大,則表示已執行工作。如果差值小於 2,則「合併」命令為無效操作。在這種情況下,沒有理由再次執行相同的「合併」命令,至少在下次更新 FTS 表格之前是如此。
如果參數為負值,並且 FTS 索引中有多個層級的 B 樹結構,則在開始合併操作之前,所有 B 樹結構都會被分配到同一層級。此外,如果參數為負值,則 usermerge 設定選項的值將不被遵守 - 來自同一層級的兩個 B 樹可能會被合併在一起。
上述說明表示,以負參數執行 'merge' 指令,直到 sqlite3_total_changes() 回傳值的差異小於二之前,與 FTS5 optimize 指令 以相同方式最佳化 FTS 索引。然而,如果在此過程中新增了一個新的 b-tree 至 FTS 索引,FTS5 會將新的 b-tree 移至與現有 b-tree 相同的層級,並重新啟動合併。為避免此情況,只有第一次呼叫 'merge' 時應指定負參數。後續每次呼叫 'merge' 都應指定正值,以便即使新增了新的 b-tree 至 FTS 索引,也能完成第一次呼叫所啟動的合併。
此指令將目前組成全文索引的所有個別 b-tree 合併成單一的大型 b-tree 結構。這可確保全文索引在資料庫中佔用最小空間,並以最快的形式進行查詢。
有關全文索引及其組成 b-tree 之間關係的更多詳細資訊,請參閱 FTS5 automerge 選項 的說明文件。
INSERT INTO ft(ft) VALUES('optimize');
由於 optimize 指令會重新組織整個 FTS 索引,因此可能需要很長時間才能執行完畢。可以使用 FTS5 merge 指令 將最佳化 FTS 索引的工作劃分為多個步驟。方法如下:
其中 N 是每次呼叫 merge 指令時要合併的資料頁數。當 merge 指令執行前後 sqlite3_total_changes() 函式回傳值的差異下降到小於二時,應用程式應停止呼叫 merge。merge 指令可以在相同或不同的交易中發出,也可以由相同或不同的資料庫用戶端發出。有關更多詳細資訊,請參閱 merge 指令 的說明文件。
此指令用於設定永久性的「pgsz」選項。
FTS5 維護的全文索引儲存在資料庫表格中一系列固定大小的 Blob。組成全文索引的所有 Blob 不一定需要大小相同。pgsz 選項決定後續索引寫入器建立的所有 Blob 的大小。預設值為 1000。
INSERT INTO ft(ft, rank) VALUES('pgsz', 4072);
此指令用於設定永久性的「rank」選項。
rank 選項用於變更 rank 欄位的預設輔助函式對應。此選項應設定為與上述 「rank MATCH ?」 詞彙所述相同格式的文字值。例如:
INSERT INTO ft(ft, rank) VALUES('rank', 'bm25(10.0, 5.0)');
此指令會先刪除整個全文索引,然後根據表格或 內容表格 的內容重建它。此指令不適用於 無內容表格。
INSERT INTO ft(ft) VALUES('rebuild');
此指令用於設定永久性的布林值「secure-delete」選項。例如:
INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
一般情況下,當 fts5 表格中的條目被更新或刪除時,並不會從全文索引中移除條目,而是將刪除鍵添加到事務創建的新 B 樹中。這樣做很有效率,但也意味著舊的全文索引條目會保留在資料庫檔案中,直到最終透過全文索引的合併操作移除。任何有權存取資料庫的人都可以使用這些條目輕鬆地重建已刪除 FTS5 表格列的內容。但是,如果將「secure-delete」選項設為 1,則在更新或刪除現有 FTS5 表格列時,全文條目會實際從資料庫中移除。這樣做速度較慢,但可以防止舊的全文條目被用於重建已刪除的表格列。
此選項可確保具有 SQL 資料庫存取權限的攻擊者無法取得舊的全文條目。為了確保具有 SQLite 資料庫檔案本身存取權限的攻擊者也無法恢復這些條目,應用程式還必須使用類似 「PRAGMA secure_delete = 1」 的指令啟用 SQLite 核心的安全刪除選項。
警告:一旦設定此選項後更新或刪除了一個或多個表格列,則低於 3.42.0 版本(此選項首次提供的版本)的任何 FTS5 版本都將無法讀取或寫入該 FTS5 表格。嘗試這樣做會導致錯誤,並顯示類似「無效的 fts5 檔案格式(找到 5,預期為 4) - 請執行 'rebuild'」的錯誤訊息。可以使用 3.42.0 或更高版本在表格上執行「rebuild」指令來還原 FTS5 檔案格式,以便較舊版本的 FTS5 可以讀取。
secure-delete 選項的預設值為 0。
此指令用於設定永久性的「usermerge」選項。
usermerge 選項類似於 automerge 和 crisismerge 選項。它是帶有正參數的「merge」指令將合併在一起的 B 樹區段的最小數量。例如:
INSERT INTO ft(ft, rank) VALUES('usermerge', 4);
usermerge 選項的預設值為 4。允許的最小值為 2,最大值為 16。
FTS5 提供 API 允許透過以下方式進行擴展:
本文中描述的內建斷詞器和輔助函式都是使用以下公開的 API 實作的。
在向 FTS5 註冊新的輔助函式或斷詞器實作之前,應用程式必須取得指向「fts5_api」結構的指標。每個註冊了 FTS5 擴充功能的資料庫連線都有一個 fts5_api 結構。為了取得指標,應用程式會使用單一參數呼叫 SQL 使用者自訂函式 fts5()。該參數必須使用 sqlite3_bind_pointer() 介面設定為指向 fts5_api 物件指標的指標。以下範例程式碼示範了此技術:
/* ** Return a pointer to the fts5_api pointer for database connection db. ** If an error occurs, return NULL and leave an error in the database ** handle (accessible using sqlite3_errcode()/errmsg()). */ fts5_api *fts5_api_from_db(sqlite3 *db){ fts5_api *pRet = 0; sqlite3_stmt *pStmt = 0; if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){ sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL); sqlite3_step(pStmt); } sqlite3_finalize(pStmt); return pRet; }
回溯相容性警告:在 SQLite 3.20.0 版(2017-08-01)之前,fts5() 的運作方式略有不同。必須修改使用舊技術擴展 FTS5 的舊版應用程式,以使用上述的新技術。
fts5_api 結構的定義如下。它公開了三個方法,分別用於註冊新的輔助函式和斷詞器,以及用於擷取現有的斷詞器。後者旨在促進類似內建 porter 斷詞器的「斷詞器包裝器」的實作。
typedef struct fts5_api fts5_api; struct fts5_api { int iVersion; /* Currently always set to 2 */ /* Create a new tokenizer */ int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); /* Find an existing tokenizer */ int (*xFindTokenizer)( fts5_api *pApi, const char *zName, void **ppUserData, fts5_tokenizer *pTokenizer ); /* Create a new auxiliary function */ int (*xCreateFunction)( fts5_api *pApi, const char *zName, void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); };
要呼叫 fts5_api 物件的方法,應將 fts5_api 指標本身作為方法的第一個參數傳遞,然後再傳遞其他特定於方法的參數。例如:
rc = pFts5Api->xCreateTokenizer(pFts5Api, ... other args ...);
以下章節將分別說明 fts5_api 結構的方法。
要建立自訂斷詞器,應用程式必須實作三個函式:斷詞器建構函式 (xCreate)、解構函式 (xDelete) 以及執行實際斷詞的函式 (xTokenize)。每個函式的類型與 fts5_tokenizer 結構的成員變數相同。
typedef struct Fts5Tokenizer Fts5Tokenizer; typedef struct fts5_tokenizer fts5_tokenizer; struct fts5_tokenizer { int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); void (*xDelete)(Fts5Tokenizer*); int (*xTokenize)(Fts5Tokenizer*, void *pCtx, int flags, /* Mask of FTS5_TOKENIZE_* flags */ const char *pText, int nText, int (*xToken)( void *pCtx, /* Copy of 2nd argument to xTokenize() */ int tflags, /* Mask of FTS5_TOKEN_* flags */ const char *pToken, /* Pointer to buffer containing token */ int nToken, /* Size of token in bytes */ int iStart, /* Byte offset of token within input text */ int iEnd /* Byte offset of end of token within input text */ ) ); }; /* Flags that may be passed as the third argument to xTokenize() */ #define FTS5_TOKENIZE_QUERY 0x0001 #define FTS5_TOKENIZE_PREFIX 0x0002 #define FTS5_TOKENIZE_DOCUMENT 0x0004 #define FTS5_TOKENIZE_AUX 0x0008 /* Flags that may be passed by the tokenizer implementation back to FTS5 ** as the third argument to the supplied xToken callback. */ #define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */
透過呼叫 fts5_api 物件的 xCreateTokenizer() 方法,將實作註冊到 FTS5 模組。如果已存在同名斷詞器,則會將其取代。如果傳遞非 NULL 的 xDestroy 參數給 xCreateTokenizer(),則在關閉資料庫控制代碼或取代斷詞器時,會以傳遞的 pUserData 指標副本作為唯一參數呼叫 xDestroy。
如果成功,xCreateTokenizer() 會回傳 SQLITE_OK。否則,它會回傳 SQLite 錯誤碼。在這種情況下,不會呼叫 xDestroy 函式。
當 FTS5 資料表使用自訂斷詞器時,FTS5 核心會呼叫 xCreate() 一次以建立斷詞器,然後呼叫 xTokenize() 零次或多次以斷詞字串,最後呼叫 xDelete() 以釋放 xCreate() 配置的任何資源。更具體地說:
此函式用於配置和初始化斷詞器實例。實際斷詞需要斷詞器實例。
傳遞給此函式的第一個參數是應用程式在使用 FTS5 註冊 fts5_tokenizer 物件時提供的 (void*) 指標副本(xCreateTokenizer() 的第三個參數)。第二個和第三個參數是一個以 null 結尾的字串陣列,其中包含斷詞器參數(如果有的話),這些參數在建立 FTS5 資料表的 CREATE VIRTUAL TABLE 陳述式中,於斷詞器名稱之後指定。
最後一個參數是一個輸出變數。如果成功,(*ppOut) 應設定為指向新的斷詞器控制代碼,並回傳 SQLITE_OK。如果發生錯誤,應回傳 SQLITE_OK 以外的值。在這種情況下,fts5 假設 *ppOut 的最終值未定義。
此函式用於刪除先前使用 xCreate() 配置的斷詞器控制代碼。Fts5 保證每次成功呼叫 xCreate() 後,都會精確地呼叫此函式一次。
此函式應將參數 pText 指示的 nText 位元組字串斷詞。pText 可能以 null 結尾,也可能未以 null 結尾。傳遞給此函式的第一個參數是指向先前呼叫 xCreate() 所回傳的 Fts5Tokenizer 物件的指標。
第二個參數指示 FTS5 請求斷詞所提供文字的原因。此參數始終是以下四個值之一:
對於輸入字串中的每個詞彙,必須呼叫提供的回呼函數 xToken()。傳遞給它的第一個參數應該是傳遞給 xTokenize() 作為第二個參數的指標的副本。第三和第四個參數是指向包含詞彙文本的緩衝區的指標,以及詞彙的大小(以位元組為單位)。第四和第五個參數是詞彙來源文本在輸入中第一個位元組和緊接第一個位元組之後的位元組偏移量。
傳遞給 xToken() 回呼函數的第二個參數("tflags")通常應設定為 0。例外情況是如果斷詞器支援同義詞。在這種情況下,請參閱下面的討論以了解詳細資訊。
FTS5 假設會針對輸入文字中出現的每個詞彙,依序呼叫 xToken() 回呼函數。
如果 xToken() 回呼函數返回 SQLITE_OK 以外的任何值,則應放棄斷詞,並且 xTokenize() 方法應立即返回 xToken() 返回值的副本。或者,如果輸入緩衝區已耗盡,xTokenize() 應返回 SQLITE_OK。最後,如果 xTokenize() 實作本身發生錯誤,它可能會放棄斷詞並返回 SQLITE_OK 或 SQLITE_DONE 以外的任何錯誤代碼。
自訂斷詞器也可以支援同義詞。考慮使用者希望查詢諸如「第一名」之類的片語的情況。使用內建斷詞器,FTS5 查詢 'first + place' 將會比對文件集中「第一名」的實例,但不會比對諸如「1st place」之類的替代形式。在某些應用程式中,最好比對所有「第一名」或「1st place」的實例,無論使用者在 MATCH 查詢文字中指定哪種形式。
在 FTS5 中有幾種方法可以解決這個問題
... MATCH 'first place'
斷詞器會提供「1st」和「first」作為 MATCH 查詢中第一個詞彙的同義詞,而 FTS5 會有效地執行類似以下的查詢
... MATCH '(first OR 1st) place'
不同的是,就輔助函數而言,查詢似乎仍然只包含兩個片語 - 「(first OR 1st)」被視為單個片語。
這樣,即使斷詞器在斷詞查詢文字時沒有提供同義詞(它不應該這樣做 - 這樣做效率很低),使用者查詢 'first + place' 或 '1st + place' 也沒有關係,因為 FTS 索引中存在對應於兩種形式的第一個詞彙的項目。
無論是剖析文件還是查詢文字,任何指定帶有 FTS5_TOKEN_COLOCATED 位元的 *tflags* 參數的 xToken 呼叫都被視為提供前一個詞彙的同義詞。例如,在剖析文件「I won first place」時,支援同義詞的斷詞器會呼叫 xToken() 5 次,如下所示
xToken(pCtx, 0, "i", 1, 0, 1); xToken(pCtx, 0, "won", 3, 2, 5); xToken(pCtx, 0, "first", 5, 6, 11); xToken(pCtx, FTS5_TOKEN_COLOCATED, "1st", 3, 6, 11); xToken(pCtx, 0, "place", 5, 12, 17);
第一次呼叫 xToken() 時指定 FTS5_TOKEN_COLOCATED 旗標是錯誤的。可以透過依序多次呼叫 xToken(FTS5_TOKEN_COLOCATED) 來為單個詞彙指定多個同義詞。單個詞彙可提供的同義詞數量沒有限制。
在許多情況下,上述方法 (1) 是最佳方法。它不會向 FTS 索引新增額外資料,也不需要 FTS5 查詢多個詞彙,因此在磁碟空間和查詢速度方面都很有效率。然而,它不太支援前綴查詢。如果如同上文建議,詞彙分析器將詞彙「first」替換為「1st」,則查詢
... MATCH '1s*'
將不會匹配包含詞彙「1st」的文件(因為詞彙分析器可能不會將「1s」映射到「first」的任何前綴)。
為了完整支援前綴,方法 (3) 可能更佳。在這種情況下,因為索引包含「first」和「1st」的項目,所以像 'fi*' 或 '1s*' 這樣的詞彙前綴查詢將能正確匹配。然而,由於向 FTS 索引添加了額外的項目,此方法會使用更多資料庫空間。
方法 (2) 提供了 (1) 和 (3) 之間的折衷方案。使用此方法,像 '1s*' 這樣的查詢將匹配包含字面詞彙「1st」的文件,但不匹配「first」(假設詞彙分析器無法為前綴提供同義詞)。然而,像 '1st' 這樣的非前綴查詢將匹配「1st」和「first」。此方法不需要額外的磁碟空間,因為沒有向 FTS 索引添加額外的項目。另一方面,執行 MATCH 查詢可能需要更多 CPU 週期,因為每個同義詞都需要分別查詢 FTS 索引。
使用方法 (2) 或 (3) 時,重要的是詞彙分析器僅在詞彙化文件文本(方法 (3))或查詢文本(方法 (2))時提供同義詞,而不是兩者都提供。這樣做不會造成任何錯誤,但效率很低。
實作自訂輔助函式類似於實作純量 SQL 函式。實作應為 fts5_extension_function 類型的 C 函式,定義如下
typedef struct Fts5ExtensionApi Fts5ExtensionApi; typedef struct Fts5Context Fts5Context; typedef struct Fts5PhraseIter Fts5PhraseIter; typedef void (*fts5_extension_function)( const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ Fts5Context *pFts, /* First arg to pass to pApi functions */ sqlite3_context *pCtx, /* Context for returning result/error */ int nVal, /* Number of values in apVal[] array */ sqlite3_value **apVal /* Array of trailing arguments */ );
透過呼叫 fts5_api 物件的 xCreateFunction() 方法向 FTS5 模組註冊實作。如果已經存在具有相同名稱的輔助函式,則會由新函式取代。如果將非 NULL 的 xDestroy 參數傳遞給 xCreateFunction(),則在關閉資料庫控制代碼或取代已註冊的輔助函式時,將使用傳遞的 pUserData 指標副本作為唯一參數來呼叫它。
如果成功,xCreateFunction() 會返回 SQLITE_OK。否則,它會返回 SQLite 錯誤代碼。在這種情況下,不會呼叫 xDestroy 函式。
傳遞給輔助函式回呼的最後三個參數(上面的 pCtx、nVal 和 apVal)類似於傳遞給純量 SQL 函式實作的三個參數。apVal[] 陣列包含除第一個傳遞給輔助函式的 SQL 參數之外的所有 SQL 參數。實作應透過內容控制代碼 pCtx 返回結果或錯誤。
傳遞給輔助函式回呼的第一個參數是指向結構(上面的 pApi)的指標,該結構包含可以呼叫的方法,以獲取有關當前查詢或列的資訊。第二個參數是一個不透明控制代碼(上面的 pFts),應作為第一個參數傳遞給任何此類方法呼叫。例如,以下輔助函式返回當前列所有欄位中詞彙的總數
/* ** Implementation of an auxiliary function that returns the number ** of tokens in the current row (including all columns). */ static void column_size_imp( const Fts5ExtensionApi *pApi, Fts5Context *pFts, sqlite3_context *pCtx, int nVal, sqlite3_value **apVal ){ int rc; int nToken; rc = pApi->xColumnSize(pFts, -1, &nToken); if( rc==SQLITE_OK ){ sqlite3_result_int(pCtx, nToken); }else{ sqlite3_result_error_code(pCtx, rc); } }
下一節將詳細描述提供給輔助函式實作的 API。更多範例可以在原始碼的「fts5_aux.c」檔案中找到。
本節概述了輔助函式 API 的功能。它並未描述每個函式。有關完整說明,請參閱下面的參考文字。
當輔助函數被呼叫時,它的實作可以存取一些 API,讓它可以向 FTS5 查詢各種資訊。這些 API 有些會返回與目前正在訪問的 FTS5 表格列相關的資訊,有些則與 FTS5 查詢將訪問的整組列相關,還有一些則與 FTS5 表格本身相關。假設有一個 FTS5 表格,其內容如下:
CREATE VIRTUAL TABLE ft USING fts5(a, b); INSERT INTO ft(rowid, a, b) VALUES (1, 'ab cd', 'cd de one'), (2, 'de fg', 'fg gh'), (3, 'gh ij', 'ij ab three four');
以及以下查詢:
SELECT my_aux_function(ft) FROM ft('ab')
那麼自訂輔助函數將會針對第 1 列和第 3 列(所有包含詞彙「ab」並因此符合查詢條件的列)被呼叫。
表格中的列數/欄數:xRowCount、xColumnCount
可以使用 xRowCount API 查詢系統中 FTS5 表格的總列數。這會提供表格中的總列數,而不是符合目前查詢條件的列數。
表格欄位的編號是從左到右,從 0 開始。 「rowid」欄位不計入在內,只有使用者宣告的欄位才算,所以在上面的例子中,「a」欄是第 0 欄,「b」欄是第 1 欄。在輔助函數的實作中,可以使用 xColumnCount API 來確定正在查詢的表格有多少個欄位。如果在上述例子中,從輔助函數 my_aux_function 的實作內呼叫 xColumnCount() API,它會返回 2。
來自目前列的資料:xColumnText、xRowid
可以使用 xRowid API 找到目前列的 rowid 值。可以使用 xColumnText API 取得目前列指定欄位中儲存的文字。
詞彙計數:xColumnSize、xColumnTotalSize
FTS5 將插入 fts 表格的檔案分割成詞彙。這些詞彙通常只是單字,可能會轉換成大寫或小寫,並移除任何標點符號。例如,預設的 unicode61 分詞器 會將文字「The tokenizer is case-insensitive」分詞成 5 個詞彙的列表:「the」、「tokenizer」、「is」、「case」和「insensitive」。如何從文字中擷取詞彙是由 分詞器 決定的。
輔助函數 API 提供了一些函數,可以查詢目前列指定欄位中的詞彙數量(xColumnSize API),或者查詢表格所有列中指定欄位中的詞彙數量(xColumnTotalSize API)。以上述範例為例,當訪問第 1 列時,針對第 0 欄,xColumnSize 返回 2,針對第 1 欄則返回 3。 xColumnTotalSize 針對第 0 欄返回 6,針對第 1 欄返回 9,無論目前列為何。
目前的全文檢索查詢:xPhraseCount、xPhraseSize、xQueryToken
FTS5 查詢包含一個或多個 片語。 xPhraseCount、xPhraseSize 和 xQueryToken API 允許輔助函數的實作查詢系統以取得目前查詢的詳細資訊。 xPhraseCount API 會返回目前查詢中的片語數量。例如,如果以下列方式查詢 FTS5 表格:
SELECT my_aux_function(ft) FROM ft('ab AND "cd ef gh" OR ij + kl')
並從輔助函數的實作中呼叫 xPhraseCount() API,它會返回 3(三個片語分別是「ab」、「ce ef gh」和「ij kl」)。
片語的編號是按照在查詢中出現的順序,從 0 開始。 xPhraseSize() API 可用於查詢查詢中指定片語的詞彙數量。在上面的例子中,片語 0 包含 1 個詞彙,片語 1 包含 3 個詞彙,片語 2 包含 2 個詞彙。
xQueryToken API 可用於存取查詢中指定片語內指定詞彙的文字。詞彙在其片語內的編號從左到右,從 0 開始。例如,若使用 xQueryToken API 請求上述範例中片語 2 的詞彙 1,它會返回文字「kl」。片語 0 的詞彙 0 為「ab」。
目前列中的片語匹配:xPhraseFirst、xPhraseNext
這兩個 API 函數可用於迭代目前列中查詢指定片語的匹配項。片語匹配由目前列中的欄位和詞彙偏移量識別。例如,假設以下範例表格
CREATE VIRTUAL TABLE ft2 USING fts5(x, y); INSERT INTO ft2(rowid, x, y) VALUES (1, 'xxx one two xxx five xxx six', 'seven four'), (2, 'five four four xxx six', 'three four five six four five six');
使用以下查詢
SELECT my_aux_function(ft2) FROM ft2( '("one two" OR "three") AND y:four NEAR(five six, 2)' );
上述查詢包含 5 個片語:「one two」、「three」、「four」、「five」和「six」。它匹配表格的所有列,因此會為每一列叫用輔助函數。
在列 1 中,對於片語 0「one two」,恰好有一個匹配項可迭代:欄位 0,詞彙偏移量 1。欄位編號為 0,因為匹配項出現在最左邊的欄位。詞彙偏移量為 1,因為在欄位值中片語匹配項之前恰好有一個詞彙(「xxx」)。對於片語 1「three」,沒有匹配項。片語 2「four」有一個匹配項,位於欄位 1,詞彙偏移量 0。片語 3「five」有一個匹配項,位於欄位 0,詞彙偏移量 4,而片語 4「six」有一個匹配項,位於欄位 0,詞彙偏移量 6。
範例中每一列中每個片語的匹配項集合如下表所示。每個匹配項以 (欄位編號, 詞彙偏移量) 表示
列 | 片語 0 | 片語 1 | 片語 2 | 片語 3 | 片語 4 |
---|---|---|---|---|---|
1 | (0, 1) | (1, 1) | (0, 4) | (0, 6) | |
2 | (1,0) | (1, 1), (1,4) | (1, 2), (1, 5) | (1, 3), (1, 6) |
第二列稍微複雜一些。沒有出現片語 0。片語 1(「three」)出現一次,位於欄位 1,詞彙偏移量 0。雖然欄位 0 中有片語 2(「four」)的實例,但 API 並未回報任何實例,因為片語 4 有一個欄位過濾器:「y:」。被欄位過濾器過濾掉的匹配項不計入。同樣地,雖然片語 3 和 4 出現在列 2 的欄位「x」中,但它們被NEAR 過濾器過濾掉。被 NEAR 過濾器過濾掉的匹配項也不計入。
目前列中的片語匹配 (2):xInstCount、xInst
xInstCount 和 xInst API 提供與上述 xPhraseFirst 和 xPhraseNext 相同的資訊。不同之處在於,xInstCount/xInst API 並非迭代單個指定片語的匹配項,而是將所有匹配項整理成單個平面陣列,並按在目前列中出現的順序排序。然後可以隨機存取此陣列的元素。
每個陣列元素由三個值組成
使用與上述 xPhraseFirst/xPhraseNext 相同的範例資料和查詢,透過 xInstCount/xInst 可存取的陣列包含每一列的以下項目
列 | xInstCount/xInst 陣列 |
---|---|
1 | (0, 0, 1), (3, 0, 4), (4, 0, 6), (2, 1, 1) |
2 | (1, 1, 0), (2, 1, 1), (3, 1, 2), (4, 1, 3), (2, 1, 4), (3, 1, 5), (4, 1, 6) |
陣列的每個項目稱為片語匹配。片語匹配從 0 開始依序編號。因此,在上述範例中,在列 2 中,片語匹配 3 為 (4, 1, 3) - 查詢的片語 4 匹配欄位 1,詞彙偏移量 3。
struct Fts5ExtensionApi { int iVersion; /* Currently always set to 3 */ void *(*xUserData)(Fts5Context*); int (*xColumnCount)(Fts5Context*); int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); int (*xTokenize)(Fts5Context*, const char *pText, int nText, /* Text to tokenize */ void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ ); int (*xPhraseCount)(Fts5Context*); int (*xPhraseSize)(Fts5Context*, int iPhrase); int (*xInstCount)(Fts5Context*, int *pnInst); int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); sqlite3_int64 (*xRowid)(Fts5Context*); int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) ); int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); void *(*xGetAuxdata)(Fts5Context*, int bClear); int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); /* Below this point are iVersion>=3 only */ int (*xQueryToken)(Fts5Context*, int iPhrase, int iToken, const char **ppToken, int *pnToken ); int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); };
返回註冊擴充函數時傳遞給 xCreateFunction() API 的 pUserData 指標的副本。
如果參數 iCol 小於零,則將輸出變數 *pnToken 設定為 FTS5 表格中所有詞彙的總數。或者,如果 iCol 非負但小於表格中的欄位數,則傳回 iCol 欄位中所有詞彙的總數,並考量 FTS5 表格中的所有列。
如果參數 iCol 大於或等於表格中的欄位數,則傳回 SQLITE_RANGE。或者,如果發生錯誤(例如 OOM 狀況或 IO 錯誤),則傳回適當的 SQLite 錯誤碼。
傳回表格中的欄位數。
如果參數 iCol 小於零,則將輸出變數 *pnToken 設定為目前列中所有詞彙的總數。或者,如果 iCol 非負但小於表格中的欄位數,則將 *pnToken 設定為目前列中 iCol 欄位的詞彙數。
如果參數 iCol 大於或等於表格中的欄位數,則傳回 SQLITE_RANGE。或者,如果發生錯誤(例如 OOM 狀況或 IO 錯誤),則傳回適當的 SQLite 錯誤碼。
如果搭配使用 "columnsize=0" 選項建立的 FTS5 表格,此函式可能會相當沒有效率。
如果參數 iCol 小於零,或大於或等於表格中的欄位數,則傳回 SQLITE_RANGE。
否則,此函式會嘗試擷取目前文件中 iCol 欄位的文字。如果成功,(*pz) 會設定為指向包含 UTF-8 編碼文字的緩衝區,(*pn) 會設定為緩衝區的大小(以位元組為單位,而不是字元),並傳回 SQLITE_OK。否則,如果發生錯誤,則傳回 SQLite 錯誤碼,且 (*pz) 和 (*pn) 的最終值未定義。
傳回目前查詢表達式中的片語數。
如果參數 iCol 小於零,或大於或等於 xPhraseCount 傳回的目前查詢中的片語數,則傳回 0。否則,此函式會傳回查詢中片語 iPhrase 的詞彙數。片語的編號從零開始。
將 *pnInst 設定為目前列中查詢內所有片語出現的總次數。如果成功,則傳回 SQLITE_OK;如果發生錯誤,則傳回錯誤碼(例如 SQLITE_NOMEM)。
如果搭配使用 "detail=none" 或 "detail=column" 選項建立的 FTS5 表格,此 API 可能會相當慢。如果 FTS5 表格是使用 "detail=none" 或 "detail=column" 和 "content=" 選項建立的(亦即,如果它是無內容表格),則此 API 一律傳回 0。
查詢目前列中片語匹配 iIdx 的詳細資訊。片語匹配的編號從零開始,因此 iIdx 引數應大於或等於零,且小於 xInstCount() 輸出的值。如果 iIdx 小於零或大於或等於 xInstCount() 傳回的值,則傳回 SQLITE_RANGE。
否則,輸出參數 *piPhrase 會設定為片語編號,*piCol 設定為其出現的欄位,*piOff 設定為片語第一個詞彙的詞彙偏移量。如果成功,則傳回 SQLITE_OK;如果發生錯誤,則傳回錯誤碼(例如 SQLITE_NOMEM)。
如果搭配使用 "detail=none" 或 "detail=column" 選項建立的 FTS5 表格,此 API 可能會相當慢。
傳回目前列的 rowid。
使用屬於 FTS5 表格的斷詞器來斷詞文字。
此 API 函數用於查詢目前查詢中第 iPhrase 個片語的 FTS 表格。具體來說,會執行一個等效於以下查詢的指令:
... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid
其中 $p 設定為等同於目前查詢中第 iPhrase 個片語的片語。任何適用於目前查詢中第 iPhrase 個片語的欄位過濾器都會包含在 $p 中。對於每個被訪問的列,都會呼叫作為第四個參數傳遞的回呼函數。傳遞給回呼函數的上下文和 API 物件可用於存取每個匹配列的屬性。呼叫 Api.xUserData() 會返回作為第三個參數傳遞給 pUserData 的指標的副本。
如果參數 iPhrase 小於零,或者大於或等於查詢中的片語數(由 xPhraseCount() 返回),則此函數返回 SQLITE_RANGE。
如果回呼函數返回 SQLITE_OK 以外的任何值,則查詢將被放棄,xQueryPhrase 函數將立即返回。如果返回的值是 SQLITE_DONE,xQueryPhrase 將返回 SQLITE_OK。否則,錯誤代碼會向上傳播。
如果查詢順利完成,則返回 SQLITE_OK。或者,如果在查詢完成之前發生錯誤或回呼中止查詢,則返回 SQLite 錯誤代碼。
將作為第二個參數傳遞的指標儲存為擴展函數的「輔助數據」。然後,可以使用 xGetAuxdata() API,由目前或將來在同一個 MATCH 查詢中呼叫的同一個 fts5 擴展函數來檢索該指標。
每個擴展函數會為每個 FTS 查詢(MATCH 表達式)分配一個輔助數據槽。如果針對單個 FTS 查詢多次呼叫擴展函數,則所有呼叫共享單個輔助數據上下文。
如果呼叫此函數時已經存在輔助數據指標,則會被新指標取代。如果原始指標指定了 xDelete 回呼,則此時會呼叫它。
如果指定了 xDelete 回呼,則在 FTS5 查詢完成後,也會在輔助數據指標上呼叫它。
如果在此函數中發生錯誤(例如 OOM 狀況),輔助數據將設定為 NULL 並返回錯誤代碼。如果 xDelete 參數不為 NULL,則在返回之前會在輔助數據指標上呼叫它。
返回 fts5 擴展函數的目前輔助數據指標。詳見 xSetAuxdata() 方法。
如果 bClear 參數非零,則在此函數返回之前清除輔助數據(設定為 NULL)。在這種情況下,不會呼叫 xDelete(如果有的話)。
此函數用於檢索表格中的總列數。換句話說,與以下指令返回的值相同:
SELECT count(*) FROM ftstable;
此函數與 Fts5PhraseIter 類型和 xPhraseNext 方法一起使用,可逐一查看目前列中單個查詢片語的所有實例。這與可通過 xInstCount/xInst API 存取的資訊相同。雖然 xInstCount/xInst API 更方便使用,但在某些情況下,此 API 的速度可能會更快。要逐一查看片語 iPhrase 的實例,請使用以下程式碼:
Fts5PhraseIter iter; int iCol, iOff; for(pApi->xPhraseFirst(pFts, iPhrase, &iter, &iCol, &iOff); iCol>=0; pApi->xPhraseNext(pFts, &iter, &iCol, &iOff) ){ // An instance of phrase iPhrase at offset iOff of column iCol }
Fts5PhraseIter 結構定義如上。應用程式不應直接修改此結構 - 它只能如上所示與 xPhraseFirst() 和 xPhraseNext() API 方法一起使用(以及由 xPhraseFirstColumn() 和 xPhraseNextColumn() 如下所示)。
如果與使用 "detail=none" 或 "detail=column" 選項建立的 FTS5 表格一起使用,此 API 的速度可能會相當慢。如果 FTS5 表格是使用 "detail=none" 或 "detail=column" 和 "content=" 選項建立的(即,如果它是無內容表格),則此 API 一律逐一查看空集合(所有對 xPhraseFirst() 的呼叫都將 iCol 設定為 -1)。
詳見上方 xPhraseFirst 的說明。
此函式和 xPhraseNextColumn() 與上述 xPhraseFirst() 和 xPhraseNext() API 類似。不同之處在於,這些 API 並非迭代目前資料列中所有片語的實例,而是用於迭代目前資料列中包含一個或多個指定片語實例的欄位集合。例如:
Fts5PhraseIter iter; int iCol; for(pApi->xPhraseFirstColumn(pFts, iPhrase, &iter, &iCol); iCol>=0; pApi->xPhraseNextColumn(pFts, &iter, &iCol) ){ // Column iCol contains at least one instance of phrase iPhrase }
如果搭配使用 "detail=none" 選項建立的 FTS5 表格,則此 API 的執行速度可能會相當慢。如果 FTS5 表格是使用 "detail=none" 或 "content=" 選項建立的(亦即,如果它是無內容表格),則此 API 一律會迭代空集合(所有對 xPhraseFirstColumn() 的呼叫都會將 iCol 設定為 -1)。
使用此 API 及其配套的 xPhraseFirstColumn() 訪問的資訊也可以使用 xPhraseFirst/xPhraseNext(或 xInst/xInstCount)取得。此 API 的主要優點是,在與 "detail=column" 表格一起使用時,其效率明顯高於其他替代方案。
詳見上方 xPhraseFirstColumn 的說明。
這用於訪問目前查詢中片語 iPhrase 的權杖 iToken。在返回之前,輸出參數 *ppToken 會被設定為指向包含所請求權杖的緩衝區,而 *pnToken 則會被設定為此緩衝區的大小(以位元組為單位)。
如果 iPhrase 或 iToken 小於零,或者如果 iPhrase 大於或等於 xPhraseCount() 報告的查詢中片語的數量,或者如果 iToken 等於或大於片語中權杖的數量,則會返回 SQLITE_RANGE,並且 *ppToken 和 *pnToken 都會被歸零。
輸出文字並非指定權杖的查詢文字的副本。它是權杖分析器模組的輸出。對於 tokendata=1 表格,這包括任何嵌入的 0x00 和尾隨資料。
這用於訪問目前資料列中片語命中 iIdx 的權杖 iToken。如果 iIdx 小於零或大於或等於 xInstCount() 返回的值,則會返回 SQLITE_RANGE。否則,輸出變數 (*ppToken) 會被設定為指向包含相符文件權杖的緩衝區,而 (*pnToken) 則會被設定為該緩衝區的大小(以位元組為單位)。如果指定的權杖與前綴查詢詞彙相符,則此 API 不可用。在這種情況下,兩個輸出變數一律會設定為 0。
輸出文字並非已權杖化的文件文字的副本。它是權杖分析器模組的輸出。對於 tokendata=1 表格,這包括任何嵌入的 0x00 和尾隨資料。
如果搭配使用 "detail=none" 或 "detail=column" 選項建立的 FTS5 表格,此 API 可能會相當慢。
fts5vocab 虛擬表格模組允許使用者直接從 FTS5 全文索引中提取資訊。fts5vocab 模組是 FTS5 的一部分 — 只要有 FTS5,它就可用。
每個 fts5vocab 表格都與單個 FTS5 表格相關聯。通常透過在 CREATE VIRTUAL TABLE 陳述式中指定兩個引數(而非欄位名稱)來建立 fts5vocab 表格 — 相關聯的 FTS5 表格的名稱和 fts5vocab 表格的類型。目前有三種類型的 fts5vocab 表格:「row」、「col」和「instance」。除非 fts5vocab 表格是在「temp」資料庫中建立的,否則它必須與相關聯的 FTS5 表格位於同一個資料庫中。
-- Create an fts5vocab "row" table to query the full-text index belonging -- to FTS5 table "ft1". CREATE VIRTUAL TABLE ft1_v USING fts5vocab('ft1', 'row'); -- Create an fts5vocab "col" table to query the full-text index belonging -- to FTS5 table "ft2". CREATE VIRTUAL TABLE ft2_v USING fts5vocab(ft2, col); -- Create an fts5vocab "instance" table to query the full-text index -- belonging to FTS5 table "ft3". CREATE VIRTUAL TABLE ft3_v USING fts5vocab(ft3, instance);
如果 fts5vocab 表格是在 temp 資料庫中建立的,則它可以與任何附加資料庫中的 FTS5 表格相關聯。為了將 fts5vocab 表格附加到位於「temp」以外的資料庫中的 FTS5 表格,需要在 CREATE VIRTUAL TABLE 引數中的 FTS5 表格名稱之前插入資料庫的名稱。例如:
-- Create an fts5vocab "row" table to query the full-text index belonging -- to FTS5 table "ft1" in database "main". CREATE VIRTUAL TABLE temp.ft1_v USING fts5vocab(main, 'ft1', 'row'); -- Create an fts5vocab "col" table to query the full-text index belonging -- to FTS5 table "ft2" in attached database "aux". CREATE VIRTUAL TABLE temp.ft2_v USING fts5vocab('aux', ft2, col); -- Create an fts5vocab "instance" table to query the full-text index -- belonging to FTS5 table "ft3" in attached database "other". CREATE VIRTUAL TABLE temp.ft2_v USING fts5vocab('aux', ft3, 'instance');
在非 "temp" 資料庫中建立 fts5vocab 表格時,指定三個參數會導致錯誤。
類型為 "row" 的 fts5vocab 表格包含關聯 FTS5 表格中每個不同詞彙的一列。表格欄位如下:
欄位 | 內容 |
---|---|
term | 儲存在 FTS5 索引中的詞彙。 |
doc | 至少包含一個詞彙實例的列數。 |
cnt | 整個 FTS5 表格中詞彙的總實例數。 |
類型為 "col" 的 fts5vocab 表格包含關聯 FTS5 表格中每個不同詞彙/欄位組合的一列。表格欄位如下:
欄位 | 內容 |
---|---|
term | 儲存在 FTS5 索引中的詞彙。 |
col | 包含詞彙的 FTS5 表格欄位名稱。 |
doc | FTS5 表格中,欄位 $col 至少包含一個詞彙實例的列數。 |
cnt | 出現在 FTS5 表格 $col 欄位中詞彙的總實例數(考慮所有列)。 |
類型為 "instance" 的 fts5vocab 表格包含關聯 FTS 索引中儲存的每個詞彙實例的一列。假設 FTS5 表格建立時 'detail' 選項設定為 'full',表格欄位如下:
欄位 | 內容 |
---|---|
term | 儲存在 FTS5 索引中的詞彙。 |
doc | 包含詞彙實例的文件的 rowid。 |
col | 包含詞彙實例的欄位名稱。 |
offset | 詞彙實例在其欄位中的索引。詞彙的編號從 0 開始,按出現順序排列。 |
如果 FTS5 表格建立時 'detail' 選項設定為 'col',則 instance 虛擬表格的 *offset* 欄位始終包含 NULL。在此情況下,表格中每個唯一的詞彙/文件/欄位組合都有一列。或者,如果 FTS5 表格建立時 'detail' 設定為 'none',則 *offset* 和 *col* 都始終包含 NULL 值。對於 detail=none 的 FTS5 表格,fts5vocab 表格中每個唯一的詞彙/文件組合都有一列。
範例
-- Assuming a database created using: CREATE VIRTUAL TABLE ft1 USING fts5(c1, c2); INSERT INTO ft1 VALUES('apple banana cherry', 'banana banana cherry'); INSERT INTO ft1 VALUES('cherry cherry cherry', 'date date date'); -- Then querying the following fts5vocab table (type "col") returns: -- -- apple | c1 | 1 | 1 -- banana | c1 | 1 | 1 -- banana | c2 | 1 | 2 -- cherry | c1 | 2 | 4 -- cherry | c2 | 1 | 1 -- date | c3 | 1 | 3 -- CREATE VIRTUAL TABLE ft1_v_col USING fts5vocab(ft1, col); -- Querying an fts5vocab table of type "row" returns: -- -- apple | 1 | 1 -- banana | 1 | 3 -- cherry | 2 | 5 -- date | 1 | 3 -- CREATE VIRTUAL TABLE ft1_v_row USING fts5vocab(ft1, row); -- And, for type "instance" INSERT INTO ft1 VALUES('apple banana cherry', 'banana banana cherry'); INSERT INTO ft1 VALUES('cherry cherry cherry', 'date date date'); -- -- apple | 1 | c1 | 0 -- banana | 1 | c1 | 1 -- banana | 1 | c2 | 0 -- banana | 1 | c2 | 1 -- cherry | 1 | c1 | 2 -- cherry | 1 | c2 | 2 -- cherry | 2 | c1 | 0 -- cherry | 2 | c1 | 1 -- cherry | 2 | c1 | 2 -- date | 2 | c2 | 0 -- date | 2 | c2 | 1 -- date | 2 | c2 | 2 -- CREATE VIRTUAL TABLE ft1_v_instance USING fts5vocab(ft1, instance);
本節概略描述 FTS 模組在資料庫中儲存索引和內容的方式。應用程式使用 FTS 時,不需閱讀或理解本節的內容。然而,對於試圖分析和理解 FTS 效能特性的應用程式開發人員,或考慮增強現有 FTS 功能集的開發人員來說,這可能很有用。
在資料庫中建立 FTS5 虛擬表格時,會在資料庫中建立 3 到 5 個實際表格。這些表格稱為「影子表格」,虛擬表格模組使用它們來儲存永久性資料。使用者不應直接存取它們。許多其他虛擬表格模組,包括 FTS3 和 rtree,也會建立和使用影子表格。
FTS5 會建立以下影子表格。在每種情況下,實際表格名稱都基於 FTS5 虛擬表格的名稱(以下,將 % 替換為虛擬表格的名稱即可找到實際的影子表格名稱)。
-- This table contains most of the full-text index data. CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); -- This table contains the remainder of the full-text index data. -- It is almost always much smaller than the %_data table. CREATE TABLE %_idx(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID; -- Contains the values of persistent configuration parameters. CREATE TABLE %_config(k PRIMARY KEY, v) WITHOUT ROWID; -- Contains the size of each column of each row in the virtual table -- in tokens. This shadow table is not present if the "columnsize" -- option is set to 0. CREATE TABLE %_docsize(id INTEGER PRIMARY KEY, sz BLOB); -- Contains the actual data inserted into the FTS5 table. There -- is one "cN" column for each indexed column in the FTS5 table. -- This shadow table is not present for contentless or external -- content FTS5 tables. CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1...);
以下章節更詳細地描述如何使用這五個表格來儲存 FTS5 資料。
以下章節指的是以「varint」格式儲存的 64 位元帶正負號整數。FTS5 使用與 SQLite 核心在各處使用的相同的 varint 格式。
Varint 的長度介於 1 到 9 個位元組之間。Varint 由零個或多個最高位元設定的位元組,後跟一個最高位元清除的位元組組成,或由九個位元組組成,以較短者為準。前八個位元組中每個位元組的低七位元和第九個位元組的所有 8 位元用於重建 64 位元二補數整數。Varint 為大端序:從 varint 較早的位元組取得的位元比從較晚的位元組取得的位元更重要。
FTS 索引是一個有序的鍵值儲存庫,其中鍵是文件詞項或詞項前綴,而關聯的值是「文件列表 (doclist)」。文件列表是一個壓縮的變長整數 (varint) 陣列,編碼了 FTS5 表中每個詞項實例的位置。單個詞項實例的位置定義為以下組合:
FTS 索引中,資料集中的每個詞彙最多包含 (nPrefix+1) 個條目,其中 nPrefix 是定義的前綴索引的數量。
與主 FTS 索引(非前綴索引)關聯的鍵以字元「0」作為前綴。第一個前綴索引的鍵以「1」作為前綴。第二個前綴索引的鍵以「2」作為前綴,依此類推。例如,如果將詞彙「document」插入到具有以 prefix="2 4" 指定的前綴索引的 FTS5 表中,則添加到 FTS 索引的鍵將是「0document」、「1do」和「2docu」。
FTS 索引條目並非儲存在單個樹狀結構或雜湊表結構中。相反地,它們儲存在一系列不可變的類似 B 樹的結構中,稱為「區段 B 樹 (segment b-tree)」。每次提交對 FTS5 表的寫入時,都會添加一個或多個(但通常只有一個)新的區段 B 樹,其中包含新增的條目和任何已刪除條目的墓碑標記 (tombstone)。查詢 FTS 索引時,讀取器會依次查詢每個區段 B 樹並合併結果,優先考慮較新的資料。
每個區段 B 樹都分配有一個數字級別。當一個新的區段 B 樹作為提交事務的一部分寫入資料庫時,它會被分配到級別 0。屬於單一級別的區段 B 樹會定期合併在一起,以創建一個單一、更大的區段 B 樹,並分配到下一級別(即級別 0 的區段 B 樹合併成為單一級別 1 的區段 B 樹)。因此,數字較大的級別包含較舊的資料,通常位於較大的區段 B 樹中。有關如何控制合併的詳細資訊,請參閱 「automerge」、「crisismerge」 和 「usermerge」 選項,以及 「merge」 和 「optimize」 命令。
如果與詞項或詞項前綴關聯的文件列表非常大,則可能會有關聯的文件列表索引。文件列表索引類似於 B 樹的內部節點集合。它允許有效地查詢大型文件列表中的列 ID 或列 ID 範圍。例如,在處理如下查詢時:
SELECT ... FROM fts_table('term') WHERE rowid BETWEEN ? AND ?
FTS5 使用區段 B 樹索引找到詞項「term」的文件列表,然後使用其文件列表索引(假設存在)有效地識別列 ID 在所需範圍內的匹配子集。
CREATE TABLE %_data( id INTEGER PRIMARY KEY, block BLOB );
%_data 表用於儲存三種類型的記錄:
系統中的每個區段 B 樹都分配有一個唯一的 16 位元區段 ID。只有在原始擁有者區段 B 樹完全合併到更高級別的區段 B 樹之後,才能重複使用區段 ID。在區段 B 樹中,每個葉子頁面都分配有一個唯一的頁碼 — 第一個葉子頁面為 1,第二個葉子頁面為 2,依此類推。
每個文件列表索引葉子頁面也會被分配一個頁碼。文件列表索引中的第一個(最左邊的)葉子頁面會被分配與其詞彙出現的區段 B 樹葉子頁面相同的頁碼(因為文件列表索引只會為具有非常長文件列表的詞彙建立,每個區段 B 樹葉子頁面最多只有一個詞彙有關聯的文件列表索引)。將此頁碼稱為 P。如果文件列表太大而需要第二個葉子頁面,則第二個葉子頁面會被分配頁碼 P+1。第三個葉子頁面為 P+2。文件列表索引 B 樹的每一層(葉子、葉子的父節點、祖父母節點等)都以此方式分配頁碼,從頁碼 P 開始。
在 %_data 表中用於儲存任何給定區段 B 樹葉子頁面或文件列表索引葉子或節點的「id」值組成如下:
列 ID 位元 | 內容 |
---|---|
38..43 | (16 位元)區段 B 樹 id 值。 |
37 | (1 位元)文件列表索引標記。針對文件列表索引頁面設定,針對區段 B 樹葉子頁面清除。 |
32..36 | (5 位元)樹中的高度。對於區段 B 樹和文件列表索引葉子,此值設定為 0,對於文件列表索引葉子的父節點設定為 1,對於祖父母節點設定為 2,依此類推。 |
0..31 | (32 位元)頁碼 |
結構記錄識別組成目前 FTS 索引的區段 B 樹集合,以及任何正在進行的增量合併操作的詳細資訊。它儲存在 %_data 表中,id=10。結構記錄以單個 32 位元無符號值開頭 - Cookie 值。每次修改結構時,此值都會遞增。Cookie 值後面跟著三個可變長度整數值,如下所示:
然後,對於從 0 到 nLevel 的每個層級:
平均值記錄,始終儲存在 %_data 表中,id=1,它並不儲存任何東西的平均值。相反,它包含一個由 (nCol+1) 個打包的可變長度整數值組成的向量,其中 nCol 是 FTS5 表中的欄位數,包括未索引的欄位。第一個可變長度整數包含 FTS5 表中的總列數。第二個包含儲存在最左邊 FTS5 表欄位中所有值的權杖總數。第三個包含下一個最左邊欄位所有值的權杖數,依此類推。未索引欄位的值始終為零。
鍵/文件列表格式是一種用於儲存一系列按排序順序排列的鍵(文件詞彙或以單個字元作為前綴的詞彙前綴,用於識別它們所屬的特定索引)的格式,每個鍵都帶有其關聯的文件列表。該格式由交替打包的鍵和文件列表組成。
第一個鍵儲存方式如下:
每個後續鍵儲存方式如下:
例如,如果 FTS5 鍵值/文件列表記錄中的前兩個鍵值是「0challenger」和「0chandelier」,那麼第一個鍵值會儲存為 varint 11,後接 11 個位元組的「0challenger」,而第二個鍵值會儲存為 varint 4 和 7,後接 7 個位元組的「ndelier」。
圖 1 - 詞彙/文件列表格式
每個文件列表會識別包含至少一個詞彙或詞彙前綴例項的行(透過它們的 rowid 值),以及一個相關聯的位置列表或「poslist」,列舉每個詞彙例項在行中的位置。在此意義上,「位置」定義為欄號和欄值中的詞彙偏移量。
在文件列表中,文件總是按 rowid 排序儲存。文件列表中的第一個 rowid 按原樣儲存為 varint。它後面緊跟著其關聯的位置列表。接著是第一個 rowid 和第二個 rowid 之間的差值(以 varint 表示),然後是與文件列表中第二個 rowid 相關聯的文件列表。依此類推。
無法透過解析來確定文件列表的大小。這必須儲存在外部。有關 FTS5 中如何完成此操作的詳細資訊,請參閱下面的章節。
圖 2 - 文件列表格式
位置列表(通常縮寫為「poslist」)會識別相關詞彙每個例項在行中的欄和詞彙偏移量。位置列表的格式為:
2, 12, 7, 3
圖 3 - 位置列表 (poslist) 在第 0 欄和第 i 欄中的偏移量
如果區段 B 樹的全部內容足夠小(預設情況下,這表示小於 4000 位元組),則可以將其以上一節中描述的鍵值/文件列表格式儲存為 %_data 表中的單個 blob。否則,鍵值/文件列表會分割成頁面(預設情況下,每個頁面大約 4000 位元組),並儲存在 %_data 表中的一組連續項目中(詳細資訊請參閱上文)。
當鍵值/文件列表分割成頁面時,會對格式進行以下修改:
每個頁面還有一個固定大小的 4 位元組標頭和一個大小可變的頁尾。標頭分為兩個 16 位元大端序整數欄位。它們包含:
頁尾由一系列 varint 組成,其中包含頁面上出現的每個鍵值的位元組偏移量。如果頁面上沒有鍵值,則頁尾的大小為零位元組。
圖 4 - 頁面格式
將區段 b 樹的內容格式化為鍵/文件列表格式,然後將其拆分成頁面的結果與 b+ 樹的葉子非常相似。FTS5 並沒有為這個 b+ 樹的內部節點創建一種格式,並將它們與葉子一起存儲在 %_data 表中,而是將原本應存儲在這些節點上的鍵添加到 %_idx 表中,其定義如下:
CREATE TABLE %_idx( segid INTEGER, -- segment id term TEXT, -- prefix of first key on page pgno INTEGER, -- (2*pgno + bDoclistIndex) PRIMARY KEY(segid, term) );
對於每個包含至少一個鍵的「葉」頁面,都會在 %_idx 表中添加一個條目。字段設置如下:
欄位 | 內容 |
---|---|
segid | 整數區段 ID。 |
term | 頁面上第一個鍵的最短前綴,該前綴大於前一頁上的所有鍵。對於區段中的第一個頁面,此前綴的大小為零位元組。 |
pgno | 此欄位同時編碼頁碼(區段內,從 1 開始)和文件列表索引標記。如果頁面上的最後一個鍵具有關聯的文件列表索引,則設置文件列表索引標記。此欄位的值為:(pgno*2 + bDoclistIndexFlag) |
然後,為了找到可能包含詞彙 t 的區段 i 的葉子,FTS5 並不是搜尋內部節點,而是執行以下查詢:
SELECT pgno FROM %_idx WHERE segid=$i AND term>=$t ORDER BY term LIMIT 1
上一節中描述的區段索引允許透過詞彙或詞彙前綴(假設存在所需大小的前綴索引)有效地查詢區段 b 樹。本節描述的資料結構,文件列表索引,允許 FTS5 在與單個詞彙或詞彙前綴關聯的文件列表中有效地搜尋 rowid 或 rowid 範圍。
並非所有鍵都具有關聯的文件列表索引。默認情況下,僅當鍵的文件列表跨越超過 4 個區段 b 樹葉頁面時,才會為該鍵添加文件列表索引。文件列表索引本身是 b 樹,葉子和內部節點都作為條目存儲在 %_data 表中,但實際上大多數文件列表都足夠小,可以放在單個葉子上。FTS5 使用與區段 b 樹葉子大致相同的大小作為文件列表索引節點和葉子的大小(默認情況下為 4000 位元組)。
文件列表索引葉子和內部節點使用相同的頁面格式。第一個位元組是「標記」位元組。對於文件列表索引 b 樹的根頁面,它設置為 0x00,對於所有其他頁面,它設置為 0x01。頁面的其餘部分是一系列緊密排列的 varint,如下所示:
對於文件列表索引中最左邊的文件列表索引葉子,最左邊的子頁面是包含鍵本身的頁面之後的第一個區段 b 樹葉子。
CREATE TABLE %_docsize( id INTEGER PRIMARY KEY, -- id of FTS5 row this record pertains to sz BLOB -- blob containing nCol packed varints );
許多常見的搜尋結果排名函數需要結果文件的詞彙大小作為輸入(因為在短文件中命中的搜尋詞彙被認為比在長文件中命中的更重要)。為了提供對此信息的快速訪問,FTS5 表中的每一行在 %_docsize 影子表中都有一個對應的記錄(具有相同的 rowid),其中包含該行中每個欄位值的詞彙大小。
欄位值大小存儲在一個 blob 中,其中包含 FTS5 表中每一欄(從左到右)的一個打包 varint。當然,varint 包含對應欄位值中的詞彙總數。未索引的欄位也包含在此 varint 向量中;對於它們,值始終設置為零。
此表格由 xColumnSize API 使用。可以透過指定 columnsize=0 選項來完全省略它。在這種情況下,xColumnSize API 仍然可用於輔助函式,但執行速度會慢得多。
CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1...);
實際的表格內容 - 插入 FTS5 表格的值,儲存在 %_content 表格中。此表格會為 FTS5 表格的每一欄建立一個 "c*" 欄,包含任何未索引的欄。最左邊 FTS5 表格欄的值儲存在 %_content 表格的 "c0" 欄中,下一個 FTS5 表格欄的值儲存在 "c1" 欄中,依此類推。
對於外部內容或無內容的 FTS5 表格,此表格會被完全省略。
CREATE TABLE %_config(k PRIMARY KEY, v) WITHOUT ROWID;
此表格儲存任何持續性設定選項的值。「k」欄儲存選項的名稱(文字),「v」欄儲存值。範例內容
sqlite> SELECT * FROM fts_tbl_config; ┌─────────────┬──────┐ │ k │ v │ ├─────────────┼──────┤ │ crisismerge │ 8 │ │ pgsz │ 8000 │ │ usermerge │ 4 │ │ version │ 4 │ └─────────────┴──────┘
也提供類似但更成熟的 FTS3/4 模組。FTS5 是 FTS4 的新版本,包含各種修正和針對 FTS4 中無法在不犧牲向下相容性的情況下修復的問題的解決方案。其中一些問題在下面描述。
為了使用 FTS5 而不是 FTS3 或 FTS4,應用程式通常只需要最少的修改。大多數修改分為三類 - 用於建立 FTS 表格的 CREATE VIRTUAL TABLE 陳述式所需的變更、用於對表格執行查詢的 SELECT 查詢所需的變更,以及使用 FTS 輔助函式 的應用程式所需的變更。
模組名稱必須從「fts3」或「fts4」更改為「fts5」。
所有類型資訊或約束規格都必須從欄位定義中移除。FTS3/4 會忽略欄位定義中欄位名稱後面的所有內容,FTS5 會嘗試解析它(如果解析失敗則會回報錯誤)。
「matchinfo=fts3」選項不可用。「columnsize=0」選項具有相同的功能。
notindexed= 選項不可用。在欄位定義中新增 UNINDEXED 具有相同的功能。
ICU 分詞器不可用。
compress=、uncompress= 和 languageid= 選項不可用。目前還沒有與其功能等效的選項。
-- FTS3/4 statement CREATE VIRTUAL TABLE t1 USING fts4( linkid INTEGER, header CHAR(20), text VARCHAR, notindexed=linkid, matchinfo=fts3, tokenizer=unicode61 ); -- FTS5 equivalent (note - the "tokenizer=unicode61" option is not -- required as this is the default for FTS5 anyway) CREATE VIRTUAL TABLE t1 USING fts5( linkid UNINDEXED, header, text, columnsize=0 );
「docid」別名不存在。應用程式必須改用「rowid」。
當欄位篩選器同時在 FTS 查詢中指定,並透過使用欄位作為 MATCH 運算子的 LHS 來指定時,查詢的行為略有不同。對於具有欄位「a」和「b」的表格,以及類似於以下內容的查詢
... a MATCH 'b: string'
FTS3/4 會在「b」欄中搜尋相符的項目。然而,FTS5 總是返回零列,因為結果會先針對「b」欄進行篩選,然後再針對「a」欄進行篩選,導致沒有結果。換句話說,在 FTS3/4 中,內部篩選器會覆蓋外部篩選器,而在 FTS5 中,兩個篩選器都會被套用。
FTS 查詢語法(MATCH 運算子的右側)在某些方面已更改。FTS5 語法與 FTS4 的「增強語法」非常接近。主要區別在於 FTS5 對查詢字串中無法辨識的標點符號和類似符號更為嚴格。大多數適用於 FTS3/4 的查詢也應該適用於 FTS5,而不適用的查詢應該會返回解析錯誤。
FTS5 沒有 matchinfo() 或 offsets() 函式,而且 snippet() 函式不像 FTS3/4 中的功能那麼齊全。然而,由於 FTS5 提供了一個允許應用程式建立自訂輔助函式的 API,因此任何所需的功能都可以在應用程式程式碼中實現。
FTS5 提供的內建輔助函式集未來可能會得到改進。
fts4aux 模組提供的功能現在由 fts5vocab 提供。這兩個表格的結構略有不同。
FTS3/4 的 "merge=X,Y" 指令已被 FTS5 的 merge 指令 取代。
FTS3/4 的 "automerge=X" 指令已被 FTS5 的 automerge 選項 取代。
FTS5 與 FTS3/4 的主要任務相似,都是維護一個索引,將每個獨特的詞彙 (token) 映射到一組文件中該詞彙所有出現位置的列表,其中每個位置都由其所在的文件及其在該文件中的位置來識別。例如:
-- Given the following SQL: CREATE VIRTUAL TABLE ft USING fts5(a, b); INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z'); INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y'); -- The FTS5 module creates the following mapping on disk: A --> (2, 0, 0) X --> (1, 0, 0) Y --> (1, 0, 1) (1, 1, 0) (2, 1, 0) (2, 1, 1) Z --> (1, 1, 1) (2, 0, 1)
在上面的例子中,每個三元組都透過列 ID (rowid)、欄號 (欄號從左到右依序從 0 開始編號) 和在欄值中的位置 (欄值中的第一個詞彙為 0,第二個為 1,依此類推) 來識別詞彙位置。利用這個索引,FTS5 能夠及時回答諸如「包含詞彙 'A' 的所有文件集合」或「包含序列 'Y Z' 的所有文件集合」等查詢。與單個詞彙關聯的位置列表稱為「位置列表」(instance-list)。
FTS3/4 和 FTS5 的主要區別在於,在 FTS3/4 中,每個位置列表都儲存為單個大型資料庫記錄,而在 FTS5 中,大型位置列表則劃分為多個資料庫記錄。這對於處理包含大型列表的大型資料庫有以下影響:
FTS5 能夠以遞增方式將位置列表載入記憶體,以減少記憶體使用量和峰值分配大小。FTS3/4 通常會將整個位置列表載入記憶體。
在處理包含多個詞彙的查詢時,FTS5 有時能夠判斷只需檢查大型位置列表的子集即可回答查詢。FTS3/4 幾乎總是需要遍歷整個位置列表。
基於這些原因,許多複雜查詢使用 FTS5 可以減少記憶體使用量並加快執行速度。
FTS5 與 FTS3/4 的其他一些不同之處如下:
FTS5 支援 "ORDER BY rank",可以按照相關性遞減的順序返回結果。
FTS5 提供了一個 API,允許使用者建立自訂的輔助函數,以用於進階排名和文字處理應用程式。特殊的 "rank" 欄位可以映射到自訂的輔助函數,以便在查詢中新增 "ORDER BY rank" 時能夠按預期工作。
FTS5 預設會辨識 Unicode 分隔字元和大小寫等價。FTS3/4 也可以做到這一點,但必須明確啟用。
查詢語法已在必要時進行了修訂,以消除歧義,並使轉義查詢詞彙中的特殊字元成為可能。
預設情況下,FTS3/4 會在使用者執行的 INSERT、UPDATE 或 DELETE 陳述式中,偶爾將構成其全文索引的兩個或多個 B 樹合併在一起。這意味著任何對 FTS3/4 資料表的運算都可能意外地變慢,因為 FTS3/4 可能會不可預測地選擇將其中的兩個或多個大型 B 樹合併在一起。FTS5 預設使用遞增合併,這限制了任何給定的 INSERT、UPDATE 或 DELETE 運算中可能發生的處理量。