Расширение SQLite FTS5
Содержание
1. Обзор FTS5
FTS5 — это модуль виртуальных таблиц SQLite, предоставляющий приложениям баз данных функциональность полнотекстового поиска. В простейшем виде системы полнотекстового поиска позволяют пользователю эффективно искать в большой коллекции документов подмножество, содержащее одно или несколько вхождений искомого термина. Функция поиска, доступная пользователям всемирной паутины через Google, в частности, представляет собой систему полнотекстового поиска, позволяющую пользователям искать все документы в интернете, содержащие, например, термин «fts5».
Для использования FTS5 пользователь создает виртуальную таблицу FTS5 с одним или несколькими столбцами. Например:
CREATE VIRTUAL TABLE email USING fts5(sender, title, body);
Добавление типов, ограничений или объявлений PRIMARY KEY в инструкцию CREATE VIRTUAL TABLE, используемую для создания таблицы FTS5, приводит к ошибке. После создания таблицу FTS5 можно заполнять с помощью инструкций INSERT, UPDATE или DELETE, как и любую другую таблицу. Как и любая другая таблица без объявления PRIMARY KEY, таблица FTS5 имеет неявное поле INTEGER PRIMARY KEY с именем rowid.
В приведенном выше примере не показано, что в инструкцию CREATE VIRTUAL TABLE также можно включить различные параметры FTS5 для настройки отдельных аспектов новой таблицы. Их можно использовать, чтобы изменить способ извлечения терминов из документов и запросов таблицей FTS5, создать на диске дополнительные индексы для ускорения префиксных запросов или создать таблицу FTS5, которая будет индексом содержимого, хранящегося в другом месте.
После заполнения содержимого таблицы FTS5 выполнить полнотекстовый запрос можно тремя способами:
- С помощью оператора MATCH в предложении WHERE инструкции SELECT;
- С помощью оператора равенства («=») в предложении WHERE инструкции SELECT; или
- с помощью синтаксиса табличной функции.
При использовании операторов MATCH или = выражение слева от оператора 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 не учитывает регистр. Как и любой другой SQL-запрос без предложения ORDER BY, приведенный выше пример возвращает результаты в произвольном порядке. Чтобы отсортировать результаты по релевантности (от наиболее релевантных к наименее релевантным), к полнотекстовому запросу можно добавить 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 позволяет искать строки, содержащие:
- любые термины, начинающиеся с заданного префикса;
- «фразы» — последовательности терминов или префиксных терминов, которые должны присутствовать в документе, чтобы он соответствовал запросу;
- наборы терминов, префиксных терминов или фраз, расположенные на заданном расстоянии друг от друга (такие запросы называются запросами «NEAR»); или
- логические комбинации любых из перечисленных выше условий.
Такие расширенные запросы задаются более сложной строкой запроса FTS5 в качестве текста справа от оператора MATCH (или оператора = либо первого аргумента в синтаксисе табличной функции). Полный синтаксис запросов описан здесь.
2. Сборка и использование FTS5
2.1. Сборка FTS5 как части SQLite
Начиная с версии 3.9.0 (2015-10-14), FTS5 входит в состав амальгамации SQLite. При использовании канонического дерева исходного кода FTS5 включается указанием параметра «--enable-fts5» при запуске скрипта configure. (В настоящее время FTS5 по умолчанию отключен для скрипта configure дерева исходного кода и включен для скрипта configure амальгамации, но в будущем значения по умолчанию могут измениться.)
Если sqlite3.c компилируется с помощью другой системы сборки, для этого нужно определить символ препроцессора SQLITE_ENABLE_FTS5.
2.2. Сборка загружаемого расширения
Кроме того, FTS5 можно собрать как загружаемое расширение.
Канонический исходный код FTS5 состоит из ряда файлов *.c и других файлов в каталоге «ext/fts5» дерева исходного кода SQLite. В процессе сборки они сводятся к двум файлам — «fts5.c» и «fts5.h», — которые можно использовать для сборки загружаемого расширения SQLite.
- Получите последнюю версию кода SQLite из fossil.
- Создайте Makefile, как описано в разделе Как скомпилировать SQLite.
- Соберите цель «fts5.c», при этом также будет создан fts5.h.
$ wget -c https://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» можно скомпилировать в загружаемое расширение или статически связать с приложением, как описано в разделе Сборка загружаемых расширений. Определены две точки входа, выполняющие одно и то же:
- sqlite3_fts_init
- sqlite3_fts5_init
Второй файл, «fts5.h», не требуется для компиляции расширения FTS5. Он используется приложениями, реализующими пользовательские токенизаторы или вспомогательные функции FTS5.
3. Синтаксис полнотекстовых запросов
В следующем блоке приведена сводка синтаксиса запросов FTS в форме BNF. Далее следует подробное объяснение.
<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 ... }
3.1. Строки FTS5
В выражении FTS строку можно задать одним из двух способов:
-
Заключив ее в двойные кавычки ("). Внутри строки двойные кавычки можно экранировать в стиле SQL — добавив второй символ двойной кавычки.
-
В виде некавыченного слова FTS5, отличного от «AND», «OR» и «NOT» (с учетом регистра). Некавыченное слово FTS5 — это строка из одного или нескольких идущих подряд символов, каждый из которых является одним из следующих:
- Символы из диапазона не-ASCII (то есть кодовые точки Unicode больше 127); или
- Один из 52 символов ASCII — букв верхнего или нижнего регистра; или
- Один из 10 десятичных символов ASCII; или
- Символ подчеркивания (кодовая точка Unicode 95);
- Символ замены (кодовая точка Unicode 26).
3.2. Фразы 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'
Фраза соответствует документу, если документ содержит хотя бы одну последовательность токенов, совпадающую с последовательностью токенов, составляющих эту фразу.
3.3. Префиксные запросы FTS5
Если за строкой в выражении FTS следует символ «*», последний токен, извлеченный из строки, помечается как префиксный токен. Как и следовало ожидать, префиксный токен соответствует любому токену документа, для которого он является префиксом. Например, первые два запроса в следующем блоке найдут любой документ, содержащий токен «one», за которым непосредственно следует токен «two», а затем любой токен, начинающийся с «thr».
... MATCH '"one two thr" * ' ... MATCH 'one + two + thr*' ... MATCH '"one two thr*"' -- May not work as expected!
Последний запрос в блоке выше может работать не так, как ожидается. Поскольку символ «*» находится внутри двойных кавычек, он будет передан токенизатору, который, вероятно, отбросит его (или, в зависимости от используемого токенизатора, включит в последний токен), а не распознает как специальный символ FTS.
3.4. Запросы FTS5 по начальным токенам
Если непосредственно перед фразой, не входящей в запрос 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!
3.5. Запросы FTS5 NEAR
Две или более фраз можно объединить в группу NEAR. Группа NEAR задается токеном «NEAR» (с учетом регистра), за которым следует открывающая скобка, затем две или более фраз, разделенных пробелами, при необходимости запятая и числовой параметр N, а затем закрывающая скобка. Например:
... MATCH 'NEAR("one two" "three four", 10)'
... MATCH 'NEAR("one two" thr* + four)'
Если параметр N не указан, по умолчанию используется значение 10. Группа NEAR соответствует документу, если он содержит хотя бы один кластер токенов, который:
- содержит хотя бы одно вхождение каждой фразы; и
- в котором число токенов между концом первой фразы и началом последней фразы кластера меньше или равно N.
Например:
CREATE VIRTUAL TABLE ft USING fts5(x);
INSERT INTO ft(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!
3.6. Фильтры столбцов FTS5
Одну фразу или группу 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';
3.7. Логические операторы FTS5
Фразы и группы 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!
4. Создание и инициализация таблиц FTS5
Каждый аргумент, указанный в инструкции «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');
В настоящее время доступны следующие параметры конфигурации:
- Параметр «tokenize», используемый для настройки пользовательского токенизатора.
- Параметр «prefix», используемый для добавления префиксных индексов в таблицу FTS5.
- Параметр «content», используемый для создания таблицы с внешним содержимым или без содержимого.
- Параметр «content_rowid», используемый для задания поля rowid таблицы с внешним содержимым.
- Параметр «columnsize», задающий, сохраняется ли отдельно в базе данных размер каждого значения таблицы FTS5 в токенах.
- Параметр «detail». Его можно использовать для уменьшения размера индекса FTS на диске за счет исключения из него некоторой информации.
4.1. Параметр столбца UNINDEXED
Содержимое столбцов, для которых задан параметр UNINDEXED, не добавляется в индекс FTS. Это означает, что при выполнении запросов MATCH и работе вспомогательных функций FTS5 в столбце нет токенов, по которым можно выполнять поиск.
Например, чтобы не добавлять содержимое поля «uuid» в индекс FTS:
CREATE VIRTUAL TABLE customers USING fts5(name, addr, uuid UNINDEXED);
4.2. Префиксные индексы
По умолчанию 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);
4.3. Токенизаторы
Параметр CREATE VIRTUAL TABLE «tokenize» используется для настройки токенизатора таблицы FTS5. Аргумент параметра должен быть некавыченным словом FTS5 или текстовым литералом SQL. Сам текст аргумента интерпретируется как последовательность из одного или нескольких некавыченных слов FTS5 или текстовых литералов SQL, разделенных пробелами. Первый элемент — это имя токенизатора. Второй и последующие элементы списка, если они есть, передаются реализации токенизатора в качестве аргументов.
В отличие от значений параметров и имен столбцов, текстовые литералы SQL, задающие токенизаторы, необходимо заключать в одинарные кавычки. Например:
-- The following are all equivalent CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = 'porter ascii'); CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = "porter ascii"); CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = "'porter' 'ascii'"); CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '''porter'' ''ascii'''); -- But this will fail: CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '"porter" "ascii"'); -- This will fail too: CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = 'porter' 'ascii');
В FTS5 предусмотрены четыре встроенных модуля токенизаторов, описанные в следующих разделах:
- Токенизатор unicode61, основанный на стандарте Unicode 6.1. Используется по умолчанию.
- Токенизатор ascii, который считает все символы за пределами диапазона кодовых точек ASCII (0–127) символами токенов.
- Токенизатор porter, реализующий алгоритм стемминга Портера.
- Токенизатор trigram, который рассматривает каждую непрерывную последовательность из трех символов как токен, позволяя FTS5 поддерживать более общий поиск подстрок.
Для FTS5 также можно создавать пользовательские токенизаторы. Соответствующий API описан здесь.
4.3.1. Токенизатор Unicode61
Токенизатор unicode классифицирует все символы Unicode как символы-разделители или символы токенов. По умолчанию все пробельные символы и знаки пунктуации, согласно определению Unicode 6.1, считаются разделителями, а все остальные символы — символами токенов. Точнее, токенами считаются все символы Unicode, отнесенные к общей категории, начинающейся с «L» или «N» (в частности, буквы и цифры), либо к категории «Co» («прочее, частное использование»). Все остальные символы являются разделителями.
Каждая непрерывная последовательность из одного или нескольких символов токенов считается токеном. Токенизатор не учитывает регистр согласно правилам Unicode 6.1.
По умолчанию диакритические знаки удаляются у всех символов латинской письменности. Это означает, например, что символы «A», «a», «À», «à», «Â» и «â» считаются эквивалентными.
Все аргументы, следующие за «unicode61» в спецификации токенизатора, трактуются как список чередующихся имен параметров и значений. Токенизатор Unicode61 поддерживает следующие параметры:
| Параметр | Использование |
|---|---|
| remove_diacritics | Для этого параметра следует задать значение "0", "1" или "2". Значение по умолчанию — "1". Если задано значение "1" или "2", диакритические знаки удаляются из символов латинского алфавита, как описано выше. Однако при значении "1" диакритические знаки не удаляются в довольно редком случае, когда для представления символа с несколькими диакритическими знаками используется одна кодовая точка Unicode. Например, диакритические знаки не удаляются из кодовой точки 0x1ED9 ("LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW"). Технически это ошибка, но исправить её нельзя без проблем с обратной совместимостью. Если для этого параметра задано значение "2", диакритические знаки корректно удаляются из всех символов латинского алфавита. |
| categories | Этот параметр позволяет изменить набор общих категорий Unicode, которые считаются соответствующими символам токенов. Аргумент должен состоять из разделённого пробелами списка двухсимвольных сокращений общих категорий (например, "Lu" или "Nd") либо из таких же сокращений, в которых второй символ заменён звёздочкой ("*"), интерпретируемой как шаблон glob. Значение по умолчанию — "L* N* Co". |
| tokenchars | Этот параметр задаёт дополнительные символы Unicode, которые следует считать символами токенов, даже если согласно Unicode 6.1 они являются пробельными символами или знаками пунктуации. Все символы строки, заданной для этого параметра, считаются символами токенов. |
| separators | Этот параметр задаёт дополнительные символы 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.
4.3.2. Токенизатор Ascii
Токенизатор Ascii похож на токенизатор Unicode61, за исключением следующего:
- Все символы не ASCII (с кодовыми точками больше 127) всегда считаются символами токенов. Если какие-либо символы не ASCII указаны в параметре separators, они игнорируются.
- Приведение регистра выполняется только для символов ASCII. Поэтому "A" и "a" считаются эквивалентными, а "Ã" и "ã" — разными.
- Параметр remove_diacritics не поддерживается.
Например:
-- 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'"
);
4.3.3. Токенизатор Porter
Токенизатор porter является обёрточным токенизатором. Он получает результат другого токенизатора и применяет к каждому токену алгоритм стемминга Porter, прежде чем передать его FTS5. Благодаря этому поисковые запросы с такими словами, как "correction", могут находить похожие слова, например "corrected" или "correcting". Алгоритм стеммера Porter предназначен только для английских слов — его использование с другими языками может как улучшить, так и не улучшить качество поиска.
По умолчанию токенизатор porter работает как обёртка над токенизатором по умолчанию (unicode61). Если после "porter" в параметре "tokenize" указаны один или несколько дополнительных аргументов, они рассматриваются как параметры базового токенизатора, используемого стеммером 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 ft USING fts5(x, tokenize = porter); CREATE VIRTUAL TABLE ft 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 ft USING fts5(x, tokenize = 'porter unicode61 remove_diacritics 1');
4.3.4. Токенизатор Trigram
Токенизатор 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 (по умолчанию). Значение 1 допустимо только при значении 0 для параметра case_sensitive — задавать значение 1 для обоих параметров нельзя. Если этот параметр включён, перед поиском из текста удаляются диакритические знаки (например, чтобы "á" совпадало с "a"). |
-- A case-sensitive trigram index CREATE VIRTUAL TABLE tri USING fts5(a, tokenize="trigram case_sensitive 1");
Если параметр remove_diacritics не задан, таблицы FTS5, использующие токенизатор trigram, также поддерживают индексированный поиск по шаблонам GLOB и LIKE. Например:
SELECT * FROM tri WHERE a LIKE '%cdefg%'; SELECT * FROM tri WHERE a GLOB '*ij klm*xyz';
Если токенизатор FTS5 trigram создан со значением 1 для параметра case_sensitive, он может индексировать запросы GLOB, но не LIKE.
Примечания:
- Подстроки, состоящие менее чем из 3 символов Unicode, не соответствуют ни одной строке при использовании в полнотекстовом запросе. Если шаблон LIKE или GLOB не содержит хотя бы одной последовательности символов Unicode, не являющихся подстановочными знаками, FTS5 выполняет полный линейный просмотр таблицы.
- Если таблица FTS5 создана с параметром detail=none или detail=column, полнотекстовые запросы не могут содержать токены длиннее 3 символов Unicode. Поиск по шаблонам LIKE и GLOB может быть немного медленнее, но всё равно работает. Если индекс будет использоваться только для поиска по шаблонам LIKE и/или GLOB, стоит поэкспериментировать с этими параметрами, чтобы уменьшить размер индекса.
- Индекс нельзя использовать для оптимизации шаблонов LIKE, если оператор LIKE содержит предложение ESCAPE.
4.4. Таблицы с внешним содержимым и таблицы без содержимого
Обычно при вставке строки в таблицу FTS5, помимо построения индекса, FTS5 сохраняет копию исходного содержимого строки. Когда пользователь или реализация вспомогательной функции запрашивает значения столбцов из таблицы FTS5, эти значения считываются из этой частной копии содержимого. Параметр "content" позволяет создать таблицу FTS5, в которой хранятся только записи полнотекстового индекса FTS. Поскольку значения столбцов обычно намного больше соответствующих записей полнотекстового индекса, это может существенно сократить объём занимаемого базой данных пространства.
Параметр "content" можно использовать двумя способами:
- Задать ему пустую строку, чтобы создать таблицу FTS5 без содержимого. В этом случае FTS5 считает, что исходные значения столбцов недоступны при обработке запросов. Полнотекстовые запросы и некоторые вспомогательные функции по-прежнему можно использовать, но из таблицы нельзя считывать значения столбцов, кроме rowid.
- Задать ему имя объекта базы данных (таблицы, виртуальной таблицы или представления), к которому FTS5 может обращаться в любой момент для получения значений столбцов. Такая таблица называется таблицей с "внешним содержимым". В этом случае доступны все возможности FTS5, однако пользователь несёт ответственность за согласованность содержимого полнотекстового индекса с указанным объектом базы данных. Если данные несогласованны, результаты запросов могут быть непредсказуемыми.
4.4.1. Таблицы без содержимого
Таблица FTS5 без содержимого создаётся, если задать для параметра "content" пустую строку. Например:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, content='');
Таблицы FTS5 без содержимого не поддерживают инструкции UPDATE и DELETE, а также инструкции INSERT, в которых для поля rowid не задано значение, отличное от NULL. Таблицы без содержимого не поддерживают обработку конфликтов REPLACE. Инструкции REPLACE и INSERT OR REPLACE обрабатываются как обычные инструкции INSERT. Строки можно удалять из таблицы без содержимого с помощью команды удаления FTS5.
При попытке прочитать из таблицы FTS5 без содержимого значение любого столбца, кроме rowid, возвращается значение SQL NULL.
4.4.2. Таблицы без содержимого с поддержкой удаления
Начиная с версии 3.43.0 также доступны таблицы без содержимого с поддержкой удаления. Такая таблица создаётся, если задать для параметра content пустую строку, а для параметра contentless_delete — значение 1. Например:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, content='', contentless_delete=1);
Таблица без содержимого с поддержкой удаления отличается от обычной таблицы без содержимого следующим:
- Таблицы без содержимого с поддержкой удаления поддерживают инструкции DELETE и "INSERT OR REPLACE INTO".
- Таблицы без содержимого с поддержкой удаления поддерживают инструкции UPDATE, но только если заданы новые значения для всех определённых пользователем столбцов таблицы fts5.
- Таблицы без содержимого с поддержкой удаления не поддерживают команду удаления FTS5.
-- Supported UPDATE statement: UPDATE ft SET a=?, b=?, c=? WHERE rowid=?; -- This UPDATE is not supported, as it does not supply a new value -- for column "c". UPDATE ft SET a=?, b=? WHERE rowid=?;
Если обратная совместимость не требуется, в новом коде следует отдавать предпочтение таблицам без содержимого с поддержкой удаления, а не таблицам без содержимого.
4.4.3. Таблицы с внешним содержимым
Таблица FTS5 с внешним содержимым создаётся, если задать для параметра content имя таблицы, виртуальной таблицы или представления (далее — "таблица содержимого") в той же базе данных. Когда FTS5 требуются значения столбцов, он обращается к таблице содержимого следующим образом, подставляя в SQL-переменную rowid строки, значения которой нужны:
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 t1 (a, b, c, d INTEGER PRIMARY KEY); CREATE VIRTUAL TABLE ft USING fts5(a, c, content=t1, content_rowid=d); -- Fts5 may issue queries such as: SELECT d, a, c FROM t1 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 t1(a INTEGER PRIMARY KEY, b, c);
CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='t1', content_rowid='a');
-- Triggers to keep the FTS index up to date.
CREATE TRIGGER t1_ai AFTER INSERT ON t1 BEGIN
INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
END;
CREATE TRIGGER t1_ad AFTER DELETE ON t1 BEGIN
INSERT INTO fts_idx(fts_idx, rowid, b, c) VALUES('delete', old.a, old.b, old.c);
END;
CREATE TRIGGER t1_au AFTER UPDATE ON t1 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.
4.4.4. Подводные камни таблиц с внешним содержимым
Пользователь несёт ответственность за согласованность внешней таблицы 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 t1(a INTEGER PRIMARY KEY, t TEXT); INSERT INTO t1 VALUES(1, 'all that glitters'); INSERT INTO t1 VALUES(2, 'is not gold'); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='t1', content_rowid='a');
то таблица содержимого содержит две строки, но в индексе FTS нет соответствующих им записей. В этом случае следующие запросы вернут противоречивые результаты:
-- Returns 2 rows. Because the query does not use the FTS index, it is
-- effectively executed against table 't1' directly, and so returns
-- both rows.
SELECT * FROM ft;
-- Returns 0 rows. This query does use the FTS index, which currently
-- contains no entries. So it returns 0 rows.
SELECT rowid, t FROM ft('gold')
В качестве альтернативы база данных могла быть создана и заполнена следующим образом:
-- Create and populate a table. CREATE TABLE t1(a INTEGER PRIMARY KEY, t TEXT); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='t1', 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 't1', which contains no data.
SELECT * FROM ft;
-- 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 "t1" 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 ft('gold')
Как описано в предыдущем разделе, триггеры на таблице содержимого — хороший способ обеспечить согласованность внешней таблицы FTS5. Однако триггеры срабатывают только при вставке, обновлении или удалении строк в таблице содержимого. Это означает, что, например, если база данных создана следующим образом:
-- Create and populate a table. CREATE TABLE t1(a INTEGER PRIMARY KEY, t TEXT); INSERT INTO t1 VALUES(1, 'all that glitters'); INSERT INTO t1 VALUES(2, 'is not gold'); -- Create an external content FTS5 table CREATE VIRTUAL TABLE ft USING fts5(t, content='t1', content_rowid='a'); -- Create triggers to keep the FTS5 table up to date CREATE TRIGGER t1_ai AFTER INSERT ON t1 BEGIN INSERT INTO ft(rowid, t) VALUES (new.a, new.t); END; <similar triggers for update + delete>
то таблица содержимого и внешняя таблица FTS5 несогласованны, поскольку создание триггеров не копирует существующие строки из таблицы содержимого в индекс FTS. Триггеры обеспечивают отражение в индексе FTS только тех изменений таблицы содержимого, которые внесены после создания триггеров.
В этом и любом другом случае, когда индекс FTS и его таблица содержимого рассогласованы, команду 'rebuild' можно использовать, чтобы полностью удалить содержимое индекса FTS и заново построить его на основе текущего содержимого таблицы.
4.4.4.1. Обновление и удаление в таблицах без содержимого
Таблицы без содержимого поддерживают инструкции UPDATE и DELETE, но использовать их следует осторожно. При выполнении DELETE для таблицы без содержимого текущие значения каждой затронутой строки извлекаются запросом к таблице содержимого следующим образом:
SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;
Затем извлечённые значения передаются настроенному для таблицы токенизатору, который выделяет из них список токенов, подлежащих удалению из индекса FTS. Если извлечённые значения не соответствуют набору записей индекса FTS для этой строки, некоторые токены могут остаться в индексе FTS, что позднее приведёт к несогласованным результатам запросов.
На практике это означает, что для удаления строки одновременно из таблицы содержимого и внешней таблицы FTS5 сначала нужно обновить таблицу FTS5, чтобы строка ещё была доступна в таблице содержимого.
В FTS5 инструкция UPDATE реализована как DELETE с последующей вставкой новых значений. Следовательно, при обновлении строки одновременно в таблице содержимого и внешней таблице FTS5 сначала нужно обновить таблицу FTS5 (опять же, чтобы строка таблицы содержимого оставалась доступной).
4.5. Параметр Columnsize
Обычно FTS5 поддерживает специальную вспомогательную таблицу в базе данных, в которой отдельно хранится размер каждого значения столбца в токенах, вставленных в основную таблицу FTS5. Эта вспомогательная таблица используется API-функцией xColumnSize, которая, в свою очередь, применяется встроенной функцией ранжирования bm25 (и, вероятно, полезна также другим функциям ранжирования).
Чтобы сэкономить место, эту вспомогательную таблицу можно не создавать, задав для параметра columnsize значение 0. Например:
-- 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, но она не является таблицей без содержимого, API-функция xColumnSize продолжает работать, но значительно медленнее. В этом случае вместо непосредственного чтения возвращаемого значения из базы данных функция считывает сам текст и при необходимости подсчитывает содержащиеся в нём токены.
Если же таблица также является таблицей без содержимого, применяются следующие правила:
-
API-функция xColumnSize всегда возвращает -1. Определить количество токенов в значении, хранящемся в таблице FTS5 без содержимого с параметром columnsize=0, невозможно.
-
При каждой вставке строки необходимо явно указывать значение rowid. Если для таблицы без содержимого задан параметр columnsize=0, попытка вставить значение NULL в rowid приводит к ошибке SQLITE_MISMATCH.
-
Все запросы к таблице должны быть полнотекстовыми. Иными словами, они должны использовать оператор MATCH или = с именем столбца таблицы в качестве левого операнда либо синтаксис табличной функции. Любой запрос, не являющийся полнотекстовым, приводит к ошибке.
Имя таблицы, в которой хранятся значения xColumnSize (если не задано columnsize=0), имеет вид "<name>_docsize", где <name> — имя самой таблицы FTS5. Инструмент sqlite3_analyzer можно использовать для анализа существующей базы данных и определения того, сколько места можно сэкономить, пересоздав таблицу FTS5 с параметром columnsize=0.
4.6. Параметр Detail
Для каждого термина в документе индекс FTS, поддерживаемый FTS5, хранит rowid документа, номер столбца, содержащего термин, и смещение термина в значении столбца. Параметр "detail" позволяет не сохранять часть этой информации. Это уменьшает объём места, занимаемого индексом в файле базы данных, но также снижает возможности системы и её эффективность.
Для параметра detail можно задать значение "full" (по умолчанию), "column" или "none". Например:
-- The following two lines are equivalent (because the default value -- of "detail" is "full". CREATE VIRTUAL TABLE ft USING fts5(a, b, c); CREATE VIRTUAL TABLE ft USING fts5(a, b, c, detail=full); CREATE VIRTUAL TABLE ft USING fts5(a, b, c, detail=column); CREATE VIRTUAL TABLE ft USING fts5(a, b, c, detail=none);
Если для параметра detail задано значение column, индекс FTS хранит для каждого термина только rowid и номер столбца, не сохраняя информацию о смещении термина. Это приводит к следующим ограничениям:
- Запросы NEAR недоступны.
- Запросы с фразами недоступны.
- Если таблица не является также таблицей без содержимого, функции xInstCount, xInst, xPhraseFirst и xPhraseNext работают медленнее обычного. Это происходит потому, что вместо считывания необходимых данных непосредственно из индекса FTS им приходится по запросу загружать и токенизировать текст документа.
- Если таблица также является таблицей без содержимого, API-функции xInstCount, xInst, xPhraseFirst и xPhraseNext ведут себя так, будто в текущей строке вообще нет совпадений с фразами (то есть xInstCount() возвращает 0).
Если для параметра detail задано значение none, индекс FTS хранит для каждого термина только rowid. Информация о столбце и смещении не сохраняется. Помимо перечисленных выше для режима detail=column ограничений, это накладывает следующие дополнительные ограничения:
- Запросы с фильтром по столбцу недоступны.
- Если таблица не является также таблицей без содержимого, функции xPhraseFirstColumn и xPhraseNextColumn работают медленнее обычного.
- Если таблица также является таблицей без содержимого, API-функции xPhraseFirstColumn и xPhraseNextColumn ведут себя так, будто в текущей строке вообще нет совпадений с фразами (то есть xPhraseFirstColumn() устанавливает итератор в EOF).
В одном тесте, в котором индексировался большой набор электронных писем (1636 МиБ на диске), размер индекса FTS составил 743 МиБ при detail=full, 340 МиБ при detail=column и 134 МиБ при detail=none.
4.7. Параметр Tokendata
Этот параметр полезен только приложениям, реализующим пользовательские токенизаторы. Обычно токенизаторы могут возвращать токены, состоящие из любой последовательности байтов, включая байты 0x00. Однако если для таблицы задан параметр tokendata=1, FTS5 при поиске игнорирует первый байт 0x00 и все последующие данные токена. При этом токен сохраняется целиком в том виде, в котором его вернул токенизатор, но остальная часть игнорируется ядром fts5.
Полная версия токена, включая любой байт 0x00 и последующие данные, доступна пользовательским вспомогательным функциям через API-функции xQueryToken и xInstToken.
Это может быть полезно для функций ранжирования. Пользовательский токенизатор может добавлять дополнительные данные к некоторым токенам документа, позволяя функции ранжирования придавать больший вес совпадениям с определёнными токенами (например, находящимися в заголовках документа).
Сочетание пользовательского токенизатора и пользовательской вспомогательной функции можно использовать для реализации асимметричного поиска. Например, токенизатор может возвращать для каждого токена документа версию токена в нижнем регистре и без диакритических знаков, за которой следуют байт 0x00 и полный текст токена из документа. При выполнении запроса fts5 выдаст результаты так, как если бы все символы запроса были приведены к нижнему регистру и очищены от диакритических знаков. Затем пользовательскую вспомогательную функцию можно использовать в предложении WHERE запроса, чтобы отфильтровать строки, не совпадающие по вторичным или третичным признакам в документе или поисковых терминах.
4.8. Параметр Locale
Этот параметр полезен только приложениям, реализующим пользовательские токенизаторы. Если таблица fts5 создана с параметром "locale=1", для привязки значения локали (например, "th_TH" или "en_US") к строкам, передаваемым в FTS5, можно использовать SQL-функцию fts5_locale(). FTS5 не использует значения локали самостоятельно, но предоставляет их реализации токенизатора при токенизации каждой строки. После этого токенизатор может изменить своё поведение в зависимости от локали.
-- The following statement creates an fts5 table with locale support.
-- The "tokenizer=..." option below must be replaced with a real tokenizer
-- specification for a tokenizer that supports locales.
CREATE VIRTUAL TABLE ft USING fts5(a, b, locale=1, tokenizer=...);
-- This statement inserts a row into the table. The value inserted into
-- column "a" uses locale "th_TH", the value written to column "b" uses the
-- tokenizer's default locale
INSERT INTO ft(a, b) VALUES(
fts5_locale('th_TH', 'Tokenize this in Thai locale'),
'Tokenize this in the default locale.'
);
-- The "en_US" locale is used to tokenize the query terms in the
--following query.
SELECT * FROM ft( fts5_locale('en_US', 'query terms') );
Попытка передать строку fts5_locale() таблице fts5, созданной без параметра locale=1, приводит к ошибке.
Когда строка fts5_locale() сохраняется в обычной таблице содержимого (то есть не в таблице без содержимого и не во внешней таблице содержимого), связанная с ней локаль также сохраняется. Если FTS5 снова токенизирует эту строку, например при удалении её строки или в рамках вычисления вспомогательной функции, связанная с ней локаль снова передаётся реализации токенизатора.
Для поддержки локалей внешняя таблица содержимого FTS5 может использовать в качестве таблицы содержимого представление SQL, возвращающее значения fts5_locale(). Например:
-- Each row of this table contains a string and its locale.
CREATE TABLE t1(val, locale);
INSERT INTO t1 VALUES('a text value', 'en_US');
-- A view to combine the string and locale from table t1.
CREATE VIEW v1 AS SELECT rowid, fts5_locale(val, locale) AS val FROM t1;
-- An FTS5 table to read locale-enabled strings from view v1.
CREATE VIRTUAL TABLE ft USING fts5(val, locale=1, content=v1, tokenize=...);
Если значение fts5_locale() записывается в столбец UNINDEXED таблицы fts5, значение локали отбрасывается и сохраняется только сама строка.
Функцию fts5_get_locale() можно использовать для получения локали значения, хранящегося в таблице FTS5.
4.9. Параметр Contentless-Unindexed
Обычно столбцы UNINDEXED в таблицах без содержимого мало полезны. Записанные в них значения не индексируются и не сохраняются, а при чтении такого столбца UNINDEXED всегда возвращается NULL. Однако если для таблицы без содержимого задан параметр "contentless_unindexed=1", значения столбцов UNINDEXED сохраняются постоянно, хотя значения, записанные в другие столбцы, не сохраняются.
-- Create a contentless table with the contentless_unindexed=1 option.
-- Of the row written to it, the value 'one' will be indexed and then
-- discarded, and the value "1" will be stored but not indexed.
CREATE VIRTUAL TABLE ft USING fts5(a, b UNINDEXED, content='', contentless_unindexed=1);
INSERT INTO ft(a, b) VALUES('one', 1);
-- This query returns 1 row with 2 columns - (NULL, 1). Reading from
-- column "a" is always NULL, as the table is contentless. But reading from
-- "b" returns the value, as the table uses contentless_unindexed=1.
SELECT a, b FROM ft('one');
Задать contentless_unindexed=1 для таблицы fts5, которая не является таблицей без содержимого или таблицей без содержимого с поддержкой удаления, нельзя.
5. Вспомогательные функции
Вспомогательные функции похожи на скалярные функции SQL, но их можно использовать только в полнотекстовых запросах (запросах с оператором MATCH или с LIKE/GLOB при использовании токенизатора trigram) к таблице FTS5. Их результаты вычисляются на основе не только переданных им аргументов, но и текущего совпадения и совпавшей строки. Например, вспомогательная функция может возвращать числовое значение, указывающее точность совпадения (см. функцию bm25()), или фрагмент текста из совпавшей строки, содержащий одно или несколько вхождений поисковых терминов (см. функцию snippet()).
Для вызова вспомогательной функции в качестве первого аргумента следует указать имя таблицы FTS5. В зависимости от вызываемой вспомогательной функции после первого аргумента могут следовать другие аргументы. Например, для вызова функции "highlight":
-- Assuming fts5 table: CREATE VIRTUAL TABLE ft USING fts5(a, b, c); -- Invoke the highlight() function: SELECT highlight(ft, 2, '<b>', '</b>') FROM ft WHERE ft MATCH 'fts5'
Встроенные вспомогательные функции FTS5 описаны в следующем разделе. Приложения также могут реализовать пользовательские вспомогательные функции на C.
5.1. Встроенные вспомогательные функции
FTS5 предоставляет три встроенные вспомогательные функции:
- Вспомогательная функция bm25() возвращает вещественное значение, отражающее точность текущего совпадения. Более точным совпадениям присваиваются численно меньшие значения.
- Вспомогательная функция highlight() возвращает копию текста из одного из столбцов текущего совпадения, заключая каждое вхождение запрошенного термина в результат в заданную разметку (например, "<b>" и "</b>").
- Вспомогательная функция snippet() выбирает короткий фрагмент текста из одного из столбцов совпавшей строки и возвращает его, заключая каждое вхождение запрошенного термина в разметку так же, как функция highlight(). Фрагмент текста выбирается таким образом, чтобы содержать как можно больше различных запрошенных терминов. Более высокий приоритет получают фрагменты в начале значения столбца или сразу после символов "." или ":" в тексте.
- Вспомогательная функция fts5_get_locale() используется для получения локали, если она связана со значением, хранящимся в таблице FTS5.
5.1.1. Функция bm25()
Встроенная вспомогательная функция bm25() возвращает вещественное значение, показывающее, насколько хорошо текущая строка соответствует полнотекстовому запросу. Чем лучше совпадение, тем меньше возвращаемое числовое значение. Чтобы вернуть совпадения в порядке от лучших к худшим, можно использовать, например, такой запрос:
SELECT * FROM ft WHERE ft MATCH ? ORDER BY bm25(ft)
Для вычисления оценки документа полнотекстовый запрос разбивается на составляющие его фразы. Затем оценка bm25 для документа D и запроса Q вычисляется следующим образом:
В приведённой выше формуле nPhrase — количество фраз в запросе. |D| — количество токенов в текущем документе, а avgdl — среднее количество токенов во всех документах таблицы FTS5. k1 и b — константы, жёстко заданные как 1.2 и 0.75 соответственно.
Член "-1" в начале формулы отсутствует в большинстве реализаций алгоритма BM25. Без него более точному совпадению присваивается численно большее значение BM25. Поскольку порядок сортировки по умолчанию — "по возрастанию", добавление "ORDER BY bm25(ft)" к запросу приведёт к возврату результатов от худших к лучшим. Чтобы сначала возвращались лучшие совпадения, потребовалось бы ключевое слово "DESC". Во избежание этой проблемы реализация BM25 в FTS5 умножает результат на -1 перед его возвратом, благодаря чему более точным совпадениям присваиваются численно меньшие оценки.
IDF(qi) — обратная частота документа для фразы i в запросе. Она вычисляется следующим образом, где N — общее количество строк в таблице FTS5, а n(qi) — общее количество строк, содержащих хотя бы одно вхождение фразы i:
Наконец, f(qi,D) — частота фразы i. По умолчанию это просто количество вхождений фразы в текущей строке. Однако, передав дополнительные вещественные аргументы SQL-функции bm25(), можно задать отдельный вес для каждого столбца таблицы и вычислять частоту фразы следующим образом:
где 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 и его вариантах см. в Википедии.
5.1.2. Функция highlight()
Функция highlight() возвращает копию текста из указанного столбца текущей строки, вставляя дополнительную разметку, обозначающую начало и конец совпадений с фразами.
После имени таблицы функции highlight() необходимо передать ровно три аргумента, которые интерпретируются следующим образом:
- Целое число, задающее индекс столбца таблицы FTS, из которого нужно прочитать текст. Нумерация столбцов начинается с нуля слева направо.
- Текст, вставляемый перед каждым совпадением с фразой.
- Текст, вставляемый после каждого совпадения с фразой.
Например:
-- Return a copy of the text from the leftmost column of the current -- row, with phrase matches marked using html "b" tags. SELECT highlight(ft, 0, '<b>', '</b>') FROM ft WHERE ft 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';
5.1.3. Функция snippet()
Функция snippet() похожа на highlight(), но вместо возврата значений столбца целиком она автоматически выбирает и извлекает короткий фрагмент текста документа для обработки и возврата. После аргумента с именем таблицы функции snippet() необходимо передать пять параметров:
- Целое число, задающее индекс столбца таблицы FTS, из которого нужно выбрать возвращаемый текст. Нумерация столбцов начинается с нуля слева направо. Отрицательное значение означает, что столбец нужно выбрать автоматически.
- Текст, вставляемый перед каждым совпадением с фразой в возвращаемом тексте.
- Текст, вставляемый после каждого совпадения с фразой в возвращаемом тексте.
- Текст, добавляемый в начало или конец выбранного фрагмента, чтобы указать, что возвращаемый текст не находится соответственно в начале или конце столбца.
- Максимальное количество токенов в возвращаемом тексте. Оно должно быть больше нуля и не превышать 64.
5.1.4. Функция fts5_get_locale()
Функция fts5_get_locale() используется для получения локали, если она есть, связанной со значением, хранящимся в таблице FTS5. После имени таблицы она принимает один аргумент — индекс столбца текущей строки, который требуется запросить. Нумерация столбцов начинается с 0 в порядке их появления в инструкции CREATE VIRTUAL TABLE.
Если таблица FTS5 не поддерживает локали (то есть была создана без параметра locale=1) или с указанным значением не связана локаль, эта функция возвращает NULL. В противном случае она возвращает текстовое значение — имя локали, связанной с указанным значением.
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1);
INSERT INTO ft VALUES(
'no locale',
fts5_locale('th_TH', 'Thai locale'),
fts5_locale('en_US', 'US locale')
);
-- The following statement returns a single row containing three values:
-- NULL, text value 'th_TH', and text value 'en_US'.
SELECT
fts5_get_locale(ft, 0),
fts5_get_locale(ft, 1),
fts5_get_locale(ft, 2)
FROM ft;
5.1.5. Функция fts5_insttoken()
Функцию fts5_insttoken() можно использовать, чтобы пометить запрос как требующий дополнительных данных для эффективной работы API xInstToken с запросами по префиксу. Подробности см. в документации по xInstToken, на которую ведёт ссылка. Функция используется следующим образом:
-- Use the fts5_insttoken() function to ensure that if the custom API
-- function used by this query invokes the xInstToken() API on a token
-- matched by the prefix 'quer*' , it does not have to make a second pass
-- of the index to gather the required data.
SELECT custom_api_function(ft) FROM ft( fts5_insttoken('prefix quer*') );
-- This query returns the same results, but may be less efficient if
-- the custom function invokes the xInstToken() API as described above.
SELECT custom_api_function(ft) FROM ft( 'prefix quer*' );
5.2. Сортировка по результатам вспомогательных функций
Во всех таблицах 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 ft WHERE ft MATCH ? ORDER BY bm25(ft); SELECT * FROM ft WHERE ft MATCH ? ORDER BY rank;
Вместо использования bm25() без завершающих аргументов конкретную вспомогательную функцию, сопоставленную столбцу rank, можно настроить отдельно для одного запроса либо задать другое постоянное значение по умолчанию для таблицы FTS.
Чтобы изменить сопоставление столбца 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 ft WHERE ft MATCH ? ORDER BY bm25(ft, 10.0, 5.0); SELECT * FROM ft WHERE ft MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank;
Для указания альтернативной функции ранжирования также можно использовать синтаксис табличной функции. В этом случае текст с описанием функции ранжирования следует указать в качестве второго аргумента табличной функции. Следующие три запроса эквивалентны:
SELECT * FROM ft WHERE ft MATCH ? AND rank MATCH 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM ft WHERE ft = ? AND rank = 'bm25(10.0, 5.0)' ORDER BY rank; SELECT * FROM ft WHERE ft(?, 'bm25(10.0, 5.0)') ORDER BY rank;
Сопоставление столбца rank по умолчанию для таблицы можно изменить с помощью параметра конфигурации ранжирования FTS5.
6. Специальные команды INSERT
6.1. Параметр конфигурации 'automerge'
Вместо использования одной структуры данных на диске для хранения полнотекстового индекса FTS5 использует ряд B-деревьев. При фиксации каждой новой транзакции в файл базы данных записывается новое B-дерево, содержащее данные зафиксированной транзакции. При запросе полнотекстового индекса необходимо отдельно опросить каждое B-дерево и объединить результаты, прежде чем вернуть их пользователю.
Чтобы число B-деревьев в базе данных не стало слишком большим (что замедлит выполнение запросов), меньшие B-деревья периодически объединяются в одно более крупное B-дерево, содержащее те же данные. По умолчанию это происходит автоматически в инструкциях INSERT, UPDATE или DELETE, изменяющих полнотекстовый индекс. Параметр 'automerge' определяет, сколько меньших B-деревьев объединяется за один раз. Небольшое значение может ускорить выполнение запросов (поскольку для получения и объединения результатов нужно опрашивать меньше B-деревьев), но также может замедлить запись в базу данных (поскольку каждая инструкция INSERT, UPDATE или DELETE должна выполнять дополнительную работу в рамках автоматического процесса объединения).
Каждому B-дереву, составляющему полнотекстовый индекс, присваивается «уровень» в зависимости от его размера. B-деревья уровня 0 — самые маленькие, поскольку содержат данные одной транзакции. B-деревья более высоких уровней образуются при объединении двух или более B-деревьев уровня 0 и поэтому имеют больший размер. FTS5 начинает объединять B-деревья, когда количество B-деревьев одного уровня достигает M или более, где M — значение параметра 'automerge'.
Максимально допустимое значение параметра 'automerge' — 16. Значение по умолчанию — 4. Установка параметра 'automerge' в 0 полностью отключает автоматическое инкрементное объединение B-деревьев.
INSERT INTO ft(ft, rank) VALUES('automerge', 8);
6.2. Параметр конфигурации 'crisismerge'
Параметр '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);
6.3. Команда 'delete'
Эта команда доступна только для таблиц с внешним содержимым и без содержимого. Она удаляет записи индекса, связанные с одной строкой, из полнотекстового индекса. Эта команда и команда 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 попытается удалить несуществующие записи индекса. Это может привести к непредсказуемому состоянию полнотекстового индекса и сделать результаты будущих запросов ненадёжными.
6.4. Команда 'delete-all'
Эта команда доступна только для таблиц с внешним содержимым и без содержимого (включая таблицы без содержимого с возможностью удаления). Она удаляет все записи из полнотекстового индекса.
INSERT INTO ft(ft) VALUES('delete-all');
6.5. Параметр конфигурации 'deletemerge'
Параметр 'deletemerge' используется только таблицами без содержимого с возможностью удаления.
При удалении строки из таблицы без содержимого с возможностью удаления записи, связанные с её токенами, не удаляются из индекса FTS немедленно. Вместо этого к B-дереву, содержащему записи индекса FTS этой строки, добавляется маркер «надгробия» с rowid удалённой строки. При запросе B-дерева все строки результатов, для которых есть маркеры надгробия, исключаются из результатов. При объединении B-дерева с другими B-деревьями удалённые строки и их маркеры надгробия отбрасываются.
Этот параметр задаёт минимальный процент строк в B-дереве, которые должны иметь маркеры надгробия, чтобы B-дерево можно было объединить — автоматически в ходе автоматического объединения или по явным пользовательским командам 'merge', — даже если оно не соответствует обычным критериям, определяемым параметрами 'automerge' и 'usermerge'.
Например, чтобы указать, что FTS5 должна рассматривать возможность объединения компонентного B-дерева, когда маркеры надгробия связаны с 15% его строк:
INSERT INTO ft(ft, rank) VALUES('deletemerge', 15);
Значение этого параметра по умолчанию — 10. Попытка установить значение меньше нуля восстанавливает значение по умолчанию. Установка этого параметра в 0 или в значение больше 100 гарантирует, что B-деревья никогда не будут допущены к объединению из-за маркеров надгробия.
6.6. Параметр конфигурации 'insttoken'
По умолчанию значение этого логического параметра равно 0. Если установить его в 1, FTS5 будет собирать дополнительные данные, необходимые API xInstToken, для всех запросов по префиксу к таблице. Подробности см. в документации по xInstToken, на которую ведёт ссылка.
-- Enable collection of extra xInstToken data for prefix queries.
INSERT INTO ft(ft, rank) VALUES('insttoken', 1);
-- Disable collection of extra xInstToken data for prefix queries.
INSERT INTO ft(ft, rank) VALUES('insttoken', 0);
6.7. Команда 'integrity-check'
Эта команда используется для проверки внутренней согласованности полнотекстового индекса и, при необходимости, его согласованности с таблицей внешнего содержимого.
Команда integrity-check вызывается вставкой текстового значения 'integrity-check' в специальный столбец с тем же именем, что и у таблицы FTS5. Если для столбца "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.
6.8. Команда 'merge'
INSERT INTO ft(ft, rank) VALUES('merge', 500);
Эта команда объединяет структуры B-деревьев до тех пор, пока в базу данных не будет записано примерно N страниц объединённых данных, где N — абсолютное значение параметра, указанного в команде 'merge'. Размер каждой страницы задаётся параметром pgsz для FTS5.
Если параметр имеет положительное значение, структуры B-деревьев допускаются к объединению только при выполнении одного из следующих условий:
- На одном уровне есть U или более таких B-деревьев (пояснение уровней B-деревьев см. в документации по параметру automerge для FTS5), где U — значение, заданное параметром usermerge для FTS5.
- Объединение уже началось (возможно, командой 'merge' с отрицательным параметром).
Определить, нашла ли команда 'merge' B-деревья для объединения, можно, проверив значение, возвращаемое API sqlite3_total_changes() до и после выполнения команды. Если разница между двумя значениями равна 2 или больше, значит, была выполнена работа. Если разница меньше 2, команда 'merge' ничего не сделала. В этом случае нет причин повторно выполнять ту же команду 'merge' — по крайней мере, до следующего изменения таблицы FTS.
Если параметр отрицательный и в индексе FTS есть структуры B-деревьев более чем на одном уровне, перед началом операции объединения все структуры B-деревьев переводятся на один уровень. Кроме того, если параметр отрицательный, значение параметра конфигурации usermerge не учитывается — можно объединить всего два B-дерева одного уровня.
Это означает, что выполнение команды 'merge' с отрицательным параметром до тех пор, пока разница между значениями sqlite3_total_changes() до и после выполнения не станет меньше двух, оптимизирует индекс FTS так же, как команда optimize для FTS5. Однако, если во время этого процесса в индекс FTS добавляется новое B-дерево, FTS5 переместит его на тот же уровень, что и существующие B-деревья, и начнёт объединение заново. Чтобы этого избежать, отрицательный параметр следует указывать только при первом вызове 'merge'. Во всех последующих вызовах 'merge' следует указывать положительное значение, чтобы объединение, начатое первым вызовом, завершилось, даже если в индекс FTS добавляются новые B-деревья.
6.9. Команда 'optimize'
Эта команда объединяет все отдельные B-деревья, из которых в данный момент состоит полнотекстовый индекс, в одну большую структуру B-дерева. Это позволяет свести к минимуму объём пространства базы данных, занимаемого полнотекстовым индексом, и обеспечить максимальную скорость выполнения запросов к нему.
Дополнительные сведения о связи между полнотекстовым индексом и составляющими его B-деревьями см. в документации по параметру automerge для FTS5.
INSERT INTO ft(ft) VALUES('optimize');
Поскольку команда optimize реорганизует весь индекс FTS, её выполнение может занять много времени. Команду merge для FTS5 можно использовать, чтобы разделить оптимизацию индекса FTS на несколько этапов. Для этого выполните следующие действия:
- Один раз вызовите команду 'merge', указав в качестве параметра -N, затем
- Вызовите команду 'merge' ноль или более раз, указав в качестве параметра N.
где N — количество страниц данных, объединяемых при каждом вызове команды merge. Приложению следует прекратить вызовы merge, когда разница между значениями, возвращаемыми функцией sqlite3_total_changes() до и после команды merge, станет меньше двух. Команды merge можно выполнять в рамках одной или разных транзакций, а также с помощью одного или разных клиентов базы данных. Дополнительные сведения см. в документации по команде merge.
6.10. Параметр конфигурации 'pgsz'
Эта команда используется для установки постоянного параметра "pgsz".
Полнотекстовый индекс, поддерживаемый FTS5, хранится в виде ряда двоичных объектов фиксированного размера в таблице базы данных. Необязательно, чтобы все двоичные объекты, составляющие полнотекстовый индекс, имели одинаковый размер. Параметр pgsz задаёт размер всех двоичных объектов, создаваемых последующими операциями записи в индекс. Значение по умолчанию — 4050.
INSERT INTO ft(ft, rank) VALUES('pgsz', 4072);
6.11. Параметр конфигурации 'rank'
Эта команда используется для установки постоянного параметра "rank".
Параметр rank используется для изменения сопоставления вспомогательных функций по умолчанию для столбца rank. Для этого параметру следует присвоить текстовое значение в том же формате, который описан выше для выражений "rank MATCH ?". Например:
INSERT INTO ft(ft, rank) VALUES('rank', 'bm25(10.0, 5.0)');
6.12. Команда 'rebuild'
Эта команда сначала удаляет весь полнотекстовый индекс, а затем перестраивает его на основе содержимого таблицы или таблицы содержимого. Она недоступна для таблиц без содержимого.
INSERT INTO ft(ft) VALUES('rebuild');
6.13. Параметр конфигурации 'secure-delete'
Эта команда используется для установки постоянного логического параметра "secure-delete". Например:
INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);
Обычно при обновлении или удалении записи в таблице fts5 вместо удаления записей из полнотекстового индекса в новое B-дерево, созданное транзакцией, добавляются ключи удаления. Это эффективно, но означает, что старые записи полнотекстового индекса остаются в файле базы данных до тех пор, пока не будут удалены в ходе операций объединения полнотекстового индекса. Любой, у кого есть доступ к базе данных, может легко использовать эти записи для восстановления содержимого удалённых строк таблицы FTS5. Однако, если параметр 'secure-delete' установлен в 1, записи полнотекстового индекса действительно удаляются из базы данных при обновлении или удалении существующих строк таблицы FTS5. Это медленнее, но не позволяет использовать старые записи полнотекстового индекса для восстановления удалённых строк таблицы.
Этот параметр гарантирует, что злоумышленники с доступом к базе данных через SQL не смогут получить старые записи полнотекстового индекса. Чтобы также исключить возможность их восстановления злоумышленниками, имеющими доступ непосредственно к файлу базы данных SQLite, приложению необходимо включить параметр безопасного удаления ядра SQLite с помощью команды, например "PRAGMA secure_delete = 1".
Предупреждение: Если с включённым параметром одна или несколько строк таблицы были обновлены или удалены, таблицу FTS5 больше нельзя читать или изменять с помощью версий FTS5 старше 3.42.0 (первой версии, в которой этот параметр был доступен). Такая попытка приводит к ошибке с сообщением вида "invalid fts5 file format (found 5, expected 4) - run 'rebuild'". Формат файла FTS5 можно вернуть к прежнему, чтобы его можно было читать более ранними версиями FTS5: для этого следует выполнить команду 'rebuild' для таблицы с помощью версии 3.42.0 или более поздней.
Значение параметра secure-delete по умолчанию — 0.
6.14. Параметр конфигурации 'usermerge'
Эта команда используется для установки постоянного параметра "usermerge".
Параметр usermerge похож на параметры automerge и crisismerge. Это минимальное количество сегментов B-дерева, которые объединяются командой 'merge' с положительным параметром. Например:
INSERT INTO ft(ft, rank) VALUES('usermerge', 4);
Значение параметра usermerge по умолчанию — 4. Минимально допустимое значение — 2, максимально допустимое — 16.
7. Расширение FTS5
В FTS5 предусмотрены API, позволяющие расширять его возможности:
- добавлять новые вспомогательные функции, реализованные на C, и
- добавлять новые токенизаторы, также реализованные на C.
Встроенные токенизаторы и вспомогательные функции, описанные в этом документе, реализованы с использованием общедоступного API, описанного ниже.
Прежде чем зарегистрировать в FTS5 новую вспомогательную функцию или реализацию токенизатора, приложению необходимо получить указатель на структуру "fts5_api". Для каждого подключения к базе данных, в котором зарегистрировано расширение FTS5, существует одна структура fts5_api. Чтобы получить указатель, приложение вызывает пользовательскую SQL-функцию fts5() с одним аргументом. Этот аргумент должен быть указателем на указатель на объект fts5_api, заданным с помощью интерфейса sqlite3_bind_pointer(). В следующем примере кода показано, как это сделать:
/*
** 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 определена следующим образом. Она предоставляет пять методов:
- xCreateTokenizer() и xCreateTokenizer_v2() — для регистрации новых реализаций пользовательских токенизаторов.
- xFindTokenizer() и xFindTokenizer_v2() — для получения существующих реализаций токенизаторов. Это может быть полезно при реализации «обёрток токенизаторов», подобных встроенному токенизатору porter.
- xCreateFunction() — для регистрации новых реализаций вспомогательных функций.
Два метода "v2", описанные выше, доступны только в том случае, если значение поля fts5_api.iVersion равно 3 или больше. Обращение к API "v2" через объект fts5_api со значением iVersion меньше 3 приводит к неопределённому поведению.
typedef struct fts5_api fts5_api;
struct fts5_api {
int iVersion; /* Currently always set to 3 */
/* 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*)
);
/* APIs below this point are only available if iVersion>=3 */
/* Create a new tokenizer */
int (*xCreateTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void *pUserData,
fts5_tokenizer_v2 *pTokenizer,
void (*xDestroy)(void*)
);
/* Find an existing tokenizer */
int (*xFindTokenizer_v2)(
fts5_api *pApi,
const char *zName,
void **ppUserData,
fts5_tokenizer_v2 **ppTokenizer
);
};
Чтобы вызвать метод объекта fts5_api, сам указатель fts5_api следует передать в качестве первого аргумента метода, а за ним — остальные аргументы, специфичные для метода. Например:
rc = pFts5Api->xCreateTokenizer(pFts5Api, ... other args ...);
Методы структуры fts5_api описаны по отдельности в следующих разделах.
7.1. Пользовательские токенизаторы
Для создания пользовательского токенизатора приложение должно реализовать три функции: конструктор токенизатора (xCreate), деструктор (xDelete) и функцию, выполняющую собственно токенизацию (xTokenize). Тип каждой функции соответствует типу переменных-членов структуры fts5_tokenizer_v2:
typedef struct Fts5Tokenizer Fts5Tokenizer;
typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2;
struct fts5_tokenizer_v2 {
int iVersion; /* Currently always 2 */
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,
const char *pLocale, int nLocale,
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 путём заполнения экземпляра структуры fts5_tokenizer_v2 и передачи указателя на него методу xCreateTokenizer_v2() объекта fts5_api. Если токенизатор с таким именем уже существует, он заменяется. Если при вызове xCreateTokenizer() передан параметр xDestroy, отличный от NULL, он вызывается с копией указателя pUserData, переданного в качестве единственного аргумента, при закрытии дескриптора базы данных или замене токенизатора.
В случае успеха xCreateTokenizer() возвращает SQLITE_OK. В противном случае она возвращает код ошибки SQLite. В этом случае функция xDestroy не вызывается.
Когда в таблице FTS5 используется пользовательский токенизатор, ядро FTS5 один раз вызывает xCreate() для создания токенизатора, затем ноль или более раз вызывает xTokenize() для токенизации строк, а после этого вызывает xDelete() для освобождения ресурсов, выделенных функцией xCreate(). В частности:
- xCreate:
-
Эта функция используется для выделения памяти и инициализации экземпляра токенизатора. Для выполнения токенизации текста требуется экземпляр токенизатора.
Первый аргумент, передаваемый этой функции, — это копия указателя (void*), предоставленного приложением при регистрации объекта fts5_tokenizer_v2 в FTS5 (третий аргумент xCreateTokenizer()). Второй и третий аргументы — это массив строк, завершающихся нулевым символом и содержащих аргументы токенизатора (если они есть), указанные после имени токенизатора в инструкции CREATE VIRTUAL TABLE, использованной для создания таблицы FTS5.
Последний аргумент — выходная переменная. В случае успеха значение (*ppOut) следует установить так, чтобы оно указывало на новый дескриптор токенизатора, и вернуть SQLITE_OK. При возникновении ошибки следует вернуть любое значение, кроме SQLITE_OK. В этом случае fts5 считает конечное значение *ppOut неопределённым.
- xDelete:
-
Эта функция вызывается для удаления дескриптора токенизатора, ранее выделенного с помощью xCreate(). Fts5 гарантирует, что эта функция будет вызвана ровно один раз для каждого успешного вызова xCreate().
- xTokenize:
-
Эта функция должна токенизировать строку длиной nText байт, указанную аргументом pText. Строка pText может завершаться нулевым символом, а может и не завершаться. Первый аргумент, передаваемый этой функции, — указатель на объект Fts5Tokenizer, возвращённый предыдущим вызовом xCreate().
Третий аргумент указывает причину, по которой FTS5 запрашивает токенизацию переданного текста. Это всегда одно из следующих четырёх значений:
- FTS5_TOKENIZE_DOCUMENT — документ добавляется в таблицу FTS или удаляется из неё. Токенизатор вызывается, чтобы определить набор токенов, которые необходимо добавить в индекс FTS или удалить из него.
- FTS5_TOKENIZE_QUERY — выполняется запрос MATCH к индексу FTS. Токенизатор вызывается для токенизации отдельного слова или строки в кавычках, указанных в запросе.
- (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) — то же, что и FTS5_TOKENIZE_QUERY, но за отдельным словом или строкой в кавычках следует символ "*", указывающий, что последний токен, возвращённый токенизатором, будет считаться префиксом токена.
- FTS5_TOKENIZE_AUX — токенизатор вызывается для выполнения запроса fts5_api.xTokenize(), сделанного вспомогательной функцией. Либо для выполнения запроса fts5_api.xColumnSize(), сделанного этой же функцией для базы данных с параметром columnsize=0.
Шестой и седьмой аргументы, передаваемые xTokenize(), — pLocale и nLocale — представляют собой указатель на буфер с локалью, используемой для токенизации (например, "en_US"), и её размер в байтах соответственно. Буфер pLocale не завершается нулевым символом. Вместо pLocale можно передать NULL (в этом случае nLocale всегда равен 0), чтобы указать, что токенизатор должен использовать локаль по умолчанию.
Для каждого токена во входной строке необходимо вызвать предоставленную функцию обратного вызова xToken(). Её первым аргументом должна быть копия указателя, переданного в качестве второго аргумента xTokenize(). Третий и четвёртый аргументы — указатель на буфер с текстом токена и размер токена в байтах. Четвёртый и пятый аргументы — смещения в байтах первого байта и байта, непосредственно следующего за текстом, из которого получен токен, во входной строке.
Второму аргументу ("tflags") функции обратного вызова xToken() обычно следует присвоить значение 0. Исключение — токенизатор, поддерживающий синонимы. В этом случае подробности см. ниже.
FTS5 предполагает, что функция обратного вызова xToken() вызывается для каждого токена в порядке их появления во входном тексте.
Если функция обратного вызова xToken() возвращает значение, отличное от SQLITE_OK, токенизацию следует прервать, а метод xTokenize() должен немедленно вернуть копию возвращённого функцией xToken() значения. Если входной буфер исчерпан, xTokenize() должен вернуть SQLITE_OK. Наконец, если в самой реализации xTokenize() возникает ошибка, она может прервать токенизацию и вернуть любой код ошибки, кроме SQLITE_OK или SQLITE_DONE.
Если токенизатор зарегистрирован с помощью объекта fts5_tokenizer_v2, метод xTokenize() принимает два дополнительных аргумента — pLocale и nLocale. Они указывают локаль, которую токенизатор должен использовать для текущего запроса. Если pLocale и nLocale равны 0, токенизатор должен использовать локаль по умолчанию. В противном случае pLocale указывает на буфер длиной nLocale байт, содержащий название локали в виде текста utf-8. Строка pLocale не завершается нулевым символом.
- FTS5_TOKENIZE_DOCUMENT — документ добавляется в таблицу FTS или удаляется из неё. Токенизатор вызывается, чтобы определить набор токенов, которые необходимо добавить в индекс FTS или удалить из него.
Существует также объект fts5_tokenizer. Это более старая, устаревшая версия fts5_tokenizer_v2. Она отличается следующим:
- в ней нет поля "iVersion";
- метод xTokenize() не принимает аргумент локали.
Устаревшие токенизаторы fts5_tokenizer необходимо регистрировать с помощью устаревшей функции xCreateTokenizer(), а не xCreateTokenizer_v2().
Реализации токенизаторов, зарегистрированные с помощью любого из этих API, можно получить как через xFindTokenizer(), так и через xFindTokenizer_v2().
7.1.1. Поддержка синонимов
Пользовательские токенизаторы также могут поддерживать синонимы. Рассмотрим случай, когда пользователь хочет выполнить поиск фразы, например "first place". При использовании встроенных токенизаторов запрос FTS5 'first + place' найдёт в наборе документов вхождения "first place", но не альтернативные формы, например "1st place". В некоторых приложениях было бы лучше находить все вхождения "first place" или "1st place" независимо от того, какую форму пользователь указал в тексте запроса MATCH.
В FTS5 есть несколько способов это реализовать:
- Путем сопоставления всех синонимов одному токену. В этом случае, используя приведенный выше пример, это означает, что токенизатор возвращает один и тот же токен для входных данных "first" и "1st". Пусть этим токеном будет "first", тогда при добавлении пользователем документа "I won 1st place" в индекс добавляются записи для токенов "i", "won", "first" и "place". Если затем пользователь выполнит запрос '1st + place', токенизатор заменит "1st" на "first", и запрос будет работать ожидаемым образом.
- Путем отдельного поиска в индексе всех синонимов каждого термина запроса. В этом случае при токенизации текста запроса токенизатор может предоставить несколько синонимов для одного термина в документе. Затем FTS5 выполняет поиск каждого синонима в индексе отдельно. Например, для запроса:
... MATCH 'first place'
токенизатор предлагает "1st" и "first" в качестве синонимов первого токена в запросе MATCH, а FTS5 фактически выполняет запрос, похожий на:
... MATCH '(first OR 1st) place'
за исключением того, что для вспомогательных функций запрос по-прежнему выглядит как содержащий всего две фразы: "(first OR 1st)" считается одной фразой.
- Путем добавления в индекс FTS нескольких синонимов одного термина. При использовании этого метода токенизатор предоставляет несколько синонимов для каждого токена при токенизации текста документа. Поэтому при токенизации документа, например "I won first place", в индекс FTS добавляются записи для "i", "won", "first", "1st" и "place".
Таким образом, даже если токенизатор не предоставляет синонимы при токенизации текста запроса (и не должен этого делать, поскольку это было бы неэффективно), не имеет значения, выполняет ли пользователь запрос 'first + place' или '1st + place': в индексе FTS есть записи для обеих форм первого токена.
Независимо от того, анализируется ли текст документа или запроса, любой вызов xToken, в котором для аргумента tflags указан бит FTS5_TOKEN_COLOCATED, считается предоставляющим синоним предыдущего токена. Например, при анализе документа "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);
Ошибкой является указание флага FTS5_TOKEN_COLOCATED при первом вызове xToken(). Для одного токена можно указать несколько синонимов, последовательно выполнив несколько вызовов xToken(FTS5_TOKEN_COLOCATED). Количество синонимов одного токена не ограничено.
Во многих случаях метод (1), описанный выше, является оптимальным. Он не добавляет в индекс FTS дополнительные данные и не требует от FTS5 выполнять поиск нескольких терминов, поэтому эффективен с точки зрения использования дискового пространства и скорости запросов. Однако он плохо поддерживает запросы с префиксами. Если, как было предложено выше, токенизатор заменяет токен "1st" на "first", то запрос:
... MATCH '1s*'
не найдет документы, содержащие токен "1st" (поскольку токенизатор, вероятно, не сопоставит "1s" ни с одним префиксом "first").
Для полной поддержки префиксов предпочтительнее может быть метод (3). В этом случае, поскольку индекс содержит записи и для "first", и для "1st", запросы с префиксами, например 'fi*' или '1s*', будут находить совпадения. Однако этот метод использует больше места в базе данных, поскольку в индекс FTS добавляются дополнительные записи.
Метод (2) представляет собой компромисс между методами (1) и (3). При использовании этого метода запрос, например '1s*', найдет документы, содержащие буквальный токен "1st", но не "first" (если предположить, что токенизатор не может предоставлять синонимы для префиксов). Однако запрос без префикса, например '1st', найдет и "1st", и "first". Этот метод не требует дополнительного места на диске, поскольку в индекс FTS не добавляются дополнительные записи. С другой стороны, для выполнения запросов MATCH может потребоваться больше циклов ЦП, поскольку для каждого синонима необходимо выполнять отдельный поиск в индексе FTS.
При использовании методов (2) или (3) важно, чтобы токенизатор предоставлял синонимы только при токенизации текста документа (метод (3)) или текста запроса (метод (2)), но не в обоих случаях. Это не приведет к ошибкам, но будет неэффективно.
7.2. Пользовательские вспомогательные функции
Реализация пользовательской вспомогательной функции похожа на реализацию скалярной функции SQL. Она должна быть функцией C типа fts5_extension_function, определенной следующим образом:
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 вызовом метода xCreateFunction() объекта fts5_api. Если вспомогательная функция с таким именем уже существует, она заменяется новой функцией. Если при вызове xCreateFunction() передан параметр xDestroy, отличный от NULL, он вызывается с копией указателя pUserData, переданного в качестве единственного аргумента, при закрытии дескриптора базы данных или замене зарегистрированной вспомогательной функции.
В случае успеха xCreateFunction() возвращает SQLITE_OK. В противном случае возвращается код ошибки SQLite. В этом случае функция xDestroy не вызывается.
Последние три аргумента, передаваемые функции обратного вызова вспомогательной функции (выше это pCtx, nVal и apVal), аналогичны трем аргументам, передаваемым реализации скалярной функции SQL. Массив apVal[] содержит все аргументы 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".
7.2.1. Обзор API пользовательских вспомогательных функций
В этом разделе представлен обзор возможностей 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
Общее количество строк в таблице FTS5 можно получить с помощью API xRowCount. Оно возвращает общее количество строк в таблице, а не количество строк, соответствующих текущему запросу.
Столбцы таблицы нумеруются слева направо, начиная с 0. Столбец "rowid" не учитывается — учитываются только объявленные пользователем столбцы, поэтому в приведенном выше примере столбец "a" имеет номер 0, а столбец "b" — номер 1. Из реализации вспомогательной функции можно вызвать API xColumnCount, чтобы определить количество столбцов в таблице, для которой выполняется запрос. Если вызвать API xColumnCount() из реализации вспомогательной функции my_aux_function в приведенном выше примере, он вернет 2.
Данные текущей строки: xColumnText, xRowid
API xRowid можно использовать для получения значения rowid текущей строки. API xColumnText позволяет получить текст, сохраненный в указанном столбце текущей строки.
Количество токенов: xColumnSize, xColumnTotalSize
FTS5 разбивает документы, вставляемые в таблицу fts5, на токены. Обычно это просто слова, возможно, приведенные к верхнему или нижнему регистру, с удаленной пунктуацией. Например, токенизатор unicode61 по умолчанию разбивает текст "The tokenizer is case-insensitive" на список из 5 токенов: "the", "tokenizer", "is", "case" и "insensitive". То, как именно из текста извлекаются токены, определяется токенизатором.
API вспомогательных функций предоставляет функции для получения количества токенов как в указанном столбце текущей строки (API xColumnSize), так и в указанном столбце всех строк таблицы (API xColumnTotalSize). Для примера в начале этого раздела при обработке строки 1 xColumnSize возвращает 2 для столбца 0 и 3 для столбца 1. xColumnTotalSize возвращает 6 для столбца 0 и 9 для столбца 1 независимо от текущей строки.
Текущий полнотекстовый запрос: xPhraseCount, xPhraseSize, xQueryToken
Запрос FTS5 содержит одну или несколько фраз. API xPhraseCount, xPhraseSize и xQueryToken позволяют реализации вспомогательной функции запросить у системы сведения о текущем запросе. API xPhraseCount возвращает количество фраз в текущем запросе. Например, если выполнить для таблицы FTS5 следующий запрос:
SELECT my_aux_function(ft) FROM ft('ab AND "cd ef gh" OR ij + kl')
и вызвать API xPhraseCount() из реализации вспомогательной функции, он вернет 3 (три фразы: "ab", "ce ef gh" и "ij kl").
Фразы нумеруются в порядке появления в запросе, начиная с 0. API xPhraseSize() позволяет получить количество токенов в указанной фразе запроса. В приведенном выше примере фраза 0 содержит 1 токен, фраза 1 — 3 токена, а фраза 2 — 2.
API xQueryToken позволяет получить текст указанного токена в указанной фразе запроса. Токены нумеруются внутри фраз слева направо, начиная с 0. Например, если с помощью API xQueryToken запросить токен 1 фразы 2 из приведенного выше примера, он вернет текст "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 встречаются в столбце "x" строки 2, они исключаются фильтром NEAR. Совпадения, исключенные фильтрами NEAR, также не учитываются.
Совпадения фраз в текущей строке (2): xInstCount, xInst
API xInstCount и xInst предоставляют доступ к той же информации, что и описанные выше xPhraseFirst и xPhraseNext. Разница заключается в том, что вместо перебора совпадений одной указанной фразы API xInstCount/xInst объединяют все совпадения в один плоский массив, отсортированный в порядке их появления в текущей строке. К элементам этого массива можно обращаться произвольно.
Каждый элемент массива состоит из трех значений:
- номера фразы,
- номера столбца и
- смещения токена
Если использовать те же данные и запрос, что и для 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.
7.2.2. Справочник по API пользовательских вспомогательных функций
struct Fts5ExtensionApi {
int iVersion; /* Currently always set to 4 */
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*);
/* Below this point are iVersion>=4 only */
int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn);
int (*xTokenize_v2)(Fts5Context*,
const char *pText, int nText, /* Text to tokenize */
const char *pLocale, int nLocale, /* Locale to pass to tokenizer */
void *pCtx, /* Context passed to xToken() */
int (*xToken)(void*, int, const char*, int, int, int) /* Callback */
);
};
- void *(*xUserData)(Fts5Context*)
-
Возвращает копию указателя pUserData, переданного API xCreateFunction() при регистрации функции расширения.
- int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken)
-
Если параметр iCol меньше нуля, присваивает выходной переменной *pnToken общее количество токенов в таблице FTS5. Если же iCol неотрицателен, но меньше количества столбцов в таблице, возвращает общее количество токенов в столбце iCol с учетом всех строк таблицы FTS5.
Если параметр iCol больше или равен количеству столбцов в таблице, возвращается SQLITE_RANGE. Если возникает ошибка (например, нехватка памяти или ошибка ввода-вывода), возвращается соответствующий код ошибки SQLite.
- int (*xColumnCount)(Fts5Context*)
-
Возвращает количество столбцов в таблице.
- int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken)
-
Если параметр iCol меньше нуля, присваивает выходной переменной *pnToken общее количество токенов в текущей строке. Если же iCol неотрицателен, но меньше количества столбцов в таблице, присваивает *pnToken количество токенов в столбце iCol текущей строки.
Если параметр iCol больше или равен количеству столбцов в таблице, возвращается SQLITE_RANGE. Если возникает ошибка (например, нехватка памяти или ошибка ввода-вывода), возвращается соответствующий код ошибки SQLite.
Эта функция может быть весьма неэффективной при использовании с таблицей FTS5, созданной с параметром "columnsize=0".
- int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn)
-
Если параметр iCol меньше нуля или больше либо равен количеству столбцов в таблице, возвращается SQLITE_RANGE.
В противном случае эта функция пытается получить текст столбца iCol текущего документа. В случае успеха (*pz) устанавливается в указатель на буфер с текстом в кодировке utf-8, (*pn) — в размер буфера в байтах (не символах), после чего возвращается SQLITE_OK. Если возникает ошибка, возвращается код ошибки SQLite, а конечные значения (*pz) и (*pn) не определены.
- int (*xPhraseCount)(Fts5Context*)
-
Возвращает количество фраз в текущем выражении запроса.
- int (*xPhraseSize)(Fts5Context*, int iPhrase)
-
Если параметр iCol меньше нуля или больше либо равен количеству фраз в текущем запросе, возвращаемому функцией xPhraseCount, возвращается 0. В противном случае эта функция возвращает количество токенов во фразе iPhrase запроса. Нумерация фраз начинается с нуля.
- int (*xInstCount)(Fts5Context*, int *pnInst)
-
Устанавливает *pnInst в общее количество вхождений всех фраз запроса в текущей строке. В случае успеха возвращает SQLITE_OK, а в случае ошибки — код ошибки (например, SQLITE_NOMEM).
Этот API может работать довольно медленно при использовании с таблицей FTS5, созданной с параметром "detail=none" или "detail=column". Если таблица FTS5 создана с параметром "detail=none" или "detail=column" и параметром "content=" (то есть является таблицей без содержимого), этот API всегда возвращает 0.
- int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff)
-
Запрашивает сведения о совпадении фразы iIdx в текущей строке. Совпадения фраз нумеруются начиная с нуля, поэтому аргумент iIdx должен быть неотрицательным и меньше значения, возвращаемого xInstCount(). Если iIdx меньше нуля или больше либо равен значению, возвращаемому xInstCount(), возвращается SQLITE_RANGE.
В противном случае выходной параметр *piPhrase устанавливается в номер фразы, *piCol — в столбец, в котором она встречается, а *piOff — в смещение токена первого токена фразы. В случае успеха возвращается SQLITE_OK, а в случае ошибки — код ошибки (например, SQLITE_NOMEM).
Этот API может работать довольно медленно при использовании с таблицей FTS5, созданной с параметром "detail=none" или "detail=column".
- sqlite3_int64 (*xRowid)(Fts5Context*)
-
Возвращает rowid текущей строки.
- int (*xTokenize)(Fts5Context*, const char *pText, int nText, void *pCtx, int (*xToken)(void*, int, const char*, int, int, int) )
-
Токенизирует текст с помощью токенизатора, используемого таблицей FTS5.
- int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) )
-
Эта функция API используется для поиска в таблице FTS фразы iPhrase текущего запроса. В частности, выполняется запрос, эквивалентный следующему:
... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid
где $p задан как фраза, эквивалентная фразе iPhrase текущего запроса. В $p включается любой фильтр столбца, применяемый к фразе iPhrase текущего запроса. Для каждой обработанной строки вызывается функция обратного вызова, переданная четвертым аргументом. Контекст и объекты API, переданные функции обратного вызова, можно использовать для доступа к свойствам каждой найденной строки. Вызов Api.xUserData() возвращает копию указателя, переданного в качестве третьего аргумента pUserData.
Если параметр iPhrase меньше нуля или больше либо равен количеству фраз в запросе, возвращаемому xPhraseCount(), эта функция возвращает SQLITE_RANGE.
Если функция обратного вызова возвращает значение, отличное от SQLITE_OK, запрос прерывается, а функция xQueryPhrase немедленно возвращает результат. Если возвращенное значение — SQLITE_DONE, xQueryPhrase возвращает SQLITE_OK. В противном случае код ошибки передается вызывающей стороне.
Если запрос завершается без ошибок, возвращается SQLITE_OK. Если до завершения запроса возникает ошибка или запрос прерывается функцией обратного вызова, возвращается код ошибки SQLite.
- int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*))
-
Сохраняет указатель, переданный вторым аргументом, в качестве «вспомогательных данных» функции расширения. Затем этот указатель можно получить при текущем или любом последующем вызове той же функции расширения fts5 в рамках того же запроса MATCH с помощью API xGetAuxdata().
Для каждого запроса FTS (выражения MATCH) каждой функции расширения выделяется один слот вспомогательных данных. Если функция расширения вызывается для одного запроса FTS более одного раза, все вызовы используют один контекст вспомогательных данных.
Если на момент вызова этой функции указатель вспомогательных данных уже существует, он заменяется новым указателем. Если для исходного указателя была задана функция обратного вызова xDelete, она вызывается в этот момент.
Функция обратного вызова xDelete, если она задана, также вызывается для указателя вспомогательных данных после завершения запроса FTS5.
Если при выполнении этой функции возникает ошибка (например, нехватка памяти), вспомогательные данные устанавливаются в NULL и возвращается код ошибки. Если параметр xDelete не равен NULL, перед возвратом он вызывается для указателя вспомогательных данных.
- void *(*xGetAuxdata)(Fts5Context*, int bClear)
-
Возвращает текущий указатель вспомогательных данных функции расширения fts5. Подробности см. в описании метода xSetAuxdata().
Если аргумент bClear не равен нулю, перед возвратом из этой функции вспомогательные данные очищаются (устанавливаются в NULL). В этом случае функция xDelete, если она задана, не вызывается.
- int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow)
-
Эта функция используется для получения общего количества строк в таблице. Иными словами, она возвращает то же значение, что и следующий запрос:
SELECT count(*) FROM ftstable;
- int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*)
-
Эта функция вместе с типом Fts5PhraseIter и методом xPhraseNext используется для перебора всех вхождений одной фразы запроса в текущей строке. Это те же сведения, которые доступны через API xInstCount/xInst. Хотя API xInstCount/xInst удобнее в использовании, в некоторых случаях этот 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 определена выше. Приложениям не следует изменять эту структуру напрямую — ее следует использовать только так, как показано выше, с методами API xPhraseFirst() и xPhraseNext() (а также с xPhraseFirstColumn() и xPhraseNextColumn(), как показано ниже).
Этот API может работать довольно медленно при использовании с таблицей FTS5, созданной с параметром "detail=none" или "detail=column". Если таблица FTS5 создана с параметром "detail=none" или "detail=column" и параметром "content=" (то есть является таблицей без содержимого), этот API всегда перебирает пустой набор (все вызовы xPhraseFirst() устанавливают iCol в -1).
Во всех случаях совпадения обрабатываются в порядке (column ASC, offset ASC), то есть сначала все совпадения в столбце 0, отсортированные по смещению, затем совпадения в столбце 1 и т. д.
- void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff)
-
См. описание xPhraseFirst выше.
- int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*)
-
Эта функция и xPhraseNextColumn() похожи на описанные выше API xPhraseFirst() и xPhraseNext(). Разница заключается в том, что вместо перебора всех вхождений фразы в текущей строке эти 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 }Этот API может работать довольно медленно при использовании с таблицей FTS5, созданной с параметром "detail=none". Если таблица FTS5 создана с параметрами "detail=none" и "content=" (то есть является таблицей без содержимого), этот API всегда перебирает пустой набор (все вызовы xPhraseFirstColumn() устанавливают iCol в -1).
Сведения, доступные через этот API и связанный с ним xPhraseFirstColumn(), также можно получить с помощью xPhraseFirst/xPhraseNext (или xInst/xInstCount). Главное преимущество этого API заключается в том, что при использовании с таблицами "detail=column" он значительно эффективнее этих альтернатив.
- void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol)
-
См. описание xPhraseFirstColumn выше.
- int (*xQueryToken)(Fts5Context*, int iPhrase, int iToken, const char **ppToken, int *pnToken )
-
Используется для доступа к токену iToken фразы iPhrase текущего запроса. Перед возвратом выходной параметр *ppToken устанавливается в указатель на буфер, содержащий запрошенный токен, а *pnToken — в размер этого буфера в байтах.
Если iPhrase или iToken меньше нуля, iPhrase больше либо равен количеству фраз в запросе, указанному функцией xPhraseCount(), или iToken больше либо равен количеству токенов во фразе, возвращается SQLITE_RANGE, а значения *ppToken и *pnToken обнуляются.
Выходной текст не является копией текста запроса, в котором был указан токен. Это результат работы модуля токенизатора. Для таблиц с tokendata=1 сюда входят все встроенные данные 0x00 и завершающие данные.
- int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*)
-
Используется для доступа к токену iToken совпадения фразы iIdx в текущей строке. Если iIdx меньше нуля или больше либо равен значению, возвращаемому xInstCount(), возвращается SQLITE_RANGE. В противном случае выходная переменная (*ppToken) устанавливается в указатель на буфер, содержащий соответствующий токен документа, а (*pnToken) — в размер этого буфера в байтах.
Выходной текст не является копией текста документа, который был токенизирован. Это результат работы модуля токенизатора. Для таблиц с tokendata=1 сюда входят все встроенные данные 0x00 и завершающие данные.
В некоторых случаях этот API может работать медленно, если токен, заданный параметрами iIdx и iToken, соответствует токену-префиксу в запросе. В большинстве случаев первый вызов этого API для каждого токена-префикса запроса вынуждает сканировать часть полнотекстового индекса, соответствующую этому префиксу, чтобы собрать дополнительные данные, необходимые API. Если токен-префикс соответствует большому количеству экземпляров токена в наборе документов, это может стать проблемой производительности.
Если пользователь заранее знает, что запрос может использовать этот API для токена-префикса, FTS5 можно настроить на сбор всех необходимых данных при первоначальном поиске в полнотекстовом индексе, полностью избегая повторного сканирования. Это также замедляет выполнение запросов с префиксами, не использующих этот API, и увеличивает расход памяти. FTS5 можно настроить таким образом отдельно для каждой таблицы с помощью параметра 'insttoken' или отдельно для каждого запроса с помощью пользовательской функции fts5_insttoken().
Этот API может работать довольно медленно при использовании с таблицей FTS5, созданной с параметром "detail=none" или "detail=column".
- int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn)
-
Если параметр iCol меньше нуля или больше либо равен количеству столбцов в таблице, возвращается SQLITE_RANGE.
В противном случае эта функция пытается получить локаль, связанную со столбцом iCol текущей строки. Обычно связанная локаль отсутствует, и выходные параметры (*pzLocale) и (*pnLocale) устанавливаются соответственно в NULL и 0. Однако, если при вставке значения в таблицу fts5 для его локали использовалась функция fts5_locale(), то (*pzLocale) устанавливается в указатель на буфер с завершающим нулевым символом, содержащий название локали в кодировке utf-8. (*pnLocale) устанавливается в размер буфера в байтах без учета завершающего нулевого символа.
В случае успеха возвращается SQLITE_OK. Если возникает ошибка, возвращается код ошибки SQLite. В этом случае конечные значения выходных параметров не определены.
- int (*xTokenize_v2)(Fts5Context*, const char *pText, int nText, const char *pLocale, int nLocale, void *pCtx, int (*xToken)(void*, int, const char*, int, int, int) )
-
Токенизирует текст с помощью токенизатора, используемого таблицей FTS5. Этот API аналогичен API xTokenize(), но позволяет указать локаль токенизатора.
8. Модуль виртуальных таблиц fts5vocab
Модуль виртуальных таблиц fts5vocab позволяет пользователям напрямую извлекать сведения из полнотекстового индекса FTS5. Модуль fts5vocab является частью FTS5 и доступен везде, где доступен FTS5.
Каждая таблица fts5vocab связана с одной таблицей FTS5. Таблицу fts5vocab обычно создают, указывая в инструкции CREATE VIRTUAL TABLE вместо имен столбцов два аргумента — имя связанной таблицы 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 с таблицей FTS5, расположенной в базе данных, отличной от "temp", имя базы данных указывается перед именем таблицы FTS5 в аргументах CREATE VIRTUAL TABLE. Например:
-- 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');
Указание трех аргументов при создании таблицы fts5vocab в любой базе данных, кроме "temp", приводит к ошибке.
Таблица fts5vocab типа "row" содержит по одной строке для каждого уникального термина в связанной таблице FTS5. Столбцы таблицы:
| Столбец | Содержимое |
|---|---|
| term | Термин в том виде, в котором он хранится в индексе FTS5. |
| doc | Количество строк, содержащих хотя бы один экземпляр термина. |
| cnt | Общее количество экземпляров термина во всей таблице FTS5. |
Таблица fts5vocab типа "col" содержит по одной строке для каждой уникальной комбинации термина и столбца в связанной таблице FTS5. Столбцы таблицы:
| Столбец | Содержимое |
|---|---|
| term | Термин в том виде, в котором он хранится в индексе FTS5. |
| col | Имя столбца таблицы FTS5, содержащего термин. |
| doc | Количество строк таблицы FTS5, в которых столбец $col содержит хотя бы один экземпляр термина. |
| cnt | Общее количество экземпляров термина в столбце $col таблицы FTS5 (с учетом всех строк). |
Таблица fts5vocab типа "instance" содержит по одной строке для каждого экземпляра термина, хранящегося в связанном индексе FTS. Если таблица FTS5 создана с параметром 'detail', установленным в 'full', столбцы таблицы выглядят следующим образом:
| Столбец | Содержимое |
|---|---|
| term | Термин в том виде, в котором он хранится в индексе FTS5. |
| doc | rowid документа, содержащего экземпляр термина. |
| col | Имя столбца, содержащего экземпляр термина. |
| offset | Индекс экземпляра термина в столбце. Термины нумеруются в порядке появления, начиная с 0. |
Если таблица FTS5 создана с параметром 'detail', установленным в 'col', то столбец offset виртуальной таблицы instance всегда содержит NULL. В этом случае таблица содержит по одной строке для каждой уникальной комбинации term/doc/col. Если же таблица FTS5 создана с параметром 'detail', установленным в 'none', то столбцы offset и col всегда содержат значения NULL. Для таблиц FTS5 с detail=none таблица fts5vocab содержит по одной строке для каждой уникальной комбинации term/doc.
Пример:
-- Assuming a database created using:
CREATE VIRTUAL TABLE ft USING fts5(c1, c2);
INSERT INTO ft VALUES('apple banana cherry', 'banana banana cherry');
INSERT INTO ft 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 ft_v_col USING fts5vocab(ft, col);
-- Querying an fts5vocab table of type "row" returns:
--
-- apple | 1 | 1
-- banana | 1 | 3
-- cherry | 2 | 5
-- date | 1 | 3
--
CREATE VIRTUAL TABLE ft_v_row USING fts5vocab(ft, row);
-- And, for type "instance"
INSERT INTO ft VALUES('apple banana cherry', 'banana banana cherry');
INSERT INTO ft 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 ft_v_instance USING fts5vocab(ft, instance);
9. Структуры данных FTS5
В этом разделе на высоком уровне описывается способ хранения модулем 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.
9.1. Формат varint
В следующих разделах упоминаются 64-разрядные целые числа со знаком, хранящиеся в формате «varint». FTS5 использует тот же формат varint, что и ядро SQLite в различных местах.
Длина varint составляет от 1 до 9 байт. Varint состоит либо из нуля или более байт, у которых установлен старший бит, за которыми следует один байт с очищенным старшим битом, либо из девяти байт — в зависимости от того, какой вариант короче. Для восстановления 64-разрядного целого числа в дополнительном коде используются младшие семь бит каждого из первых восьми байт и все 8 бит девятого байта. Varint хранится в порядке от старшего к младшему: биты из более ранних байтов varint значимее битов из последующих байтов.
9.2. Индекс FTS (таблицы %_idx и %_data)
Индекс FTS — это упорядоченное хранилище «ключ-значение», где ключами служат термины документа или префиксы терминов, а соответствующими значениями — «списки документов» (doclist). Список документов — это упакованный массив varint, кодирующий позицию каждого экземпляра термина в таблице FTS5. Позиция отдельного экземпляра термина определяется сочетанием следующих значений:
- rowid строки таблицы FTS5, в которой он встречается;
- индекс столбца, в котором встречается экземпляр термина (столбцы нумеруются слева направо, начиная с нуля); и
- смещение термина в значении столбца (то есть количество токенов, которые встречаются в значении столбца перед ним).
Для каждого токена в наборе данных индекс FTS содержит до (nPrefix+1) записей, где nPrefix — количество определенных префиксных индексов.
Ключи основного индекса FTS (не являющегося префиксным индексом) имеют префикс "0". Ключи первого префиксного индекса имеют префикс "1". Ключи второго префиксного индекса имеют префикс "2" и так далее. Например, если токен "document" вставлен в таблицу FTS5 с префиксными индексами, заданными параметром prefix="2 4", в индекс FTS будут добавлены ключи "0document", "1do" и "2docu".
Записи индекса FTS хранятся не в единой древовидной структуре или хеш-таблице. Вместо этого они хранятся в ряде неизменяемых структур, подобных B-деревьям, которые называются «сегментными B-деревьями». Каждый раз при фиксации записи в таблице FTS5 добавляется одно или несколько (обычно только одно) новых сегментных B-деревьев, содержащих новые записи и надгробия для удаленных записей. При выполнении запроса к индексу FTS чтение по очереди выполняется из каждого сегментного B-дерева, а результаты объединяются с приоритетом более новых данных.
Каждому сегментному B-дереву присваивается числовой уровень. Когда при фиксации транзакции в базу данных записывается новое сегментное B-дерево, ему присваивается уровень 0. Сегментные B-деревья одного уровня периодически объединяются в одно более крупное сегментное B-дерево, которому присваивается следующий уровень (то есть сегментные B-деревья уровня 0 объединяются в одно сегментное B-дерево уровня 1). Таким образом, данные более высоких числовых уровней старее и (обычно) хранятся в более крупных сегментных B-деревьях. Подробнее об управлении объединением см. параметры 'automerge', 'crisismerge' и 'usermerge', а также команды 'merge' и 'optimize'.
Если список документов, связанный с термином или префиксом термина, очень велик, для него может существовать индекс списка документов. Индекс списка документов похож на набор внутренних узлов B-дерева. Он позволяет эффективно выполнять запросы по rowid или диапазонам rowid для большого списка документов. Например, при обработке запроса вида:
SELECT ... FROM ft('term') WHERE rowid BETWEEN ? AND ?
FTS5 использует индекс сегментного B-дерева, чтобы найти список документов для термина "term", а затем использует его индекс списка документов (если он существует), чтобы эффективно определить совпадения с rowid в требуемом диапазоне.
9.2.1. Пространство rowid таблицы %_data
CREATE TABLE %_data( id INTEGER PRIMARY KEY, block BLOB );
Таблица %_data используется для хранения записей трех типов:
- специальная запись структуры, хранящаяся с id=10;
- специальная запись средних значений, хранящаяся с id=1;
- запись для хранения каждого листа сегментного B-дерева, а также листа и внутреннего узла индекса списка документов. О том, как вычисляются значения id для этих записей, см. ниже.
Каждому сегментному B-дереву в системе присваивается уникальный 16-разрядный идентификатор сегмента. Идентификаторы сегментов можно повторно использовать только после полного слияния исходного сегментного B-дерева-владельца с сегментным B-деревом более высокого уровня. В сегментном B-дереве каждой странице-листу присваивается уникальный номер: 1 для первой страницы-листа, 2 для второй и так далее.
Каждой странице-листу индекса списка документов также присваивается номер страницы. Первой (самой левой) странице-листу индекса списка документов присваивается тот же номер страницы, что и странице-листу сегментного B-дерева, на которой находится ее термин (поскольку индексы списков документов создаются только для терминов с очень длинными списками документов, не более одного термина на странице-листе сегментного B-дерева может иметь связанный с ним индекс списка документов). Обозначим этот номер страницы через P. Если список документов настолько велик, что для него требуется вторая страница-лист, ей присваивается номер P+1. Третьей — P+2. Каждому уровню B-дерева индекса списка документов (листьям, их родительским узлам, узлам следующего уровня и т. д.) таким же образом присваиваются номера страниц, начиная с номера P.
Значение "id", используемое в таблице %_data для хранения заданного листа или узла сегментного B-дерева либо индекса списка документов, формируется следующим образом:
| Биты rowid | Содержимое |
|---|---|
| 38..43 | (16 бит) Идентификатор сегментного B-дерева. |
| 37 | (1 бит) Флаг индекса списка документов. Установлен для страниц индекса списка документов, очищен для страниц-листьев сегментного B-дерева. |
| 32..36 | (5 бит) Высота дерева. Для листьев сегментного B-дерева и индекса списка документов равна 0, для родительских узлов листьев индекса списка документов — 1, для узлов следующего уровня — 2 и т. д. |
| 0..31 | (32 бита) Номер страницы |
9.2.2. Формат записи структуры
Запись структуры определяет набор сегментных B-деревьев, составляющих текущий индекс FTS, а также содержит сведения о выполняемых операциях инкрементального слияния. Она хранится в таблице %_data с id=10. Запись структуры начинается с одного 32-разрядного беззнакового значения — значения cookie. Оно увеличивается при каждом изменении структуры. За значением cookie следуют три значения varint:
- Количество уровней индекса (то есть максимальный уровень, связанный с каким-либо сегментным B-деревом, плюс один).
- Общее количество сегментных B-деревьев в индексе.
- Общее количество страниц-листьев сегментных B-деревьев, записанных в деревья уровня 0 с момента создания таблицы FTS5.
Затем для каждого уровня от 0 до nLevel:
- Количество входных сегментов предыдущего уровня, используемых в качестве входных данных для текущего инкрементального слияния, либо ноль, если инкрементальное слияние для создания нового сегментного B-дерева этого уровня не выполняется.
- Общее количество сегментных B-деревьев на этом уровне.
- Затем для каждого сегментного B-дерева от самого старого к самому новому:
- Идентификатор сегмента.
- Номер первой страницы-листа (обычно 1, всегда >0).
- Номер последней страницы-листа (всегда >0).
9.2.3. Формат записи средних значений
Запись средних значений, всегда хранящаяся с id=1 в таблице %_data, не содержит среднее значение чего-либо. Вместо этого она содержит вектор из (nCol+1) упакованных значений varint, где nCol — количество столбцов в таблице FTS5, включая неиндексируемые столбцы. Первый varint содержит общее количество строк в таблице FTS5. Второй — общее количество токенов во всех значениях самого левого столбца таблицы FTS5. Третий — количество токенов во всех значениях следующего столбца слева и так далее. Для неиндексируемых столбцов значение всегда равно нулю.
9.2.4. Формат сегментного B-дерева
9.2.4.1. Формат ключа/списка документов
Формат ключа/списка документов используется для хранения упорядоченной последовательности ключей (терминов документов или префиксов терминов с однобайтовым префиксом, указывающим конкретный индекс, к которому они относятся), каждому из которых соответствует список документов. Формат состоит из чередующихся ключей и списков документов, упакованных вместе.
Первый ключ хранится следующим образом:
- varint, указывающий количество байтов в ключе (N), за которым следует
- непосредственно данные ключа (N байт).
Каждый последующий ключ хранится следующим образом:
- varint, указывающий размер общего с предыдущим ключом префикса в байтах;
- varint, указывающий количество байтов в ключе после общего префикса (N), за которым следует
- непосредственно суффикс ключа (N байт).
Например, если первые два ключа в записи ключа/списка документов FTS5 — "0challenger" и "0chandelier", то первый ключ хранится как varint 11, за которым следуют 11 байт "0challenger", а второй ключ хранится как varint 4 и varint 7, за которыми следуют 7 байт "ndelier".
Рисунок 1 — Формат термина/списка документов
Каждый список документов определяет строки (по значениям их rowid), содержащие хотя бы один экземпляр термина или префикса термина, а также связанный список позиций, или «poslist», перечисляющий позицию каждого экземпляра термина в строке. В этом контексте «позиция» определяется как номер столбца и смещение термина в значении столбца.
В списке документов документы всегда хранятся в порядке сортировки по rowid. Первый rowid списка документов хранится без изменений в виде varint. Сразу за ним следует связанный с ним список позиций. Далее в виде varint записывается разница между первым и вторым rowid, за которой следует список документов, связанный со вторым rowid. И так далее.
Размер списка документов невозможно определить путем его разбора. Его необходимо хранить отдельно. Подробнее о том, как это реализовано в FTS5, см. в разделе ниже.
Рисунок 2 — Формат списка документов
Список позиций (часто сокращенно «poslist») определяет столбец и смещение токена в строке для каждого экземпляра рассматриваемого токена. Формат poslist:
- Varint, равный удвоенному размеру poslist без учета этого поля, плюс один, если для записи установлен флаг "delete".
- Список (возможно, пустой) смещений для столбца 0 (самого левого столбца) строки. Каждое смещение хранится в виде varint. Первый varint содержит значение первого смещения плюс 2. Второй varint содержит разницу между вторым и первым смещениями плюс 2 и т. д. Например, если в списке смещений должны быть значения 0, 10, 15 и 16, он кодируется следующими значениями, упакованными в виде varint подряд:
2, 12, 7, 3 - Для каждого столбца, кроме столбца 0, содержащего один или несколько экземпляров токена:
- Байтовое значение 0x01.
- Номер столбца в виде varint.
- Список смещений в том же формате, что и список смещений для столбца 0.
Рисунок 3 — Список позиций (poslist) со смещениями в столбцах 0 и i
9.2.4.2. Разбиение на страницы
Если содержимое сегментного B-дерева достаточно мало (по умолчанию — меньше 4000 байт), оно целиком может храниться в формате ключа/списка документов, описанном в предыдущем разделе, как один двоичный объект в таблице %_data. В противном случае ключ/список документов разбивается на страницы (по умолчанию примерно по 4000 байт каждая) и хранится в непрерывном наборе записей таблицы %_data (подробности см. выше).
При разделении ключа/списка документов на страницы в формат вносятся следующие изменения:
- Отдельный varint или поле данных ключа никогда не занимает две страницы.
- Первый ключ на каждой странице не сжимается с использованием префикса. Он хранится в формате, описанном выше для первого ключа списка документов: размер в виде varint, за которым следуют данные ключа.
- Если на странице перед первым ключом есть один или несколько rowid, первый из них не сжимается с помощью дельта-кодирования. Он хранится без изменений, как если бы это был первый rowid в его списке документов (что может быть так, а может и нет).
У каждой страницы также есть заголовок фиксированного размера — 4 байта — и футер переменного размера. Заголовок разделен на 2 16-разрядных целых числа в порядке от старшего байта к младшему. В них содержатся:
- Смещение в байтах первого значения rowid на странице, если оно находится перед первым ключом, или 0 в противном случае.
- Смещение футера страницы в байтах.
Футер страницы состоит из последовательности varint, содержащих смещения в байтах всех ключей на странице. Если на странице нет ключей, размер футера равен нулю байт.
Рисунок 4 — Формат страницы
9.2.4.3. Формат сегментного индекса
После форматирования содержимого сегментного B-дерева в формате ключа/списка документов и разбиения его на страницы получается структура, очень похожая на листья B+-дерева. Вместо создания формата для внутренних узлов этого 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 | Целочисленный идентификатор сегмента. |
| term | Наименьший префикс первого ключа на странице, который больше всех ключей на предыдущей странице. Для первой страницы сегмента длина этого префикса равна нулю байт. |
| pgno | Это поле кодирует и номер страницы (в сегменте, начиная с 1), и флаг индекса списка документов. Флаг индекса списка документов установлен, если у последнего ключа на странице есть связанный индекс списка документов. Значение этого поля:
(pgno*2 + bDoclistIndexFlag)
|
Затем, чтобы найти лист сегмента i, который может содержать термин t, вместо поиска по внутренним узлам FTS5 выполняет запрос:
SELECT pgno FROM %_idx WHERE segid=$i AND term>=$t ORDER BY term LIMIT 1
9.2.4.4. Формат индекса списка документов
Сегментный индекс, описанный в предыдущем разделе, позволяет эффективно выполнять поиск в сегментном B-дереве по термину или, если существует префиксный индекс необходимого размера, по префиксу термина. Описанная в этом разделе структура данных — индексы списков документов — позволяет FTS5 эффективно искать rowid или диапазон rowid в списке документов, связанном с отдельным термином или префиксом термина.
Не у всех ключей есть связанные индексы списков документов. По умолчанию индекс списка документов добавляется к ключу, только если его список документов занимает более 4 страниц-листьев сегментного B-дерева. Индексы списков документов сами являются B-деревьями: их листья и внутренние узлы хранятся в виде записей таблицы %_data, но на практике большинство списков документов достаточно малы, чтобы поместиться на одной странице-листе. Для узлов и листьев индекса списка документов FTS5 использует примерно тот же размер, что и для листьев сегментного B-дерева (по умолчанию 4000 байт).
Страницы-листья и внутренние узлы индекса списка документов используют один и тот же формат страницы. Первый байт — это байт «флагов». Для корневой страницы B-дерева индекса списка документов он равен 0x00, а для всех остальных страниц — 0x01. Оставшаяся часть страницы представляет собой последовательность плотно упакованных значений varint:
- номер самой левой дочерней страницы, за которым следует
- наименьшее значение rowid на самой левой дочерней странице, за которым следует
- по одному varint для каждой следующей дочерней страницы, содержащему значение:
- 0x00, если на дочерней странице нет rowid (это возможно, только если «дочерняя» страница на самом деле является страницей-листом сегментного B-дерева); или
- разницу между наименьшим rowid на дочерней странице и предыдущим значением rowid, сохраненным на странице индекса списка документов.
Для самой левой страницы-листа индекса списка документов самой левой дочерней страницей является первая страница-лист сегментного B-дерева после страницы, содержащей сам ключ.
9.3. Таблица размеров документов (таблица %_docsize)
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), содержащая размер каждого значения столбца этой строки в токенах.
Размеры значений столбцов хранятся в двоичном объекте, содержащем по одному упакованному varint для каждого столбца таблицы FTS5 слева направо. Varint, разумеется, содержит общее количество токенов в соответствующем значении столбца. Неиндексируемые столбцы также включены в этот вектор varint; для них значение всегда равно нулю.
Эта таблица используется API xColumnSize. Ее можно полностью исключить, указав параметр columnsize=0. В этом случае API xColumnSize по-прежнему доступен вспомогательным функциям, но работает значительно медленнее.
9.4. Содержимое таблицы (таблица %_content)
-- locale=0 (the default) table CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1...); -- locale=1 table CREATE TABLE %_content(id INTEGER PRIMARY KEY, c0, c1..., l0, l1...);
Фактическое содержимое таблицы — значения, вставленные в таблицу FTS5, — хранится в таблице %_content. В этой таблице создается по одному столбцу "c*" для каждого столбца таблицы FTS5, включая неиндексируемые столбцы. Значения самого левого столбца таблицы FTS5 хранятся в столбце "c0" таблицы %_content, значения следующего столбца таблицы FTS5 — в столбце "c1" и так далее.
Для таблицы FTS5 с параметром locale, установленным в 1, таблица %_content также содержит по одному столбцу "l*" для каждого индексируемого (то есть не UNINDEXED) столбца таблицы. Для значений, записанных в таблицу fts5 с использованием локали по умолчанию, этот столбец содержит NULL. Для значений, записанных с указанием связанной локали (значения fts5_locale()), этот столбец содержит имя локали в виде текста.
Каждое имя столбца "l*" имеет ту же целочисленную часть, что и имя связанного с ним столбца "c*". Это означает, что если в таблице fts5 есть один или несколько столбцов UNINDEXED, набор имен столбцов "l*" может не содержать непрерывной последовательности целочисленных частей. Например:
-- This fts5 table: CREATE VIRTUAL TABLE ft USING fts5(a, b UNINDEXED, c, locale=1); -- uses a %_content table with no "l1" column: CREATE TABLE ft_content(id INTEGER PRIMARY KEY, c0, c1, c2, l0, l2);
Если не указан параметр contentless_unindexed=1, эта таблица полностью отсутствует в таблицах FTS5 с внешним содержимым или без содержимого. Для таблиц без содержимого, в которых указан параметр contentless_unindexed=1, таблица %_content создается, но содержит только те столбцы "c*", которые соответствуют столбцам UNINDEXED таблицы fts5. Например:
-- This fts5 table: CREATE VIRTUAL TABLE ft USING fts5(a, b UNINDEXED, c, contentless_unindexed=1); -- uses a %_content table with only the "c1" (b) column CREATE TABLE ft_content(id INTEGER PRIMARY KEY, c1);
9.5. Параметры конфигурации (таблица %_config)
CREATE TABLE %_config(k PRIMARY KEY, v) WITHOUT ROWID;
В этой таблице хранятся значения постоянных параметров конфигурации. В столбце "k" хранится имя параметра (текст), а в столбце "v" — его значение. Пример содержимого:
sqlite> SELECT * FROM ft_config; ┌─────────────┬──────┐ │ k │ v │ ├─────────────┼──────┤ │ crisismerge │ 8 │ │ pgsz │ 8000 │ │ usermerge │ 4 │ │ version │ 4 │ └─────────────┴──────┘
Приложение A: сравнение с FTS3/4
Также доступен похожий, но более зрелый модуль FTS3/4. FTS5 — это новая версия FTS4, содержащая различные исправления и решения проблем, которые невозможно было устранить в FTS4 без нарушения обратной совместимости. Некоторые из этих проблем описаны ниже.
Руководство по портированию приложений
Для использования FTS5 вместо FTS3 или FTS4 приложениям обычно требуется внести минимальные изменения. Большинство из них относятся к трем категориям: изменения оператора CREATE VIRTUAL TABLE, используемого для создания таблицы FTS; изменения запросов SELECT, используемых для выполнения запросов к таблице; и изменения приложений, использующих вспомогательные функции FTS.
Изменения операторов CREATE VIRTUAL TABLE
-
Имя модуля необходимо изменить с «fts3» или «fts4» на «fts5».
-
Из определений столбцов необходимо удалить все сведения о типах и спецификации ограничений. FTS3/4 игнорирует всё, что следует за именем столбца в его определении, а FTS5 пытается это разобрать (и выдаст ошибку, если не сможет).
-
Параметр «matchinfo=fts3» недоступен. Эквивалентом является параметр «columnsize=0».
-
Параметр notindexed= недоступен. Его эквивалент — добавление UNINDEXED в определение столбца.
-
Токенизатор ICU недоступен.
-
Параметры compress=, uncompress= и languageid= недоступны. На данный момент их функциональность ничем не заменена.
-- FTS3/4 statement CREATE VIRTUAL TABLE ft 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 ft USING fts5( linkid UNINDEXED, header, text, columnsize=0 );
Изменения в операторах SELECT
-
Псевдоним «docid» отсутствует. Приложениям вместо него следует использовать «rowid».
-
Поведение запросов немного различается, если фильтр по столбцу указан и в составе FTS-запроса, и с помощью столбца в качестве левой части оператора MATCH. Для таблицы со столбцами «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.
Команда FTS3/4 «automerge=X» заменена параметром automerge FTS5.
Краткое описание технических различий
FTS5 похожа на FTS3/4 тем, что основная задача каждой из них — поддерживать индекс, сопоставляющий каждый уникальный токен со списком его вхождений в набор документов. Каждое вхождение определяется документом, в котором оно встречается, и его позицией в этом документе. Например:
-- 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)
В приведённом выше примере каждый набор из трёх значений определяет расположение вхождения токена: rowid, номер столбца (столбцы нумеруются последовательно слева направо, начиная с 0) и позиция в значении столбца (первый токен в значении столбца имеет номер 0, второй — 1 и так далее). Используя этот индекс, FTS5 может быстро отвечать на запросы, например «множество всех документов, содержащих токен „A“» или «множество всех документов, содержащих последовательность „Y Z“». Список вхождений, связанных с одним токеном, называется «списком вхождений».
Основное различие между FTS3/4 и FTS5 заключается в том, что в FTS3/4 каждый список вхождений хранится в виде одной большой записи базы данных, тогда как в FTS5 большие списки вхождений разделяются между несколькими записями базы данных. Это имеет следующие последствия при работе с большими базами данных, содержащими большие списки:
-
FTS5 может загружать списки вхождений в память постепенно, чтобы уменьшить использование памяти и максимальный размер выделяемого блока. FTS3/4 часто загружает списки вхождений в память целиком.
-
При обработке запросов, содержащих более одного токена, FTS5 иногда может определить, что на запрос можно ответить, просмотрев только часть большого списка вхождений. FTS3/4 почти всегда приходится обходить списки вхождений целиком.
- Если список вхождений становится настолько большим, что превышает ограничение SQLITE_MAX_LENGTH, FTS3/4 не может его обработать. У FTS5 такой проблемы нет.
По этим причинам многие сложные запросы в FTS5 могут использовать меньше памяти и выполняться быстрее.
Другие различия между FTS5 и FTS3/4:
-
FTS5 поддерживает «ORDER BY rank» для возврата результатов в порядке убывания релевантности.
-
FTS5 предоставляет API, позволяющий пользователям создавать пользовательские вспомогательные функции для расширенного ранжирования и обработки текста. Специальный столбец «rank» можно связать с пользовательской вспомогательной функцией, чтобы добавление «ORDER BY rank» в запрос давало ожидаемый результат.
-
По умолчанию FTS5 распознаёт символы-разделители Unicode и эквивалентность регистра. Это возможно и в FTS3/4, но такую поддержку необходимо включить явно.
-
Синтаксис запросов был пересмотрен там, где это было необходимо, чтобы устранить неоднозначности и обеспечить возможность экранирования специальных символов в терминах запроса.
-
По умолчанию FTS3/4 время от времени объединяет две или более B-деревьев, составляющих полнотекстовый индекс, в ходе выполняемого пользователем оператора INSERT, UPDATE или DELETE. Это означает, что любая операция с таблицей FTS3/4 может оказаться неожиданно медленной: FTS3/4 может непредсказуемо решить объединить два или более больших B-дерева. По умолчанию FTS5 использует инкрементальное слияние, ограничивающее объём обработки, который может выполняться в рамках любой отдельной операции INSERT, UPDATE или DELETE.
Последнее обновление этой страницы: 2025-07-12 15:11:36Z
SQLite is in the Public Domain.
https://sqlite.org/fts5.html