Spec-Zone.ru › SQLite

Расширение сеанса

Содержание
1. Введение
1.1. Типичный случай использования
1.2. Получение расширения сеанса
1.3. Ограничения
2. Понятия
2.1. Наборы изменений и наборы исправлений
2.2. Конфликты
2.3. Построение набора изменений
3. Использование расширения сеанса
3.1. Запись набора изменений
3.2. Применение набора изменений к базе данных
3.3. Просмотр содержимого набора изменений
4. Расширенные возможности

1. Введение

Расширение сеанса предоставляет механизм для удобной записи изменений в некоторые или все определенные таблицы в базе данных SQLite и упаковки этих изменений в файл «набор изменений» или «набор исправлений», который впоследствии может быть использован для применения того же набора изменений к другой базе данных с такой же схемой и совместимыми начальными данными. «Набор изменений» также может быть инвертирован и использован для «отката» сеанса.

Этот документ является введением в расширение сеанса. Подробности интерфейса приведены в отдельном документе Интерфейс расширения сеанса на языке C.

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

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

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

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

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

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

-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK

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

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

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

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

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

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

2. Понятия

2.1. Наборы изменений и наборы исправлений

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

  • Вставка. Изменение вставки содержит одну строку для добавления в таблицу базы данных. Данные изменения вставки состоят из значений для каждого поля новой строки.

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

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

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

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

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

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

Наборы изменений могут применяться только к базам данных, содержащим таблицы, соответствующие этим трём критериям, как хранится в наборе изменений.

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

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

  • Для изменения обновления данные содержат поля PRIMARY KEY и новые значения изменённых полей. Исходные значения изменённых полей не хранятся в составе набора исправлений.

2.2. Конфликты

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

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

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

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

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

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

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

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

2.3. Построение набора изменений

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

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

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

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

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

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

В этом разделе приведены примеры, демонстрирующие, как использовать расширение сессий.

3.1. Захват набора изменений

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

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

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

  2. Объект сессии настраивается с набором таблиц для отслеживания изменений.

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

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

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

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

  4. Blob набора изменений извлекается из объекта сессии с помощью вызова sqlite3session_changeset() (или, если используются наборы исправлений, вызова функции sqlite3session_patchset()).

  5. Объект сессии удаляется с помощью вызова функции API sqlite3session_delete().

    Удалять объект сессии после извлечения набора изменений или набора исправлений из него необязательно. Его можно оставить присоединенным к дескриптору базы данных, и он будет продолжать отслеживать изменения в настроенных таблицах, как и раньше. Однако, если sqlite3session_changeset() или sqlite3session_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. Применение набора изменений к базе данных

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

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

/*
** Conflict handler callback used by apply_changeset(). See below.
*/
static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_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 SQLITE_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. Просмотр содержимого набора изменений

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

  1. Вызывается API sqlite3changeset_start() для создания и инициализации итератора для итерации по содержимому набора изменений. Изначально итератор не указывает ни на один элемент.

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

  3. Если итератор указывает на действительный элемент, API sqlite3changeset_op() можно использовать для определения типа изменения (ВСТАВКА, ОБНОВЛЕНИЕ или УДАЛЕНИЕ), на который указывает итератор. Кроме того, с помощью этого же API можно получить имя таблицы, к которой относится изменение, и ожидаемое количество столбцов и столбцов первичного ключа.

  4. Если итератор указывает на действительный элемент ВСТАВКИ или ОБНОВЛЕНИЯ, API sqlite3changeset_new() можно использовать для получения новых.* значений в полезной нагрузке изменения.

  5. Если итератор указывает на действительный элемент УДАЛЕНИЯ или ОБНОВЛЕНИЯ, API sqlite3changeset_old() можно использовать для получения старых.* значений в полезной нагрузке изменения.

  6. Итератор удаляется с помощью вызова API sqlite3changeset_finalize(). Если при итерации произошла ошибка, возвращается код ошибки 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. Расширенные возможности

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

  • Два или более набора изменений/наборов исправлений могут быть объединены с помощью интерфейсов sqlite3changeset_concat() или sqlite3_changegroup.

  • Набор изменений можно "инвертировать" с помощью функции API sqlite3changeset_invert(). Инвертированный набор изменений отменяет изменения, внесенные исходным. Если набор изменений C+ является обратным набору изменений C, то применение C и затем C+ к базе данных должно оставить базу данных неизменной.

Эта страница в последний раз была изменена 01.06.2024 14:57:22 UTC

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

Spec-Zone.ru

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