Механизм виртуальных таблиц SQLite
Содержание
1. Введение
Виртуальная таблица — это объект, зарегистрированный в открытом подключении к базе данных SQLite. С точки зрения SQL-запроса, виртуальная таблица выглядит как любая другая таблица или представление. Но за кулисами запросы и обновления виртуальной таблицы вызывают методы обратного вызова объекта виртуальной таблицы вместо чтения и записи в файле базы данных.
Механизм виртуальных таблиц позволяет приложению публиковать интерфейсы, доступные из SQL-запросов, как если бы они были таблицами. SQL-запросы могут делать практически все с виртуальной таблицей, что они могут делать с реальной таблицей, за исключением следующих пунктов:
- Нельзя создать триггер для виртуальной таблицы.
- Нельзя создать дополнительные индексы для виртуальной таблицы. (Виртуальные таблицы могут иметь индексы, но они должны быть включены в реализацию виртуальной таблицы. Индексы не могут быть добавлены отдельно с помощью операторов CREATE INDEX.)
- Нельзя выполнить команды ALTER TABLE ... ADD COLUMN для виртуальной таблицы.
Индивидуальные реализации виртуальных таблиц могут налагать дополнительные ограничения. Например, некоторые виртуальные реализации могут предоставлять только для чтения таблицы. Или некоторые реализации виртуальных таблиц могут разрешать INSERT или DELETE, но не UPDATE. Или некоторые реализации виртуальных таблиц могут ограничивать типы обновлений, которые можно выполнить.
Виртуальная таблица может представлять структуру данных в оперативной памяти. Или она может представлять представление данных на диске, которые не находятся в формате SQLite. Или приложение может вычислять содержимое виртуальной таблицы по запросу.
Вот некоторые существующие и предполагаемые применения виртуальных таблиц:
- Интерфейс полнотекстового поиска
- Пространственные индексы с использованием R-деревьев
- Отображение содержимого файла базы данных SQLite (виртуальная таблица dbstat)
- Чтение и/или запись содержимого файла с разделителями (CSV)
- Доступ к файловой системе хост-компьютера, как если бы это была таблица базы данных
- Включение возможности SQL-обработки данных в статистических пакетах, таких как R
См. страницу список виртуальных таблиц для более длинного списка фактических реализаций виртуальных таблиц.
1.1. Использование
Виртуальная таблица создается с помощью оператора CREATE VIRTUAL TABLE.
Оператор CREATE VIRTUAL TABLE создаёт новую таблицу с именем имя_таблицы, которая основана на классе имя_модуля. имя_модуля — это имя, зарегистрированное для виртуальной таблицы интерфейсом sqlite3_create_module().
CREATE VIRTUAL TABLE tablename USING modulename;
Можно также указать аргументы, разделённые запятыми, после имени модуля:
CREATE VIRTUAL TABLE tablename USING modulename(arg1, arg2, ...);
Формат аргументов модуля очень общий. Каждый аргумент_модуля может содержать ключевые слова, строковые литералы, идентификаторы, числа и знаки препинания. Каждый аргумент_модуля передаётся в неизменном виде (в виде текста) в метод конструктора реализации виртуальной таблицы при создании виртуальной таблицы, и этот конструктор отвечает за разбор и интерпретацию аргументов. Синтаксис аргументов достаточно общий для того, чтобы реализация виртуальной таблицы, если это необходимо, могла интерпретировать свои аргументы как определения столбцов в обычном операторе CREATE TABLE. Реализация также может назначить аргументам другое толкование.
После создания виртуальной таблицы, её можно использовать как любую другую таблицу, за исключением указанных выше особенностей и ограничений, наложенных конкретными реализациями виртуальных таблиц. Виртуальная таблица удаляется с помощью обычного синтаксиса DROP TABLE.
1.1.1. Временные виртуальные таблицы
Нет оператора "CREATE TEMP VIRTUAL TABLE". Для создания временной виртуальной таблицы добавьте "temp" перед именем виртуальной таблицы.
CREATE VIRTUAL TABLE temp.tablename USING module(arg1, ...);
1.1.2. Виртуальные таблицы с собственным именем
Некоторые виртуальные таблицы автоматически существуют в схеме «основной» каждого подключения к базе данных, в котором зарегистрирован их модуль, даже без оператора CREATE VIRTUAL TABLE. Такие виртуальные таблицы называются «виртуальными таблицами с собственным именем». Для использования виртуальной таблицы с собственным именем достаточно использовать имя модуля как имя таблицы. Виртуальные таблицы с собственным именем существуют только в схеме «основной», поэтому они не будут работать, если перед именем модуля указано другое имя схемы.
Пример виртуальной таблицы с собственным именем — виртуальная таблица dbstat. Для использования виртуальной таблицы dbstat как виртуальной таблицы с собственным именем, просто запросите её по имени модуля «dbstat», как если бы это была обычная таблица. (Обратите внимание, что SQLite необходимо скомпилировать с опцией SQLITE_ENABLE_DBSTAT_VTAB, чтобы включить виртуальную таблицу dbstat в сборку.)
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 против этой виртуальной таблицы, произойдёт переход на указатель NULL, что приведёт к сбою.
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 → Реализация функции генерации последовательностей table-valued function.
- json.c → Содержит исходные коды функций json_each() и json_tree() table-valued function.
- csv.c → Виртуальная таблица для чтения CSV-файлов.
В дереве исходных кодов SQLite есть множество других реализаций виртуальных таблиц, которые можно использовать в качестве примеров. Найдите эти другие реализации виртуальных таблиц, выполнив поиск по фразе «sqlite3_create_module».
Возможно, вы также захотите реализовать свою новую виртуальную таблицу как загружаемое расширение.
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» для временной базы данных или имя, указанное в конце оператора 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), то виртуальная таблица является виртуальной таблицей только с собственным именем eponymous-only virtual table. Новые экземпляры виртуальной таблицы не могут быть созданы с помощью CREATE VIRTUAL TABLE, и виртуальная таблица может использоваться только через имя своего модуля. Обратите внимание, что версии SQLite до 3.9.0 (2015-10-14) не понимают виртуальные таблицы только с собственным именем и вызовут ошибку сегментации (segfault), если будет предпринята попытка CREATE VIRTUAL TABLE для виртуальной таблицы только с собственным именем, потому что метод xCreate не был проверен на null.
Если метод xCreate является точно тем же указателем, что и метод xConnect, это указывает на то, что виртуальной таблице не нужно инициализировать хранилище данных. Такая виртуальная таблица может использоваться как eponymous virtual table или как именованная виртуальная таблица с помощью CREATE VIRTUAL TABLE, или и то, и другое.
2.1.1. Скрытые столбцы в виртуальных таблицах
Если тип данных столбца содержит специальное ключевое слово "HIDDEN" (в любом сочетании верхних и нижних регистров), то это ключевое слово опускается из имени типа данных столбца, а столбец помечается как скрытый столбец внутри. Скрытый столбец отличается от обычного столбца в трех отношениях:
- Скрытые столбцы не перечисляются в наборе данных, возвращаемом "PRAGMA table_info",
- Скрытые столбцы не включаются в расширение выражения "*" в наборе результатов SELECT, и
- Скрытые столбцы не включаются в неявный список столбцов, используемый оператором INSERT, который не имеет явного списка столбцов.
Например, если следующий SQL передается в sqlite3_declare_vtab():
CREATE TABLE x(a HIDDEN VARCHAR(12), b INTEGER, c INTEGER Hidden);
Тогда виртуальная таблица будет создана с двумя скрытыми столбцами и типами данных "VARCHAR(12)" и "INTEGER".
Пример использования скрытых столбцов можно увидеть в реализации виртуальной таблицы FTS3, где каждая виртуальная таблица FTS содержит скрытый столбец FTS hidden column, который используется для передачи информации из виртуальной таблицы в вспомогательные функции FTS auxiliary functions и оператор FTS MATCH.
2.1.2. Табличные функции
Виртуальная таблица virtual table, содержащая скрытые столбцы, может использоваться как табличная функция в предложении FROM оператора SELECT. Аргументы табличной функции становятся ограничениями на скрытые столбцы виртуальной таблицы.
Например, расширение "generate_series" (расположенное в файле ext/misc/series.c в дереве исходного кода) реализует виртуальную таблицу eponymous virtual table со следующей схемой:
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
Начиная с версии SQLite 3.14.0 (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 является действительным — что идентифицированные столбцы действительно уникальны и 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 одинаковы, таблица является eponymous virtual table.
В качестве примера рассмотрим реализацию виртуальной таблицы, которая предоставляет только чтение к существующим файлам CSV (comma-separated-value) на диске. Для такой виртуальной таблицы нет необходимости создавать или инициализировать хранилище данных (поскольку файлы 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. Однако нет гарантии, что xFilter будет вызван после успешного выполнения xBestIndex.
Метод xBestIndex необходим для каждой реализации виртуальной таблицы.
2.3.1. Входы
Основное, что ядро SQLite пытается передать виртуальной таблице, — это ограничения, которые можно использовать для ограничения количества строк, которые необходимо искать. Массив aConstraint[] содержит по одному элементу для каждого ограничения. В этом массиве будет ровно nConstraint элементов.
Каждое ограничение обычно соответствует термину в предложении WHERE или в предложениях USING или ON, имеющего вид
столбец ОП ВЫРАЖЕНИЕ
Где "столбец" — это столбец в виртуальной таблице, ОП — это оператор, например, "=" или "<", а ВЫРАЖЕНИЕ — произвольное выражение. Например, если предложение 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. Идентификатор строки виртуальной таблицы имеет индекс -1. Поле aConstraint[].op указывает используемый оператор. Константы SQLITE_INDEX_CONSTRAINT_* сопоставляют целочисленные константы со значениями операторов. Столбцы появляются в том порядке, в котором они были определены при вызове sqlite3_declare_vtab() в методах xCreate или xConnect. Скрытые столбцы учитываются при определении индекса столбца.
Если для виртуальной таблицы определен метод xFindFunction() и если метод xFindFunction() иногда возвращает SQLITE_INDEX_CONSTRAINT_FUNCTION или большее значение, то ограничения могут также иметь вид:
ФУНКЦИЯ( столбец, ВЫРАЖЕНИЕ)
В этом случае значение aConstraint[].op совпадает со значением, возвращаемым методом xFindFunction() для ФУНКЦИИ.
Массив aConstraint[] содержит информацию обо всех ограничениях, применимых к виртуальной таблице. Но некоторые ограничения могут быть неприменимы из-за способа упорядочения таблиц в соединении. Поэтому метод xBestIndex должен учитывать только ограничения, у которых флаг aConstraint[].usable имеет значение true.
В дополнение к ограничениям предложения WHERE ядро SQLite также сообщает методу xBestIndex об предложении ORDER BY. (В запросе агрегации ядро SQLite может поместить информацию о предложении GROUP BY вместо информации о предложении ORDER BY, но этот факт не должен повлиять на работу метода xBestIndex.) Если все члены предложения ORDER BY являются столбцами в виртуальной таблице, то nOrderBy будет количеством членов в предложении ORDER BY, а массив aOrderBy[] будет указывать столбец для каждого члена в предложении ORDER BY и указывать, является ли этот столбец ASC или DESC.
В SQLite версии 3.10.0 (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(ВЫРАЖЕНИЕ, столбец)
GLOB(ВЫРАЖЕНИЕ, столбец)
REGEXP(ВЫРАЖЕНИЕ, столбец)
MATCH(ВЫРАЖЕНИЕ, столбец)
Таким образом, с точки зрения метода xBestIndex() следующие две формы эквивалентны:
столбец LIKE ВЫРАЖЕНИЕ
LIKE(ВЫРАЖЕНИЕ,столбец)
Это специальное поведение, которое рассматривает второй аргумент функции, характерно только для функций 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, завершается нулем.
Значение idxStr может быть строкой, полученной из функции выделения памяти SQLite, например, sqlite3_mprintf(). В этом случае флаг needToFreeIdxStr должен быть установлен в true, чтобы ядро SQLite знало, что нужно вызвать sqlite3_free() для этой строки по завершении работы с ней, и тем самым избежать утечки памяти. Значение idxStr также может быть статической константой, в этом случае переменная needToFreeIdxStr должна остаться false.
Поле estimatedCost должно быть установлено в оценку количества операций доступа к диску, необходимых для выполнения этого запроса в виртуальной таблице. Ядро SQLite часто вызывает метод xBestIndex несколько раз с разными ограничениями, получает несколько оценок стоимости, а затем выбирает план запроса, дающий наименьшую оценку. Ядро SQLite инициализирует estimatedCost очень большим значением перед вызовом xBestIndex, поэтому если xBestIndex определит, что текущая комбинация параметров нежелательна, оно может оставить поле estimatedCost без изменений, чтобы отговорить от ее использования.
Если текущая версия SQLite 3.8.2 или выше, поле estimatedRows может быть установлено в оценку количества строк, возвращаемых предложенным планом запроса. Если это значение не установлено явно, используется стандартная оценка в 25 строк.
Если текущая версия SQLite 3.9.0 или выше, поле idxFlags может быть установлено в SQLITE_INDEX_SCAN_UNIQUE, чтобы указать, что виртуальная таблица вернет ноль или одну строку при заданных входных ограничениях. Дополнительные биты поля idxFlags могут быть поняты в более поздних версиях SQLite.
Массив aConstraintUsage[] содержит по одному элементу для каждого из nConstraint ограничений в разделе входов структуры sqlite3_index_info. Массив aConstraintUsage[] используется методом xBestIndex для указания ядру, как он использует ограничения.
Метод xBestIndex может установить значения aConstraintUsage[].argvIndex больше нуля. Точно один элемент должен быть установлен в 1, другой в 2, и так далее, до тех пор, сколько потребуется методу xBestIndex. Тогда ВЫРАЖЕНИЕ соответствующих ограничений будут переданы в качестве параметров argv[] методу xFilter.
Например, если aConstraint[3].argvIndex установлен в 1, то при вызове xFilter аргумент argv[0] методу xFilter будет содержать значение ВЫРАЖЕНИЯ ограничения 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() завершится ошибкой "нет решения для запроса".
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
Просканировать все строки tablevalued func и для каждой строки найти строки в 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_double()
- sqlite3_result_int()
- sqlite3_result_int64()
- sqlite3_result_null()
- sqlite3_result_text()
- sqlite3_result_text16()
- sqlite3_result_text16le()
- sqlite3_result_text16be()
- sqlite3_result_zeroblob()
Если реализация метода xColumn не вызывает ни одну из перечисленных функций, то значение столбца по умолчанию устанавливается как SQL NULL.
Для поднятия ошибки метод xColumn должен использовать один из методов result_text() для установки текста сообщения об ошибке, а затем вернуть соответствующий код ошибки SQLite. Метод 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 (но не в виртуальной таблице без 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 будет истинным. Если 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 командной строки, записывает данные непосредственно в дополнительные таблицы.
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() или эквивалента. Владение строкой сообщения об ошибке переходит к ядру SQLite при возвращении xIntegrity. Ядро позаботится о вызове 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.
Последнее изменение этой страницы: 2024-05-23 20:19:17 UTC
SQLite is in the Public Domain.
https://sqlite.org/vtab.html