Архив рубрики: SQLite

Справился с ошибкой svn E155010

Решил как-то добавить древние файлы в систему контроля версий, да не смог:

$ svn st | grep -e '^\?' | cut -c9-999 | xargs svn add
A         noframe.html.en
svn: E000013: Can't create temporary file from template '/usr/local/www/path/to/svn-XXXXXX': Permission denied

Оказалось, не имел права писать во временный каталог Subversion .svn/tmp где-то выше. Изменил права, повторяю попытку добавления — фиг:

$ svn st | grep -e '^\?' | cut -c9-999 | xargs svn add
svn: E155037: Previous operation has not finished; run 'cleanup' if it was interrupted

Команда svn cleanup тоже не помогла

$ svn cleanup
svn: E155010: The node '/usr/local/www/path/to/af_0011.html.ru' was not found.

Гугление с чтением форумов не особо помогло — пришлось ковырять базу данных, где Subversion хранит состояние рабочей копии — это файл .svn/wc.db, для работы с которым нужен SQLite. Будете ковырять — не забудьте сделать резервную копию!

$ sqlite3 wc.db

sqlite> SELECT * FROM LOCK;
sqlite> SELECT * FROM WC_LOCK;
1|path/to|-1
sqlite> DELETE FROM WC_LOCK;
sqlite> SELECT * FROM WORK_QUEUE;
26|(sync-file-flags path/to/af_0011.html.ru)
sqlite> DELETE FROM WORK_QUEUE;

В моём случае сработало.

Больше букв

Функция selectall_arrayref перлового модуля DBI хороша для тех, кому лень писать:

This utility method combines «prepare», «execute» and «fetchall_arrayref» into a single call. It returns a reference to an array containing a reference to an array (or hash, see below) for each row of data fetched.

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

Реальный пример: скрипт, извлекающий тайлы из пакета, созданного Тайлмиллом, пытался читать данные как раз функцией selectall_arrayref. Зная, что применяется запрос

SELECT * FROM tiles

и что представление tiles содержит, помимо прочего, содержимое тайлов, занимающее места больше всего остального, нетрудно догадаться, что попытка выполнения запроса потребует выделения памяти в объёме, сопоставимом с размером файла, в котором сидит база (пакет с тайлами — это база SQLite).

Набор тайлов для территории размером 600×400 км в средних широтах — например, с Челябинском по центру, Ашой на западе, Карталами на юге и Тюменью на северо-востоке — займёт больше гигабайта для набора масштабов не больше шестнадцатого. На практике так и получилось: скрипт отжирал больше гигабайта памяти и всё никак не мог приступить к полезной части, пытаясь отожрать ещё. Если же увеличивать масштаб, затраты вырастут ещё сильнее: добавим семнадцатый зум масштаб — понадобятся ещё три-четыре гигабайта, Добавим восемнадцатый, которого хватит даже для любопытных исследователей карт — ещё на десять-двадцать объём вырастет. Если будем сохранять тайлы с глубиной цвета 24 бита, а не восемь — ещё больше места израсходуем. Получается, что средних размеров российская область может занять своими тайлами десятки гигабайт. И скрипт бы безуспешно пытался эти десятки получить.

Переписал:

-my $tiles = $dbh->selectall_arrayref(
-    'SELECT * FROM tiles',
-    { Slice => {} }
-);
-
-foreach my $tile ( @$tiles ) {

+my $sth = $dbh->prepare('SELECT * FROM tiles');
+   $sth->execute;
+
+while ( my $tile = $sth->fetchrow_hashref ) {

и всё наладилось: скрипт перестал жрать память (ему хватило десяти мегабайт) и ждать её выделения — сразу работает.

Вывод: не всегда надо экономить рабочее время программиста — иногда надо и о машинном времени задумываться.