データベースをアップグレードする時

SQLiteのデータベースを使用する際はSQLiteOpenHelperクラスを使います。一般的には下のようなコードになると思います。

    public class DatabaseHelper extends SQLiteOpenHelper {
        public DatabaseHelper(Context context) {
            super(context, "test.db", null, 1);
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + test_table + " (" 
                    + BaseColumns._ID + " INTEGER PRIMARY KEY,"
                    + "name TEXT"
                    + ");" );
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }
    }

使用法はコンストラクタからインスタンスを作成し、getReadableDatabaseかgetWritableDatabaseを呼び出して参照や更新のクエリを発行するSQLiteDatabaseクラスを取得することになります。
onCreateは初めてデータベースにアクセスする際に呼ばれるので、ここでデータベースの初期化を行います。onUpgradeはバージョンアップの際に呼び出されますので、ここでALTER TABLEなどのSQLを使って対応することになります。oldVersionとnewVersionが渡ってくるので、バージョンが1から2になった場合、2から3になった時などで分岐させられます。

バージョンアップの方法

上で作成したデータベースに、機能追加で新しいテーブルを追加することになった場合、以下のようにします。

  • コンストラクタで呼び出す最後の引数が、新しいデータベースのバージョンになります。前回1だったので、今回は2にします。
  • onCreateで行う初期化は最新バージョンのものにします。古いデータベースが作成されている場合はonCreateは実行されません。
  • onUpgradeに、古いデータベースが作成されている場合のバージョンアップコードを書きます。
    public class DatabaseHelper extends SQLiteOpenHelper {
        public DatabaseHelper(Context context) {
            super(context, "test.db", null, 2);
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + test_table + " (" 
                    + BaseColumns._ID + " INTEGER PRIMARY KEY,"
                    + "name TEXT"
                    + ");" );
            db.execSQL("CREATE TABLE " + test_table2 + " ("
                    + BaseColumns._ID + " INTEGER PRIMARY KEY,"
                    + "name TEXT"
                    + ");" );
        }
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            if( oldVersion == 1 && newVersion == 2 ){
                db.execSQL("CREATE TABLE " + test_table2 + " (" 
                    + BaseColumns._ID + " INTEGER PRIMARY KEY,"
                    + "name TEXT"
                    + ");" );
	    }
        }
    }

これで、新しいバージョンを新規にインストールした時と、古いバージョンからバージョンアップした時のテーブル構成を同じにすることが出来ます。

内部の処理

実際にSQLiteOpenHelperクラスはどんな処理をしているのか、SDKのソースを覗いてみました。onCreateやonUpgradeは、getReadableDatabaseやgetWritableDatabaseの内部で実行されていました。その中のコードを引用します。
SQLiteOpenHelper.java

            mIsInitializing = true;
            if (mName == null) {
                db = SQLiteDatabase.create(null);
            } else {
                db = mContext.openOrCreateDatabase(mName, 0, mFactory);
            }

            int version = db.getVersion();
            if (version != mNewVersion) {
                db.beginTransaction();
                try {
                    if (version == 0) {
                        onCreate(db);
                    } else {
                        onUpgrade(db, version, mNewVersion);
                    }
                    db.setVersion(mNewVersion);
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            }

内部の処理では、最初にバージョン0として空のデータベースを作成しonCreateを呼び出しています。データベースが存在していてバージョンが変わっていればonUpgradeを呼び出していますね。どちらもトランザクションで囲まれているので、onCreateやonUpgradeの中でトランザクションを使用する必要は無いみたいです。
ところで、現在のデータベースのバージョンはどこに保存されているのか、SQLiteDatabase.getVersion()とsetVersion()のソースを見てみます。
SQLiteDatabase.java

    public int getVersion() {
        SQLiteStatement prog = null;
        lock();
        try {
            prog = new SQLiteStatement(this, "PRAGMA user_version;");
            long version = prog.simpleQueryForLong();
            return (int) version;
        } finally {
            if (prog != null) prog.close();
            unlock();
        }
    }

    public void setVersion(int version) {
        execSQL("PRAGMA user_version = " + version);
    }

テーブルでは無く、SQLiteのPRAGMAのuser_versionで管理していました。PRAGMAって知らなかったのですが、SQLite3での環境変数のようなものみたいです。http://www.sqlite.org/pragma.html
なので、データベースのバージョンが知りたい場合は、AndroidSDKのshellからSQLiteでDBを開き、上記のクエリーを発行すれば良さそうです。

データベースの構造を変えるのは大変ですが、こういう仕組みが用意されているのは有り難いですね。