Загружаемые во время выполнения расширения
Содержание
1. Обзор
SQLite может загружать расширения (включая новые пользовательские SQL-функции, последовательности сортировки, виртуальные таблицы и VFS) во время выполнения. Эта возможность позволяет разрабатывать и тестировать код расширений отдельно от приложения и затем загружать их по мере необходимости.
Расширения также можно статически компоновать с приложением. Приведенный ниже шаблон кода будет работать так же хорошо как статически компонуемое расширение, как и загружаемое во время выполнения расширение, за исключением того, что вы должны дать функции точки входа ("sqlite3_extension_init") другое имя, чтобы избежать конфликтов имен, если ваше приложение содержит два или более расширений.
2. Загрузка расширения
Расширение SQLite — это динамическая библиотека или DLL. Для её загрузки необходимо предоставить SQLite имя файла, содержащего динамическую библиотеку или DLL, и точку входа для инициализации расширения. В коде C эта информация предоставляется с помощью API sqlite3_load_extension(). Дополнительную информацию см. в документации по этой процедуре.
Обратите внимание, что разные операционные системы используют разные постфиксы имён файлов для своих динамических библиотек. Windows использует ".dll", Mac — ".dylib", а большинство других Unix-систем, кроме Mac, используют ".so". Если вы хотите сделать свой код портативным, вы можете опустить постфикс из имени файла динамической библиотеки, и соответствующий постфикс будет автоматически добавлен интерфейсом sqlite3_load_extension().
Также существует SQL-функция, которая может использоваться для загрузки расширений: load_extension(X,Y). Она работает так же, как и C-интерфейс sqlite3_load_extension().
Оба метода загрузки расширения позволяют указать имя точки входа для расширения. Вы можете оставить этот аргумент пустым — передав указатель NULL для C-языкового интерфейса sqlite3_load_extension() или опуская второй аргумент для SQL-интерфейса load_extension() — и механизм загрузки расширения попытается определить точку входа самостоятельно. Сначала он попробует найти точку входа с общим именем "sqlite3_extension_init". Если это не сработает, он создаст точку входа, используя шаблон "sqlite3_X_init", где X заменяется строчной эквивалентом каждого ASCII-символа в имени файла после последнего "/" и перед первым последующим ".", опуская первые три символа, если они случайным образом оказываются "lib". Например, если имя файла — "/usr/lib/libmathfunc-4.8.so", то имя точки входа будет "sqlite3_mathfunc_init". Или если имя файла — "./SpellFixExt.dll", то точка входа будет называться "sqlite3_spellfixext_init".
По соображениям безопасности, загрузка расширений по умолчанию отключена. Для использования функций загрузки расширений на C или SQL необходимо сначала включить загрузку расширений, используя C-языковый API sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,1,NULL) в вашем приложении.
Из командной оболочки расширения можно загрузить с помощью команды ".load". Например:
.load ./YourCode
Обратите внимание, что программа командной оболочки уже включила загрузку расширений для вас (вызвав интерфейс sqlite3_enable_load_extension() как часть своей настройки), поэтому приведенная выше команда работает без каких-либо специальных переключателей, настройки или других осложнений.
Команда ".load" с одним аргументом вызывает sqlite3_load_extension() с параметром zProc, установленным в NULL, что заставляет SQLite сначала искать точку входа с именем "sqlite3_extension_init", а затем "sqlite3_X_init", где "X" получено из имени файла. Если у вашего расширения есть точка входа с другим именем, просто укажите это имя в качестве второго аргумента. Например:
.load ./YourCode nonstandard_entry_point
3. Компиляция загружаемого расширения
Загружаемые расширения написаны на C. Для их компиляции на большинстве Unix-подобных операционных систем, обычная команда выглядит примерно так:
gcc -g -fPIC -shared YourCode.c -o YourCode.so
Mac — это Unix-подобная система, но она не следует обычным соглашениям о динамических библиотеках. Для компиляции динамической библиотеки на Mac используйте команду, подобную этой:
gcc -g -fPIC -dynamiclib YourCode.c -o YourCode.dylib
Если при попытке загрузить вашу библиотеку вы получите сообщение об ошибке, в котором говорится "mach-o, но неверная архитектура", возможно, вам нужно добавить командные параметры "-arch i386" или "arch x86_64" к gcc, в зависимости от того, как построено ваше приложение.
Для компиляции на Windows с помощью MSVC обычно работает похожая команда:
cl YourCode.c -link -dll -out:YourCode.dll
Для компиляции на Windows с помощью MinGW командная строка аналогична Unix, за исключением того, что постфикс выходного файла изменяется на ".dll", а аргумент -fPIC опускается:
gcc -g -shared YourCode.c -o YourCode.dll
4. Программирование загружаемых расширений
Шаблон загружаемого расширения содержит следующие три элемента:
-
Используйте "
#include <sqlite3ext.h>" в начале ваших файлов исходного кода вместо "#include <sqlite3.h>". -
Поместите макрос "
SQLITE_EXTENSION_INIT1" на отдельной строке сразу после строки "#include <sqlite3ext.h>". -
Добавьте процедуру точки входа для загрузки расширения, которая выглядит примерно так:
#ifdef _WIN32 __declspec(dllexport) #endif int sqlite3_extension_init( /* <== Change this name, maybe */ sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); /* insert code to initialize your extension here */ return rc; }Рекомендуется настроить имя точки входа в соответствии с именем динамической библиотеки, которую вы будете создавать, а не использовать общее имя "sqlite3_extension_init". Присвоение расширению пользовательского имени точки входа позволит статически компоновать два или более расширений в одну программу без конфликта компоновщика, если вы позже решите использовать статическую компоновку вместо загрузки во время выполнения. Если ваша динамическая библиотека получит имя "YourCode.so" или "YourCode.dll" или "YourCode.dylib", как показано в примерах компилятора выше, то правильным именем точки входа будет "sqlite3_yourcode_init".
Вот полный шаблон расширения, который вы можете скопировать/вставить, чтобы начать работу:
/* Add your header comment here */
#include <sqlite3ext.h> /* Do not use <sqlite3.h>! */
SQLITE_EXTENSION_INIT1
/* Insert your extension code here */
#ifdef _WIN32
__declspec(dllexport)
#endif
/* TODO: Change the entry point name so that "extension" is replaced by
** text derived from the shared library filename as follows: Copy every
** ASCII alphabetic character from the filename after the last "/" through
** the next following ".", converting each character to lowercase, and
** discarding the first three characters if they are "lib".
*/
int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc = SQLITE_OK;
SQLITE_EXTENSION_INIT2(pApi);
/* Insert here calls to
** sqlite3_create_function_v2(),
** sqlite3_create_collation_v2(),
** sqlite3_create_module_v2(), and/or
** sqlite3_vfs_register()
** to register the new features that your extension adds.
*/
return rc;
}
4.1. Примеры расширений
Многие примеры полных и работающих загружаемых расширений можно найти в дереве исходного кода SQLite в подкаталоге ext/misc. Каждый файл в этой папке является отдельным расширением. Документация предоставляется заголовочным комментарием к файлу. Вот краткие заметки о некоторых расширениях в подкаталоге ext/misc:
carray.c — Реализация функции с массивом значений таблицы.
compress.c — Реализация пользовательских SQL-функций compress() и uncompress(), которые выполняют сжатие zLib текста или содержимого blob.
json1.c — Реализация JSON SQL-функций и функций с массивом значений таблицы. Это расширение большего размера и более сложное.
memvfs.c — Реализация нового VFS, который хранит всё содержимое в памяти.
rot13.c — Реализация SQL-функции rot13(). Это очень простой пример функции расширения и полезен в качестве шаблона для создания новых расширений.
series.c — Реализация виртуальной таблицы generate_series и функции с массивом значений таблицы. Это относительно простой пример реализации виртуальной таблицы, который может служить шаблоном для написания новых виртуальных таблиц.
Другие и более сложные расширения можно найти в подпапках в ext/ кроме ext/misc/.
5. Постоянно загружаемые расширения
По умолчанию загружаемое расширение выгружается из памяти процесса при закрытии базы данных, которая первоначально вызвала sqlite3_load_extension(). (Другими словами, для всех расширений вызывается метод xDlClose объекта sqlite3_vfs при закрытии соединения с базой данных). Однако, если процедура инициализации возвращает SQLITE_OK_LOAD_PERMANENTLY вместо SQLITE_OK, расширение не будет выгружаться (xDlClose не будет вызываться), и расширение останется в памяти процесса на неопределённое время. Значение возврата SQLITE_OK_LOAD_PERMANENTLY полезно для расширений, которые хотят зарегистрировать новые VFS.
Для уточнения: расширение, для которого функция инициализации возвращает SQLITE_OK_LOAD_PERMANENTLY, продолжает существовать в памяти после закрытия соединения с базой данных. Однако расширение не автоматически регистрируется в последующих подключениях к базе данных. Это позволяет загружать расширения, которые реализуют новые VFS. Для постоянной загрузки и регистрации расширения, которое реализует новые SQL-функции, последовательности сортировки и/или виртуальные таблицы, так чтобы добавленные возможности были доступны всем последующим подключениям к базе данных, процедура инициализации должна также вызвать sqlite3_auto_extension() для подпроцедуры, которая зарегистрирует эти службы.
Расширение vfsstat.c демонстрирует пример загружаемого расширения, которое постоянно регистрирует новый VFS и новую виртуальную таблицу. Функция инициализации sqlite3_vfsstat_init() в этом расширении вызывается только один раз при первой загрузке расширения. Она регистрирует новый VFS "vfslog" только один раз и возвращает SQLITE_OK_LOAD_PERMANENTLY, чтобы код, реализующий VFS "vfslog", оставался в памяти. Функция инициализации также вызывает sqlite3_auto_extension() по указателю на функцию "vstatRegister()", чтобы все последующие подключения к базе данных вызывали функцию "vstatRegister()" при запуске и, таким образом, регистрировали виртуальную таблицу "vfsstat".
6. Статическая компоновка загружаемого во время выполнения расширения
Точно такой же исходный код можно использовать как для загружаемой во время выполнения разделяемой библиотеки или DLL, так и для модуля, статически связанного с приложением. Это обеспечивает гибкость и позволяет повторно использовать один и тот же код различными способами.
Для статической компоновки расширения достаточно добавить опцию компиляции -DSQLITE_CORE. Макрос SQLITE_CORE заставляет макросы SQLITE_EXTENSION_INIT1 и SQLITE_EXTENSION_INIT2 стать пустыми. Затем измените приложение, чтобы вызвать точку входа напрямую, передав нулевой указатель в качестве третьего параметра "pApi".
Если вы будете статически компоновать два или более расширений, очень важно использовать имя точки входа, основанное на имени файла расширения, а не на общем имени точки входа "sqlite3_extension_init". Если вы используете общее имя, будет несколько определений одного и того же символа, и компоновка завершится ошибкой.
Если в вашем приложении будет открываться несколько подключений к базе данных, вместо вызова точек входа расширения для каждого подключения к базе данных по отдельности, вы можете использовать интерфейс sqlite3_auto_extension() для регистрации ваших расширений и их автоматического запуска при открытии каждого подключения к базе данных. Вам нужно зарегистрировать каждое расширение только один раз, и вы можете сделать это в начале вашей основной функции main(). Использование интерфейса sqlite3_auto_extension() для регистрации ваших расширений позволяет им работать так, как если бы они были встроенными в ядро SQLite — они автоматически существуют при каждом открытии нового подключения к базе данных без необходимости инициализации. Просто убедитесь, что вы выполните необходимые конфигурации с помощью sqlite3_config() до регистрации ваших расширений, так как интерфейс sqlite3_auto_extension() неявно вызывает sqlite3_initialize().
7. Подробности реализации
SQLite реализует загрузку расширений во время выполнения, используя методы xDlOpen(), xDlError(), xDlSym() и xDlClose() объекта sqlite3_vfs. Эти методы реализуются с помощью библиотеки dlopen() на Unix (что объясняет, почему SQLite обычно необходимо связывать с библиотекой "-ldl" на системах Unix) и с помощью API LoadLibrary() на Windows. В пользовательском VFS для необычных систем эти методы можно опустить, в этом случае механизм загрузки расширений во время выполнения не будет работать (хотя вы все равно сможете статически компоновать код расширения, предполагая, что указатели на точки входа имеют уникальные имена). SQLite можно скомпилировать с SQLITE_OMIT_LOAD_EXTENSION, чтобы исключить код загрузки расширений из сборки.
SQLite is in the Public Domain.
https://sqlite.org/loadext.html