![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Расширение RBU это добавление для SQLite, разработанное
для использования с большими файлами базы данных SQLite на устройствах малой
мощности на краю сети. RBU может использоваться для двух отдельных задач: RBU это сокращение от "Resumable Bulk Update". Обе задачи RBU могут быть достигнуты, используя встроенные команды
SQL SQLITE: RBU update через серию INSERT,
DELETE и
UPDATE
в единственной транзакции, а RBU vacuum просто командой
VACUUM. Модуль 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 при тех же самых условиях. Продолжающаяся операция RBU
не вмешивается в доступ для чтения к файлу базы данных. Операции RBU могут быть приостановлены и затем позже возобновлены,
возможно с прошедшими отключениями электроэнергии и/или системным сбросом.
Для обновления RBU оригинальное содержание базы данных остается видимым всем
читателям базы данных, пока все обновление не было применено,
даже если обновление приостановлено и затем позже возобновлено. Расширение RBU не позволено по умолчанию. Чтобы позволить его, соберите
объединение с
SQLITE_ENABLE_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. Никакие другие записи не могут произойти на целевой базе данных в то
время, как обновление RBU применяется. Блокировка чтения, как считается, на
целевой базе данных предотвращает это. Все изменения, которые будут применены RBU, сохранены в отдельной базе
данных SQLite, названной "RBU database". Базу данных, которая должна быть
изменена, называют "целевой базой данных". Для каждой таблицы в целевой базе данных, которая будет изменена
обновлением, соответствующая таблица составлена в базе данных RBU.
Схема таблицы базы данных RBU не то же самое, как схема из целевой базы
данных, но получена из нее, как
описано ниже. Таблица базы данных RBU содержит единственную строку
для каждой целевой строки базы данных, вставленной, обновленной или удаленной
обновлением. Заполнение таблиц базы данных 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
не должен иметь типа вообще. Например, если целевая база данных содержит: Тогда база данных RBU должна содержать: Порядок колонок в таблице data_% не имеет значения. Если целевая таблица базы данных это
виртуальная таблица или у нее нет декларации PRIMARY KEY, data_%
должна также содержать колонку, названную "rbu_rowid". rbu_rowid
отображается к табличному ROWID.
Например, если целевая база данных содержит любое из следующего: тогда база данных RBU должна содержать: Виртуальные таблицы, для которых колонка "rowid"
не функционирует как значение
первичного ключа, не могут быть обновлены, используя RBU. Все нескрытые столбцы (то есть, все колонки, соответствовавшие
"SELECT *") целевого стола, должны присутствовать во входной
таблице. Для виртуальных таблиц скрытые столбцы дополнительные,
они обновляются RBU, если существуют во входной таблице.
Например, чтобы написать таблицу fts4
со скрытой колонкой languageid, такой как: Любая из следующих схем входной таблицы может использоваться: Для каждой строки 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 должен быть установлен в '.'.
Например, учитывая приведенные выше таблицы: представляется строкой data_t1: Если RBU используется, чтобы обновить большое значение BLOB
в целевой базе данных, может быть более эффективно сохранить участок или
дельту, которая может использоваться, чтобы изменить существующий BLOB вместо
совершенно нового значения в базе данных RBU. 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()" и пишет в целевом столбце таблицы. Например, эта строка: предписывает RBU, чтобы обновить целевую таблицу базы данных, в
некотором роде подобную: Если целевая таблица базы данных виртуальная или без PRIMARY KEY, значение
rbu_control не должно включать символ, соответствующий rbu_rowid: вызывает результат, подобный: У таблиц data_% не должно быть деклараций PRIMARY KEY. Однако, RBU более
эффективен, читая строки от таблиц data_% в порядке "rowid", это
примерно то же самое, как чтение отсортированной PRIMARY KEY соответствующей
целевой таблицы базы данных. Другими словами, строки
должны быть сортированы, используя целевую таблицу области PRIMARY KEY,
прежде чем они будут вставлены в data_%.
Обычно таблицы FTS3 или FTS4
это пример виртуальной таблицы с rowid, который работает как PRIMARY KEY.
Так, для следующих FTS4: data_% могут быть созданы следующим образом: И наполнены как будто целевая таблица была обычной таблицей
без явных колонок PRIMARY KEY. Таблицы Contentless FTS4
обработаны точно так же за исключением того, что любая попытка обновить или
удалить строки вызовет ошибку, применяя обновление.
Таблицы FTS4 с внешним контентом также могут быть обновлены, используя
RBU. В этом случае пользователь обязан формировать базу данных RBU так, чтобы
тот же самый набор операций UPDATE, DELETE и INSERT был применен к индексу
FTS4 относительно основной таблицы
содержания. Что касается всех обновлений внешнего содержания
FTS4, пользователь также обязан гарантировать, что любой UPDATE или DELETE
применяется к индексу FTS4, прежде чем они будут применены к основной
таблице содержания (обратитесь к документации FTS4 для подробного
объяснения). В RBU это сделано, гарантировав, что название таблицы
data_% для записи таблицы FTS4 отсортировано перед названием таблицы data_%,
применяемой для обновления основной таблицы
содержания, используя сортирующую последовательность
BINARY.
Чтобы избежать дублировать данные в базе данных RBU, представление SQL
может использоваться вместо одной из таблиц data_%.
Например, для целевой схемы базы данных: Следующая схема базы данных RBU может использоваться: Таблица 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, это
не нужно. Это неудобно, но может быть решено, используя более сложное
представление, следующим образом: Функция substr() в представлении SQL
выше возвращает текст rbu_control с удаленным первым символом
(соответствующим колонке "i", которая не требуется таблице FTS).
С SQLite version 3.9.0 (2015-10-14)
утилита sqldiff в состоянии произвести базы данных
RBU, представляющие различие между двумя базами данных с идентичными схемами.
Например, следующая команда: Выведет скрипт SQL, чтобы создать базу данных RBU, которая, если
используется, чтобы обновить базу данных t1.db, исправляет ее так, чтобы ее
содержание было идентично той из базы данных t2.db. По умолчанию sqldiff пытается обработать все невиртуальные таблицы
в этих двух базах данных. Если какая-либо таблица
появляется в одной базе данных, но не в другой, или если у какой-либо таблицы
есть немного отличающаяся схема в одной базе данных, это ошибка.
Выбор "--table" может быть полезным, если это вызывает проблему. Виртуальные таблицы проигнорированы по умолчанию sqldiff.
Однако, возможно явно создать таблицу RBU data_%
для виртуальной таблицы, которая включает rowid, который функционирует как
первичный ключ, используя команду, такую как: К сожалению, даже при том, что виртуальные таблицы
проигнорированы по умолчанию, любые
основные таблицы базы данных, которые они
создают, чтобы хранить данные в базе данных, не игнорируются, и
sqldiff будет добавлять их
к любой базе данных RBU. Поэтому пользователи, пытающиеся использовать
sqldiff, чтобы создать обновления RBU, чтобы применить
к целевым базам данных с одним или более виртуальной таблицей, должны будут,
вероятно, управлять sqldiff с использованием опции --table
отдельно для каждой таблицы, чтобы обновить в целевой базе данных. Дополнительный интерфейс RBU позволяет
применить обновление RBU, сохраненное в базе данных RBU к существующей
целевой базе данных. Процедура следующая: Откройте дескриптор RBU, используя sqlite3rbu_open(T,A,S). T название целевого файла базы данных. A это название файла базы данных
RBU. S это название "базы данных состояний", используемой, чтобы
хранить информацию статуса, чтобы возобновить обновление после прерывания.
S может быть NULL, в этом случае информация хранится в базе данных RBU в
различных таблицах с именами, начинающимися на "rbu_". sqlite3rbu_open(T,A,S) вернет указатель на объект
"sqlite3rbu", который передается в последующие интерфейсы. Зарегистрируйте любые необходимые виртуальные модули таблицы в
дескрипторе базы данных, возвращенном sqlite3rbu_db(X) (X это указатель
sqlite3rbu, возвращенный из sqlite3rbu_open()).
Кроме того, при необходимости зарегистрируйте функцию SQL rbu_delta(),
используя sqlite3_create_function_v2()
. Вызовите sqlite3rbu_step(X) один или несколько раз на указателе X
объекта sqlite3rbu. Каждый вызов sqlite3rbu_step()
выполняет единственную операцию b-дерева, таким образом, тысячи вызовов
могут потребоваться, чтобы применять полное обновление. sqlite3rbu_step()
возвратит SQLITE_DONE, когда обновление будет полностью применено. Вызовите sqlite3rbu_close(X), чтобы закрыть указатель объекта
sqlite3rbu. Если sqlite3rbu_step(X) вызвали достаточно раз, чтобы полностью
применить обновление целевой базы данных, то база данных RBU отмечена, как
"полностью применена". Иначе состояние применения обновления RBU сохранено
в базе данных состояний (или в базе данных RBU, если названием файла базы
данных состояний в sqlite3rbu_open()
является NULL) для более позднего возобновления обновления. Если обновление только частично применено к целевой базе данных
на момент вызова sqlite3rbu_close(), информация сохранена в базе данных
состояний, если это существует, или иначе в базе данных RBU. Это позволяет
последующим процессам автоматически возобновлять обновление RBU с того места,
где оно кончилось. Если информация хранится в базе данных RBU, она может
быть удалена, удалив все таблицы, имена которых начинаются с "rbu_". Для получения дополнительной информации обратитесь к
комментариям в файле заголовка
sqlite3rbu.h.
При сравнении со встроенной командой VACUUM SQLITE у RBU Vacuum
есть следующие ограничения: Это не может использоваться на базе данных, которая содержит
индексы по выражениям. База данных не может быть в режиме
WAL. Эта секция предоставляет обзор и пример кода, демонстрирующий интеграцию
RBU Vacuum в прикладную программу. Для полного изложения обратитесь к
комментариям в файле заголовка
sqlite3rbu.h. Приложения RBU Vacuum
все реализует некоторое изменение следующей процедуры: Дескриптор 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> это
название базы данных. Любые свои сортирующие последовательности, используемые индексами в
базе данных, зарегистрированы в обеих обработчиках
базы данных, возвращенных sqlite3rbu_db(). Функция sqlite3rbu_step() вызвана на дескрипторе RBU, пока вакуум RBU
не закончен, ошибка происходит или надо приостановить вакуум RBU. Каждый вызов sqlite3rbu_step()
делает небольшое количество работы для завершения вакуумной операции.
В зависимости от размера базы данных, единственный вакуум может потребовать
тысяч обращений к sqlite3rbu_step(). sqlite3rbu_step() вернет SQLITE_DONE,
если вакуумная операция закончилась, SQLITE_OK, если вакуумная операция не
закончилась, но никакая ошибка не произошла, и код ошибки SQLite, если с
ошибкой сталкиваются. Если ошибка действительно происходит, все последующие
вызовы sqlite3rbu_step() немедленно возвращают тот же
самый код ошибки. sqlite3rbu_close() вызвана, чтобы закрыть дескриптор RBU.
Если применение прекратило вызывать sqlite3rbu_step()
прежде, чем вакуум закончен или ошибка произошла, состояние вакуума сохранено
в базе данных состояний так, чтобы это могло быть возобновлено позже. Подобно sqlite3rbu_step(), если вакуумная операция закончилась,
sqlite3rbu_close() вернет SQLITE_DONE.
Если вакуум не закончился, но никакая ошибка не произошла, SQLITE_OK
возвращен. Или, если ошибка произошла, код ошибки SQLite возвращен.
Если ошибка произошла как часть предшествующего вызова sqlite3rbu_step(),
sqlite3rbu_close() возвращает тот же самый код ошибки. Следующий пример кода иллюстрирует методы, описанные выше.
Choose any three.
1.
Расширение RBU
2. Обновления RBU
2.1. Ограничения обновлений RBU
Целевая база данных не может содержать
индексы по выражениям.
Индексы по выражениям поддерживаются, начиная с SQLite 3.30.0
(2019-10-04).2.2.
Подготовка файла обновления RBU
2.2.1. Схема базы данных RBU
CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT, c UNIQUE);
CREATE TABLE data_t1(a INTEGER, b TEXT, c, rbu_control);
CREATE VIRTUAL TABLE x1 USING fts3(a, b);
CREATE TABLE x1(a, b);
CREATE TABLE data_x1(a, b, rbu_rowid, rbu_control);
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
UPDATE t1 SET c = 'usa' WHERE a = 4;
INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..x');
INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..d');
UPDATE t1 SET c = rbu_delta(c, 'usa') WHERE a = 4;
INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control)
VALUES(NULL, 'usa', 12, '.x');
UPDATE ft1 SET b = 'usa' WHERE rowid = 12;
2.2.3. RBU и таблицы FTS3/4
CREATE VIRTUAL TABLE ft1 USING fts4(addr, text);
CREATE VIRTUAL TABLE ft2 USING fts4; -- implicit "content" column
CREATE TABLE data_ft1 USING fts4(addr, text, rbu_rowid, rbu_control);
CREATE TABLE data_ft2 USING fts4(content, rbu_rowid, rbu_control);
CREATE TABLE ccc(addr, text);
CREATE VIRTUAL TABLE ccc_fts USING fts4(addr, text, content=ccc);
CREATE TABLE data_ccc(addr, text, rbu_rowid, rbu_control);
CREATE VIEW data0_ccc_fts AS SELECT * FROM data_ccc;
-- 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;
2.2.4. Автоматическое создание обновлений RBU с sqldiff
sqldiff --rbu t1.db t2.db
sqldiff --rbu --table <virtual-table-name> t1.db t2.db
2.3.
RBU и таблицы FTS3/4
3. RBU Vacuum
3.1. Ограничения RBU Vacuum
3.2.
RBU Vacuum C/C++
/*
** 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;
}