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

Small. Fast. Reliable.
Choose any three.

1. Расширение RBU

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

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

RBU это сокращение от "Resumable Bulk Update".

Обе задачи RBU могут быть достигнуты, используя встроенные команды SQL SQLITE: RBU update через серию INSERT, DELETE и UPDATE в единственной транзакции, а RBU vacuum просто командой VACUUM. Модуль RBU обеспечивает следующие преимущества перед этими более простыми подходами:

  1. RBU может быть более эффективным

    Самый эффективный способ применить изменения B-дерева (структура данных, используемая SQLite, чтобы сохранить каждую таблицу и индекс на диске) состоит в том, чтобы внести изменения в ключевом порядке. Но если у таблицы SQL есть один или несколько индексов, ключевой порядок на каждом индексе может отличаться от главной таблицы и других вспомогательных индексов. В результате, выполняя серию INSERT, UPDATE и DELETE в общем случае не получится упорядочить операции так, чтобы все b-деревья были обновлены в ключевом порядке. Процесса обновления RBU обходит это, применяя все изменения главной таблицы в одном проходе, затем применяя изменения каждого индекса в отдельных проходах, гарантируя, что каждое B-дерево обновляется оптимально. Для большого файла базы данных (который не помещается в дисковый кэш OS) эта процедура может привести к обновлению на два порядка величины быстрее.

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

    С другой стороны RBU Vacuum использует больше CPU, чем регулярный VACUUM SQLite, в одном тесте в пять раз больше. Поэтому RBU Vacuum часто значительно медленнее, чем VACUUM SQLite при тех же самых условиях.

  2. RBU работает в фоне

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

  3. RBU работает с приращением

    Операции RBU могут быть приостановлены и затем позже возобновлены, возможно с прошедшими отключениями электроэнергии и/или системным сбросом. Для обновления RBU оригинальное содержание базы данных остается видимым всем читателям базы данных, пока все обновление не было применено, даже если обновление приостановлено и затем позже возобновлено.

Расширение RBU не позволено по умолчанию. Чтобы позволить его, соберите объединение с SQLITE_ENABLE_RBU.

2. Обновления RBU

2.1. Ограничения обновлений RBU

Следующие ограничения относятся к обновлениям RBU:

  • Изменения должны состоять только из INSERT, UPDATE и DELETE. CREATE и DROP не поддерживаются.

  • INSERT не могут использовать значения по умолчанию.

  • UPDATE и DELETE должны определить целевые строки по rowid или значениями не-NULL PRIMARY KEY.

  • UPDATE не могут изменить PRIMARY KEY или rowid.

  • Обновления RBU не могут быть применены ни к каким таблицам, которые содержат колонку, названную "rbu_control".

  • Обновления RBU не срабатывают триггеры.

  • Обновление RBU не обнаружит или предотвратит нарушения внешнего ключа или ограничения CHECK.

  • Обновления RBU используют ограничительный механизм обработки "OR ROLLBACK".

  • Целевая база данных не может быть в режиме WAL.

  • Целевая база данных не может содержать индексы по выражениям. Индексы по выражениям поддерживаются, начиная с SQLite 3.30.0 (2019-10-04).
  • Никакие другие записи не могут произойти на целевой базе данных в то время, как обновление RBU применяется. Блокировка чтения, как считается, на целевой базе данных предотвращает это.

2.2. Подготовка файла обновления RBU

Все изменения, которые будут применены RBU, сохранены в отдельной базе данных SQLite, названной "RBU database". Базу данных, которая должна быть изменена, называют "целевой базой данных".

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

Таблица базы данных RBU содержит единственную строку для каждой целевой строки базы данных, вставленной, обновленной или удаленной обновлением. Заполнение таблиц базы данных RBU описано здесь.

2.2.1. Схема базы данных RBU

Для каждой таблицы в целевой базе данных база данных RBU должна содержать таблицу "data<integer>_<target-table-name>", где <target-table-name> это название таблицы в целевой базе данных, и <integer> это любая последовательность из ноля или большего количества цифровых символов (0-9). Таблицы в базе данных RBU обрабатываются в порядке по имени (от самого маленького до самого большого согласно сортирующей последовательности BINARY), таким образом, порядок, в котором обновляются целевые таблицы, под влиянием части <integer> имени таблицы data_%. В то время как это может быть полезно, используя RBU, чтобы обновить определенные типы виртуальных таблиц, обычно нет никакой причины использовать что-либо кроме пустой строки вместо <integer>.

Таблица data_% должна иметь все одинаковые колонки, как у целевой таблицы плюс одна дополнительная колонка, названная "rbu_control". Таблица data_% не должна иметь ограничений PRIMARY KEY или UNIQUE, но у каждой колонки должен быть тот же самый тип, как у соответствующей колонки в целевой базе данных. Столбец rbu_control не должен иметь типа вообще. Например, если целевая база данных содержит:

CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c UNIQUE);

Тогда база данных RBU должна содержать:

CREATE TABLE data_t1(a INTEGER, b TEXT, c, rbu_control);

Порядок колонок в таблице data_% не имеет значения.

Если целевая таблица базы данных это виртуальная таблица или у нее нет декларации PRIMARY KEY, data_% должна также содержать колонку, названную "rbu_rowid". rbu_rowid отображается к табличному ROWID. Например, если целевая база данных содержит любое из следующего:

CREATE VIRTUAL TABLE x1 USING fts3(a, b);
CREATE TABLE x1(a, b);

тогда база данных RBU должна содержать:

CREATE TABLE data_x1(a, b, rbu_rowid, rbu_control);

Виртуальные таблицы, для которых колонка "rowid" не функционирует как значение первичного ключа, не могут быть обновлены, используя RBU.

Все нескрытые столбцы (то есть, все колонки, соответствовавшие "SELECT *") целевого стола, должны присутствовать во входной таблице. Для виртуальных таблиц скрытые столбцы дополнительные, они обновляются RBU, если существуют во входной таблице. Например, чтобы написать таблицу fts4 со скрытой колонкой languageid, такой как:

CREATE VIRTUAL TABLE ft1 USING fts4(a, b, languageid='langid');

Любая из следующих схем входной таблицы может использоваться:

CREATE TABLE data_ft1(a, b, langid, rbu_rowid, rbu_control);
CREATE TABLE data_ft1(a, b, rbu_rowid, rbu_control);

2.2.2. Содержание базы данных RBU

Для каждой строки INSERT в целевую базу данных как часть обновления RBU соответствующая таблица data_% должна содержать единственную запись с колонкой "rbu_control" со значением integer 0. Другие колонки должны быть установлены в значения, которые составляют новую запись, чтобы вставить.

Столбец "rbu_control" может также быть установлена в целочисленное значение 2 для INSERT. В этом случае новая строка тихо заменяет любой существующий, у которого есть те же самые значения первичного ключа. Это эквивалентно DELETE, сопровождаемому INSERT с теми же самыми значениями первичного ключа. Это не то же самое как команда REPLACE SQL, поскольку в этом случае новая строка может заменить любые противоречивые строки (то есть, те, которые находятся в противоречии из-за ограничений UNIQUE или индексов), не только с противоречивыми первичными ключами.

Если у целевой таблицы базы данных есть INTEGER PRIMARY KEY, невозможно вставить NULL в столбец IPK. Попытка сделать так ведет к ошибке SQLITE_MISMATCH.

Для каждой строки для DELETE из целевой базы данных как часть обновления RBU соответствующая таблица data_% должна содержать единственную запись с "rbu_control" integer 1. Реальные значения первичного ключа строки, чтобы удалить должны быть сохранены в соответствующих колонках таблицы data_%. Значения, сохраненные в других колонках, не используются.

Для каждой строки UPDATE из целевой базы данных как часть обновления RBU соответствующая таблица data_% должна содержать единственную запись с "rbu_control", чтобы содержать значение текста типа. Реальные значения первичного ключа, определяющие строку, чтобы обновить, должны быть сохранены в соответствующих колонках data_%, как новые значения всех колонок, являющихся обновлением. Текстовое значение в колонке "rbu_control" должно содержать то же самое количество знаков, как у колонки в целевой таблице базы данных и должно состоять полностью из знаков 'x' и'.' (или в некоторых особых случаях 'd'). Для каждой колонки, которая обновляется, соответствующий символ установлен в 'x'. Для тех, которые остаются, как есть, соответствующий символ rbu_control должен быть установлен в '.'. Например, учитывая приведенные выше таблицы:

UPDATE t1 SET c = 'usa' WHERE a = 4;

представляется строкой data_t1:

INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..x');

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

  • В формате "fossil delta", формат, используемый для дельт blob в Fossil source-code management system,
  • В формате, определенном применением RBU.

Формат fossil delta может использоваться только, чтобы обновить BLOB. Вместо того, чтобы хранить новый BLOB в data_%, fossil delta сохранена вместо этого. И вместо того, чтобы определить 'x' как часть последовательности rbu_control для колонки, которая будет обновлена, сохранен символ 'f'. Обрабатывая обновление 'f', RBU загружает оригинальные данные BLOB с диска, применяет дельту fossil к нему и пишет результаты назад в файл базы данных. Базы данных RBU, произведенные sqldiff --rbu, используют дельты fossil везде, где выполнение этого оставило бы свободное место в базе данных RBU.

Чтобы использовать свой формат дельты, применение RBU должно зарегистрировать определенную пользователями функцию SQL, названную "rbu_delta" прежде, чем начать обрабатывать обновление. rbu_delta() берет два аргумента: исходное значение, сохраненное в целевом столбце таблицы и значение дельты, предоставленной как часть обновления RBU. Это должно возвратить результат из применения дельты к исходному значению. Чтобы использовать свою функцию дельты, символ значения rbu_control, соответствующей целевому столбцу, чтобы обновить, должен быть установлен в 'd' вместо 'x'. Затем вместо того, чтобы обновить целевую таблицу значением, сохраненным в соответствующем столбце data_%, RBU вызывает определенную пользователями функцию SQL "rbu_delta()" и пишет в целевом столбце таблицы.

Например, эта строка:

INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..d');

предписывает RBU, чтобы обновить целевую таблицу базы данных, в некотором роде подобную:

UPDATE t1 SET c = rbu_delta(c, 'usa') WHERE a = 4;

Если целевая таблица базы данных виртуальная или без PRIMARY KEY, значение rbu_control не должно включать символ, соответствующий rbu_rowid:

INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control)
       VALUES(NULL, 'usa', 12, '.x');

вызывает результат, подобный:

UPDATE ft1 SET b = 'usa' WHERE rowid = 12;

У таблиц data_% не должно быть деклараций PRIMARY KEY. Однако, RBU более эффективен, читая строки от таблиц data_% в порядке "rowid", это примерно то же самое, как чтение отсортированной PRIMARY KEY соответствующей целевой таблицы базы данных. Другими словами, строки должны быть сортированы, используя целевую таблицу области PRIMARY KEY, прежде чем они будут вставлены в data_%.

2.2.3. RBU и таблицы FTS3/4

Обычно таблицы FTS3 или FTS4 это пример виртуальной таблицы с rowid, который работает как PRIMARY KEY. Так, для следующих FTS4:

CREATE VIRTUAL TABLE ft1 USING fts4(addr, text);
CREATE VIRTUAL TABLE ft2 USING fts4;   -- implicit "content" column

data_% могут быть созданы следующим образом:

CREATE TABLE data_ft1 USING fts4(addr, text, rbu_rowid, rbu_control);
CREATE TABLE data_ft2 USING fts4(content, rbu_rowid, rbu_control);

И наполнены как будто целевая таблица была обычной таблицей без явных колонок PRIMARY KEY.

Таблицы Contentless FTS4 обработаны точно так же за исключением того, что любая попытка обновить или удалить строки вызовет ошибку, применяя обновление.

Таблицы FTS4 с внешним контентом также могут быть обновлены, используя RBU. В этом случае пользователь обязан формировать базу данных RBU так, чтобы тот же самый набор операций UPDATE, DELETE и INSERT был применен к индексу FTS4 относительно основной таблицы содержания. Что касается всех обновлений внешнего содержания FTS4, пользователь также обязан гарантировать, что любой UPDATE или DELETE применяется к индексу FTS4, прежде чем они будут применены к основной таблице содержания (обратитесь к документации FTS4 для подробного объяснения). В RBU это сделано, гарантировав, что название таблицы data_% для записи таблицы FTS4 отсортировано перед названием таблицы data_%, применяемой для обновления основной таблицы содержания, используя сортирующую последовательность BINARY. Чтобы избежать дублировать данные в базе данных RBU, представление SQL может использоваться вместо одной из таблиц data_%. Например, для целевой схемы базы данных:

CREATE TABLE ccc(addr, text);
CREATE VIRTUAL TABLE ccc_fts USING fts4(addr, text, content=ccc);

Следующая схема базы данных RBU может использоваться:

CREATE TABLE data_ccc(addr, text, rbu_rowid, rbu_control);
CREATE VIEW data0_ccc_fts AS SELECT * FROM data_ccc;

Таблица data_ccc может тогда быть наполнена обновлениями, предназначенными для целевой таблицы базы данных ccc. Те же самые обновления будут читаться RBU из представления data0_ccc_fts и относиться к таблице FTS ccc_fts. Так как "data0_ccc_fts" меньше "data_ccc", таблица FTS будет обновлена сначала, как требуется.

Случаи, в которых у основной таблицы содержания есть явная колонка INTEGER PRIMARY KEY, немного более трудные, поскольку текстовые значения, сохраненные в колонке rbu_control, немного отличаются для индекса FTS и его основной таблицы содержания. Для основной таблицы содержания символ должен быть включен в любые текстовые значения rbu_control для явного IPK, но для самой таблицы FTS, у которой есть неявный rowid, это не нужно. Это неудобно, но может быть решено, используя более сложное представление, следующим образом:

-- Target database schema
CREATE TABLE ddd(i INTEGER PRIMARY KEY, k TEXT);
CREATE VIRTUAL TABLE ddd_fts USING fts4(k, content=ddd);

-- RBU database schema
CREATE TABLE data_ccc(i, k, rbu_control);
CREATE VIEW data0_ccc_fts AS SELECT i AS rbu_rowid, k,
       CASE WHEN rbu_control IN (0,1) THEN rbu_control
       ELSE substr(rbu_control, 2) END FROM data_ccc;

Функция substr() в представлении SQL выше возвращает текст rbu_control с удаленным первым символом (соответствующим колонке "i", которая не требуется таблице FTS).

2.2.4. Автоматическое создание обновлений RBU с sqldiff

С SQLite version 3.9.0 (2015-10-14) утилита sqldiff в состоянии произвести базы данных RBU, представляющие различие между двумя базами данных с идентичными схемами. Например, следующая команда:

sqldiff --rbu t1.db t2.db

Выведет скрипт SQL, чтобы создать базу данных RBU, которая, если используется, чтобы обновить базу данных t1.db, исправляет ее так, чтобы ее содержание было идентично той из базы данных t2.db.

По умолчанию sqldiff пытается обработать все невиртуальные таблицы в этих двух базах данных. Если какая-либо таблица появляется в одной базе данных, но не в другой, или если у какой-либо таблицы есть немного отличающаяся схема в одной базе данных, это ошибка. Выбор "--table" может быть полезным, если это вызывает проблему.

Виртуальные таблицы проигнорированы по умолчанию sqldiff. Однако, возможно явно создать таблицу RBU data_% для виртуальной таблицы, которая включает rowid, который функционирует как первичный ключ, используя команду, такую как:

sqldiff --rbu --table <virtual-table-name> t1.db t2.db

К сожалению, даже при том, что виртуальные таблицы проигнорированы по умолчанию, любые основные таблицы базы данных, которые они создают, чтобы хранить данные в базе данных, не игнорируются, и sqldiff будет добавлять их к любой базе данных RBU. Поэтому пользователи, пытающиеся использовать sqldiff, чтобы создать обновления RBU, чтобы применить к целевым базам данных с одним или более виртуальной таблицей, должны будут, вероятно, управлять sqldiff с использованием опции --table отдельно для каждой таблицы, чтобы обновить в целевой базе данных.

2.3. RBU и таблицы FTS3/4

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

  1. Откройте дескриптор RBU, используя sqlite3rbu_open(T,A,S).

    T название целевого файла базы данных. A это название файла базы данных RBU. S это название "базы данных состояний", используемой, чтобы хранить информацию статуса, чтобы возобновить обновление после прерывания. S может быть NULL, в этом случае информация хранится в базе данных RBU в различных таблицах с именами, начинающимися на "rbu_".

    sqlite3rbu_open(T,A,S) вернет указатель на объект "sqlite3rbu", который передается в последующие интерфейсы.

  2. Зарегистрируйте любые необходимые виртуальные модули таблицы в дескрипторе базы данных, возвращенном sqlite3rbu_db(X) (X это указатель sqlite3rbu, возвращенный из sqlite3rbu_open()). Кроме того, при необходимости зарегистрируйте функцию SQL rbu_delta(), используя sqlite3_create_function_v2() .

  3. Вызовите sqlite3rbu_step(X) один или несколько раз на указателе X объекта sqlite3rbu. Каждый вызов sqlite3rbu_step() выполняет единственную операцию b-дерева, таким образом, тысячи вызовов могут потребоваться, чтобы применять полное обновление. sqlite3rbu_step() возвратит SQLITE_DONE, когда обновление будет полностью применено.

  4. Вызовите sqlite3rbu_close(X), чтобы закрыть указатель объекта sqlite3rbu. Если sqlite3rbu_step(X) вызвали достаточно раз, чтобы полностью применить обновление целевой базы данных, то база данных RBU отмечена, как "полностью применена". Иначе состояние применения обновления RBU сохранено в базе данных состояний (или в базе данных RBU, если названием файла базы данных состояний в sqlite3rbu_open() является NULL) для более позднего возобновления обновления.

Если обновление только частично применено к целевой базе данных на момент вызова sqlite3rbu_close(), информация сохранена в базе данных состояний, если это существует, или иначе в базе данных RBU. Это позволяет последующим процессам автоматически возобновлять обновление RBU с того места, где оно кончилось. Если информация хранится в базе данных RBU, она может быть удалена, удалив все таблицы, имена которых начинаются с "rbu_".

Для получения дополнительной информации обратитесь к комментариям в файле заголовка sqlite3rbu.h.

3. RBU Vacuum

3.1. Ограничения RBU Vacuum

При сравнении со встроенной командой VACUUM SQLITE у RBU Vacuum есть следующие ограничения:

  • Это не может использоваться на базе данных, которая содержит индексы по выражениям.

  • База данных не может быть в режиме WAL.

3.2. RBU Vacuum C/C++

Эта секция предоставляет обзор и пример кода, демонстрирующий интеграцию RBU Vacuum в прикладную программу. Для полного изложения обратитесь к комментариям в файле заголовка sqlite3rbu.h.

Приложения RBU Vacuum все реализует некоторое изменение следующей процедуры:

  1. Дескриптор RBU создается, вызывая sqlite3rbu_vacuum(T, S).

    T это название файла базы данных, чтобы вакуумировать. Аргументом S является название базы данных, в которой модуль RBU хранит свое состояние, если вакуумная операция будет приостановлена.

    Если база данных состояний S не существует, когда вызван sqlite3rbu_vacuum(), это автоматически создано и наполнено единственной таблицей, используемой, чтобы сохранить состояние вакуума RBU, "rbu_state". Если продолжающийся вакуум RBU приостановлен, эта таблица наполнена статусными данными. Следующий раз sqlite3rbu_vacuum() вызывают с тем же самым параметром S, он обнаруживает эти данные и делает попытку возобновить приостановленную вакуумную операцию. Когда вакуумная операция по RBU закончена или сталкивается с ошибкой, RBU автоматически удаляет содержание rbu_state. В этом случае следующий вызов sqlite3rbu_vacuum() начинает совершенно новую вакуумную операцию с нуля.

    Это хорошая идея установить соглашение для определения имени базы данных вакуума RBU на основе целевого имени базы данных. Пример кода ниже применяет "<target>-vacuum", где <target> это название базы данных.

  2. Любые свои сортирующие последовательности, используемые индексами в базе данных, зарегистрированы в обеих обработчиках базы данных, возвращенных sqlite3rbu_db().

  3. Функция sqlite3rbu_step() вызвана на дескрипторе RBU, пока вакуум RBU не закончен, ошибка происходит или надо приостановить вакуум RBU.

    Каждый вызов sqlite3rbu_step() делает небольшое количество работы для завершения вакуумной операции. В зависимости от размера базы данных, единственный вакуум может потребовать тысяч обращений к sqlite3rbu_step(). sqlite3rbu_step() вернет SQLITE_DONE, если вакуумная операция закончилась, SQLITE_OK, если вакуумная операция не закончилась, но никакая ошибка не произошла, и код ошибки SQLite, если с ошибкой сталкиваются. Если ошибка действительно происходит, все последующие вызовы sqlite3rbu_step() немедленно возвращают тот же самый код ошибки.

  4. sqlite3rbu_close() вызвана, чтобы закрыть дескриптор RBU. Если применение прекратило вызывать sqlite3rbu_step() прежде, чем вакуум закончен или ошибка произошла, состояние вакуума сохранено в базе данных состояний так, чтобы это могло быть возобновлено позже.

    Подобно sqlite3rbu_step(), если вакуумная операция закончилась, sqlite3rbu_close() вернет SQLITE_DONE. Если вакуум не закончился, но никакая ошибка не произошла, SQLITE_OK возвращен. Или, если ошибка произошла, код ошибки SQLite возвращен. Если ошибка произошла как часть предшествующего вызова sqlite3rbu_step(), sqlite3rbu_close() возвращает тот же самый код ошибки.

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

/*
** Either start a new RBU vacuum or resume a suspended RBU vacuum on 
** database zTarget. Return when either an error occurs, the RBU 
** vacuum is finished or when the application signals an interrupt
** (code not shown).
**
** If the RBU vacuum is completed successfully, return SQLITE_DONE.
** If an error occurs, return SQLite error code. Or, if the application
** signals an interrupt, suspend the RBU vacuum operation so that it
** may be resumed by a subsequent call to this function and return
** SQLITE_OK.
**
** This function uses the database named "<zTarget>-vacuum" for
** the state database, where <zTarget> is the name of the database 
** being vacuumed.
*/

int do_rbu_vacuum(const char *zTarget)
{
  int rc;
  char *zState;                   /* Name of state database */
  sqlite3rbu *pRbu;               /* RBU vacuum handle */

  zState = sqlite3_mprintf("%s-vacuum", zTarget);
  if( zState==0 ) return SQLITE_NOMEM;
  pRbu = sqlite3rbu_vacuum(zTarget, zState);
  sqlite3_free(zState);

  if( pRbu ){
    sqlite3 *dbTarget = sqlite3rbu_db(pRbu, 0);
    sqlite3 *dbState = sqlite3rbu_db(pRbu, 1);

    /* Any custom collation sequences used by the target database must
    ** be registered with both database handles here.  */

    while( sqlite3rbu_step(pRbu)==SQLITE_OK ){
      if( <application has signaled interrupt> ) break;
    }
  }
  rc = sqlite3rbu_close(pRbu);
  return rc;
}