Spec-Zone.ru › SQLite

Переход с SQLite 3.4.2 на 3.5.0

SQLite версии 3.5.0 (2007-09-04) представляет новый уровень интерфейса ОС, несовместимый со всеми предыдущими версиями SQLite. Кроме того, несколько существующих интерфейсов были обобщены, чтобы работать со всеми подключениями к базам данных в процессе, а не только со всеми подключениями в одном потоке. Цель этой статьи — подробно описать изменения в версии 3.5.0, чтобы пользователи предыдущих версий SQLite могли оценить, какие усилия, если таковые потребуются, необходимы для перехода на более новые версии.

1.0 Обзор изменений

Здесь приведён краткий перечень изменений в SQLite версии 3.5.0. В последующих разделах эти изменения описываются подробнее.

  1. Уровень интерфейса ОС полностью переработан:
    1. Недокументированный интерфейс sqlite3_os_switch() удалён.
    2. Флаг компиляции SQLITE_ENABLE_REDEF_IO больше не действует. Теперь процедуры ввода-вывода всегда можно переопределять.
    3. Для задания процедур ввода-вывода определены три новых объекта: sqlite3_vfs, sqlite3_file и sqlite3_io_methods.
    4. Для создания альтернативных интерфейсов ОС используются три новых интерфейса: sqlite3_vfs_register(), sqlite3_vfs_unregister() и sqlite3_vfs_find().
    5. Добавлен новый интерфейс, позволяющий дополнительно управлять созданием подключений к базам данных: sqlite3_open_v2(). Устаревшие интерфейсы sqlite3_open() и sqlite3_open16() по-прежнему полностью поддерживаются.
  2. Необязательные функции общего кэша и управления памятью, появившиеся в версии 3.3.0, теперь можно использовать в нескольких потоках одного процесса. Ранее эти расширения применялись только к подключениям к базам данных, работающим в одном потоке.
    1. Интерфейс sqlite3_enable_shared_cache() теперь применяется ко всем потокам процесса, а не только к потоку, в котором он был вызван.
    2. Интерфейс sqlite3_soft_heap_limit() теперь применяется ко всем потокам процесса, а не только к потоку, в котором он был вызван.
    3. Интерфейс sqlite3_release_memory() теперь пытается уменьшить использование памяти всеми подключениями к базам данных во всех потоках, а не только подключениями в потоке, где вызван интерфейс.
    4. Интерфейс sqlite3_thread_cleanup() стал пустой операцией.
  3. Сняты ограничения на использование одного подключения к базе данных несколькими потоками. Теперь несколько потоков могут безопасно одновременно использовать одно и то же подключение к базе данных.
  4. Добавлен параметр компиляции, позволяющий приложению задавать альтернативные реализации malloc()/free() без изменения какого-либо кода ядра SQLite.
  5. Добавлен параметр компиляции, позволяющий приложению задавать альтернативные реализации мьютексов без изменения какого-либо кода ядра SQLite.

Из этих изменений только 1a и 2a–2c являются несовместимыми в строгом смысле. Однако пользователи, ранее вносившие собственные изменения в исходный код SQLite (например, добавлявшие специальный уровень ОС для встроенного оборудования), могут обнаружить, что эти изменения оказывают более существенное влияние. С другой стороны, одна из важных целей этих изменений — значительно упростить настройку SQLite для использования в разных операционных системах.

2.0 Уровень интерфейса ОС

Если ваша система определяет собственный интерфейс ОС для SQLite или вы использовали недокументированный интерфейс sqlite3_os_switch(), то для перехода на SQLite версии 3.5.0 потребуется внести изменения. На первый взгляд это может показаться непростой задачей. Но, изучив вопрос подробнее, вы, вероятно, обнаружите, что новый интерфейс SQLite позволяет сократить ваши изменения и упрощает их понимание и поддержку. Скорее всего, ваши изменения теперь будут также беспрепятственно работать с объединённым исходным кодом SQLite. Вам больше не придётся изменять исходный код ядра SQLite. Все изменения можно внести в код приложения, который можно связать со стандартной, неизменённой версией объединённого исходного кода SQLite. Кроме того, уровень интерфейса ОС, ранее не документированный, теперь официально поддерживается SQLite. Поэтому можно рассчитывать, что это изменение потребуется внести только один раз, а новая серверная часть продолжит работать и в будущих версиях SQLite.

2.1 Объект виртуальной файловой системы

Новый интерфейс ОС для SQLite построен вокруг объекта с именем sqlite3_vfs. «vfs» означает «Virtual File System» (виртуальная файловая система). По сути, объект sqlite3_vfs — это структура, содержащая указатели на функции, реализующие элементарные операции дискового ввода-вывода, необходимые SQLite для чтения и записи баз данных. В этой статье мы часто будем называть объекты sqlite3_vfs «VFS».

SQLite может одновременно использовать несколько VFS. Каждое отдельное подключение к базе данных связано только с одной VFS. Но если у вас несколько подключений к базам данных, каждое подключение может быть связано с отдельной VFS.

Всегда существует VFS по умолчанию. Устаревшие интерфейсы sqlite3_open() и sqlite3_open16() всегда используют VFS по умолчанию. Новый интерфейс для создания подключений к базам данных, sqlite3_open_v2(), позволяет указать имя VFS, которую следует использовать.

2.1.1 Регистрация новых объектов VFS

Стандартные сборки SQLite для Unix или Windows поставляются с одной VFS, которая называется соответственно «unix» или «win32». Эта VFS также является VFS по умолчанию. Поэтому при использовании устаревших функций открытия всё продолжит работать как прежде. Изменение заключается в том, что теперь приложение может добавлять новые модули VFS для реализации настроенного уровня ОС. API sqlite3_vfs_register() можно использовать, чтобы сообщить SQLite об одном или нескольких модулях VFS, определённых приложением:

int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);

Приложения могут вызывать sqlite3_vfs_register() в любое время, хотя, разумеется, VFS необходимо зарегистрировать до её использования. Первый аргумент — указатель на подготовленный приложением объект VFS с нужными настройками. Установите второй аргумент в значение true, чтобы новая VFS стала VFS по умолчанию и использовалась устаревшими API sqlite3_open() и sqlite3_open16(). Если новая VFS не является VFS по умолчанию, для её использования, вероятно, потребуется новый API sqlite3_open_v2(). Однако обратите внимание: если новая VFS — единственная VFS, известная SQLite (если SQLite скомпилирована без обычной VFS по умолчанию или предварительно скомпилированная VFS по умолчанию удалена с помощью sqlite3_vfs_unregister()), то новая VFS автоматически становится VFS по умолчанию независимо от аргумента makeDflt, переданного в sqlite3_vfs_register().

Стандартные сборки включают VFS по умолчанию «unix» или «win32». Однако при использовании параметра компиляции -DOS_OTHER=1 SQLite собирается без VFS по умолчанию. В этом случае приложение должно зарегистрировать как минимум одну VFS до вызова sqlite3_open(). Именно такой подход следует использовать во встроенных приложениях. Вместо изменения исходного кода SQLite для добавления альтернативного уровня ОС, как это делалось в предыдущих выпусках SQLite, скомпилируйте неизменённый исходный файл SQLite (предпочтительно объединённый исходный код) с параметром -DOS_OTHER=1, а затем вызовите sqlite3_vfs_register(), чтобы определить интерфейс к базовой файловой системе до создания подключений к базам данных.

2.1.2 Дополнительное управление объектами VFS

API sqlite3_vfs_unregister() используется для удаления существующей VFS из системы.

int sqlite3_vfs_unregister(sqlite3_vfs*);

API sqlite3_vfs_find() используется для поиска VFS по имени. Его прототип выглядит следующим образом:

sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);

Аргумент — это символическое имя нужной VFS. Если аргумент является указателем NULL, возвращается VFS по умолчанию. Функция возвращает указатель на объект sqlite3_vfs, реализующий VFS. Если объект, соответствующий критериям поиска, не найден, возвращается указатель NULL.

2.1.3 Изменение существующих VFS

После регистрации VFS её не следует изменять. Если требуется изменить поведение, нужно зарегистрировать новую VFS. Например, приложение может использовать sqlite3_vfs_find(), чтобы найти старую VFS, скопировать её в новый объект sqlite3_vfs, внести нужные изменения в новую VFS, отменить регистрацию старой VFS, а затем зарегистрировать новую вместо неё. Существующие подключения к базам данных продолжат использовать старую VFS, даже после отмены её регистрации, но новые подключения будут использовать новую VFS.

2.1.4 Объект VFS

Объект VFS является экземпляром следующей структуры:

typedef struct sqlite3_vfs sqlite3_vfs;
struct sqlite3_vfs {
  int iVersion;            /* Structure version number */
  int szOsFile;            /* Size of subclassed sqlite3_file */
  int mxPathname;          /* Maximum file pathname length */
  sqlite3_vfs *pNext;      /* Next registered VFS */
  const char *zName;       /* Name of this virtual file system */
  void *pAppData;          /* Pointer to application-specific data */
  int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
               int flags, int *pOutFlags);
  int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
  int (*xAccess)(sqlite3_vfs*, const char *zName, int flags);
  int (*xGetTempName)(sqlite3_vfs*, char *zOut);
  int (*xFullPathname)(sqlite3_vfs*, const char *zName, char *zOut);
  void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
  void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
  void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol);
  void (*xDlClose)(sqlite3_vfs*, void*);
  int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
  int (*xSleep)(sqlite3_vfs*, int microseconds);
  int (*xCurrentTime)(sqlite3_vfs*, double*);
  /* New fields may be appended in figure versions.  The iVersion
  ** value will increment whenever this happens. */
};

Чтобы создать новую VFS, приложение заполняет экземпляр этой структуры подходящими значениями, а затем вызывает sqlite3_vfs_register().

Для SQLite версии 3.5.0 поле iVersion объекта sqlite3_vfs должно иметь значение 1. В будущих версиях SQLite это число может увеличиться, если потребуется каким-либо образом изменить объект VFS. Надеемся, что этого не произойдёт, но такая возможность предусмотрена на случай необходимости.

Поле szOsFile содержит размер в байтах структуры, определяющей открытый файл, — объекта sqlite3_file. Этот объект будет подробнее описан ниже. Здесь важно отметить, что каждая реализация VFS может определять собственный объект sqlite3_file и хранить в нём любую информацию, необходимую реализации VFS об открытом файле. Однако SQLite должна знать размер этого объекта, чтобы предварительно выделить достаточно места для его хранения.

Поле mxPathname задаёт максимальную длину пути к файлу, которую может использовать эта VFS. Иногда SQLite требуется предварительно выделять буферы такого размера, поэтому значение должно быть как можно меньше, насколько это разумно. Некоторые файловые системы допускают очень длинные пути, но на практике они редко превышают примерно 100 байт. Здесь не требуется указывать самый длинный путь, который может обрабатывать базовая файловая система. Достаточно указать самый длинный путь, который должна уметь обрабатывать SQLite. В большинстве случаев подходит значение в несколько сотен.

Поле pNext используется внутри SQLite. В частности, SQLite использует это поле для формирования связного списка зарегистрированных VFS.

Поле zName содержит символическое имя VFS. По этому имени функция sqlite3_vfs_find() выполняет поиск VFS.

Указатель pAppData не используется ядром SQLite. Он предназначен для хранения вспомогательной информации, которую реализация VFS может использовать.

В остальных полях объекта sqlite3_vfs хранятся указатели на функции, реализующие элементарные операции. Мы называем их «методами». Первый метод, xOpen, используется для открытия файлов на базовом носителе данных. Результатом является объект sqlite3_file. Дополнительные методы, определённые самим объектом sqlite3_file, используются для чтения, записи и закрытия файла. Они подробно описаны ниже. Имя файла представлено в кодировке UTF-8. SQLite гарантирует, что строка zFilename, переданная в xOpen(), будет полным путём, сформированным функцией xFullPathname(), и останется корректной и неизменной до вызова xClose(). Поэтому объект sqlite3_file может хранить указатель на имя файла, если ему необходимо запомнить его по какой-либо причине. Аргумент flags для xOpen() является копией аргумента flags для sqlite3_open_v2(). Если используется sqlite3_open() или sqlite3_open16(), то flags имеет значение SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE. Если xOpen() открывает файл только для чтения, она устанавливает в *pOutFlags флаг SQLITE_OPEN_READONLY. Можно установить и другие биты в *pOutFlags. Кроме того, SQLite добавляет к вызову xOpen() один из следующих флагов в зависимости от открываемого объекта:

  • SQLITE_OPEN_MAIN_DB
  • SQLITE_OPEN_MAIN_JOURNAL
  • SQLITE_OPEN_TEMP_DB
  • SQLITE_OPEN_TEMP_JOURNAL
  • SQLITE_OPEN_TRANSIENT_DB
  • SQLITE_OPEN_SUBJOURNAL
  • SQLITE_OPEN_SUPER_JOURNAL
Реализация файлового ввода-вывода может использовать флаги типа объекта, чтобы изменить способ обработки файлов. Например, приложение, которому не требуется восстановление после сбоя или откат, может выполнять открытие файла журнала как пустую операцию. Запись в этот журнал также будет пустой операцией. Любая попытка прочитать журнал возвращает SQLITE_IOERR. Или же реализация может распознать, что файл базы данных будет читать и записывать страницы, выровненные по границам секторов, в случайном порядке, и соответствующим образом настроить подсистему ввода-вывода. SQLite также может добавить к методу xOpen один из следующих флагов:
  • SQLITE_OPEN_DELETEONCLOSE
  • SQLITE_OPEN_EXCLUSIVE
Флаг SQLITE_OPEN_DELETEONCLOSE означает, что файл следует удалить при закрытии. Он всегда устанавливается для временных баз данных, журналов и поджурналов. Флаг SQLITE_OPEN_EXCLUSIVE означает, что файл следует открыть для исключительного доступа. Этот флаг устанавливается для всех файлов, кроме основного файла базы данных. Структуру sqlite3_file, передаваемую третьим аргументом в xOpen, выделяет вызывающий код. Функция xOpen только заполняет её. Вызывающий код выделяет для структуры sqlite3_file не менее szOsFile байт.

Разница между базой данных SQLITE_OPEN_TEMP_DB и базой данных SQLITE_OPEN_TRANSIENT_DB заключается в следующем: SQLITE_OPEN_TEMP_DB используется для явно объявленных и именованных временных таблиц (с синтаксисом CREATE TEMP TABLE) или для именованных таблиц во временной базе данных, которая создаётся при открытии базы данных с пустым именем файла. База данных SQLITE_OPEN_TRANSIENT_DB содержит таблицу, автоматически создаваемую SQLite для вычисления подзапроса или предложения ORDER BY либо GROUP BY. Базы данных TEMP_DB и TRANSIENT_DB являются приватными и удаляются автоматически. Базы данных TEMP_DB существуют в течение всего срока действия подключения к базе данных. Базы данных TRANSIENT_DB существуют только во время выполнения одного оператора SQL.

Метод xDelete используется для удаления файла. Имя файла передаётся вторым параметром. Имя файла представлено в кодировке UTF-8. VFS должна преобразовать имя файла в представление символов, ожидаемое базовой операционной системой. Если параметр syncDir имеет значение true, метод xDelete не должен возвращать управление, пока изменение содержимого каталога, содержащего удалённый файл, не будет синхронизировано с диском. Это необходимо, чтобы файл не «появился снова» при отключении питания вскоре после удаления.

Метод xAccess используется для проверки разрешений доступа к файлу. Имя файла закодировано в UTF-8. Аргумент flags будет равен SQLITE_ACCESS_EXISTS для проверки существования файла, SQLITE_ACCESS_READWRITE для проверки возможности чтения и записи файла или SQLITE_ACCESS_READ для проверки наличия хотя бы разрешения на чтение. Объект, называемый «файлом» во втором параметре, может быть каталогом или папкой.

Метод xGetTempName вычисляет имя временного файла, который может использовать SQLite. Имя следует записать в буфер, указанный вторым параметром. SQLite выделит для этого буфера место как минимум под mxPathname байт. Сгенерированное имя файла должно быть в кодировке UTF-8. Во избежание проблем с безопасностью имя временного файла должно содержать достаточно случайных данных, чтобы злоумышленник не мог заранее его угадать.

Метод xFullPathname используется для преобразования относительного пути в полный. Результирующий полный путь записывается в буфер, указанный третьим параметром. SQLite выделит для выходного буфера не менее mxPathname байт. И входные, и выходные имена должны быть в кодировке UTF-8.

Методы xDlOpen, xDlError, xDlSym и xDlClose используются для доступа к динамическим библиотекам во время выполнения. Их можно опустить (и установить соответствующие указатели в ноль), если библиотека скомпилирована с SQLITE_OMIT_LOAD_EXTENSION или если интерфейс sqlite3_enable_load_extension() никогда не используется для включения загрузки динамических расширений. Метод xDlOpen открывает разделяемую библиотеку или DLL и возвращает указатель на дескриптор. Если открыть библиотеку не удалось, возвращается NULL. В этом случае для получения текстового сообщения об ошибке можно использовать метод xDlError. Сообщение записывается в буфер zErrMsg третьего параметра, длина которого составляет не менее nByte байт. Метод xDlSym возвращает указатель на символ в разделяемой библиотеке. Имя символа передаётся вторым параметром. Предполагается кодировка UTF-8. Если символ не найден, возвращается указатель NULL. Процедура xDlClose закрывает разделяемую библиотеку.

Метод xRandomness используется ровно один раз для инициализации генератора псевдослучайных чисел (PRNG) внутри SQLite. Используется только метод xRandomness VFS по умолчанию. SQLite никогда не обращается к методам xRandomness других VFS. Процедура xRandomness запрашивает запись nByte байт случайных данных в zOut. Процедура возвращает фактическое количество полученных случайных байт. Качество полученных случайных данных определяет качество случайных значений, генерируемых встроенными функциями SQLite, такими как random() и randomblob(). SQLite также использует PRNG для генерации имён временных файлов. На некоторых платформах (например, Windows) SQLite считает имена временных файлов уникальными, не проверяя фактическое наличие совпадений, поэтому качественный генератор случайных чисел важен, даже если функции random() и randomblob() никогда не используются.

Метод xSleep приостанавливает вызывающий поток как минимум на указанное количество микросекунд. Этот метод используется для реализации API sqlite3_sleep() и sqlite3_busy_timeout(). В случае sqlite3_sleep() всегда используется метод xSleep VFS по умолчанию. Если базовая система не поддерживает приостановку с точностью до микросекунды, время ожидания следует округлить вверх. xSleep возвращает это округлённое значение.

Метод xCurrentTime определяет текущие дату и время и записывает результат в виде числа с плавающей точкой двойной точности по указателю, переданному вторым параметром. Дата и время указаны во всемирном координированном времени (UTC) в виде дробного числа юлианских дней.

2.1.5 Объект открытого файла

Результат открытия файла — экземпляр объекта sqlite3_file. Объект sqlite3_file является абстрактным базовым классом, определённым следующим образом:

typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
  const struct sqlite3_io_methods *pMethods;
};

Каждая реализация VFS создаёт подкласс sqlite3_file, добавляя в конец дополнительные поля для хранения любой информации об открытом файле, которая необходима VFS. Содержимое этих полей не имеет значения, если общий размер структуры не превышает значение szOsFile, указанное в объекте sqlite3_vfs.

Объект sqlite3_io_methods — это структура, содержащая указатели на методы чтения, записи и других операций с файлами. Она определена следующим образом:

typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
  int iVersion;
  int (*xClose)(sqlite3_file*);
  int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
  int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
  int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
  int (*xSync)(sqlite3_file*, int flags);
  int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
  int (*xLock)(sqlite3_file*, int);
  int (*xUnlock)(sqlite3_file*, int);
  int (*xCheckReservedLock)(sqlite3_file*);
  int (*xFileControl)(sqlite3_file*, int op, void *pArg);
  int (*xSectorSize)(sqlite3_file*);
  int (*xDeviceCharacteristics)(sqlite3_file*);
  /* Additional methods may be added in future releases */
};

Поле iVersion объекта sqlite3_io_methods предусмотрено на случай будущих усовершенствований. Для SQLite версии 3.5 это значение всегда должно быть равно 1.

Метод xClose закрывает файл. Память, выделенная для структуры sqlite3_file, освобождается вызывающим кодом. Однако если sqlite3_file содержит указатели на другую выделенную память или ресурсы, метод xClose должен освободить эти ресурсы.

Метод xRead считывает iAmt байт из файла, начиная со смещения iOfst в байтах. Прочитанные данные сохраняются по указателю второго параметра. При успехе xRead возвращает SQLITE_OK, если не удалось прочитать все байты из-за достижения конца файла — SQLITE_IOERR_SHORT_READ, а при любой другой ошибке — SQLITE_IOERR_READ.

Метод xWrite записывает iAmt байт данных из второго параметра в файл, начиная со смещения iOfst байт. Если до записи размер файла меньше iOfst байт, xWrite должен обеспечить расширение файла нулями до iOfst байт, прежде чем начать запись. xWrite должен расширить файл при необходимости, чтобы после завершения вызова xWrite размер файла составлял не менее iAmt+iOfst байт. При успехе метод xWrite возвращает SQLITE_OK. Если запись не может завершиться из-за заполнения базового носителя данных, возвращается SQLITE_FULL. При любой другой ошибке следует возвращать SQLITE_IOERR_WRITE.

Метод xTruncate усекает файл до размера nByte байт. Если размер файла уже равен nByte байт или меньше, этот метод ничего не делает. При успехе метод xTruncate возвращает SQLITE_OK, а при любой ошибке — SQLITE_IOERR_TRUNCATE.

Метод xSync используется для принудительной записи ранее записанных данных из кэша операционной системы в энергонезависимую память. Второй параметр обычно равен SQLITE_SYNC_NORMAL. Если второй параметр равен SQLITE_SYNC_FULL, метод xSync должен также обеспечить сброс данных через кэш контроллера диска. Параметр SQLITE_SYNC_FULL эквивалентен ioctl() F_FULLSYNC в Mac OS X. При успехе метод xSync возвращает SQLITE_OK, а при любой ошибке — SQLITE_IOERR_FSYNC.

Метод xFileSize() определяет текущий размер файла в байтах и записывает это значение в *pSize. При успехе он возвращает SQLITE_OK, а если возникает ошибка — SQLITE_IOERR_FSTAT.

Методы xLock и xUnlock используются для установки и снятия блокировок файлов. SQLite поддерживает пять уровней блокировки файлов, перечисленных по возрастанию:

  • SQLITE_LOCK_NONE
  • SQLITE_LOCK_SHARED
  • SQLITE_LOCK_RESERVED
  • SQLITE_LOCK_PENDING
  • SQLITE_LOCK_EXCLUSIVE
Базовая реализация может поддерживать любое подмножество этих уровней блокировки при условии соблюдения остальных требований, приведённых в этом абзаце. Уровень блокировки задаётся вторым аргументом методов xLock и xUnlock. Метод xLock повышает уровень блокировки до указанного или более высокого. Метод xUnlock понижает уровень блокировки, но не ниже указанного. SQLITE_LOCK_NONE означает, что файл разблокирован. SQLITE_LOCK_SHARED предоставляет право чтения файла. Несколько подключений к базам данных могут одновременно удерживать блокировку SQLITE_LOCK_SHARED. SQLITE_LOCK_RESERVED, как и SQLITE_LOCK_SHARED, предоставляет право чтения файла. Однако в каждый момент времени только одно подключение может удерживать резервную блокировку. SQLITE_LOCK_PENDING также предоставляет право чтения файла. Другие подключения могут продолжать читать файл, но ни одно из них не может повысить блокировку с уровня NONE до SHARED. SQLITE_LOCK_EXCLUSIVE предоставляет право записи в файл. Только одно подключение может удерживать исключительную блокировку, и пока оно её удерживает, никакое другое подключение не может удерживать какую-либо блокировку, кроме «none». При успехе xLock возвращает SQLITE_OK, если получить блокировку не удалось — SQLITE_BUSY, а при иной ошибке — SQLITE_IOERR_RDLOCK. При успехе метод xUnlock возвращает SQLITE_OK, а при ошибках — SQLITE_IOERR_UNLOCK.

Метод xCheckReservedLock() проверяет, удерживает ли в данный момент другое подключение или другой процесс резервную, ожидающую или исключительную блокировку файла. Он возвращает true или false.

Метод xFileControl() — это универсальный интерфейс, позволяющий пользовательским реализациям VFS напрямую управлять открытым файлом с помощью (нового и экспериментального) интерфейса sqlite3_file_control(). Второй аргумент «op» — целочисленный код операции. Третий аргумент — универсальный указатель, предназначенный для указания на структуру, которая может содержать аргументы или место для записи возвращаемых значений. Возможные варианты использования xFileControl() включают функции включения блокировок с ожиданием и тайм-аутами, изменения стратегии блокировки (например, перехода на блокировки с помощью файлов-маркеров), проверки состояния блокировки или снятия устаревших блокировок. Ядро SQLite резервирует для собственного использования коды операций меньше 100. Доступен список кодов операций меньше 100. Приложениям, определяющим пользовательский метод xFileControl, следует использовать коды операций больше 100 во избежание конфликтов.

Метод xSectorSize возвращает «размер сектора» базового энергонезависимого носителя. «Сектор» определяется как наименьшая единица хранения, которую можно записать, не затронув соседние области. До недавнего времени «размер сектора» дисковых накопителей составлял 512 байт, хотя сейчас предпринимаются усилия по увеличению этого значения до 4 КиБ. SQLite необходимо знать размер сектора, чтобы записывать данные целыми секторами и тем самым избегать повреждения соседних областей хранения при отключении питания во время записи.

Метод xDeviceCharacteristics возвращает целочисленный битовый вектор, задающий специальные свойства базового носителя данных, которые SQLite может использовать для повышения производительности. Возвращаемое значение может быть побитовым ИЛИ следующих значений:

  • SQLITE_IOCAP_ATOMIC
  • SQLITE_IOCAP_ATOMIC512
  • SQLITE_IOCAP_ATOMIC1K
  • SQLITE_IOCAP_ATOMIC2K
  • SQLITE_IOCAP_ATOMIC4K
  • SQLITE_IOCAP_ATOMIC8K
  • SQLITE_IOCAP_ATOMIC16K
  • SQLITE_IOCAP_ATOMIC32K
  • SQLITE_IOCAP_ATOMIC64K
  • SQLITE_IOCAP_SAFE_APPEND
  • SQLITE_IOCAP_SEQUENTIAL
Бит SQLITE_IOCAP_ATOMIC означает, что все операции записи на это устройство атомарны: либо выполняется вся запись целиком, либо не выполняется ничего. Остальные значения SQLITE_IOCAP_ATOMICnnn указывают на атомарность записи выровненных блоков указанного размера. SQLITE_IOCAP_SAFE_APPEND означает, что при добавлении новых данных в файл сначала записываются новые данные и только потом обновляется размер файла. Поэтому при отключении питания файл не может быть дополнен случайными данными. Бит SQLITE_IOCAP_SEQUENTIAL означает, что все записи выполняются в заданном порядке и не переупорядочиваются базовой файловой системой.

2.1.6 Контрольный список для создания новой VFS

В предыдущих абзацах содержится много информации. Чтобы упростить создание новой VFS для SQLite, приводим следующий контрольный список реализации:

  1. Определите подходящий подкласс объекта sqlite3_file.
  2. Реализуйте методы, необходимые объекту sqlite3_io_methods.
  3. Создайте статический константный объект sqlite3_io_methods, содержащий указатели на методы, реализованные на предыдущем шаге.
  4. Реализуйте метод xOpen, который открывает файл и заполняет объект sqlite3_file, в том числе устанавливает pMethods так, чтобы он указывал на объект sqlite3_io_methods, созданный на предыдущем шаге.
  5. Реализуйте остальные методы, необходимые объекту sqlite3_vfs.
  6. Определите статическую (но не константную) структуру sqlite3_vfs, содержащую указатели на метод xOpen и остальные методы, а также подходящие значения полей iVersion, szOsFile, mxPathname, zName и pAppData.
  7. Реализуйте процедуру, которая вызывает sqlite3_vfs_register() и передаёт ей указатель на структуру sqlite3_vfs, созданную на предыдущем шаге. Эта процедура, вероятно, будет единственным экспортируемым символом в исходном файле, реализующем вашу VFS.

В приложении вызовите процедуру, реализованную на последнем шаге, в рамках процесса инициализации — до открытия каких-либо подключений к базам данных.

3.0 Подсистема выделения памяти

Начиная с версии 3.5 SQLite получает всю необходимую динамическую память с помощью функций sqlite3_malloc(), sqlite3_free() и sqlite3_realloc(). Эти функции существовали и в предыдущих версиях SQLite, но раньше SQLite обходила их и использовала собственный распределитель памяти. В версии 3.5.0 всё изменилось.

Исходное дерево SQLite фактически содержит несколько версий распределителя памяти. Версия с высокой производительностью по умолчанию, находящаяся в исходном файле «mem1.c», используется в большинстве сборок. Но если включён флаг SQLITE_MEMDEBUG, вместо неё используется отдельный распределитель памяти из исходного файла «mem2.c». Распределитель mem2.c реализует множество точек перехвата для проверки ошибок и имитации сбоев выделения памяти в целях тестирования. Оба этих распределителя используют реализацию malloc()/free() из стандартной библиотеки C.

Приложения не обязаны использовать ни один из этих стандартных распределителей памяти. Если SQLite скомпилирована с SQLITE_OMIT_MEMORY_ALLOCATION, реализации функций sqlite3_malloc(), sqlite3_realloc() и sqlite3_free() не предоставляются. Вместо этого приложение, которое связывается с SQLite, должно предоставить собственные реализации этих функций. Распределитель памяти, предоставленный приложением, не обязан использовать реализацию malloc()/free() из стандартной библиотеки C. Например, встроенное приложение может предоставить альтернативный распределитель памяти, использующий память из фиксированного пула, зарезервированного исключительно для SQLite.

Приложения, реализующие собственный распределитель памяти, должны предоставить реализации обычных трёх функций выделения памяти: sqlite3_malloc(), sqlite3_realloc() и sqlite3_free(). Кроме того, они должны реализовать четвёртую функцию:

int sqlite3_memory_alarm(
  void(*xCallback)(void *pArg, sqlite3_int64 used, int N),
  void *pArg,
  sqlite3_int64 iThreshold
);

Процедура sqlite3_memory_alarm используется для регистрации обратного вызова при событиях выделения памяти. Эта процедура регистрирует или отменяет обратный вызов, который срабатывает, когда объём выделенной памяти превышает iThreshold. Одновременно может быть зарегистрирован только один обратный вызов. Каждый вызов sqlite3_memory_alarm() заменяет предыдущий обратный вызов. Обратный вызов отключается, если присвоить xCallback значение указателя NULL.

Параметрами обратного вызова являются значение pArg, объём памяти, используемой в данный момент, и размер выделения, вызвавшего обратный вызов. Предполагается, что обратный вызов вызовет sqlite3_free(), чтобы освободить память. Обратный вызов может вызвать sqlite3_malloc() или sqlite3_realloc(), но в таком случае рекурсивные вызовы не приведут к дополнительным обратным вызовам.

Интерфейс sqlite3_soft_heap_limit() работает, регистрируя сигнализатор памяти на уровне мягкого ограничения кучи и вызывая sqlite3_release_memory() в обработчике сигнализатора. Приложениям не следует пытаться использовать интерфейс sqlite3_memory_alarm(), поскольку это помешает работе модуля sqlite3_soft_heap_limit(). Этот интерфейс предоставлен только для того, чтобы приложения могли реализовать альтернативный вариант, когда ядро SQLite скомпилировано с SQLITE_OMIT_MEMORY_ALLOCATION.

Встроенные распределители памяти SQLite также предоставляют следующие дополнительные интерфейсы:

sqlite3_int64 sqlite3_memory_used(void);
sqlite3_int64 sqlite3_memory_highwater(int resetFlag);

Эти интерфейсы позволяют приложению отслеживать, сколько памяти использует SQLite. Процедура sqlite3_memory_used() возвращает количество байт памяти, используемой в данный момент, а sqlite3_memory_highwater() возвращает максимальный мгновенный объём использованной памяти. Ни одна из процедур не учитывает накладные расходы распределителя памяти. Эти процедуры предназначены для использования приложением. Сама SQLite их никогда не вызывает. Поэтому, если приложение предоставляет собственную подсистему распределения памяти, оно при желании может не реализовывать эти интерфейсы.

4.0 Подсистема мьютексов

SQLite всегда была потокобезопасной в том смысле, что разные подключения к базам данных SQLite можно безопасно одновременно использовать в разных потоках. Ограничение заключалось в том, что одно и то же подключение к базе данных нельзя было одновременно использовать в двух разных потоках. В версии SQLite 3.5.0 это ограничение снято.

Чтобы разрешить нескольким потокам одновременно использовать одно подключение к базе данных, SQLite должна активно использовать мьютексы. По этой причине была добавлена новая подсистема мьютексов. Она предоставляет следующий интерфейс:

sqlite3_mutex *sqlite3_mutex_alloc(int);
void sqlite3_mutex_free(sqlite3_mutex*);
void sqlite3_mutex_enter(sqlite3_mutex*);
int sqlite3_mutex_try(sqlite3_mutex*);
void sqlite3_mutex_leave(sqlite3_mutex*);

Хотя эти процедуры предназначены для ядра SQLite, при необходимости их может использовать и код приложения. Мьютекс — это объект sqlite3_mutex. Процедура sqlite3_mutex_alloc() выделяет новый объект мьютекса и возвращает указатель на него. Аргументом sqlite3_mutex_alloc() должно быть значение SQLITE_MUTEX_FAST или SQLITE_MUTEX_RECURSIVE для нерекурсивного или рекурсивного мьютекса соответственно. Если базовая система не предоставляет нерекурсивные мьютексы, вместо них можно использовать рекурсивный мьютекс. Аргументом sqlite3_mutex_alloc() также может быть константа, обозначающая один из нескольких статических мьютексов:

  • SQLITE_MUTEX_STATIC_MAIN
  • SQLITE_MUTEX_STATIC_MEM
  • SQLITE_MUTEX_STATIC_MEM2
  • SQLITE_MUTEX_STATIC_PRNG
  • SQLITE_MUTEX_STATIC_LRU
Эти статические мьютексы зарезервированы для внутреннего использования SQLite, и приложениям не следует их использовать. Все статические мьютексы являются нерекурсивными.

Для освобождения нестатического мьютекса следует использовать процедуру sqlite3_mutex_free(). Если этой процедуре передать статический мьютекс, поведение не определено.

Процедура sqlite3_mutex_enter() пытается захватить мьютекс и блокируется, если его уже захватил другой поток. Процедура sqlite3_mutex_try() пытается захватить мьютекс и возвращает SQLITE_OK в случае успеха или SQLITE_BUSY, если мьютекс уже захвачен другим потоком. Процедура sqlite3_mutex_leave() освобождает мьютекс. Мьютекс остаётся захваченным, пока количество освобождений не сравняется с количеством захватов. Если вызвать sqlite3_mutex_leave() для мьютекса, который текущий поток не удерживает, поведение не определено. Если вызвать любую процедуру для освобождённого мьютекса, поведение не определено.

Исходный код SQLite содержит несколько реализаций этих API для различных сред. Если SQLite скомпилирована с флагом SQLITE_THREADSAFE=0, предоставляется пустая реализация мьютексов, которая работает быстро, но не обеспечивает фактического взаимного исключения. Эта реализация подходит для однопоточных приложений или приложений, использующих SQLite только в одном потоке. Также доступны другие полноценные реализации мьютексов, основанные на базовой операционной системе.

Встроенным приложениям может потребоваться собственная реализация мьютексов. Если SQLite скомпилирована с флагом времени компиляции -DSQLITE_MUTEX_APPDEF=1, ядро SQLite не предоставляет подсистему мьютексов, и приложение, связывающееся с SQLite, должно предоставить подсистему мьютексов, соответствующую описанному выше интерфейсу.

5.0 Другие изменения интерфейса

В версии SQLite 3.5.0 поведение нескольких API изменено способами, формально нарушающими совместимость. Однако эти API используются редко, и даже при их использовании трудно представить сценарий, в котором изменения могли бы что-либо нарушить. Благодаря изменениям эти интерфейсы стали полезнее и функциональнее.

До версии 3.5.0 API sqlite3_enable_shared_cache() включал и отключал функцию общего кэша для всех подключений в одном потоке — том же потоке, из которого вызывалась процедура sqlite3_enable_shared_cache(). Подключения к базам данных, использовавшие общий кэш, должны были выполняться в том же потоке, в котором они были открыты. Начиная с версии 3.5.0, sqlite3_enable_shared_cache() применяется ко всем подключениям к базам данных во всех потоках процесса. Теперь подключения к базам данных, работающие в разных потоках, могут совместно использовать кэш. Кроме того, подключения к базам данных с общим кэшем могут переходить из одного потока в другой.

До версии 3.5.0 процедура sqlite3_soft_heap_limit() устанавливала верхний предел использования памяти кучи для всех подключений к базам данных в одном потоке. Каждый поток мог иметь собственный предел кучи. Начиная с версии 3.5.0, для всего процесса устанавливается единый предел кучи. Это может показаться более строгим ограничением (один предел вместо нескольких), но на практике именно этого и хочет большинство пользователей.

До версии 3.5.0 функция sqlite3_release_memory() пыталась освободить память во всех подключениях к базам данных в том же потоке, из которого была вызвана sqlite3_release_memory(). Начиная с версии 3.5.0, функция sqlite3_release_memory() пытается освободить память во всех подключениях к базам данных во всех потоках.

6.0 Итоги

Переход с версии SQLite 3.4.2 на 3.5.0 — значительное изменение. Пришлось изменить каждый файл исходного кода ядра SQLite, некоторые из них — весьма существенно. Кроме того, это изменение привело к небольшим нарушениям совместимости интерфейса C. Однако мы считаем, что преимущества перехода с 3.4.2 на 3.5.0 с лихвой компенсируют сложности переноса. Новый уровень VFS теперь чётко определён и стабилен, что должно упростить дальнейшую адаптацию. Уровень VFS, а также отделяемые подсистемы распределения памяти и мьютексов позволяют использовать стандартную объединённую версию исходного кода SQLite во встроенном проекте без изменений, значительно упрощая управление конфигурацией. Кроме того, получившаяся система гораздо лучше подходит для проектов с большим количеством потоков.

Последнее обновление этой страницы: 2025-07-12 15:11:36Z

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

Spec-Zone.ru

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