Spec-Zone.ru › SQLite

Расширение SQLite FTS5

Содержание
1. Обзор FTS5
2. Компиляция и использование FTS5
2.1. Компиляция FTS5 как части SQLite
2.2. Компиляция загружаемого расширения
3. Синтаксис запросов по полному тексту
3.1. Строки FTS5
3.2. Фразы FTS5
3.3. Префиксные запросы FTS5
3.4. Запросы на начальные токены FTS5
3.5. Запросы NEAR FTS5
3.6. Фильтры столбцов FTS5
3.7. Булевы операторы FTS5
4. Создание и инициализация таблицы FTS5
4.1. Опция UNINDEXED для столбца
4.2. Префиксные индексы
4.3. Токенизаторы
4.3.1. Токенизатор Unicode61
4.3.2. Токенизатор ASCII
4.3.3. Токенизатор Портера
4.3.4. Токенизатор триграмм
4.4. Внешнее содержимое и таблицы без содержимого
4.4.1. Таблицы без содержимого
4.4.2. Таблицы без содержимого с удалением
4.4.3. Таблицы с внешним содержимым
4.4.4. Ловушки таблиц с внешним содержимым
4.5. Опция Columnsize
4.6. Опция Detail
4.7. Опция Tokendata
4.8. Опция Locale
4.9. Опция Contentless-Unindexed
5. Вспомогательные функции
5.1. Встроенные вспомогательные функции
5.1.1. Функция bm25()
5.1.2. Функция highlight()
5.1.3. Функция snippet()
5.1.4. Функция fts5_get_locale()
5.2. Сортировка по результатам вспомогательных функций
6. Специальные команды INSERT
6.1. Опция конфигурации 'automerge'
6.2. Опция конфигурации 'crisismerge'
6.3. Команда 'delete'
6.4. Команда 'delete-all'
6.5. Опция конфигурации 'deletemerge'
6.6. Команда проверки целостности
6.7. Команда 'merge'
6.8. Команда 'optimize'
6.9. Опция конфигурации 'pgsz'
6.10. Опция конфигурации 'rank'
6.11. Команда 'rebuild'
6.12. Опция конфигурации 'secure-delete'
6.13. Опция конфигурации 'usermerge'
7. Расширение FTS5
7.1. Пользовательские токенизаторы
7.1.1. Поддержка синонимов
7.2. Пользовательские вспомогательные функции
7.2.1. Обзор API пользовательских вспомогательных функций
7.2.2. Справочник по API пользовательских вспомогательных функций
8. Модуль виртуальной таблицы fts5vocab
9. Структуры данных FTS5
9.1. Формат Varint
9.2. Таблицы индекса FTS (%_idx и %_data)
9.2.1. Пространство идентификаторов строк таблицы %_data
9.2.2. Формат записи структуры
9.2.3. Формат записи средних значений
9.2.4. Формат дерева B-дерева сегмента
9.2.4.1. Формат ключ/список документов
9.2.4.2. Страничная навигация
9.2.4.3. Формат индекса сегмента
9.2.4.4. Формат индекса списка документов
9.3. Таблица размеров документов (%_docsize)
9.4. Таблица содержимого таблицы (%_content)
9.5. Опции конфигурации (%_config)
Приложение A: Сравнение с FTS3/4
Руководство по переносу приложений
Изменения в операторах CREATE VIRTUAL TABLE
Изменения в операторах SELECT
Изменения в вспомогательных функциях
Другие вопросы
Резюме технических различий

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.

В приведённом выше примере не показано, что существуют также различные опции, которые могут быть предоставлены FTS5 в качестве части оператора CREATE VIRTUAL TABLE для конфигурации различных аспектов новой таблицы. Эти опции могут использоваться для изменения способа извлечения 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 амальгамирования. При использовании одной из двух систем сборки autoconf, FTS5 включается путем указания опции «--enable-fts5» при выполнении скрипта конфигурации. (FTS5 в настоящее время отключен по умолчанию для скрипта конфигурации source-tree и включен по умолчанию для скрипта конфигурации амальгамирования, но эти значения по умолчанию могут измениться в будущем.)

Или, если sqlite3.c компилируется с использованием другой системы сборки, путем обеспечения определения препроцессора SQLITE_ENABLE_FTS5.

2.2. Компиляция загружаемого расширения

В качестве альтернативы FTS5 можно скомпилировать как загружаемое расширение.

Канонический исходный код FTS5 состоит из ряда файлов *.c и других файлов в каталоге «ext/fts5» дерева исходных кодов SQLite. Процесс сборки сводит его к двум файлам — «fts5.c» и «fts5.h» — которые можно использовать для создания загружаемого расширения SQLite.

  1. Получите последний код SQLite из fossil.
  2. Создайте Makefile, как описано в Руководстве по компиляции SQLite.
  3. Скомпилируйте цель «fts5.c». При этом также создается fts5.h.
$ wget -c https://www.sqlite.org/src/tarball/SQLite-trunk.tgz?uuid=trunk -O SQLite-trunk.tgz
.... output ...
$ tar -xzf SQLite-trunk.tgz
$ cd SQLite-trunk
$ ./configure && make fts5.c
... lots of output ...
$ ls fts5.[ch]
fts5.c        fts5.h

Код в «fts5.c» затем можно скомпилировать в загружаемое расширение или статически связать с приложением, как описано в Компиляция загружаемых расширений. Определены две точки входа, которые делают одно и то же:

  • sqlite3_fts_init
  • sqlite3_fts5_init

Другой файл «fts5.h» не требуется для компиляции расширения FTS5. Он используется приложениями, которые реализуют пользовательские FTS5-токены или вспомогательные функции.

3. Полный синтаксис запроса

В следующем блоке приведён сводный синтаксис запросов FTS в форме БНФ. Далее следует подробное объяснение.

<phrase>    := string [*]
<phrase>    := <phrase> + <phrase>
<neargroup> := NEAR ( <phrase> <phrase> ... [, N] )
<query>     := [ [-] <colspec> :] [^] <phrase>
<query>     := [ [-] <colspec> :] <neargroup>
<query>     := [ [-] <colspec> :] ( <query> )
<query>     := <query> AND <query>
<query>     := <query> OR <query>
<query>     := <query> NOT <query>
<colspec>   := colname
<colspec>   := { colname1 colname2 ... }

3.1. Строки FTS5

В выражении FTS строка может быть указана двумя способами:

  • Заключив её в двойные кавычки ("). Внутри строки любые вложенные двойные кавычки могут быть экранированы в стиле SQL — путём добавления второй двойной кавычки.

  • В качестве FTS5-слова, которое не является «AND», «OR» или «NOT» (регистрозависимое). FTS5-слово — это строка из одного или нескольких последовательных символов, которые все являются:

    • символами не ASCII-диапазона (т. е. кодовые точки юникода больше 127), или
    • одним из 52 прописных или строчных ASCII-символов, или
    • одним из 10 десятичных ASCII-символов, или
    • символом нижнего подчёркивания (кодовая точка юникода 96).
    • замещающим символом (кодовая точка юникода 26).
    Строки, которые содержат любые другие символы, должны быть заключены в кавычки. Символы, которые в настоящее время не разрешены в словах, не являются символами кавычек и не служат никакой специальной цели в выражениях запросов FTS5, могут в будущем быть разрешены в словах или использоваться для реализации новых функций запросов. Это означает, что запросы, которые в настоящее время являются синтаксическими ошибками из-за включения такого символа вне строки в кавычках, могут быть интерпретированы по-другому некоторыми будущими версиями FTS5.

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 соответствует документу, если документ содержит по крайней мере один фрагмент токенов, которые:

  1. содержат по крайней мере один экземпляр каждой фразы, и
  2. для которого количество токенов между концом первой фразы и началом последней фразы во фрагменте не превышает 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> Соответствует, если запрос1 соответствует, а запрос2 не соответствует.
<query1> AND <query2> Соответствует, если запрос1 и запрос2 соответствуют.
<query1> OR <query2> Соответствует, если соответствует либо запрос1, либо запрос2.

Скобки могут использоваться для группирования выражений, чтобы изменить приоритет операторов обычным способом. Например:

-- 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. Для простоты они не показаны в грамматике БНФ выше. В сущности, любая последовательность фраз или групп NEAR (включая те, которые ограничены соответствием указанным столбцам), разделенные только пробелами, обрабатывается так, как будто между каждой парой фраз или групп NEAR существует неявный оператор 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», используемый для создания таблицы FTS5 как внешней таблицы содержимого или таблицы без содержимого.
  • Параметр «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» устанавливается либо на целое положительное число, либо на текстовое значение, содержащее список одного или нескольких положительных целых чисел, разделённых пробелами. Для каждого указанного целого числа создаётся префиксный индекс. Если несколько параметров «prefix» указаны в одном операторе CREATE VIRTUAL TABLE, все они применяются.

-- 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. Токенизаторы

Параметр «tokenize» оператора CREATE VIRTUAL TABLE используется для настройки конкретного токенизатора, используемого таблицей 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 («малая буква O с диакритикой circ и точкой снизу»). Это технически ошибка, но её нельзя исправить без создания проблем обратной совместимости. Если этот параметр установлен на «2», то диакритики корректно удаляются из всех символов латинского алфавита.
categories Этот параметр можно использовать для изменения набора категорий Unicode, которые считаются соответствующими символам токенов. Аргумент должен состоять из списка категорий Unicode, разделённых пробелами, состоящих из двух символов (например, «Lu» или «Nd»), или того же с вторым символом, заменённым на звёздочку («*»), интерпретируемой как шаблон подстановки. Значение по умолчанию — «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 — это обёртковый токенизатор. Он принимает выходные данные какого-либо другого токенизатора и применяет алгоритм стеминга Портера к каждому токену перед его возвращением в FTS5. Это позволяет поисковым запросам, таким как «correction», совпадать с похожими словами, такими как «corrected» или «correcting». Алгоритм стеминга Портера предназначен только для использования с английскими терминами — его использование с другими языками может или не может улучшить полезность поиска.

По умолчанию токенизатор porter работает как обёртка над токенизатором по умолчанию (unicode61). Либо, если один или несколько дополнительных аргументов добавлены к параметру «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, если параметр case_sensitive установлен на 0 — установка обоих параметров на 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 создаётся с параметром case_sensitive, установленным на 1, он может индексировать только запросы GLOB, а не LIKE.

Примечания:

  • Подстроки, состоящие менее чем из 3 юникодных символов, не соответствуют ни одной строке при использовании полнотекстового запроса. Если шаблон LIKE или GLOB не содержит по крайней мере одну последовательность символов Юникода без подстановочных знаков, FTS5 переходит к линейному сканированию всей таблицы.
  • Если таблица FTS5 создана с указанным параметром detail=none или detail=column, полнотекстовые запросы могут не содержать никаких токенов длиннее 3 юникодных символов. Сопоставление шаблонов 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. Таблицы без контента не поддерживают обработку конфликтов REPLACE. Операции REPLACE и INSERT OR REPLACE обрабатываются как обычные инструкции INSERT. Строки могут быть удалены из таблицы без контента с помощью команды удаления FTS5.

Попытка считать любое значение столбца, кроме rowid, из таблицы FTS5 без контента возвращает 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 требуется получить значения столбцов, он выполняет запрос к таблице содержимого следующим образом, привязывая rowid строки, для которой требуются значения, к переменной SQL:

SELECT <content_rowid>, <cols> FROM <content> WHERE <content_rowid> = ?;

В приведенном выше примере <содержимое> заменяется именем таблицы содержимого. По умолчанию <content_rowid> заменяется литеральным текстом "rowid". Или, если параметр "content_rowid" задан в инструкции CREATE VIRTUAL TABLE, значением этого параметра. <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_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.5. Параметр Columnsize

Обычно FTS5 поддерживает специальную вспомогательную таблицу в базе данных, которая хранит размер каждого значения столбца в токенах, вставленных в основную таблицу FTS5 в отдельной таблице. Эта вспомогательная таблица используется функцией API xColumnSize, которая, в свою очередь, используется встроенной функцией ранжирования bm25 (и, вероятно, будет полезна и другим функциям ранжирования).

Для экономии места эту вспомогательную таблицу можно опустить, установив параметр columnsize в ноль. Например:

-- A table without the xColumnSize() values stored on disk:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=0);

-- Three equivalent ways of creating a table that does store the
-- xColumnSize() values on disk:
CREATE VIRTUAL TABLE ft USING fts5(a, b, c);
CREATE VIRTUAL TABLE ft USING fts5(a, b, c, columnsize=1);
CREATE VIRTUAL TABLE ft USING fts5(a, b, columnsize='1', c);

Ошибка задать параметр columnsize любому значению, кроме 0 или 1.

Если таблица FTS5 настроена с columnsize=0, но не является таблицей без контента, функция API xColumnSize всё ещё работает, но работает намного медленнее. В этом случае вместо непосредственного считывания возвращаемого значения из базы данных, она считывает само текстовое значение и подсчитывает токены по мере необходимости.

Или, если таблица также является таблицей без контента, то применимы следующие пункты:

  • Функция API xColumnSize всегда возвращает -1. Нет способа определить количество токенов в значении, хранящемся в таблице FTS5 без контента, настроенной с columnsize=0.

  • Каждая вставленная строка должна сопровождаться явно указанным значением rowid. Если таблица без контента настроена с columnsize=0, попытка вставить NULL в значение rowid является ошибкой SQLITE_MISMATCH.

  • Все запросы к таблице должны быть полнотекстовыми запросами. Другими словами, они должны использовать оператор MATCH или = с столбцом имени таблицы в качестве левого операнда или использовать синтаксис таблично-значимой функции. Любой запрос, который не является полнотекстовым запросом, приводит к ошибке.

Имя таблицы, в которой хранятся значения xColumnSize (если не указано columnsize=0), — "<имя>_docsize", где <имя> — само имя таблицы 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() устанавливает итератор в конец файла).

В одном тесте, в котором индексировался большой набор электронных писем (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, то SQL-функция fts5_locale() может использоваться для сопоставления значения локали (например, "th_TH" или "en_US") со строками, переданными в FTS5. Сам 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() записывается в НЕИНДЕКСИРОВАННЫЙ столбец таблицы fts5, значение локали отбрасывается, и сама строка хранится.

Функция fts5_get_locale() может использоваться для извлечения локали значения, сохранённого в таблице FTS5.

4.9. Параметр Contentless-Unindexed

Обычно НЕИНДЕКСИРОВАННЫЕ столбцы, принадлежащие таблицам без содержимого, не очень полезны. Значения, записанные в них, не индексируются и не хранятся, а чтение из такого НЕИНДЕКСИРОВАННОГО столбца всегда возвращает NULL. Однако, если параметр "contentless_unindexed=1" указан для таблицы без содержимого, то значения НЕИНДЕКСИРОВАННЫХ столбцов хранятся постоянно, даже если значения, записанные в другие столбцы, не сохраняются.

-- 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 с токенизатором триграмм) к таблице 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() должна вызываться ровно с тремя аргументами после имени таблицы. Они интерпретируются следующим образом:

  1. Целое число, указывающее индекс столбца таблицы FTS, из которого следует считать текст. Столбцы нумеруются слева направо, начиная с нуля.
  2. Текст, который следует вставлять перед каждой фразой совпадения.
  3. Текст, который следует вставлять после каждой фразы совпадения.

Например:

-- 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() должна принимать пять параметров после аргумента имени таблицы:

  1. Целое число, указывающее индекс столбца таблицы FTS для выбора возвращаемого текста. Столбцы нумеруются слева направо, начиная с нуля. Отрицательное значение указывает, что столбец должен быть выбран автоматически.
  2. Текст, который следует вставлять перед каждой фразой совпадения в возвращаемом тексте.
  3. Текст, который следует вставлять после каждой фразы совпадения в возвращаемом тексте.
  4. Текст, который следует добавлять к началу или концу выбранного текста, чтобы указать, что возвращаемый текст не находится в начале или конце своего столбца, соответственно.
  5. Максимальное количество лексем в возвращаемом тексте. Это значение должно быть больше нуля и меньше или равно 64.

5.1.4. Функция fts5_get_locale()

Функция fts5_get_locale() используется для извлечения локализационных данных, если таковые имеются, связанных со значением, хранящимся в таблице FTS5. Она принимает один аргумент после имени таблицы — индекс столбца текущей строки для запроса. Столбцы нумеруются в том порядке, в котором они были указаны в операторе CREATE VIRTUAL TABLE, начиная с 0.

Если таблица 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.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 rank.

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-деревья, когда существует M или более B-деревьев одного уровня, где 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 строки, прикрепляется "маркер-метка" (tombstone), содержащий 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. Команда 'integrity-check'

Эта команда используется для проверки того, что индекс полного текста внутренне согласован, и, необязательно, что он согласован с любой таблицей внешнего содержимого external content.

Команда проверки целостности вызывается путем вставки текстового значения '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.7. Команда 'merge'

INSERT INTO ft(ft, rank) VALUES('merge', 500);

Эта команда объединяет структуры b-деревьев до тех пор, пока примерно N страниц объединенных данных не будут записаны в базу данных, где N — абсолютное значение параметра, указанного в команде 'merge'. Размер каждой страницы определяется параметром FTS5 pgsz.

Если параметр имеет положительное значение, структуры B-деревьев подходят для слияния только в том случае, если выполняется одно из следующих условий:

  • Существует U или более таких b-деревьев на одном уровне (см. документацию к параметру FTS5 automerge для объяснения уровней b-деревьев), где U — значение, назначенное параметру FTS5 usermerge.
  • Слияние уже начато (возможно, командой 'merge', которая указала отрицательный параметр).

Можно определить, обнаружила ли команда 'merge' какие-либо b-деревья для объединения, проверив значение, возвращаемое API sqlite3_total_changes() до и после выполнения команды. Если разница между двумя значениями равна 2 или больше, то работа была выполнена. Если разница меньше 2, то команда 'merge' была бесполезной. В этом случае нет необходимости выполнять ту же команду 'merge' снова, по крайней мере, до следующего обновления таблицы FTS.

Если параметр имеет отрицательное значение и в индексе FTS существуют структуры b-деревьев на нескольких уровнях, все структуры b-деревьев назначаются одному уровню перед началом операции слияния. Кроме того, если параметр отрицательный, значение параметра конфигурации usermerge игнорируется — можно объединить всего два b-дерева с одного уровня.

Вышесказанное означает, что выполнение команды 'merge' с отрицательным параметром до тех пор, пока разница между значениями до и после выполнения функции sqlite3_total_changes() не станет меньше двух, оптимизирует индекс FTS таким же образом, как команда FTS5 optimize. Однако если новый b-дерево добавляется в индекс FTS во время этого процесса, FTS5 переместит новое b-дерево на тот же уровень, что и существующие, и перезапустит слияние. Чтобы этого избежать, только первый вызов 'merge' должен иметь отрицательный параметр. Каждый последующий вызов 'merge' должен иметь положительное значение, чтобы слияние, начатое первым вызовом, завершилось даже в случае добавления новых b-деревьев в индекс FTS.

6.8. Команда 'optimize'

Эта команда объединяет все отдельные b-деревья, которые в настоящее время составляют индекс полного текста, в одну большую структуру b-дерева. Это гарантирует, что индекс полного текста занимает минимальное пространство в базе данных и имеет наилучшую производительность при запросах.

Дополнительные сведения об отношениях между индексом полного текста и его составляющими b-деревьями см. в документации к параметру FTS5 automerge.

INSERT INTO ft(ft) VALUES('optimize');

Поскольку команда optimize перестраивает весь индекс FTS, она может занимать много времени. Команду FTS5 merge можно использовать для разделения работы по оптимизации индекса FTS на несколько шагов. Для этого:

  • Вызовите команду 'merge' один раз, установив параметр в -N, а затем
  • Вызовите команду 'merge' ноль или более раз, установив параметр в N.

где N — количество страниц данных, которые нужно объединить при каждом вызове команды merge. Приложение должно прекратить вызовы merge, когда разница в значении, возвращаемом функцией sqlite3_total_changes() до и после команды merge, станет меньше двух. Команды merge могут быть выполнены в рамках одной или нескольких транзакций и разными клиентами базы данных. Дополнительные сведения см. в документации к команде merge.

6.9. Параметр конфигурации 'pgsz'

Эта команда используется для установки параметра "pgsz".

Индекс полного текста, поддерживаемый FTS5, хранится в виде серии блоков фиксированного размера в таблице базы данных. Не обязательно, чтобы все блоки, составляющие индекс полного текста, имели одинаковый размер. Параметр pgsz определяет размер всех блоков, созданных последующими записывателями индекса. Значение по умолчанию равно 4050.

INSERT INTO ft(ft, rank) VALUES('pgsz', 4072);

6.10. Параметр конфигурации 'rank'

Эта команда используется для установки параметра "rank".

Параметр rank используется для изменения отображения по умолчанию вспомогательной функции для столбца rank. Параметр должен быть установлен в текстовое значение в том же формате, что и для "rank MATCH ?" терминов выше. Например:

INSERT INTO ft(ft, rank) VALUES('rank', 'bm25(10.0, 5.0)');

6.11. Команда 'rebuild'

Эта команда сначала удаляет весь индекс полного текста, а затем перестраивает его на основе содержимого таблицы или таблицы контента. Она недоступна для таблиц без содержимого.

INSERT INTO ft(ft) VALUES('rebuild');

6.12. Параметр конфигурации 'secure-delete'

Эта команда используется для установки параметра "secure-delete". Например:

INSERT INTO ft(ft, rank) VALUES('secure-delete', 1);

Обычно, когда запись в таблице fts5 обновляется или удаляется, вместо удаления записей из индекса полного текста, ключи удаления добавляются в новое b-дерево, созданное транзакцией. Это эффективно, но это означает, что старые записи индекса полного текста остаются в файле базы данных до тех пор, пока они не будут удалены операциями слияния в индексе полного текста. Любой пользователь с доступом к базе данных может использовать эти записи для тривиального восстановления содержимого удаленных строк таблицы FTS5. Однако если параметр 'secure-delete' установлен в 1, тогда записи полного текста фактически удаляются из базы данных при обновлении или удалении существующих строк таблицы FTS5. Это медленнее, но предотвращает использование старых записей полного текста для восстановления удаленных строк таблиц.

Этот параметр гарантирует, что старые записи полного текста недоступны для злоумышленников с SQL-доступом к базе данных. Чтобы также гарантировать, что их нельзя восстановить злоумышленникам с доступом к самому файлу базы данных SQLite, приложение также должно включить параметр secure-delete ядра SQLite командой типа "PRAGMA secure_delete = 1".

Предупреждение: После обновления или удаления одной или нескольких строк таблицы с установленным этим параметром таблица FTS5 может больше не читаться или записываться ни одной версией FTS5, предшествующей 3.42.0 (первой версией, в которой этот параметр был доступен). Попытка сделать это приведет к ошибке с сообщением об ошибке, подобным "неверный формат файла fts5 (найдено 5, ожидалось 4) — запустите 'rebuild'". Формат файла FTS5 может быть изменен, чтобы его могли читать более ранние версии FTS5, путем запуска команды 'rebuild' в таблице с использованием версии 3.42.0 или более поздней.

Значение параметра secure-delete по умолчанию равно 0.

6.13. Параметр конфигурации '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 приводит к неопределенному поведению.

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() передаётся не NULL xDestroy, то он вызывается с копией указателя 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(). Третий и четвёртый аргументы — указатель на буфер, содержащий текст токена, и размер токена в байтах. 4-й и 5-й аргументы — байтовые смещения первого байта и первого байта, непосредственно следующего за текстом, из которого получен токен, в входных данных.

Второй аргумент, передаваемый в обратный вызов xToken («tflags»), обычно должен быть установлен в 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_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:

  1. Путем сопоставления всех синонимов с одним токеном. В данном примере это означает, что токенизатор возвращает один и тот же токен для входов «first» и «1st». Предположим, что этот токен на самом деле «first», так что при вставке документа «I won 1st place» в индекс будут добавлены записи для токенов «i», «won», «first» и «place». Если пользователь затем выполняет запрос '1st + place', токенизатор заменяет «1st» на «first», и запрос работает как ожидалось.

  2. Путем запроса к индексу всех синонимов каждого поискового термина по отдельности. В этом случае, при токенизации текста запроса, токенизатор может предоставить несколько синонимов для одного термина в документе. FTS5 затем выполняет запрос к индексу для каждого синонима индивидуально. Например, перед запросом:

    ... MATCH 'first place'
    

    токенизатор предлагает как «1st», так и «first» в качестве синонимов для первого токена в запросе MATCH, и FTS5 фактически выполняет запрос, аналогичный:

    ... MATCH '(first OR 1st) place'
    

    за исключением того, что в целях вспомогательных функций запрос по-прежнему содержит только две фразы — «(first OR 1st)» обрабатывается как одна фраза.

  3. Путем добавления нескольких синонимов для одного термина в индекс 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 выполнял запрос для нескольких терминов, поэтому он эффективен с точки зрения занимаемого места на диске и скорости запроса. Однако он не очень хорошо поддерживает запросы с префиксом. Если, как предполагалось выше, токен «first» подставляется вместо «1st» токенизатором, то запрос:

... 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, он вызывается с копией указателя 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 есть примеры фразы 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 текущего запроса. Любой фильтр столбцов, применимый к фразе iPhrase текущего запроса, включается в $p. Для каждой посещаемой строки вызывается функция обратного вызова, переданная в качестве четвёртого аргумента. Контекст и объекты 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, все вызовы используют один контекст дополнительных данных.

Если при вызове этой функции уже есть указатель на данные, он заменяется новым указателем. Если вместе с исходным указателем был указан callback 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 фразы hit iIdx в текущей строке. Если iIdx меньше нуля или больше или равно значению, возвращаемому функцией xInstCount(), возвращается SQLITE_RANGE. В противном случае выходная переменная (*ppToken) устанавливается для указания на буфер, содержащий соответствующий маркер документа, а (*pnToken) — на размер этого буфера в байтах. Этот API недоступен, если указанный маркер соответствует префиксу запроса. В этом случае обе выходные переменные всегда устанавливаются в 0.

Выводной текст не является копией текста документа, который был промаркирован. Это вывод модуля токенизации. Для таблиц tokendata=1 это включает любые встроенные 0x00 и хвостовые данные.

Этот 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_locale() была использована для связывания локали со значением при его вставке в таблицу fts5, то (*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; «строка», «столбец» и «экземпляр». Если таблица 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 типа «строка» содержит одну строку для каждого уникального термина в связанной таблице FTS5. Столбцы таблицы следующие:

Столбец Содержание
термин Термин, как он хранится в индексе FTS5.
документ Количество строк, содержащих по крайней мере один экземпляр термина.
кол-во Общее количество экземпляров термина во всей таблице FTS5.

Таблица fts5vocab типа «столбец» содержит одну строку для каждой уникальной комбинации термин/столбец в связанной таблице FTS5. Столбцы таблицы следующие:

Столбец Содержание
термин Термин, как он хранится в индексе FTS5.
столбец Название столбца таблицы FTS5, который содержит термин.
документ Количество строк в таблице FTS5, для которых столбец $col содержит по крайней мере один экземпляр термина.
кол-во Общее количество экземпляров термина, которые появляются в столбце $col таблицы FTS5 (с учетом всех строк).

Таблица fts5vocab типа «экземпляр» содержит одну строку для каждого экземпляра термина, хранящегося в связанном индексе FTS. Предполагая, что таблица FTS5 создана с параметром 'detail' установленным в 'full', столбцы таблицы следующие:

Столбец Содержание
термин Термин, как он хранится в индексе FTS5.
документ Идентификатор строки документа, содержащего экземпляр термина.
столбец Название столбца, содержащего экземпляр термина.
смещение Индекс экземпляра термина в его столбце. Термины нумеруются в порядке появления, начиная с 0.

Если таблица FTS5 создана с опцией 'detail' установленной в 'col', то столбец смещение виртуальной таблицы экземпляров всегда содержит NULL. В этом случае в таблице есть одна строка для каждой уникальной комбинации термин/документ/столбец. Или, если таблица FTS5 создана с опцией 'detail' установленной в 'none', то столбцы смещение и столбец всегда содержат значения NULL. Для таблиц FTS5 с detail=none в таблице fts5vocab есть одна строка для каждой уникальной комбинации термин/документ.

Пример:

-- 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 состоит либо из нуля или более байтов, у которых установлен старший бит, за которым следует один байт со сброшенным старшим битом, либо из девяти байтов, в зависимости от того, что короче. Нижние семь битов каждого из первых восьми байтов и все 8 битов девятого байта используются для восстановления 64-битного целого числа со знаком дополнения до двух. Варинты хранятся в формате big-endian: биты, взятые из предыдущего байта varint, более значимы, чем биты, взятые из последующих байтов.

9.2. Индекс FTS (%_idx и %_data таблицы)

Индекс FTS представляет собой упорядоченное хранилище ключ-значение, где ключами являются термины документов или префиксы терминов, а связанными значениями являются «списки документов». Список документов — это упакованный массив 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-дерева. Он позволяет эффективно запрашивать большой список документов для идентификаторов строк или диапазонов идентификаторов строк. Например, при обработке запроса типа:

SELECT ... FROM ft('term') WHERE rowid BETWEEN ? AND ?

FTS5 использует индекс b-дерева сегмента для поиска списка документов для термина «термин», а затем использует его индекс списка документов (если он присутствует) для эффективной идентификации подмножества совпадений с идентификаторами строк в требуемом диапазоне.

9.2.1. Пространство идентификаторов строк таблицы %_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-дерева сегмента или листа/узла индекса списка документов, формируется следующим образом:

striped="1">
Разряды 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", а второй ключ хранится как varints 4 и 7, за которыми следуют 7 байт "ndelier".

список документов 0 список документов 1 ключ/список документов 2... данные ключа 0 размер ключа 0 (varint) размер префикса ключа 1 (varint) размер суффикса ключа 1 (varint) данные префикса ключа 1

Рисунок 1 - Формат термин/список документов

Каждый список документов идентифицирует строки (по их значениям rowid), которые содержат по крайней мере одну запись термина или префикса термина и связанный список позиций или "poslist", перечисляющий позиции каждой записи термина в строке. В этом смысле "позиция" определяется как номер столбца и смещение термина в значении столбца.

В пределах списка документов документы всегда хранятся в отсортированном порядке по rowid. Первый rowid в списке документов хранится как есть, как значение varint. За ним сразу следует связанный с ним список позиций. После этого следует разность между первым rowid и вторым, как значение varint, за которым следует список документов, связанный со вторым rowid в списке документов. И так далее.

Нет способа определить размер списка документов путем его анализа. Это должно быть сохранено внешним образом. См. раздел ниже для получения подробностей о том, как это делается в FTS5.

список позиций 0 список позиций 1 список позиций 2... rowid 0 (varint) rowid 1 (delta-закодированный varint) rowid 3 (delta-закодированный varint)

Рисунок 2 - Формат списка документов

Список позиций - часто сокращается до "poslist" - определяет столбец и смещение токена в строке каждой записи рассматриваемого токена. Формат poslist:

  • Значение varint, равное удвоенному размеру poslist, не включая это поле, плюс единица, если для записи установлен флаг "удаления".
  • (Возможный пустой) список смещений для столбца 0 (самого левого столбца) строки. Каждое смещение хранится как значение varint. Первое значение varint содержит значение первого смещения, плюс 2. Второе значение varint содержит разницу между вторым и первым смещениями, плюс 2. И так далее. Например, если список смещений должен содержать смещения 0, 10, 15 и 16, он кодируется путем упаковки следующих значений, закодированных как значения varint, от конца к началу:
               2, 12, 7, 3
    
  • Для каждого столбца, отличного от столбца 0, содержащего одну или несколько записей токена:
    • Значение байта 0x01.
    • Номер столбца, как значение varint.
    • Список смещений, в том же формате, что и список смещений для столбца 0.
сдвиг списка col 0 0x01 сдвиг списка col i nSize*2 + bDel (varint) номер столбца (i) байт nSize

Рисунок 3 - Список позиций (poslist) со сдвигами в столбцах 0 и i

9.2.4.2. Странирование

Если размер достаточно мал (по умолчанию это означает меньше 4000 байт), всё содержимое дерева отрезка b может быть сохранено в формате key/doclist, описанном в предыдущем разделе, как единый блок в таблице %_data. В противном случае, key/doclist разбивается на страницы (по умолчанию, примерно по 4000 байт каждая) и сохраняется в непрерывном наборе записей в таблице %_data (см. выше для подробностей).

Когда key/doclist делится на страницы, вносится следующие изменения в формат:

  • Одно varint или поле данных ключа никогда не охватывает две страницы.
  • Первый ключ на каждой странице не сжатый с префиксом. Он хранится в формате, описанном выше для первого ключа doclist — его размер как varint, за которым следуют данные ключа.
  • Если на странице есть один или несколько rowid перед первым ключом, то первый из них не сжат по дельте. Он хранится как есть, так же, как если бы он был первым rowid своего doclist (которым он может или не может быть).

Каждая страница также имеет заголовок фиксированного размера 4 байта и переменный по размеру футер. Заголовок делится на 2 поля 16-битных целых чисел big-endian. Они содержат:

  • Смещение в байтах первого значения rowid на странице, если оно встречается до первого ключа, или 0 в противном случае.
  • Смещение в байтах футера страницы.

Футер страницы состоит из последовательности varint, содержащих смещение в байтах каждого ключа, который появляется на странице. Футер страницы имеет размер 0 байт, если на странице нет ключей.

hdr измененные данные key/doclist футер 4 байта переменного размера

Рисунок 4 - Формат страницы

9.2.4.3. Формат индекса сегмента

Результат форматирования содержимого дерева сегмента b в формате key/doclist и последующего разделения его на страницы очень похож на листья 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 Самый короткий префикс первого ключа на странице, который больше всех ключей на предыдущей странице. Для первой страницы в сегменте этот префикс имеет размер 0 байт.
pgno Это поле кодирует номер страницы (внутри сегмента - начиная с 1) и флаг индекса doclist. Флаг индекса doclist устанавливается, если у конечного ключа на странице есть индекс doclist. Значение этого поля равно:
       (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. Формат индекса doclist

Индекс сегмента, описанный в предыдущем разделе, позволяет эффективно запрашивать дерево отрезка b по термину или, предположительно, имея индекс префикса, по префиксу термина. Структура данных, описанная в этом разделе, индексы doclist, позволяет FTS5 эффективно искать rowid или диапазон или rowids внутри doclist, связанных с одним термином или префиксом термина.

Не все ключи имеют связанные индексы doclist. По умолчанию индекс doclist добавляется только для ключа, если его doclist охватывает более 4 страниц листьев дерева отрезка b. Индексы doclist сами являются деревьями b, с листьями и внутренними узлами, хранящимися как записи в таблице %_data, но на практике большинство doclist достаточно малы, чтобы поместиться на одном листе. FTS5 использует тот же приблизительный размер для узла и листа индекса doclist, что и для листьев дерева отрезка b (по умолчанию 4000 байт).

Листья и внутренние узлы индекса doclist используют тот же формат страницы. Первый байт — байт «флагов». Он устанавливается в 0x00 для корневой страницы индекса doclist b-дерева и в 0x01 для всех других страниц. Остальная часть страницы представляет собой серию плотно упакованных varint, как указано ниже:

  • номер страницы левого дочернего элемента, за которым следует
  • наименьшее значение rowid на левой дочерней странице, за которым следует
  • один varint для каждой последующей дочерней страницы, содержащий значение:
    • 0x00, если на дочерней странице нет rowid (это может произойти только тогда, когда «дочерняя» страница фактически является листом дерева отрезка b), или
    • разница между наименьшим rowid на дочерней странице и предыдущим значением rowid, хранящимся на странице индекса doclist.

Для левого листа индекса doclist в индексе doclist левая дочерняя страница — это первый лист дерева отрезка b после того, который содержит сам ключ.

9.3. Таблица размеров документов (%_docsize table)

CREATE TABLE %_docsize(
    id INTEGER PRIMARY KEY,   -- id of FTS5 row this record pertains to
    sz BLOB                   -- blob containing nCol packed varints
);

Многие распространенные функции ранжирования результатов поиска требуют в качестве входных данных размер результата документа в токенах (так как попадание поискового термина в короткий документ считается более значимым, чем в длинном). Для быстрого доступа к этой информации для каждой строки в таблице FTS5 существует соответствующая запись (с тем же rowid) в таблице %_docsize shadow, которая содержит размер каждого значения столбца в строке в токенах.

Размеры значений столбцов хранятся в блоке, содержащем один упакованный varint для каждого столбца таблицы FTS5 слева направо. Varint содержит, конечно, общее количество токенов в соответствующем значении столбца. Неиндексированные столбцы включаются в этот вектор varint; для них значение всегда устанавливается в ноль.

Эта таблица используется API xColumnSize. Её можно полностью пропустить, указав параметр columnsize=0. В этом случае API xColumnSize по-прежнему доступен для вспомогательных функций, но работает гораздо медленнее.

9.4. Содержимое таблицы (%_content table)

-- 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 имеет один или несколько неиндексированных столбцов, набор имён столбцов «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*», которые соответствуют неиндексированным столбцам таблицы 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 table)

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

  1. Имя модуля необходимо изменить с "fts3" или "fts4" на "fts5".

  2. Вся информация о типе или спецификации ограничений должна быть удалена из определений столбцов. FTS3/4 игнорирует все, что следует за именем столбца в определении столбца, FTS5 пытается его разобрать (и сообщит об ошибке, если не сможет).

  3. Опция "matchinfo=fts3" недоступна. Опция "columnsize=0" эквивалентна.

  4. Опция notindexed= недоступна. Добавление UNINDEXED в определение столбца эквивалентно.

  5. Токенизатор ICU недоступен.

  6. Опции 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

  1. Псевдоним "docid" не существует. Приложения должны использовать "rowid" вместо него.

  2. Поведение запросов, когда фильтр столбца указан как часть запроса FTS, так и с использованием столбца в качестве левой части оператора MATCH, немного отличается. Для таблицы со столбцами "a" и "b" и запросом, аналогичным:

    ... a MATCH 'b: string'
    

    FTS3/4 ищет совпадения в столбце "b". Однако FTS5 всегда возвращает ноль строк, так как результаты сначала фильтруются для столбца "b", затем для столбца "a", не оставляя результатов. Другими словами, в FTS3/4 внутренний фильтр переопределяет внешний, в FTS5 оба фильтра применяются.

  3. Синтаксис запроса FTS (правая часть оператора MATCH) изменился некоторыми способами. Синтаксис FTS5 довольно близок к синтаксису FTS4 "enhanced syntax". Основное отличие состоит в том, что FTS5 более требователен к нераспознанным знакам препинания и аналогичным символам внутри строк запроса. Большинство запросов, работающих с FTS3/4, также должны работать с FTS5, а те, что не работают, должны возвращать ошибки разбора.

Изменения вспомогательных функций

FTS5 не имеет функций matchinfo() или offsets(), а функция snippet() не так полнофункциональна, как в FTS3/4. Однако, поскольку FTS5 предоставляет API, позволяющий приложениям создавать собственные вспомогательные функции, необходимая функциональность может быть реализована в коде приложения.

Набор встроенных вспомогательных функций, предоставляемых FTS5, может быть улучшен в будущем.

Другие проблемы

  1. Функциональность, предоставляемая модулем fts4aux, теперь предоставляется модулем fts5vocab. Схема этих двух таблиц немного отличается.

  2. Команда FTS3/4 "merge=X,Y" была заменена командой слияния FTS5.

  3. Команда FTS3/4 "automerge=X" была заменена опцией FTS5 automerge.

Обзор технических различий

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 распознает разделители Юникода и эквивалентность регистров по умолчанию. Это также возможно с помощью FTS3/4, но должно быть явно включено.

  • Синтаксис запроса был пересмотрен по мере необходимости, чтобы устранить неоднозначности и сделать возможным экранирование специальных символов в терминах запроса.

  • По умолчанию FTS3/4 иногда объединяет две или более бинарных деревьев, составляющих его полнотекстовый индекс, в рамках операторов INSERT, UPDATE или DELETE, выполняемых пользователем. Это означает, что любая операция с таблицей FTS3/4 может оказаться неожиданно медленной, так как FTS3/4 может непредсказуемо объединить два или более крупных бинарных дерева в ней. FTS5 по умолчанию использует инкрементное слияние, которое ограничивает объем обработки, который может быть выполнен в рамках любой операции INSERT, UPDATE или DELETE.

SQLite is in the Public Domain.
https://sqlite.org/fts5.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API