Механизм виртуальных таблиц SQLite
Содержание
1. Введение
Виртуальная таблица — это объект, зарегистрированный в открытом подключении к базе данных SQLite. С точки зрения инструкции SQL объект виртуальной таблицы выглядит как любая другая таблица или представление. Однако за кулисами запросы к виртуальной таблице и обновления в ней вызывают методы обратного вызова объекта виртуальной таблицы, а не выполняют чтение и запись в файле базы данных.
Механизм виртуальных таблиц позволяет приложению предоставлять интерфейсы, доступные из инструкций SQL так, словно это таблицы. С инструкциями SQL над виртуальной таблицей можно выполнять почти всё то же, что и над обычной таблицей, за следующими исключениями:
- Для виртуальной таблицы нельзя создать триггер.
- Для виртуальной таблицы нельзя создать дополнительные индексы. (У виртуальных таблиц могут быть индексы, но они должны быть встроены в реализацию виртуальной таблицы. Индексы нельзя добавлять отдельно с помощью инструкций CREATE INDEX.)
- Для виртуальной таблицы нельзя выполнять команды ALTER TABLE ... ADD COLUMN.
Конкретные реализации виртуальных таблиц могут накладывать дополнительные ограничения. Например, некоторые реализации могут предоставлять таблицы только для чтения. Другие могут разрешать INSERT или DELETE, но не UPDATE. Некоторые реализации виртуальных таблиц могут ограничивать виды выполняемых обновлений UPDATE.
Виртуальная таблица может представлять структуру данных в памяти. Она также может представлять представление данных на диске, хранящихся не в формате SQLite. Кроме того, приложение может вычислять содержимое виртуальной таблицы по требованию.
Ниже приведены некоторые существующие и возможные варианты применения виртуальных таблиц:
- Интерфейс полнотекстового поиска
- Пространственные индексы на основе R-деревьев
- Анализ содержимого файла базы данных SQLite на диске (виртуальная таблица dbstat)
- Чтение и/или запись содержимого файла со значениями, разделёнными запятыми (CSV)
- Доступ к файловой системе компьютера-хоста как к таблице базы данных
- Возможность обрабатывать данные в статистических пакетах, таких как R, с помощью SQL
Более длинный список реально существующих реализаций приведён на странице списка виртуальных таблиц.
1.1. Использование
Виртуальная таблица создаётся с помощью инструкции CREATE VIRTUAL TABLE.
Инструкция CREATE VIRTUAL TABLE создаёт новую таблицу с именем table-name, основанную на классе module-name. module-name — это имя, зарегистрированное для виртуальной таблицы интерфейсом sqlite3_create_module().
CREATE VIRTUAL TABLE tablename USING modulename;
После имени модуля ему также можно передать аргументы, разделённые запятыми:
CREATE VIRTUAL TABLE tablename USING modulename(arg1, arg2, ...);
Формат аргументов модуля очень общий. Каждый module-argument может содержать ключевые слова, строковые литералы, идентификаторы, числа и знаки пунктуации. Каждый module-argument передаётся без изменений (в виде текста) в метод-конструктор реализации виртуальной таблицы при её создании; этот конструктор отвечает за разбор и интерпретацию аргументов. Синтаксис аргументов достаточно универсален, чтобы реализация виртуальной таблицы при желании могла интерпретировать их как определения столбцов обычной инструкции CREATE TABLE. Реализация также может задать для аргументов другую интерпретацию.
После создания виртуальную таблицу можно использовать как любую другую, за исключениями, указанными выше или предусмотренными конкретной реализацией виртуальной таблицы. Виртуальная таблица удаляется с помощью обычного синтаксиса DROP TABLE.
1.1.1. Временные виртуальные таблицы
Инструкции «CREATE TEMP VIRTUAL TABLE» не существует. Чтобы создать временную виртуальную таблицу, укажите схему «temp» перед именем виртуальной таблицы.
CREATE VIRTUAL TABLE temp.tablename USING module(arg1, ...);
1.1.2. Виртуальные таблицы с именем модуля
Некоторые виртуальные таблицы автоматически существуют в схеме «main» каждого подключения к базе данных, в котором зарегистрирован их модуль, даже без инструкции CREATE VIRTUAL TABLE. Такие виртуальные таблицы называются «виртуальными таблицами с именем модуля». Чтобы использовать такую таблицу, достаточно указать имя модуля так, как если бы это было имя таблицы. Виртуальные таблицы с именем модуля существуют только в схеме «main», поэтому они не будут работать, если указать перед ними имя другой схемы.
Пример виртуальной таблицы с именем модуля — виртуальная таблица dbstat. Чтобы использовать dbstat таким образом, просто выполните запрос к модулю «dbstat», как если бы это была обычная таблица. (Обратите внимание: для включения виртуальной таблицы dbstat в сборку SQLite необходимо скомпилировать с параметром SQLITE_ENABLE_DBSTAT_VTAB.)
SELECT * FROM dbstat;
Виртуальная таблица является таблицей с именем модуля, если её метод xCreate — это в точности та же функция, что и метод xConnect, либо если метод xCreate имеет значение NULL. Метод xCreate вызывается при первом создании виртуальной таблицы с помощью инструкции CREATE VIRTUAL TABLE. Метод xConnect вызывается всякий раз, когда подключение к базе данных присоединяет схему или повторно разбирает её. Совпадение этих двух методов указывает на то, что у виртуальной таблицы нет постоянного состояния, которое требуется создавать и уничтожать.
1.1.3. Виртуальные таблицы только с именем модуля
Если метод xCreate имеет значение NULL, инструкции CREATE VIRTUAL TABLE запрещены для этой виртуальной таблицы, которая в таком случае называется «виртуальной таблицей только с именем модуля». Такие виртуальные таблицы полезны в качестве табличных функций.
Обратите внимание: до версии 3.9.0 (2015-10-14) SQLite не проверяла, равен ли метод xCreate значению NULL, прежде чем вызывать его. Поэтому если виртуальная таблица только с именем модуля зарегистрирована в SQLite версии 3.8.11.1 (2015-07-29) или более ранней и для этого модуля выполняется команда CREATE VIRTUAL TABLE, произойдёт переход по нулевому указателю, что приведёт к аварийному завершению программы.
1.2. Реализация
В реализации виртуальных таблиц используются несколько новых объектов уровня C:
typedef struct sqlite3_vtab sqlite3_vtab; typedef struct sqlite3_index_info sqlite3_index_info; typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; typedef struct sqlite3_module sqlite3_module;
Структура sqlite3_module определяет объект модуля, используемый для реализации виртуальной таблицы. Модуль можно считать классом, на основе которого можно создавать несколько виртуальных таблиц со сходными свойствами. Например, можно создать модуль, предоставляющий доступ только для чтения к файлам CSV на диске. Затем этот модуль можно использовать для создания нескольких виртуальных таблиц, каждая из которых ссылается на отдельный файл CSV.
Структура модуля содержит методы, которые SQLite вызывает для выполнения различных действий с виртуальной таблицей, таких как создание новых экземпляров виртуальной таблицы или уничтожение старых, чтение и запись данных, поиск и удаление, обновление или добавление строк. Структура модуля подробнее описана ниже.
Каждый экземпляр виртуальной таблицы представлен структурой sqlite3_vtab. Структура sqlite3_vtab выглядит так:
struct sqlite3_vtab {
const sqlite3_module *pModule;
int nRef;
char *zErrMsg;
};
Обычно реализации виртуальных таблиц расширяют эту структуру, добавляя дополнительные закрытые поля и поля, специфичные для реализации. Поле nRef используется ядром SQLite и не должно изменяться реализацией виртуальной таблицы. Реализация виртуальной таблицы может передать ядру текст сообщения об ошибке, поместив строку с сообщением об ошибке в zErrMsg. Память для этой строки необходимо выделить с помощью функции выделения памяти SQLite, например sqlite3_mprintf() или sqlite3_malloc(). Перед присваиванием нового значения zErrMsg реализация виртуальной таблицы должна освободить предыдущее содержимое zErrMsg с помощью sqlite3_free(). Если этого не сделать, произойдёт утечка памяти. Ядро SQLite освободит и обнулит содержимое zErrMsg, когда передаст текст сообщения об ошибке клиентскому приложению или уничтожит виртуальную таблицу. Реализации виртуальной таблицы нужно освобождать содержимое zErrMsg только при его перезаписи новым, отличающимся сообщением об ошибке.
Структура sqlite3_vtab_cursor представляет указатель на конкретную строку виртуальной таблицы. Структура sqlite3_vtab_cursor выглядит так:
struct sqlite3_vtab_cursor {
sqlite3_vtab *pVtab;
};
И здесь практические реализации, скорее всего, расширят эту структуру, добавив дополнительные закрытые поля.
Структура sqlite3_index_info используется для передачи информации в метод xBestIndex модуля, реализующего виртуальную таблицу, и получения информации из него.
Прежде чем можно будет выполнить инструкцию CREATE VIRTUAL TABLE, указанный в ней модуль необходимо зарегистрировать в подключении к базе данных. Это выполняется с помощью одного из интерфейсов sqlite3_create_module() или sqlite3_create_module_v2():
int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *, /* Methods for the module */ void * /* Client data for xCreate/xConnect */ ); int sqlite3_create_module_v2( sqlite3 *db, /* SQLite connection to register module with */ const char *zName, /* Name of the module */ const sqlite3_module *, /* Methods for the module */ void *, /* Client data for xCreate/xConnect */ void(*xDestroy)(void*) /* Client data destructor function */ );
Функции sqlite3_create_module() и sqlite3_create_module_v2() связывают имя модуля со структурой sqlite3_module и отдельным указателем на клиентские данные, уникальным для каждого модуля. Единственное различие между двумя методами create_module заключается в том, что метод _v2 принимает дополнительный параметр, задающий деструктор для указателя на клиентские данные. Поведение виртуальной таблицы определяется структурой модуля. Структура модуля выглядит так:
struct sqlite3_module {
int iVersion;
int (*xCreate)(sqlite3*, void *pAux,
int argc, char *const*argv,
sqlite3_vtab **ppVTab,
char **pzErr);
int (*xConnect)(sqlite3*, void *pAux,
int argc, char *const*argv,
sqlite3_vtab **ppVTab,
char **pzErr);
int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
int (*xDisconnect)(sqlite3_vtab *pVTab);
int (*xDestroy)(sqlite3_vtab *pVTab);
int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
int (*xClose)(sqlite3_vtab_cursor*);
int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
int argc, sqlite3_value **argv);
int (*xNext)(sqlite3_vtab_cursor*);
int (*xEof)(sqlite3_vtab_cursor*);
int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
int (*xRowid)(sqlite3_vtab_cursor*, sqlite_int64 *pRowid);
int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite_int64 *);
int (*xBegin)(sqlite3_vtab *pVTab);
int (*xSync)(sqlite3_vtab *pVTab);
int (*xCommit)(sqlite3_vtab *pVTab);
int (*xRollback)(sqlite3_vtab *pVTab);
int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
void **ppArg);
int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
/* The methods above are in version 1 of the sqlite_module object. Those
** below are for version 2 and greater. */
int (*xSavepoint)(sqlite3_vtab *pVTab, int);
int (*xRelease)(sqlite3_vtab *pVTab, int);
int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
/* The methods above are in versions 1 and 2 of the sqlite_module object.
** Those below are for version 3 and greater. */
int (*xShadowName)(const char*);
/* The methods above are in versions 1 through 3 of the sqlite_module object.
** Those below are for version 4 and greater. */
int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema,
const char *zTabName, int mFlags, char **pzErr);
};
Структура модуля определяет все методы для каждого объекта виртуальной таблицы. Она также содержит поле iVersion, определяющее конкретную версию структуры таблицы модуля. В настоящее время значение iVersion всегда равно 4 или меньше, однако в будущих выпусках SQLite определение структуры модуля может быть расширено дополнительными методами; в этом случае максимальное значение iVersion будет увеличено.
Остальная часть структуры модуля состоит из методов, используемых для реализации различных возможностей виртуальной таблицы. Описание назначения каждого из этих методов приведено далее.
1.3. Виртуальные таблицы и общий кэш
До выпуска SQLite версии 3.6.17 (2009-08-10) механизм виртуальных таблиц исходил из предположения, что каждое подключение к базе данных хранит собственную копию схемы базы данных. Поэтому механизм виртуальных таблиц нельзя было использовать в базе данных с включённым режимом общего кэша. Интерфейс sqlite3_create_module() возвращал ошибку, если был включён режим общего кэша. Это ограничение было снято начиная с SQLite версии 3.6.17.
1.4. Создание новых реализаций виртуальных таблиц
Чтобы создать собственную виртуальную таблицу, выполните следующие шаги:
- Реализуйте все необходимые методы.
- Создайте экземпляр структуры sqlite3_module, содержащий указатели на все методы из шага 1.
- Зарегистрируйте структуру sqlite3_module с помощью одного из интерфейсов sqlite3_create_module() или sqlite3_create_module_v2().
- Выполните команду CREATE VIRTUAL TABLE, указав новый модуль в предложении USING.
Единственная действительно сложная часть — шаг 1. Можно взять существующую реализацию виртуальной таблицы и изменить её в соответствии со своими потребностями. В дереве исходного кода SQLite есть множество реализаций виртуальных таблиц, которые можно скопировать, в том числе:
- templatevtab.c → виртуальная таблица, специально созданная в качестве шаблона для других пользовательских виртуальных таблиц.
- series.c → реализация табличной функции generate_series().
- json.c → содержит исходный код табличных функций json_each() и json_tree().
- csv.c → виртуальная таблица для чтения файлов CSV.
В дереве исходного кода SQLite есть множество других реализаций виртуальных таблиц, которые можно использовать в качестве примеров. Чтобы найти их, выполните поиск по строке «sqlite3_create_module».
Можно также реализовать новую виртуальную таблицу в виде загружаемого расширения.
1.5. Вопросы безопасности
Пользовательские виртуальные таблицы могут создавать уязвимости безопасности, если реализованы небрежно. Чтобы помочь предотвратить проблемы, SQLite предоставляет для виртуальных таблиц следующие дополнительные средства безопасности:
Метод xConnect и/или xCreate может вызвать sqlite3_vtab_config() с параметром SQLITE_VTAB_DIRECTONLY, чтобы запретить использование виртуальной таблицы внутри триггеров или представлений. Это рекомендуется делать, если использование виртуальной таблицы внутри триггеров или представлений не требуется.
Не используйте sqlite3_vtab_config() с параметром SQLITE_VTAB_INNOCUOUS, если в этом нет крайней необходимости. Устанавливайте SQLITE_VTAB_INNOCUOUS только в том случае, если вы уверены, что виртуальная таблица не имеет побочных эффектов и её назначение невозможно реализовать без этого параметра.
Если виртуальная таблица сама выполняет инструкции SQL, зависящие от аргументов инструкции CREATE VIRTUAL TABLE, создавшей эту таблицу, используйте интерфейс sqlite3_prepare_v3() с параметром SQLITE_PREPARE_FROM_DDL, чтобы предотвратить обход настройки SQLITE_DBCONFIG_TRUSTED_SCHEMA. Если вы не уверены, зависит ли инструкция SQL от инструкции CREATE VIRTUAL TABLE, обычно безопасно указывать SQLITE_PREPARE_FROM_DDL, даже если это строго не требуется (в зависимости от семантики виртуальной таблицы). Поэтому по умолчанию, вероятно, следует использовать SQLITE_PREPARE_FROM_DDL, если у вас нет конкретной причины поступить иначе.
См. также разделы Последствия для безопасности пользовательских функций SQL и Защита от тёмных искусств.
2. Методы виртуальных таблиц
2.1. Метод xCreate
int (*xCreate)(sqlite3 *db, void *pAux,
int argc, char *const*argv,
sqlite3_vtab **ppVTab,
char **pzErr);
Метод xCreate вызывается для создания нового экземпляра виртуальной таблицы в ответ на инструкцию CREATE VIRTUAL TABLE. Если метод xCreate совпадает с методом xConnect, то виртуальная таблица является виртуальной таблицей с именем модуля. Если метод xCreate отсутствует (то есть представляет собой указатель NULL), виртуальная таблица является виртуальной таблицей только с именем модуля.
Параметр db — это указатель на подключение к базе данных SQLite, выполняющее оператор CREATE VIRTUAL TABLE. Аргумент pAux — это копия указателя на данные клиента, который был четвертым аргументом вызова sqlite3_create_module() или sqlite3_create_module_v2(), зарегистрировавшего модуль виртуальной таблицы. Параметр argv — это массив из argc указателей на строки, завершающиеся нулевым символом. Первая строка, argv[0], — это имя вызываемого модуля. Имя модуля — это имя, указанное вторым аргументом в вызове sqlite3_create_module() и в предложении USING выполняемого оператора CREATE VIRTUAL TABLE. Вторая строка, argv[1], — это имя базы данных, в которой создается новая виртуальная таблица. Для основной базы данных именем базы данных является "main", для базы данных TEMP — "temp", а для подключенных баз данных — имя, указанное в конце оператора ATTACH. Третий элемент массива, argv[2], — это имя новой виртуальной таблицы, указанное после ключевого слова TABLE в операторе CREATE VIRTUAL TABLE. Если присутствуют, четвертая и последующие строки массива argv[] содержат аргументы имени модуля в операторе CREATE VIRTUAL TABLE.
Задача этого метода — создать новый объект виртуальной таблицы (объект sqlite3_vtab) и вернуть указатель на него в *ppVTab.
В рамках создания новой структуры sqlite3_vtab этот метод должен вызвать sqlite3_declare_vtab(), чтобы сообщить ядру SQLite о столбцах и типах данных виртуальной таблицы. API sqlite3_declare_vtab() имеет следующий прототип:
int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable)
Первым аргументом sqlite3_declare_vtab() должен быть тот же указатель на подключение к базе данных, что и первый параметр этого метода. Второй аргумент sqlite3_declare_vtab() должен быть строкой UTF-8, завершающейся нулевым символом и содержащей корректный оператор CREATE TABLE, определяющий столбцы виртуальной таблицы и их типы данных. Имя таблицы в этом операторе CREATE TABLE игнорируется, как и все ограничения. Важны только имена столбцов и типы данных. Строка оператора CREATE TABLE не обязана храниться в постоянной памяти. Ее можно освободить и/или использовать повторно сразу после возврата из функции sqlite3_declare_vtab().
Метод xConnect также может при необходимости запросить специальные возможности виртуальной таблицы, выполнив один или несколько вызовов интерфейса sqlite3_vtab_config():
int sqlite3_vtab_config(sqlite3 *db, int op, ...);
Вызовы sqlite3_vtab_config() необязательны. Однако для максимальной безопасности рекомендуется, чтобы реализации виртуальных таблиц вызывали "sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY)", если виртуальная таблица не будет использоваться внутри триггеров или представлений.
Методу xCreate не требуется инициализировать поля pModule, nRef и zErrMsg объекта sqlite3_vtab. Об этом позаботится ядро SQLite.
Метод xCreate должен возвращать SQLITE_OK в случае успешного создания новой виртуальной таблицы или SQLITE_ERROR, если создать ее не удалось. В случае неудачи структура sqlite3_vtab не должна быть выделена. При неудаче в *pzErr можно дополнительно вернуть сообщение об ошибке. Память для строки сообщения об ошибке должна выделяться с помощью функции выделения памяти SQLite, например sqlite3_malloc() или sqlite3_mprintf(), поскольку после передачи сообщения об ошибке приложению ядро SQLite попытается освободить эту память с помощью sqlite3_free().
Если метод xCreate не задан (оставлен как указатель NULL), виртуальная таблица является виртуальной таблицей только с одноименным модулем. Создать новые экземпляры виртуальной таблицы с помощью CREATE VIRTUAL TABLE нельзя, и использовать виртуальную таблицу можно только через имя ее модуля. Обратите внимание, что версии SQLite до 3.9.0 (2015-10-14) не поддерживают виртуальные таблицы только с одноименным модулем и вызывают ошибку сегментации, если попытаться выполнить CREATE VIRTUAL TABLE для такой таблицы, поскольку указатель xCreate не проверялся на null.
Если метод xCreate является в точности тем же указателем, что и метод xConnect, это означает, что виртуальной таблице не требуется инициализировать хранилище. Такую виртуальную таблицу можно использовать как виртуальную таблицу с одноименным модулем, как именованную виртуальную таблицу с помощью CREATE VIRTUAL TABLE или обоими способами.
2.1.1. Скрытые столбцы в виртуальных таблицах
Если тип данных столбца содержит специальное ключевое слово "HIDDEN" (в любом сочетании прописных и строчных букв), это ключевое слово исключается из имени типа данных столбца, а сам столбец внутри помечается как скрытый. Скрытый столбец отличается от обычного в трех отношениях:
- Скрытые столбцы не отображаются в наборе данных, возвращаемом оператором "PRAGMA table_info",
- Скрытые столбцы не включаются при раскрытии выражения "*" в результирующем наборе SELECT, и
- Скрытые столбцы не включаются в неявный список столбцов, используемый оператором INSERT, в котором не задан явный список столбцов.
Например, если передать в sqlite3_declare_vtab() следующий SQL-код:
CREATE TABLE x(a HIDDEN VARCHAR(12), b INTEGER, c INTEGER Hidden);
то виртуальная таблица будет создана с двумя скрытыми столбцами и типами данных "VARCHAR(12)" и "INTEGER".
Пример использования скрытых столбцов можно увидеть в реализации виртуальной таблицы FTS3: каждая виртуальная таблица FTS содержит скрытый столбец FTS, который используется для передачи информации из виртуальной таблицы в вспомогательные функции FTS и оператору FTS MATCH.
2.1.2. Функции, возвращающие таблицу
Виртуальную таблицу, содержащую скрытые столбцы, можно использовать как функцию, возвращающую таблицу, в предложении FROM оператора SELECT. Аргументы такой функции становятся ограничениями для скрытых столбцов виртуальной таблицы.
Например, расширение "generate_series" (находящееся в файле ext/misc/series.c в дереве исходного кода) реализует виртуальную таблицу с одноименным модулем со следующей схемой:
CREATE TABLE generate_series( value, start HIDDEN, stop HIDDEN, step HIDDEN );
Метод sqlite3_module.xBestIndex в реализации этой таблицы проверяет ограничения равенства для скрытых столбцов и использует их в качестве входных параметров для определения диапазона генерируемых целочисленных значений "value". Для столбцов без ограничений используются разумные значения по умолчанию. Например, чтобы вывести все целые числа от 5 до 50:
SELECT value FROM generate_series(5,50);
Предыдущий запрос эквивалентен следующему:
SELECT value FROM generate_series WHERE start=5 AND stop=50;
Аргументы, указанные после имени виртуальной таблицы, сопоставляются со скрытыми столбцами по порядку. Аргументов может быть меньше, чем скрытых столбцов; в этом случае последующие скрытые столбцы не имеют ограничений. Однако возникает ошибка, если аргументов больше, чем скрытых столбцов виртуальной таблицы.
2.1.3. Виртуальные таблицы WITHOUT ROWID
Начиная с версии 3.14.0 SQLite (2016-08-08), оператор CREATE TABLE, передаваемый в sqlite3_declare_vtab(), может содержать предложение WITHOUT ROWID. Это полезно в случаях, когда строки виртуальной таблицы сложно сопоставить с уникальными целыми числами. Оператор CREATE TABLE с предложением WITHOUT ROWID должен определять один или несколько столбцов как PRIMARY KEY. Каждый столбец PRIMARY KEY по отдельности должен иметь ограничение NOT NULL, а все столбцы каждой строки в совокупности должны быть уникальными.
Обратите внимание, что SQLite не обеспечивает соблюдение PRIMARY KEY для виртуальной таблицы WITHOUT ROWID. Это обязанность реализации виртуальной таблицы. Однако SQLite предполагает, что ограничение PRIMARY KEY действительно, то есть указанные столбцы действительно UNIQUE и NOT NULL, и использует это предположение для оптимизации запросов к виртуальной таблице.
Столбец rowid недоступен в виртуальной таблице WITHOUT ROWID (разумеется).
Метод xUpdate изначально был разработан для работы с ROWID как единственным значением. Метод xUpdate был расширен для работы с произвольным PRIMARY KEY вместо ROWID, однако PRIMARY KEY по-прежнему должен состоять только из одного столбца. Поэтому SQLite отклонит любую виртуальную таблицу WITHOUT ROWID, у которой несколько столбцов PRIMARY KEY и задан ненулевой метод xUpdate.
2.2. Метод xConnect
int (*xConnect)(sqlite3*, void *pAux,
int argc, char *const*argv,
sqlite3_vtab **ppVTab,
char **pzErr);
Метод xConnect очень похож на xCreate. У него те же параметры, и он создает новую структуру sqlite3_vtab так же, как xCreate. Он также должен вызывать sqlite3_declare_vtab(), как и xCreate. Кроме того, он должен выполнять те же вызовы sqlite3_vtab_config(), что и xCreate.
Разница заключается в том, что xConnect вызывается для установки нового подключения к существующей виртуальной таблице, тогда как xCreate вызывается для создания виртуальной таблицы с нуля.
Методы xCreate и xConnect различаются только в том случае, если виртуальная таблица использует хранилище, которое необходимо инициализировать при ее первом создании. Метод xCreate создает и инициализирует хранилище. Метод xConnect лишь подключается к существующему хранилищу. Если xCreate и xConnect совпадают, таблица является виртуальной таблицей с одноименным модулем.
В качестве примера рассмотрим реализацию виртуальной таблицы, предоставляющей доступ только для чтения к существующим на диске файлам CSV (значения, разделенные запятыми). Для такой виртуальной таблицы не нужно создавать или инициализировать хранилище (поскольку файлы CSV уже существуют на диске), поэтому методы xCreate и xConnect этого модуля будут идентичны.
Другой пример — виртуальная таблица, реализующая полнотекстовый индекс. Метод xCreate должен создать и инициализировать структуры данных для хранения словаря и списков вхождений этого индекса. Метод xConnect, напротив, должен только найти и использовать существующие словарь и списки вхождений, созданные предыдущим вызовом xCreate.
Метод xConnect должен возвращать SQLITE_OK в случае успешного создания новой виртуальной таблицы или SQLITE_ERROR, если создать ее не удалось. В случае неудачи структура sqlite3_vtab не должна быть выделена. При неудаче в *pzErr можно дополнительно вернуть сообщение об ошибке. Память для строки сообщения об ошибке должна выделяться с помощью функции выделения памяти SQLite, например sqlite3_malloc() или sqlite3_mprintf(), поскольку после передачи сообщения об ошибке приложению ядро SQLite попытается освободить эту память с помощью sqlite3_free().
Метод xConnect обязателен для каждой реализации виртуальной таблицы, хотя указатели xCreate и xConnect объекта sqlite3_module могут указывать на одну и ту же функцию, если виртуальной таблице не требуется инициализировать хранилище.
2.3. Метод xBestIndex
SQLite использует метод xBestIndex модуля виртуальной таблицы, чтобы определить оптимальный способ доступа к виртуальной таблице. Метод xBestIndex имеет следующий прототип:
int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
Ядро SQLite взаимодействует с методом xBestIndex, заполняя определенные поля структуры sqlite3_index_info и передавая указатель на эту структуру в xBestIndex в качестве второго параметра. Метод xBestIndex заполняет другие поля этой структуры, формируя ответ. Структура sqlite3_index_info выглядит так:
struct sqlite3_index_info {
/* Inputs */
const int nConstraint; /* Number of entries in aConstraint */
const struct sqlite3_index_constraint {
int iColumn; /* Column constrained. -1 for ROWID */
unsigned char op; /* Constraint operator */
unsigned char usable; /* True if this constraint is usable */
int iTermOffset; /* Used internally - xBestIndex should ignore */
} *const aConstraint; /* Table of WHERE clause constraints */
const int nOrderBy; /* Number of terms in the ORDER BY clause */
const struct sqlite3_index_orderby {
int iColumn; /* Column number */
unsigned char desc; /* True for DESC. False for ASC. */
} *const aOrderBy; /* The ORDER BY clause */
/* Outputs */
struct sqlite3_index_constraint_usage {
int argvIndex; /* if >0, constraint is part of argv to xFilter */
unsigned char omit; /* Do not code a test for this constraint */
} *const aConstraintUsage;
int idxNum; /* Number used to identify the index */
char *idxStr; /* String, possibly obtained from sqlite3_malloc */
int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */
int orderByConsumed; /* True if output is already ordered */
double estimatedCost; /* Estimated cost of using this index */
/* Fields below are only available in SQLite 3.8.2 and later */
sqlite3_int64 estimatedRows; /* Estimated number of rows returned */
/* Fields below are only available in SQLite 3.9.0 and later */
int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */
/* Fields below are only available in SQLite 3.10.0 and later */
sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */
};
Обратите внимание на предупреждения для полей "estimatedRows", "idxFlags" и "colUsed". Эти поля были добавлены соответственно в версиях SQLite 3.8.2, 3.9.0 и 3.10.0. Любое расширение, которое читает или записывает эти поля, должно сначала проверить, что используемая версия библиотеки SQLite не ниже соответствующей версии, например сравнив значение, возвращаемое функцией sqlite3_libversion_number(), с константами 3008002, 3009000 и/или 3010000. Результаты попытки доступа к этим полям в структуре sqlite3_index_info, созданной более старой версией SQLite, не определены.
Кроме того, определены следующие константы:
#define SQLITE_INDEX_CONSTRAINT_EQ 2 #define SQLITE_INDEX_CONSTRAINT_GT 4 #define SQLITE_INDEX_CONSTRAINT_LE 8 #define SQLITE_INDEX_CONSTRAINT_LT 16 #define SQLITE_INDEX_CONSTRAINT_GE 32 #define SQLITE_INDEX_CONSTRAINT_MATCH 64 #define SQLITE_INDEX_CONSTRAINT_LIKE 65 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_GLOB 66 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_REGEXP 67 /* 3.10.0 and later */ #define SQLITE_INDEX_CONSTRAINT_NE 68 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNOT 69 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_ISNULL 71 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_IS 72 /* 3.21.0 and later */ #define SQLITE_INDEX_CONSTRAINT_LIMIT 73 /* 3.38.0 and later */ #define SQLITE_INDEX_CONSTRAINT_OFFSET 74 /* 3.38.0 and later */ #define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* 3.25.0 and later */ #define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
Используйте интерфейс sqlite3_vtab_collation(), чтобы узнать имя последовательности сортировки, которую следует использовать при оценке i-го ограничения:
const char *sqlite3_vtab_collation(sqlite3_index_info*, int i);
Ядро SQLite вызывает метод xBestIndex при компиляции запроса, использующего виртуальную таблицу. Иными словами, SQLite вызывает этот метод при выполнении sqlite3_prepare() или эквивалентной функции. Вызывая этот метод, ядро SQLite сообщает виртуальной таблице, что ему нужен доступ к некоторому подмножеству ее строк, и запрашивает наиболее эффективный способ такого доступа. Метод xBestIndex предоставляет сведения, которые ядро SQLite может затем использовать для эффективного поиска в виртуальной таблице.
При компиляции одного SQL-запроса ядро SQLite может несколько раз вызвать xBestIndex с разными настройками в sqlite3_index_info. Затем ядро SQLite выберет комбинацию, которая, по его оценке, обеспечивает наилучшую производительность.
Перед вызовом этого метода ядро SQLite инициализирует экземпляр структуры sqlite3_index_info сведениями о запросе, который оно обрабатывает. Эти сведения в основном берутся из предложений WHERE и ORDER BY или GROUP BY запроса, а также из предложений ON или USING, если запрос является соединением. Информация, предоставляемая ядром SQLite методу xBestIndex, хранится в части структуры, помеченной как "Inputs". Раздел "Outputs" инициализируется нулями.
Информация в структуре sqlite3_index_info временная и может быть перезаписана или освобождена сразу после возврата из метода xBestIndex. Если методу xBestIndex нужно сохранить какую-либо часть структуры sqlite3_index_info, следует создать ее копию. Необходимо сохранить копию в месте, где она будет освобождена, например в поле idxStr, установив needToFreeIdxStr в 1.
Обратите внимание, что xBestIndex всегда вызывается до xFilter, поскольку выходные значения idxNum и idxStr метода xBestIndex необходимы в качестве входных данных для xFilter. Однако нет гарантии, что после успешного вызова xBestIndex будет вызван xFilter.
Метод xBestIndex обязателен для каждой реализации виртуальной таблицы.
2.3.1. Входные данные
Главная задача ядра SQLite — сообщить виртуальной таблице об ограничениях, доступных для сокращения числа строк, которые необходимо просмотреть. Массив aConstraint[] содержит по одной записи для каждого ограничения. В этом массиве будет ровно nConstraint записей.
Каждое ограничение обычно соответствует термину в предложении WHERE или в предложении USING либо ON следующего вида:
column OP EXPR
Здесь "column" — столбец виртуальной таблицы, OP — оператор, например "=" или "<", а EXPR — произвольное выражение. Так, например, если бы предложение WHERE содержало такой термин:
a = 5
то одно из ограничений относилось бы к столбцу "a", использовало бы оператор "=" и выражение "5". Ограничения не обязательно точно соответствуют тексту предложения WHERE. Оптимизатор запросов может преобразовывать предложение WHERE, чтобы извлечь как можно больше ограничений. Например, если бы предложение WHERE содержало что-то вроде:
x BETWEEN 10 AND 100 AND 999>y
оптимизатор запросов мог бы преобразовать его в три отдельных ограничения:
x >= 10 x <= 100 y < 999
Для каждого такого ограничения поле aConstraint[].iColumn указывает, какой столбец находится в левой части ограничения. Первый столбец виртуальной таблицы имеет номер 0. Для rowid виртуальной таблицы используется номер столбца -1. Поле aConstraint[].op указывает используемый оператор. Целочисленные константы SQLITE_INDEX_CONSTRAINT_* соответствуют значениям операторов. Столбцы следуют в том порядке, в котором они были определены вызовом sqlite3_declare_vtab() в методе xCreate или xConnect. При определении индекса столбца учитываются скрытые столбцы.
Если для виртуальной таблицы определен метод xFindFunction() и xFindFunction() иногда возвращает значение SQLITE_INDEX_CONSTRAINT_FUNCTION или большее, ограничения также могут иметь следующий вид:
FUNCTION( column, EXPR)
В этом случае значение aConstraint[].op совпадает со значением, возвращаемым xFindFunction() для FUNCTION.
Массив aConstraint[] содержит сведения обо всех ограничениях, применимых к виртуальной таблице. Однако некоторые ограничения могут оказаться непригодными из-за порядка таблиц в соединении. Поэтому метод xBestIndex должен учитывать только ограничения, у которых флаг aConstraint[].usable имеет значение true.
Кроме ограничений предложения WHERE, ядро SQLite также сообщает методу xBestIndex о предложении ORDER BY. (В агрегатном запросе ядро SQLite может передать сведения из предложения GROUP BY вместо сведений из ORDER BY, но для метода xBestIndex это не должно иметь значения.) Если все термины предложения ORDER BY являются столбцами виртуальной таблицы, nOrderBy будет равно числу терминов в предложении ORDER BY, а массив aOrderBy[] будет указывать столбец для каждого термина и направление сортировки этого столбца — ASC или DESC.
В версии 3.10.0 SQLite (2016-01-06) и более поздних версиях поле colUsed указывает, какие поля виртуальной таблицы фактически используются подготавливаемым оператором. Если установлен младший бит colUsed, это означает, что используется первый столбец. Следующий по значимости бит соответствует второму столбцу и так далее. Если установлен старший бит colUsed, это означает, что используется один или несколько столбцов за пределами первых 63. Если методу xFilter нужны сведения об использовании столбцов, необходимые биты следует закодировать либо в выходном поле idxNum, либо в содержимом idxStr.
2.3.1.1. Функции LIKE, GLOB, REGEXP и MATCH
Для операторов LIKE, GLOB, REGEXP и MATCH значение aConstraint[].iColumn — это столбец виртуальной таблицы, являющийся левым операндом оператора. Однако если эти операторы представлены вызовами функций, а не операторами, значение aConstraint[].iColumn указывает на столбец виртуальной таблицы, являющийся вторым аргументом этой функции:
LIKE(EXPR, column)
GLOB(EXPR, column)
REGEXP(EXPR, column)
MATCH(EXPR, column)
Таким образом, для метода xBestIndex() следующие две формы эквивалентны:
column LIKE EXPR
LIKE(EXPR,column)
Это особое поведение, при котором рассматривается второй аргумент функции, встречается только у функций LIKE, GLOB, REGEXP и MATCH. Для всех остальных функций значение aConstraint[].iColumn указывает на первый аргумент функции.
Однако эта особенность LIKE, GLOB, REGEXP и MATCH не распространяется на метод xFindFunction(). Метод xFindFunction() всегда ориентируется на левый операнд операторов LIKE, GLOB, REGEXP или MATCH, но для эквивалентных вызовов функций — на первый аргумент.
2.3.1.2. LIMIT и OFFSET
Если aConstraint[].op равно SQLITE_INDEX_CONSTRAINT_LIMIT или SQLITE_INDEX_CONSTRAINT_OFFSET, это означает, что в SQL-запросе, использующем виртуальную таблицу, есть предложение LIMIT или OFFSET. У операторов LIMIT и OFFSET нет левого операнда, поэтому, если aConstraint[].op равно SQLITE_INDEX_CONSTRAINT_LIMIT или SQLITE_INDEX_CONSTRAINT_OFFSET, значение aConstraint[].iColumn не имеет смысла и не должно использоваться.
2.3.1.3. Значения правой части ограничений
Интерфейс sqlite3_vtab_rhs_value() можно использовать, чтобы попытаться получить правый операнд ограничения. Однако значение правого операнда может быть неизвестно во время выполнения метода xBestIndex, поэтому вызов sqlite3_vtab_rhs_value() может завершиться неудачей. Обычно правый операнд ограничения доступен методу xBestIndex, только если в исходном SQL он задан как литерал. Если правый операнд задан как выражение или параметр привязки, вероятно, метод xBestIndex не сможет получить к нему доступ. У некоторых операторов, таких как SQLITE_INDEX_CONSTRAINT_ISNULL и SQLITE_INDEX_CONSTRAINT_ISNOTNULL, нет правого операнда. Для таких операторов интерфейс sqlite3_vtab_rhs_value() всегда возвращает SQLITE_NOTFOUND.
2.3.2. Выходные данные
Используя все приведенные выше сведения, метод xBestIndex должен определить оптимальный способ поиска в виртуальной таблице.
Метод xBestIndex передает стратегию индексирования методу xFilter через поля idxNum и idxStr. Значение idxNum и содержимое строки idxStr произвольны для ядра SQLite и могут иметь любое значение, если xBestIndex и xFilter одинаково трактуют их. Ядро SQLite лишь копирует информацию из xBestIndex в метод xFilter, предполагая только, что последовательность символов, на которую ссылается idxStr, завершается NUL.
Значением idxStr может быть строка, полученная с помощью функции выделения памяти SQLite, например sqlite3_mprintf(). В этом случае флаг needToFreeIdxStr должен быть установлен в true, чтобы ядро SQLite знало, что по окончании работы со строкой следует вызвать для нее sqlite3_free() и тем самым избежать утечки памяти. Значением idxStr также может быть статическая строковая константа; в этом случае логическое значение needToFreeIdxStr должно оставаться false.
В поле estimatedCost следует указать предполагаемое число операций доступа к диску, необходимых для выполнения этого запроса к виртуальной таблице. Ядро SQLite часто вызывает xBestIndex несколько раз с разными ограничениями, получает несколько оценок стоимости, а затем выбирает план запроса с наименьшей оценкой. Перед вызовом xBestIndex ядро SQLite инициализирует estimatedCost очень большим значением. Поэтому, если xBestIndex определит, что текущая комбинация параметров нежелательна, поле estimatedCost можно оставить без изменений, чтобы не поощрять ее использование.
Если текущая версия SQLite — 3.8.2 или более поздняя, в поле estimatedRows можно указать оценочное число строк, возвращаемых предлагаемым планом запроса. Если это значение не задано явно, используется оценка по умолчанию — 25 строк.
Если текущая версия SQLite — 3.9.0 или более поздняя, для поля idxFlags можно задать значение SQLITE_INDEX_SCAN_UNIQUE, указывающее, что виртуальная таблица вернет не более одной строки при заданных входных ограничениях. В более поздних версиях SQLite могут использоваться и другие биты поля idxFlags.
Массив aConstraintUsage[] содержит по одному элементу для каждого из nConstraint ограничений в разделе входных данных структуры sqlite3_index_info. Метод xBestIndex использует массив aConstraintUsage[], чтобы сообщить ядру, как он использует ограничения.
Метод xBestIndex может присваивать элементам aConstraintUsage[].argvIndex значения больше нуля. Ровно одному элементу следует присвоить значение 1, другому — 2, третьему — 3 и так далее, вплоть до любого нужного методу xBestIndex количества. После этого EXPR соответствующих ограничений будут переданы как параметры argv[] в xFilter.
Например, если aConstraint[3].argvIndex равен 1, то при вызове xFilter переданное ему значение argv[0] будет содержать значение EXPR ограничения aConstraint[3].
2.3.2.1. Пропуск проверки ограничений в байт-коде
По умолчанию SQLite генерирует байт-код, который повторно проверяет все ограничения для каждой строки виртуальной таблицы, чтобы убедиться, что они выполняются. Если виртуальная таблица может гарантировать выполнение ограничения, она может попытаться отключить эту повторную проверку, установив aConstraintUsage[].omit. Однако, за некоторыми исключениями, это лишь рекомендация, и нет гарантии, что избыточная проверка ограничения будет пропущена. Основные моменты:
Флаг omit учитывается, только если значение argvIndex для ограничения больше 0 и меньше или равно 16. Проверка ограничений никогда не пропускается для ограничений, правый операнд которых не передается методу xFilter. В текущей реализации можно пропускать избыточную проверку ограничений только для первых 16 значений, передаваемых в xFilter, хотя в будущих выпусках это ограничение может быть снято.
Флаг omit всегда учитывается для ограничений SQLITE_INDEX_CONSTRAINT_OFFSET, если argvIndex больше 0. Установка флага omit для ограничения SQLITE_INDEX_CONSTRAINT_OFFSET сообщает SQLite, что сама виртуальная таблица пропустит первые N выходных строк, где N — правый операнд оператора OFFSET. Если реализация виртуальной таблицы установит omit для ограничения SQLITE_INDEX_CONSTRAINT_OFFSET, но не пропустит первые N выходных строк, результат всего запроса будет неверным.
2.3.2.2. ORDER BY и orderByConsumed
Если виртуальная таблица возвращает строки в порядке, заданном предложением ORDER BY, флаг orderByConsumed можно установить в true. Если выходные данные автоматически не имеют нужного порядка, orderByConsumed необходимо оставить в исходном значении false. Это сообщит ядру SQLite, что после получения данных из виртуальной таблицы потребуется отдельный проход сортировки. Установка orderByConsumed — это оптимизация. Если оставить orderByConsumed со значением по умолчанию (0), запрос всегда вернет правильный результат. Установка orderByConsumed может позволить избежать ненужной сортировки и ускорить запрос, но неправильная установка orderByConsumed может привести к неверному результату. Рекомендуется, чтобы в новых реализациях виртуальных таблиц значение orderByConsumed сначала не задавалось, а после проверки корректной работы всего остального можно было вернуться к оптимизации и установить orderByConsumed там, где это уместно.
Иногда флаг orderByConsumed можно безопасно установить, даже если выходные данные виртуальной таблицы не строго соответствуют порядку, заданному nOrderBy и aOrderBy. Если интерфейс sqlite3_vtab_distinct() возвращает 1 или 2, это означает, что требования к порядку можно ослабить. Дополнительные сведения см. в документации по sqlite3_vtab_distinct().
2.3.3. Возвращаемое значение
В случае успеха метод xBestIndex должен вернуть SQLITE_OK. При возникновении какой-либо критической ошибки следует вернуть соответствующий код ошибки (например, SQLITE_NOMEM).
Если xBestIndex возвращает SQLITE_CONSTRAINT, это не означает, что произошла ошибка. Напротив, SQLITE_CONSTRAINT указывает, что заданной комбинации входных параметров недостаточно для выполнения задачи виртуальной таблицей. По смыслу это равносильно установке estimatedCost в бесконечность. Если каждый вызов xBestIndex для определенного плана запроса возвращает SQLITE_CONSTRAINT, это означает, что безопасно использовать виртуальную таблицу невозможно, и вызов sqlite3_prepare() завершится ошибкой "no query solution".
2.3.4. Обязательные параметры функций, возвращающих таблицу
Возвращаемое значение SQLITE_CONSTRAINT метода xBestIndex полезно для функций, возвращающих таблицу, у которых есть обязательные параметры. Если поле aConstraint[].usable для одного из обязательных параметров имеет значение false, метод xBestIndex должен вернуть SQLITE_CONSTRAINT. Если обязательное поле вообще отсутствует в массиве aConstraint[], это означает, что соответствующий параметр не указан во входном SQL. В этом случае xBestIndex должен записать сообщение об ошибке в pVTab->zErrMsg и вернуть SQLITE_ERROR. Итак:
Значение aConstraint[].usable для обязательного параметра равно false → вернуть SQLITE_CONSTRAINT.
Обязательный параметр отсутствует в массиве aConstraint[] → Установить сообщение об ошибке в pVTab->zErrMsg и вернуть SQLITE_ERROR
Следующий пример нагляднее покажет использование SQLITE_CONSTRAINT в качестве возвращаемого значения из xBestIndex:
SELECT * FROM realtab, tablevaluedfunc(realtab.x);
Предположим, что первый скрытый столбец "tablevaluedfunc" — это "param1". Тогда приведённый выше запрос семантически эквивалентен следующему:
SELECT * FROM realtab, tablevaluedfunc WHERE tablevaluedfunc.param1 = realtab.x;
Планировщик запросов должен выбрать один из множества возможных вариантов выполнения этого запроса, но особого внимания заслуживают два плана:
Просканировать все строки realtab и для каждой строки найти строки в tablevaluedfunc, где param1 равен realtab.x
Просканировать все строки tablevaluedfunc и для каждой строки найти строки в realtab, где x равен tablevaluedfunc.param1.
Метод xBestIndex будет вызван один раз для каждого из приведённых выше возможных планов. Для плана 1 флаг aConstraint[].usable для ограничения SQLITE_CONSTRAINT_EQ на столбце param1 будет иметь значение true, поскольку значение в правой части ограничения "param1 = ?" будет известно: оно определяется внешним циклом realtab. Однако для плана 2 флаг aConstraint[].usable для "param1 = ?" будет иметь значение false, поскольку значение в правой части определяется внутренним циклом и, следовательно, неизвестно. Поскольку param1 является обязательным входным параметром табличных функций, метод xBestIndex должен вернуть SQLITE_CONSTRAINT при выборе плана 2, указывая на отсутствие обязательного входного параметра. Это заставляет планировщик запросов выбрать план 1.
2.4. Метод xDisconnect
int (*xDisconnect)(sqlite3_vtab *pVTab);
Этот метод закрывает соединение с виртуальной таблицей. Уничтожается только объект sqlite3_vtab. Виртуальная таблица не уничтожается, и связанное с ней хранилище сохраняется. Этот метод отменяет действия, выполненные методом xConnect.
Этот метод является деструктором соединения с виртуальной таблицей. Сравните его с методом xDestroy. Метод xDestroy является деструктором всей виртуальной таблицы.
Метод xDisconnect обязателен для каждой реализации виртуальной таблицы, хотя для конкретной виртуальной таблицы методы xDisconnect и xDestroy могут быть одной и той же функцией, если это имеет смысл.
2.5. Метод xDestroy
int (*xDestroy)(sqlite3_vtab *pVTab);
Этот метод закрывает соединение с виртуальной таблицей, как и метод xDisconnect, а также уничтожает базовую реализацию таблицы. Этот метод отменяет действия, выполненные методом xCreate.
Метод xDisconnect вызывается при закрытии соединения с базой данных, использующего виртуальную таблицу. Метод xDestroy вызывается только при выполнении оператора DROP TABLE для виртуальной таблицы.
Метод xDestroy обязателен для каждой реализации виртуальной таблицы, хотя для конкретной виртуальной таблицы методы xDisconnect и xDestroy могут быть одной и той же функцией, если это имеет смысл.
2.6. Метод xOpen
int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
Метод xOpen создаёт новый курсор для доступа (чтения и/или записи) к виртуальной таблице. При успешном вызове этого метода выделяется память для sqlite3_vtab_cursor (или его подкласса), инициализируется новый объект, а *ppCursor указывает на этот объект. Затем успешный вызов возвращает SQLITE_OK.
Для каждого успешного вызова этого метода ядро SQLite позднее вызовет метод xClose, чтобы уничтожить выделенный курсор.
Методу xOpen не требуется инициализировать поле pVtab структуры sqlite3_vtab_cursor. Ядро SQLite выполнит эту задачу автоматически.
Реализация виртуальной таблицы должна поддерживать произвольное количество одновременно открытых курсоров.
При первоначальном открытии курсор находится в неопределённом состоянии. Ядро SQLite вызовет для курсора метод xFilter до любой попытки переместить курсор или прочитать данные из него.
Метод xOpen обязателен для каждой реализации виртуальной таблицы.
2.7. Метод xClose
int (*xClose)(sqlite3_vtab_cursor*);
Метод xClose закрывает курсор, ранее открытый методом xOpen. Ядро SQLite всегда вызывает xClose один раз для каждого курсора, открытого с помощью xOpen.
Этот метод должен освободить все ресурсы, выделенные соответствующим вызовом xOpen. Процедура не будет вызвана повторно, даже если она вернёт ошибку. После закрытия ядро SQLite больше не будет использовать sqlite3_vtab_cursor.
Метод xClose обязателен для каждой реализации виртуальной таблицы.
2.8. Метод xEof
int (*xEof)(sqlite3_vtab_cursor*);
Метод xEof должен возвращать false (ноль), если указанный курсор в данный момент указывает на допустимую строку данных, и true (ненулевое значение) в противном случае. Этот метод вызывается ядром SQL сразу после каждого вызова xFilter и xNext.
Метод xEof обязателен для каждой реализации виртуальной таблицы.
2.9. Метод xFilter
int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
int argc, sqlite3_value **argv);
Этот метод начинает поиск в виртуальной таблице. Первый аргумент — курсор, открытый методом xOpen. Следующие два аргумента задают определённый поисковый индекс, ранее выбранный методом xBestIndex. Конкретное значение idxNum и idxStr неважно, если xFilter и xBestIndex согласны относительно этого значения.
Функция xBestIndex могла запросить значения определённых выражений, используя значения aConstraintUsage[].argvIndex структуры sqlite3_index_info. Эти значения передаются в xFilter через параметры argc и argv.
Если виртуальная таблица содержит одну или несколько строк, соответствующих критериям поиска, курсор должен указывать на первую строку. Последующие вызовы xEof должны возвращать false (ноль). Если соответствующих строк нет, курсор должен находиться в состоянии, при котором xEof возвращает true (ненулевое значение). Ядро SQLite будет использовать методы xColumn и xRowid для доступа к содержимому этой строки. Для перехода к следующей строке будет использоваться метод xNext.
В случае успеха этот метод должен возвращать SQLITE_OK, а при ошибке — код ошибки SQLite.
Метод xFilter обязателен для каждой реализации виртуальной таблицы.
2.10. Метод xNext
int (*xNext)(sqlite3_vtab_cursor*);
Метод xNext перемещает курсор виртуальной таблицы к следующей строке набора результатов, начатого методом xFilter. Если при вызове этой процедуры курсор уже указывает на последнюю строку, то он перестаёт указывать на допустимые данные, и последующий вызов метода xEof должен вернуть true (ненулевое значение). Если курсор успешно перемещён к другой строке данных, последующие вызовы xEof должны возвращать false (ноль).
В случае успеха этот метод должен возвращать SQLITE_OK, а при ошибке — код ошибки SQLite.
Метод xNext обязателен для каждой реализации виртуальной таблицы.
2.11. Метод xColumn
int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int N);
Ядро SQLite вызывает этот метод, чтобы получить значение N-го столбца текущей строки. Нумерация N начинается с нуля, поэтому первый столбец имеет номер 0. Метод xColumn может вернуть результат в SQLite с помощью одного из следующих интерфейсов:
- sqlite3_result_blob()
- sqlite3_result_blob64()
- sqlite3_result_double()
- sqlite3_result_int()
- sqlite3_result_int64()
- sqlite3_result_null()
- sqlite3_result_pointer()
- sqlite3_result_text()
- sqlite3_result_text16()
- sqlite3_result_text16le()
- sqlite3_result_text16be()
- sqlite3_result_text64()
- sqlite3_result_value()
- sqlite3_result_zeroblob()
- sqlite3_result_zeroblob64()
Если реализация метода xColumn не вызывает ни одну из перечисленных выше функций, значение столбца по умолчанию равно SQL NULL.
Чтобы вызвать ошибку, метод xColumn должен использовать один из методов result_text(), чтобы задать текст сообщения об ошибке, а затем вернуть соответствующий код ошибки. При успешном выполнении метод xColumn должен возвращать SQLITE_OK.
Метод xColumn обязателен для каждой реализации виртуальной таблицы.
2.12. Метод xRowid
int (*xRowid)(sqlite3_vtab_cursor *pCur, sqlite_int64 *pRowid);
При успешном вызове этого метода в *pRowid записывается rowid строки, на которую в данный момент указывает курсор виртуальной таблицы pCur. В случае успеха метод возвращает SQLITE_OK. В случае сбоя он возвращает соответствующий код ошибки.
Метод xRowid обязателен для каждой реализации виртуальной таблицы.
2.13. Метод xUpdate
int (*xUpdate)( sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite_int64 *pRowid );
Все изменения виртуальной таблицы выполняются с помощью метода xUpdate. Этот метод можно использовать для вставки, удаления или обновления данных.
Параметр argc задаёт количество элементов в массиве argv. Значение argc равно 1 для операции удаления и N+2 для вставки, замены или обновления, где N — количество столбцов в таблице. В предыдущем предложении N включает скрытые столбцы.
Каждый элемент argv будет иметь ненулевое значение в C, но может содержать значение SQL NULL. Иными словами, всегда верно, что argv[i]!=0 для i от 0 до argc-1. Однако возможно, что sqlite3_value_type(argv[i])==SQLITE_NULL.
Параметр argv[0] — это rowid строки виртуальной таблицы, которую нужно удалить. Если argv[0] имеет значение SQL NULL, удаление не выполняется.
Параметр argv[1] — это rowid новой строки, которую нужно вставить в виртуальную таблицу. Если argv[1] имеет значение SQL NULL, реализация должна выбрать rowid для новой строки. Последующие элементы argv[] содержат значения столбцов виртуальной таблицы в порядке их объявления. Количество столбцов должно соответствовать объявлению таблицы, созданному методом xConnect или xCreate с помощью вызова sqlite3_declare_vtab(). Включаются все скрытые столбцы.
При вставке без rowid (argc>1, argv[1] имеет значение SQL NULL) в виртуальную таблицу, использующую ROWID (но не в виртуальную таблицу WITHOUT ROWID), реализация должна установить *pRowid в rowid новой строки; это значение будет возвращено функцией sqlite3_last_insert_rowid(). Установка этого значения во всех остальных случаях безвредна и не влияет на результат; ядро SQLite игнорирует возвращаемое значение *pRowid, если argc==1 или argv[1] не имеет значения SQL NULL.
Каждый вызов xUpdate относится к одному из перечисленных ниже случаев. Обратите внимание, что ссылки на argv[i] означают значение SQL, хранящееся в объекте argv[i], а не сам объект argv[i].
argc = 1 argv[0] ≠ NULL DELETE: The single row with rowid or PRIMARY KEY equal to argv[0] is deleted. No insert occurs. argc > 1 argv[0] = NULL INSERT: A new row is inserted with column values taken from argv[2] and following. In a rowid virtual table, if argv[1] is an SQL NULL, then a new unique rowid is generated automatically. The argv[1] will be NULL for a WITHOUT ROWID virtual table, in which case the implementation should take the PRIMARY KEY value from the appropriate column in argv[2] and following. argc > 1 argv[0] ≠ NULL argv[0] = argv[1] UPDATE: The row with rowid or PRIMARY KEY argv[0] is updated with new values in argv[2] and following parameters. argc > 1 argv[0] ≠ NULL argv[0] ≠ argv[1] UPDATE with rowid or PRIMARY KEY change: The row with rowid or PRIMARY KEY argv[0] is updated with the rowid or PRIMARY KEY in argv[1] and new values in argv[2] and following parameters. This will occur when an SQL statement updates a rowid, as in the statement: UPDATE table SET rowid=rowid+1 WHERE ...;
Метод xUpdate должен возвращать SQLITE_OK тогда и только тогда, когда он выполнен успешно. В случае сбоя xUpdate должен вернуть соответствующий код ошибки. При сбое элемент pVTab->zErrMsg может быть заменён текстом сообщения об ошибке, хранящимся в памяти, выделенной SQLite с помощью таких функций, как sqlite3_mprintf() или sqlite3_malloc().
Если метод xUpdate нарушает какое-либо ограничение виртуальной таблицы (включая, помимо прочего, попытку сохранить значение неверного типа данных, слишком большое или слишком маленькое значение либо попытку изменить значение только для чтения), xUpdate должен завершиться с соответствующим кодом ошибки.
Если метод xUpdate выполняет UPDATE, с помощью sqlite3_value_nochange(X) можно определить, какие столбцы виртуальной таблицы действительно были изменены оператором UPDATE. Интерфейс sqlite3_value_nochange(X) возвращает true для столбцов, которые не изменились. При каждом UPDATE SQLite сначала отдельно вызывает xColumn для каждого неизменяемого столбца таблицы, чтобы получить его значение. Метод xColumn может проверить, не изменился ли столбец на уровне SQL, вызвав sqlite3_vtab_nochange(). Если xColumn обнаруживает, что столбец не изменяется, он должен вернуться, не устанавливая результат с помощью интерфейсов sqlite3_result_xxxxx(). Только в этом случае вызов sqlite3_value_nochange() в методе xUpdate вернёт true. Если xColumn вызывает один или несколько интерфейсов sqlite3_result_xxxxx(), SQLite считает это изменением значения столбца, и вызов sqlite3_value_nochange() для этого столбца в методе xUpdate вернёт false.
На момент вызова метода xUpdate для данного экземпляра виртуальной таблицы, а возможно, и для её строки, могут быть открыты и использоваться один или несколько объектов sqlite3_vtab_cursor. Реализация xUpdate должна быть готова к попыткам удалить или изменить строки таблицы, на которые указывают другие существующие курсоры. Если виртуальная таблица не может поддерживать такие изменения, метод xUpdate должен вернуть код ошибки.
Метод xUpdate необязателен. Если указатель xUpdate в sqlite3_module виртуальной таблицы равен NULL, виртуальная таблица доступна только для чтения.
2.14. Метод xFindFunction
int (*xFindFunction)( sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg );
Этот метод вызывается во время sqlite3_prepare(), чтобы предоставить реализации виртуальной таблицы возможность переопределять функции. Этот метод можно установить в NULL, и тогда переопределение не выполняется.
Если функция использует столбец виртуальной таблицы в качестве первого аргумента, вызывается этот метод, чтобы выяснить, хочет ли виртуальная таблица переопределить функцию. Первые три параметра являются входными: виртуальная таблица, количество аргументов функции и имя функции. Если переопределение не требуется, метод возвращает 0. Чтобы переопределить функцию, метод записывает новую реализацию функции в *pxFunc, пользовательские данные — в *ppArg и возвращает 1 либо число от SQLITE_INDEX_CONSTRAINT_FUNCTION до 255.
Исторически возвращаемым значением xFindFunction() было либо ноль, либо единица. Ноль означает, что функция не переопределена, а единица — что переопределена. Возможность возвращать значения SQLITE_INDEX_CONSTRAINT_FUNCTION или выше была добавлена в версии 3.25.0 (2018-09-15). Если xFindFunction возвращает SQLITE_INDEX_CONSTRAINT_FUNCTION или большее значение, это означает, что функция принимает два аргумента, может использоваться как логическое значение в предложении WHERE запроса, а виртуальная таблица может задействовать эту функцию для ускорения получения результата запроса. Если xFindFunction возвращает SQLITE_INDEX_CONSTRAINT_FUNCTION или большее значение, возвращённое значение становится значением sqlite3_index_info.aConstraint.op для одного из ограничений, переданных в xBestIndex(). Первый аргумент функции — это столбец, указанный полем aConstraint[].iColumn ограничения, а второй аргумент функции — значение, передаваемое в xFilter() (если задано значение aConstraintUsage[].argvIndex) или значение, возвращённое sqlite3_vtab_rhs_value().
Модуль Geopoly — пример виртуальной таблицы, использующей SQLITE_INDEX_CONSTRAINT_FUNCTION для повышения производительности. Метод xFindFunction() для Geopoly возвращает SQLITE_INDEX_CONSTRAINT_FUNCTION для SQL-функции geopoly_overlap() и SQLITE_INDEX_CONSTRAINT_FUNCTION+1 для SQL-функции geopoly_within(). Это позволяет оптимизировать поиск в таких запросах, как:
SELECT * FROM geopolytab WHERE geopoly_overlap(_shape, $query_polygon); SELECT * FROM geopolytab WHERE geopoly_within(_shape, $query_polygon);
Обратите внимание, что инфиксные функции (LIKE, GLOB, REGEXP и MATCH) меняют порядок аргументов на обратный. Поэтому "like(A,B)" обычно работает так же, как "B like A". Однако xFindFunction() всегда проверяет крайний левый аргумент, а не первый логический аргумент. Следовательно, в форме "B like A" SQLite проверяет левый операнд "B" и, если этот операнд является столбцом виртуальной таблицы, вызывает метод xFindFunction() для этой виртуальной таблицы. Но если используется форма "like(A,B)", SQLite проверяет выражение A, чтобы выяснить, является ли оно столбцом виртуальной таблицы, и в таком случае вызывает метод xFindFunction() для виртуальной таблицы, которой принадлежит столбец A.
Указатель на функцию, возвращаемый этой процедурой, должен быть действителен в течение всего срока существования объекта sqlite3_vtab, переданного в первом параметре.
2.15. Метод xBegin
int (*xBegin)(sqlite3_vtab *pVTab);
Этот метод начинает транзакцию для виртуальной таблицы. Метод необязателен. Указатель xBegin в sqlite3_module может быть равен NULL.
За этим методом всегда следует один вызов метода xCommit или xRollback. Транзакции виртуальных таблиц не вкладываются, поэтому метод xBegin не будет вызван для одной виртуальной таблицы более одного раза без промежуточного вызова xCommit или xRollback. Между xBegin и соответствующим xCommit или xRollback может произойти и, вероятнее всего, произойдёт несколько вызовов других методов.
2.16. Метод xSync
int (*xSync)(sqlite3_vtab *pVTab);
Этот метод обозначает начало двухфазной фиксации транзакции виртуальной таблицы. Метод необязателен. Указатель xSync в sqlite3_module может быть равен NULL.
Этот метод вызывается только после вызова метода xBegin и до вызова xCommit или xRollback. Для реализации двухфазной фиксации метод xSync вызывается для всех виртуальных таблиц до вызова метода xCommit для любой из них. Если любой из методов xSync завершится ошибкой, вся транзакция будет откачена.
2.17. Метод xCommit
int (*xCommit)(sqlite3_vtab *pVTab);
Этот метод фиксирует транзакцию виртуальной таблицы. Метод необязателен. Указатель xCommit в sqlite3_module может быть равен NULL.
Этому методу всегда предшествует вызов xBegin и xSync.
2.18. Метод xRollback
int (*xRollback)(sqlite3_vtab *pVTab);
Этот метод откатывает транзакцию виртуальной таблицы. Метод необязателен. Указатель xRollback в sqlite3_module может быть равен NULL.
Этому методу всегда предшествует вызов xBegin.
2.19. Метод xRename
int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
Этот метод уведомляет реализацию виртуальной таблицы о том, что ей будет присвоено новое имя. Если метод возвращает SQLITE_OK, SQLite переименовывает таблицу. Если метод возвращает код ошибки, переименование отменяется.
Метод xRename необязателен. Если он не реализован, виртуальную таблицу нельзя переименовать с помощью команды ALTER TABLE RENAME.
Перед вызовом этого метода включается параметр PRAGMA legacy_alter_table, а после завершения метода значение legacy_alter_table восстанавливается. Это необходимо для правильной работы виртуальных таблиц, использующих теневые таблицы, которые требуется переименовать в соответствии с новым именем виртуальной таблицы. Если legacy_alter_format выключен, метод xConnect будет вызываться для виртуальной таблицы каждый раз, когда метод xRename пытается изменить имя теневой таблицы.
2.20. Методы xSavepoint, xRelease и xRollbackTo
int (*xSavepoint)(sqlite3_vtab *pVtab, int); int (*xRelease)(sqlite3_vtab *pVtab, int); int (*xRollbackTo)(sqlite3_vtab *pVtab, int);
Эти методы предоставляют реализации виртуальной таблицы возможность реализовать вложенные транзакции. Они всегда необязательны и вызываются только в SQLite версии 3.7.7 (2011-06-23) и более поздних версиях.
Вызов xSavepoint(X,N) означает, что виртуальная таблица X должна сохранить своё текущее состояние в качестве точки сохранения N. Последующий вызов xRollbackTo(X,R) означает, что состояние виртуальной таблицы должно быть восстановлено до состояния на момент последнего вызова xSavepoint(X,R). Вызов xRollbackTo(X,R) аннулирует все точки сохранения с N>R; откатить или освободить ни одну из аннулированных точек сохранения нельзя, пока она не будет повторно инициализирована вызовом xSavepoint(). Вызов xRelease(X,M) аннулирует все точки сохранения, для которых N>=M.
Методы xSavepoint(), xRelease() и xRollbackTo() никогда не вызываются иначе как между вызовами xBegin() и xCommit() или xRollback().
2.21. Метод xShadowName
Некоторые реализации виртуальных таблиц (например, FTS3, FTS5 и RTREE) используют реальные (невиртуальные) таблицы базы данных для хранения содержимого. Например, при вставке содержимого в виртуальную таблицу FTS3 данные в конечном счёте сохраняются в реальных таблицах с именами "%_content", "%_segdir", "%_segments", "%_stat" и "%_docsize", где "%" — имя исходной виртуальной таблицы. Эти вспомогательные реальные таблицы, хранящие содержимое виртуальной таблицы, называются «теневыми таблицами». Дополнительные сведения см. в разделах (1), (2) и (3).
Метод xShadowName позволяет SQLite определить, является ли определённая реальная таблица теневой таблицей виртуальной таблицы.
SQLite считает реальную таблицу теневой, если выполняются все следующие условия:
- Имя таблицы содержит один или несколько символов "_".
- Часть имени до последнего "_" в точности совпадает с именем виртуальной таблицы, созданной с помощью CREATE VIRTUAL TABLE. (Теневые таблицы не распознаются для эпонимических виртуальных таблиц и табличных функций.)
- Виртуальная таблица содержит метод xShadowName.
- Метод xShadowName возвращает true, если ему передана часть имени таблицы после последнего символа "_".
Если SQLite распознаёт таблицу как теневую и установлен флаг SQLITE_DBCONFIG_DEFENSIVE, то для обычных операторов SQL теневая таблица доступна только для чтения. Записывать в теневую таблицу по-прежнему можно, но только с помощью SQL, вызванного из одного из методов какой-либо реализации виртуальной таблицы.
Основная цель метода xShadowName — защитить содержимое теневых таблиц от повреждения вредоносным SQL. Любая реализация виртуальной таблицы, использующая теневые таблицы, должна уметь обнаруживать и обрабатывать повреждённое содержимое таких таблиц. Однако ошибки в конкретной реализации виртуальной таблицы могут позволить намеренно повредить теневую таблицу и вызвать аварийное завершение или другую неисправность. Механизм xShadowName помогает избежать эксплойтов нулевого дня, запрещая обычным операторам SQL намеренно повреждать теневые таблицы.
По умолчанию теневые таблицы доступны для чтения и записи. Они становятся доступными только для чтения, только если флаг SQLITE_DBCONFIG_DEFENSIVE установлен с помощью sqlite3_db_config(). По умолчанию теневые таблицы должны быть доступны для чтения и записи для обеспечения обратной совместимости. Например, текст SQL, создаваемый командой .dump программы CLI, записывает данные непосредственно в теневые таблицы.
2.22. Метод xIntegrity
Если значение iVersion для sqlite3_module равно 4 или больше и xIntegrity не равен NULL, команды PRAGMA integrity_check и PRAGMA quick_check вызовут xIntegrity в ходе обработки. Если метод xIntegrity записывает строку сообщения об ошибке в пятый параметр, PRAGMA integrity_check включит эту ошибку в свой вывод. Иными словами, метод xIntegrity позволяет команде PRAGMA integrity_check проверять целостность содержимого виртуальной таблицы.
Метод xIntegrity вызывается с пятью параметрами:
- pVTab → Указатель на объект sqlite3_vtab, соответствующий проверяемой виртуальной таблице.
- zSchema → Имя схемы ("main", "temp" и т. д.), в которой определена виртуальная таблица.
- zTabName → Имя виртуальной таблицы.
- mFlags → Флаг, указывающий, выполняется ли "integrity_check" или "quick_check". В настоящее время этот параметр всегда равен 0 или 1, хотя в будущих версиях SQLite могут использоваться и другие биты целого числа для указания дополнительных параметров обработки.
- pzErr → Этот параметр указывает на "char*", инициализированный значением NULL. Если реализация xIntegrity() обнаружит проблему, она должна присвоить *pzErr указатель на строку ошибки, полученную с помощью sqlite3_malloc() или эквивалентной функции.
Обычно метод xIntegrity должен возвращать SQLITE_OK, даже если обнаружит проблемы в содержимом виртуальной таблицы. Любой другой код ошибки означает, что сам метод xIntegrity столкнулся с проблемами при проверке содержимого виртуальной таблицы. Например, если обнаружена внутренняя несогласованность инвертированного индекса FTS5, метод xIntegrity должен записать соответствующее сообщение об ошибке в параметр pzErr и вернуть SQLITE_OK. Но если метод xIntegrity не может завершить проверку содержимого виртуальной таблицы из-за нехватки памяти, он должен вернуть SQLITE_NOMEM.
Если формируется сообщение об ошибке, память для его строки следует выделить с помощью sqlite3_malloc64() или эквивалентной функции. После возврата xIntegrity владение строкой сообщения об ошибке переходит к ядру SQLite. Ядро обеспечит вызов sqlite3_free() для освобождения памяти после завершения работы с сообщением об ошибке. Команда PRAGMA integrity_check, вызывающая метод xIntegrity, не изменяет возвращённое сообщение об ошибке. Сам метод xIntegrity должен включать имя виртуальной таблицы в сообщение. Для этого и предоставлены параметры zSchema и zName.
Параметр mFlags в настоящее время является логическим значением (0 или 1), указывающим, был ли метод xIntegrity вызван командой PRAGMA integrity_check (mFlags==0) или командой PRAGMA quick_check (mFlags==1). В целом метод xIntegrity должен выполнять все доступные проверки корректности, требующие линейного времени, независимо от этого значения, но выполнять проверки, требующие сверхлинейного времени, только если (mFlags&1)==0. В будущих версиях SQLite старшие биты параметра mFlags могут использоваться для указания дополнительных параметров обработки.
Поддержка метода xIntegrity добавлена в SQLite версии 3.44.0 (2023-11-01). В этом же выпуске метод xIntegrity добавлен во многие встроенные виртуальные таблицы, такие как FTS3, FTS5 и RTREE, чтобы содержимое этих таблиц автоматически проверялось на согласованность при выполнении команды PRAGMA integrity_check.
Последнее обновление этой страницы: 2025-12-22 18:00:39Z
SQLite is in the Public Domain.
https://sqlite.org/vtab.html