RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

1. Введение

Расширение сессии обеспечивает механизм для записи изменений некоторых или всех определенных таблиц в базе данных SQLite, и упаковывающий те изменения в файл "changeset" или "patchset", который может позже использоваться, чтобы применить тот же самый набор изменений к другой базе данных с той же самой схемой и совместимыми стартовыми данными. "changeset" может также инвертироваться и использоваться, чтобы "отменить" сессию.

Этот документ это введение в расширение сессии. Детали интерфейса находятся в отдельном документе здесь.

1.1. Типичный вариант использования

Предположим, что SQLite используется в качестве формата файла приложения для конкретного применения. Два пользователя, Элис и Боб, каждый работают с массивом данных, который приблизительно гигабайт в размере. Они весь день работают параллельно, каждый делает их собственные настройки. В конце дня они хотели бы слить свои изменения вместе в единственный объединенный объект.

Расширение сессии облегчает это, делая запись всех изменений и баз данных Элис и Боба и соединяя те изменения в changeset или patchset файлы. В конце дня Элис может послать свой changeset Бобу, и Боб может "применить" его к своей базе данных. Результат (допустим, нет никаких конфликтов) состоит в том, что база данных Боба тогда содержит его изменения и изменения Элис. Аналогично, Боб может послать changeset своей работы Элис, и она может применить его изменения к своей базе данных.

Другими словами, расширение сессии предоставляет средство для файлов базы данных SQLite, которое подобно утилите unix patch или возможностям "merge" систем управления версиями, таким как Fossil, Git или Mercurial.

1.2. Получение расширения сессии

С version 3.13.0 (2016-05-18) расширение сессии было включено в исходное SQLite объединение. По умолчанию расширение сессии отключено. Чтобы позволить его, соберите со следующими параметрами компилятора:

-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK

Или, используя систему сборки autoconf, передайте скрипту configure опцию --enable-session.

1.3. Ограничения

  • До SQLite version 3.17.0 расширение сессии работало только с таблицами rowid, но не с WITHOUT ROWID. С 3.17.0 оба типа таблиц поддерживаются. Однако, дополнительные шаги необходимы, чтобы сделать запись первичных ключей для изменений таблицы WITHOUT ROWID.

  • Нет никакой поддержки виртуальных таблиц. Их изменения не захватываются.

  • Расширение сессии работает только с таблицами, у которых есть заявленный PRIMARY KEY. PRIMARY KEY может быть INTEGER PRIMARY KEY (псевдоним rowid) или внешний PRIMARY KEY.

  • SQLite позволяет хранить NULL в столбцах PRIMARY KEY. Однако, расширение сессии игнорирует все такие строки. Никакие изменения, затрагивающие строки с одним или более NULL в столбцах PRIMARY KEY, не зарегистрированы модулем сессий.

2. Понятия

2.1. Changesets и Patchsets

Модуль сессий вращается вокруг создания и управления changeset. changeset это blob данных, которые кодируют серию изменений базы данных. Каждое изменение в changeset это одно из следующего:

  • INSERT. Изменение INSERT содержит единственную строку, чтобы добавить к таблице базы данных. Полезный груз изменения INSERT состоит из значений для каждой области новой строки.

  • DELETE. Изменение DELETE представляет строку, определенную значениями первичного ключа, чтобы удалить из таблицы базы данных. Полезный груз изменения DELETE состоит из значений для всех областей удаленной строки.

  • UPDATE. Изменение UPDATE представляет модификацию одной или более областей не-PRIMARY KEY единственной строки в таблице базы данных, определенной ее областями PRIMARY KEY. Полезный груз для изменения UPDATE состоит из:

    • Значения PRIMARY KEY, определяющие измененную строку
    • Новые значения для каждой измененной области строки
    • Исходные значения для каждой измененной области строки

    Изменение UPDATE не содержит информации относительно областей не-PRIMARY KEY, которые не изменяются этим изменением. Для изменения UPDATE невозможно определить модификации к областям PRIMARY KEY.

Единственный changeset может содержать изменения, которые относятся больше, чем к одной таблице базы данных. Для каждой таблицы, для которой changeset включает по крайней мере одно изменение, это также кодирует следующие данные:

  • Название таблицы базы данных
  • Количество колонок, которые таблица имеет
  • Какие из тех колонок являются колонками PRIMARY KEY

Changeset может быть применен только к базам данных, которые содержат таблицы, соответствующие вышеупомянутым трем критериям, как сохранено в changeset.

patchset подобен changeset. Это немного более компактно, чем changeset, но обеспечивает более ограниченное обнаружение конфликта и параметры разрешения (см. следующую секцию для деталей). Различия между patchset и changeset в том, что:

  • Для изменения DELETE полезный груз состоит только из областей PRIMARY KEY. Исходные значения других областей не сохранены как часть patchset.

  • Для изменения UPDATE полезный груз состоит только из областей PRIMARY KEY и новых значений измененных областей. Исходные значения измененных областей не сохранены как часть patchset.

2.2. Конфликты

Когда changeset или patchset применяются к базе данных, предпринята попытка, чтобы вставить новую стрку для каждого изменения INSERT, удалить строку для каждого изменения DELETE и изменить строку для каждого изменения UPDATE. Если целевая база данных находится в том же самом состоянии, как оригинальная база данных, на которой был зарегистрирован changeset, это простой вопрос. Однако, если содержание целевой базы данных не находится в точно этом состоянии, конфликты могут произойти, применяя changeset или patchset.

Обрабатывая изменение INSERT, следующие конфликты могут произойти:

  • Целевая база данных может уже содержать строку с теми же самыми значениями PRIMARY KEY, как определено изменением INSERT.
  • Некоторое другое ограничение базы данных, например ограничение UNIQUE или CHECK, может быть нарушено, когда новая строка вставляется.

Обрабатывая изменение DELETE, следующие конфликты могут быть обнаружены:

  • Целевая база данных не может содержать строку с указанными значениями PRIMARY KEY, чтобы удалить.
  • Целевая база данных может содержать строку с указанными значениями PRIMARY KEY, но другие области могут содержать значения, которые не соответствуют сохраненным как часть changeset. Этот тип конфликта не обнаружен, используя patchset.

Обрабатывая изменение UPDATE, следующие конфликты могут быть обнаружены:

  • Целевая база данных не может содержать строку с указанными значениями PRIMARY KEY, чтобы изменить.
  • Целевая база данных может содержать строку с указанными зеначениями PRIMARY KEY, но текущее значение областей, которые будут изменены этим изменением, может не соответствовать исходным значениям, сохраненным в changeset. Этот тип конфликта не обнаружен, используя patchset.
  • Некоторое другое ограничение базы данных, например ограничение UNIQUE или CHECK, может быть нарушено, когда строка обновляется.

В зависимости от типа конфликта у применения сессий есть множество конфигурируемых возможностей для контакта с конфликтами, в пределах от исключения противоречивого изменения, прерывания применения всего changeset или применения изменения несмотря на конфликт. Для получения дополнительной информации см. документацию для sqlite3changeset_apply() API.

2.3. Создание Changeset

После того, как объект сессии формировался, он начинает контролировать изменения его таблиц. Однако, это не делает запись всего изменения каждый раз, когда строка в базе данных изменяется. Вместо этого это делает запись просто областей PRIMARY KEY для каждой вставленной строки и просто PRIMARY KEY и всех первоначальных значений строки для любых обновленных или удаленных строк. Если строка изменяется несколько раз единственной сессией, никакая новая информация не зарегистрирована.

Другая информация, запрошенная, чтобы создать changeset или patchset, прочитана из файла базы данных, когда вызвана sqlite3session_changeset() или sqlite3session_patchset().

  • Для каждого первичного ключа, зарегистрированного в результате операции INSERT, модуль сессий проверяет, есть ли строка с соответствующим первичным ключом все еще в таблице. Если так, изменение INSERT добавляется к changeset.

  • Для каждого первичного ключа, зарегистрированного в результате операции UPDATE или DELETE, модуль сессий также проверяет на строку с соответствующим первичным ключом в таблице. Если можно найти, но одна или больше областей не-PRIMARY KEY не соответствует первоначально зарегистрированному значению, UPDATE добавляется к changeset. Или, если нет никакой строки вообще с указанным первичным ключом, DELETE добавляется к changeset. Если строка действительно существует, но ни одна из областей не-PRIMARY KEY не была изменена, никакое изменение не добавляется к changeset.

Одно значение вышеупомянутого: если изменение внесено и затем разрушено в единственной сессии (например, если строка вставлена и затем удалена снова), модуль сессий не сообщает ни о каком изменении вообще. Или если строка обновляется многократно в той же самой сессии, все обновления соединены в единственное обновление в любом changeset или patchset blob.

3. Используя расширение сессии

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

3.1. Захват Changeset

Пример кода ниже демонстрирует шаги, вовлеченные в захват changeset, выполняя команды SQL. Таким образом:

  1. Объект сессии (тип sqlite3_session*) создается, вызывая sqlite3session_create() API.

    Единственный объект сессии наблюдает изменения, сделанные к единой базе данных (то есть, "main", "temp" или приложенная база данных) через единственный обработчик базы данных sqlite3*.

  2. Объект сессии формируется с рядом таблиц, чтобы наблюдать изменения.

    По умолчанию объект сессии не наблюдает изменения ни на какой таблице базы данных. Прежде чем он это сделает, это должно формироваться. Есть три способа формировать набор таблиц, чтобы наблюдать изменения:

    • Явно определяя таблицы, используя один вызов sqlite3session_attach() для каждой таблицы
    • Определяя, что все таблицы в базе данных должны быть проверены для изменений, используя обращение к sqlite3session_attach() с параметром NULL
    • Формируя отзыв, который будет вызван в первый раз, когда каждая таблица написана, чтобы указать модулю сессии, должны ли наблюдаться изменения в ней.

    Пример кода ниже использует второй из методов, перечисленных выше: он контролирует изменения на всех таблицах базы данных.

  3. Изменения внесены в базу данных, выполнив SQL-операторы. Объект сессии пишет эти изменения.

  4. changeset blob извлечен из объекта сессии, используя to sqlite3session_changeset() (или, используя patchset, sqlite3session_patchset()).

  5. Объект сессии удален, используя sqlite3session_delete() API.

    Не надо удалять объект сессии после извлечения changeset или patchset из него. Это можно оставить приложенным к дескриптору базы данных и продолжить контролировать изменения в таблицах как прежде. Однако, если sqlite3session_changeset() или sqlite3session_patchset() вызовут во второй раз на объекте сессии, changeset или patchset будут содержать все изменения, которые произошли на связи после того, как сессия была создана. Другими словами, объект сессии не перезагружен или обнулен sqlite3session_changeset() или sqlite3session_patchset().

/*
** Argument zSql points to a buffer containing an SQL script to execute
** against the database handle passed as the first argument. As well as
** executing the SQL script, this function collects a changeset recording
** all changes made to the "main" database file. Assuming no error occurs,
** output variables (*ppChangeset) and (*pnChangeset) are set to point
** to a buffer containing the changeset and the size of the changeset in
** bytes before returning SQLITE_OK. In this case it is the responsibility
** of the caller to eventually free the changeset blob by passing it to
** the sqlite3_free function.
**
** Or, if an error does occur, return an SQLite error code. The final
** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
*/
int sql_exec_changeset
(
  sqlite3 *db,                  /* Database handle */
  const char *zSql,             /* SQL script to execute */
  int *pnChangeset,             /* OUT: Size of changeset blob in bytes */
  void **ppChangeset            /* OUT: Pointer to changeset blob */
)
{
  sqlite3_session *pSession = 0;
  int rc;

  /* Create a new session object */
  rc = sqlite3session_create(db, "main", &pSession);
  /* Configure the session object to record changes to all tables */
  if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
  /* Execute the SQL script */
  if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
  /* Collect the changeset */
  if( rc==SQLITE_OK ){
    rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
  }
  /* Delete the session object */
  sqlite3session_delete(pSession);
  return rc;
}

3.2. Применение Changeset к базе данных

Применение Changeset к базе данных более просто, чем захват changeset. Обычно единственное обращение к sqlite3changeset_apply(), как изображено в примере кода ниже, достаточно.

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

/*
** Conflict handler callback used by apply_changeset(). See below.
*/
static int xConflict(void *pCtx, int eConflict, sqlite3_changset_iter *pIter)
{
  int ret = (int)pCtx;
  return ret;
}

/*
** Apply the changeset contained in blob pChangeset, size nChangeset bytes,
** to the main database of the database handle passed as the first argument.
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
**
** If parameter bIgnoreConflicts is true, then any conflicting changes
** within the changeset are simply ignored. Or, if bIgnoreConflicts is
** false, then this call fails with an SQLTIE_ABORT error if a changeset
** conflict is encountered.
*/
int apply_changeset(
  sqlite3 *db,                  /* Database handle */
  int bIgnoreConflicts,         /* True to ignore conflicting changes */
  int nChangeset,               /* Size of changeset in bytes */
  void *pChangeset              /* Pointer to changeset blob */
)
{
  return sqlite3changeset_apply(db, nChangeset, pChangeset, 0, xConflict,
                                (void*)bIgnoreConflicts);
}

3.3. Просмотр содержания Changeset

Пример кода ниже демонстрирует методы, которые проходят набор и извлекают данные, связанные со всеми изменениями в changeset.

  1. sqlite3changeset_start() API вызывают, чтобы создать и инициализировать iterator, чтобы пройти через содержание changeset. Первоначально iterator не указывает ни на какой элемент вообще.

  2. Первое обращение к sqlite3changeset_next() на iterator перемещает его, чтобы указать на первое изменение в changeset (или к EOF, если changeset абсолютно пуст). sqlite3changeset_next() вернет SQLITE_ROW, если это перемещает iterator, чтобы указать на действительный доступ, SQLITE_DONE, если это перемещает iterator в EOF или код ошибки SQLite, если ошибка происходит.

  3. Если iterator указывает на действительный доступ, sqlite3changeset_op() API может использоваться, чтобы определить тип изменения (INSERT, UPDATE или DELETE), на который указывает iterator. Кроме того, тот же самый API может использоваться, чтобы получить название таблицы, к которой изменение относится и ожидаемое количество колонок первичного ключа и колонок.

  4. Если iterator указывает на действительный вход INSERT или UPDATE, sqlite3changeset_new() API может использоваться, чтобы получить значения new.* в полезном грузе изменения.

  5. Если iterator указывает на действительный вход DELETE или UPDATE, sqlite3changeset_old() API может использоваться, чтобы получить значения old.* в полезном грузе изменения.

  6. iterator удален, используя sqlite3changeset_finalize() API. Если ошибка произошла, повторяя, код ошибки SQLite возвращен (даже если тот же самый код ошибки был уже возвращен sqlite3changeset_next()). Или, если никакая ошибка не произошла, возвращен SQLITE_OK.

/*
** Print the contents of the changeset to stdout.
*/
static int print_changeset(void *pChangeset, int nChangeset)
{
  int rc;
  sqlite3_changeset_iter *pIter = 0;

  /* Create an iterator to iterate through the changeset */
  rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
  if( rc!=SQLITE_OK ) return rc;
  /* This loop runs once for each change in the changeset */
  while( SQLITE_ROW==sqlite3changeset_next(pIter))
  {
    const char *zTab;           /* Table change applies to */
    int nCol;                   /* Number of columns in table zTab */
    int op;                     /* SQLITE_INSERT, UPDATE or DELETE */
    sqlite3_value *pVal;

    /* Print the type of operation and the table it is on */
    rc = sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
    if( rc!=SQLITE_OK ) goto exit_print_changeset;
      printf("%s on table %s\n",
             op==SQLITE_INSERT?"INSERT" : op==SQLITE_UPDATE?"UPDATE" : "DELETE",
             zTab);
    /* If this is an UPDATE or DELETE, print the old.* values */
    if( op==SQLITE_UPDATE || op==SQLITE_DELETE )
    {
      printf("Old values:");
      for(i=0; i<nCol; i++)
      {
        rc = sqlite3changeset_old(pIter, i, &pVal);
        if( rc!=SQLITE_OK ) goto exit_print_changeset;
        printf(" %s", pVal ? sqlite3_value_text(pVal) : "-");
      }
      printf("\n");
    }
    /* If this is an UPDATE or INSERT, print the new.* values */
    if( op==SQLITE_UPDATE || op==SQLITE_INSERT )
    {
      printf("New values:");
      for(i=0; i<nCol; i++)
      {
        rc = sqlite3changeset_new(pIter, i, &pVal);
        if( rc!=SQLITE_OK ) goto exit_print_changeset;
        printf(" %s", pVal ? sqlite3_value_text(pVal) : "-");
      }
      printf("\n");
    }
  }
  /* Clean up the changeset and return an error code (or SQLITE_OK) */
  exit_print_changeset:
  rc2 = sqlite3changeset_finalize(pIter);
  if( rc==SQLITE_OK ) rc = rc2;
  return rc;
}

4. Расширенная функциональность

Большинство запросов будут использовать только функциональность модуля сессии, описанную в предыдущей секции. Однако, следующая дополнительная функциональность доступна для использования и манипуляции changeset и patchset blob:

  • Два или больше changeset/patchsets могут быть объединены, используя sqlite3changeset_concat() или sqlite3_changegroup.

  • changeset может быть "инвертирован", используя sqlite3changeset_invert() API. Перевернутый changeset отменяет изменения, внесенные оригиналом. Если changeset C+ является инверсией changeset C, то применение C и затем C + к базе данных должно оставить базу данных без изменений.