У меня есть сценарий python, который поддерживает параметр --edit-the-database
для вызова редактора, предпочитаемого пользователем, для дампа базы данных SQLite сценария. Этот параметр предназначен для облегчения быстрого доступа к частям базы данных, к которым другие параметры сценария не предоставляют доступа, особенно во время разработки этого сценария.
После того, как сценарий сбросил содержимое базы данных, запустил Editor и убедился, что измененное содержимое по-прежнему актуально, затем необходимо заменить существующее содержимое базы данных.
Сначала он удаляет весь существующий контент, выполнив этот SQL (используя модуль sqlite python):
PRAGMA writable_schema = 1;
DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger');
PRAGMA writable_schema = 0;
VACUUM;
, а затем загружает новый контент с помощью модуля sqlite executescript()
метод:
cursor.executescript(sql_slurped_from_user_modified_dump)
Проблема в том, что эти две операции (удаление существующего содержимого, загрузка нового содержимого) не выполняются атомарно: нажмите CTRL-C
в неподходящий момент, и содержимое базы данных будет потеряно.
Если я попытаюсь выполнить эти два блока кода внутри транзакции, я получу сообщение об ошибке:
Error: cannot VACUUM from within a transaction
И если я сохраню транзакцию, но удалю VACUUM
, тогда я получу ошибка:
Error: table first_table already exists
У меня есть уродливый обходной путь: перед вызовом редактора сценарий копирует файл дампа в безопасное место, пишет пользователю предупреждающее сообщение:
WARNING: if anything goes wrong then a backup of the database
can be found in /some/path
и, если сценарий продолжит работу и завершит загрузку нового содержимого, он удалит копию дампа. Но это довольно уродливо!
Я мог бы использовать DROP TABLE
вместо DELETE FROM sqlite_master ...
, но если я пытаюсь разрешить изменение базы данных таким образом, я разрешаю что сам список таблиц может меняться. Т.е. если пользователь добавляет это в дамп:
CREATE TABLE t3 (n INT);
, то жестко запрограммированный список DROP
s, например:
BEGIN TRANSACTION
DROP TABLE t1;
DROP TABLE t2;
DROP INDEX ...
...
cursor.executescript(sql_slurped_from_user_modified_dump)
...
END TRANSACTION;
, не будет работать второй раз. round (потому что он не удаляет таблицу t3).
Я мог бы использовать операции filesystem-atomi c (например, что-то вроде: загрузить измененный дамп в новый файл базы данных; жесткая ссылка новый файл в старый), но для этого потребуется, чтобы сценарий закрыл соединение с базой данных и затем снова открыл его, что по причинам, выходящим за рамки этого вопроса, я бы предпочел не делать.
Есть ли у кого-нибудь есть ли лучшие идеи для атомарно замены всего содержимого базы данных, список таблиц которой непредсказуем?