Управление базой данных в Android

В процессе разработки с androidом и, соответственно, разработки под sqlite базу данных, возникает ряд задач. Ниже описаны сами задачи и их решения.

1. **Извлечение базы данных из устройства**
Конечно, программно управлять базой хорошо, но иногда возникает желание посмотреть как физически выглядит полученный результат. Особенно ярко это желание возникает, когда происходит апгрейд базы и соответственно требуется оценить, правильно ли он произошёл или разработчик что-нибудь забыл сделать в процессе.

Решается это с помощью утилиты adb, которая идёт в комплекте с android sdk. Находится она в директории platform-tools.
{{{lang=bash
> cd /platofrm-tools
> ./adb shell ‘cp /data/data/<название приложения, допустим com.my.app>
/databases/.db /sdcard/.db’
> ./adb pull /sdcard/.db
}}}

Тут есть несколько моментов, на которые стоит обратить внимание. Во-первых, утилита adb в принципе очень полезная, и команда adb shell запускает команду на устройстве, и в неё можно передавать обычные юниксовые команды. Например, если вы не очень понимаете, какова структура папок конкретно у вас на устройстве, то можно сделать
{{{ lang=bash
> ./adb shell ‘ls -al /data’
}}}
и дальше обычным образом просмотреть всю файловую структуру устройства, что обычно очень удобно для понимания происходящего.

Во-вторых, у меня телефон рутованый, поэтому возможно что указанная команда не будет выполнена. На форумах и stackoverflow советуют в этом случае исполнить
{{{lang=bash
> ./adb shell ‘run-as <название приложения, допустим com.my.app> cat
/data/data/<название приложения, допустим com.my.app>/databases/
.db > /sdcard/.db’
}}}

2. **Миграция баз данных**
При миграции базы возникает проблема корректного обновления старой версии базы до новой. Естественно, что если у вас база переходит с версии 1 на версию 2, то написать миграционный скрипт не сложно. Но если миграций штук десять и пользователь может перейти с любой версии на любую, то хотелось бы иметь хорошо управляемую систему, которая бы автоматически отслеживала любые возможные ситуации и применяла корректную последовательность миграций. На stackoverflow нашлось отличное решение этой проблемы. Как известно, миграция происходит при вызове метода onUpgrade, который принимает в себя номер старой и новой версии. Так вот, проблему корректных миграций решает следующая структура
{{{lang=java
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch(oldVersion) {
case 1:
db.execSQL(DATABASE_CREATE_SOMETHING);
// we want both updates, so no break statement here…
case 2:
db.execSQL(DATABASE_CREATE_SOMETHINGELSE);
}
}
}}}
Ключевым моментом является идея о том, что между разными case не надо ставить break! В этом случае изменения начнут применяться с правильной версии и будут накатываться до самого конца.

3. **Изменение существующих таблиц**
Помимо проблемы корректной миграции есть ещё задача правильного изменения уже существующих таблиц. В большинстве больших баз данных (типа postgre) есть масса команд вроде ALTER TABLE ALTER COLUMN, которые позволяют поменять тип колонки, добавить/удалить constraints, удалять колонки и сделать много много чего ещё. К сожалению, sqlite намного более ограничен в этом вопросе, и хотя у него есть частично подобный функционал, но он мягко говоря неудобен. Но решение существует, и общая идея состоит в том, что создаётся временная таблица, в которую копируется существующая, после чего существующая удаляется и на её месте создаётся новая с новыми правилами. В эту новую таблицу вставляются данные из бэкапа, который затем удаляется. Минус в том, что создаются промежуточные таблицы, и операции вставки занимают довольно много времени и процессорных ресурсов. С другой стороны, sqlite базы редко бывают большими, особенно на android, так что вряд ли это такая уж серьёзная проблема.
{{{lang=sql
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT
}}}