2012/03/02

Android:データベース周りのユーティリティDatabaseUtils


DatabaseUtilsというクラスがあります。
このクラスは、データベースとCursorに関するユーティリティメソッドを提供します。
参考:http://developer.android.com/reference/android/database/DatabaseUtils.html

便利なメソッドがいくつかあるので抜粋しました。

●Cursorの内容をダンプするユーティリティ
Cursorの内容をダンプする場合、dumpCursorを使えば簡単です。
ログ出力のためにPositionを1つずつ進めたり...といった手間が省けます。

サンプルコード:
Cursor c = this.getContentResolver().query(
        Browser.BOOKMARKS_URI, new String[]{"_id", "title"},
        null, null, null);
DatabaseUtils.dumpCursor(c);

結果:
 I/System.out(2944): >>>>> Dumping cursor android.content.ContentResolver$CursorWrapperInner@416bd260
 I/System.out(2944): 0 {
 I/System.out(2944):    _id=1dumpCurrentRow (Cursor cursor)
 I/System.out(2944):    title=Bookmarks
 I/System.out(2944): }
 I/System.out(2944): 1 {
 I/System.out(2944):    _id=2
 I/System.out(2944):    title=Yahoo!
 I/System.out(2944): }
 I/System.out(2944): 2 {
 I/System.out(2944):    _id=3
 I/System.out(2944):    title=Google
 I/System.out(2944): }
 I/System.out(2944): <<<<<

Cursorが指す現在行のみをダンプしたい場合はdumpCurrentRowが使えます。
# dumpCursorは内部でdumpCurrentRowをコールしています。

dumpCursorでダンプしてもCursorのpositionは変化しません。


●WHERE句文字列を生成するユーティリティ
WHERE句を構築する場合はconcatenateWhereを使えば簡単です。
半角スペースや括弧について実装の負担を減らせます。

サンプルコード:
DatabaseUtils.concatenateWhere("criteria1", "criteria2");

結果:
 (criteria1) AND (criteria2)

引数1と2は、それぞれ括弧で囲まれた上でAND条件結合されます。
片方がnullあるいは空白である場合はもう片方の引数がそのまま返却されます。

下記はconcatenateWhereのコードです。
public static String concatenateWhere(String a, String b) {
    if (TextUtils.isEmpty(a)) {
        return b;
    }
    if (TextUtils.isEmpty(b)) {
        return a;
    }

    return "(" + a + ") AND (" + b + ")";
}


●テーブル内の行数を返すユーティリティ
指定されたテーブルに含まれる行数を調べるにはqueryNumEntriesが使えます。
引数にはSQLiteDatabaseインスタンスが必要になるので専らContentProvider側で使用さ
れるでしょう.

サンプルコード:
DatabaseUtils.queryNumEntries(db, "test");

結果:
 4  // testテーブルにはカラムが4つ存在しているとする

内部ではSELECT count(*)でカラムをカウントしています。


●SQL文字列のステートメント種別を判別するユーティリティ
getSqlStatementTypeはSQL文字列のステートメント種別を返します。
種別は定数定義されており下記になります。
  • STATEMENT_SELECT
  • STATEMENT_UPDATE
  • STATEMENT_ATTACH
  • STATEMENT_BEGIN
  • STATEMENT_COMMIT
  • STATEMENT_ABORT
  • STATEMENT_PRAGMA
  • STATEMENT_DDL
  • STATEMENT_UNPREPARED
  • STATEMENT_OTHER


サンプルソース:
DatabaseUtils.getSqlStatementType("SELECT * FROM bookmarks;");
DatabaseUtils.getSqlStatementType("PRAGMA user_version;");
DatabaseUtils.getSqlStatementType(";");
DatabaseUtils.getSqlStatementType("INSERT SELECT;");

結果:(上から順に)
 1(STATEMENT_SELECT)
 7(STATEMENT_PRAGMA)
 99(STATEMENT_OTHER)
 2(STATEMENT_UPDATE)

4番目の結果を見るとわかるように、厳密な解析はしてくれません。
非常に単純な解析しかしてくれないので使用には注意が必要です。

getSqlStatementTypeのコードは下記になります。
public static int getSqlStatementType(String sql) {
    sql = sql.trim();
    if (sql.length() < 3) {
        return STATEMENT_OTHER;
    }
    String prefixSql = sql.substring(0, 3).toUpperCase();
    if (prefixSql.equals("SEL")) {
        return STATEMENT_SELECT;
    } else if (prefixSql.equals("INS") ||
            prefixSql.equals("UPD") ||
            prefixSql.equals("REP") ||
            prefixSql.equals("DEL")) {
        return STATEMENT_UPDATE;
    } else if (prefixSql.equals("ATT")) {
        return STATEMENT_ATTACH;
    } else if (prefixSql.equals("COM")) {
        return STATEMENT_COMMIT;
    } else if (prefixSql.equals("END")) {
        return STATEMENT_COMMIT;
    } else if (prefixSql.equals("ROL")) {
        return STATEMENT_ABORT;
    } else if (prefixSql.equals("BEG")) {
        return STATEMENT_BEGIN;
    } else if (prefixSql.equals("PRA")) {
        return STATEMENT_PRAGMA;
    } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
            prefixSql.equals("ALT")) {
        return STATEMENT_DDL;
    } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
        return STATEMENT_UNPREPARED;
    }
    return STATEMENT_OTHER;
}

SQL文字列の頭3文字だけを抽出して比較するだけのものです。


●文字列をエスケープ処理するユーティリティ
クエリのWHERE句を手動で構築するケースがあります。
手動でクエリを構築する際にはエスケープ処理に注意する必要があります。
指定された文字列のエスケープ処理にはsqlEscapeStringメソッドが便利です。

サンプルソース:
DatabaseUtils.sqlEscapeString("'");

結果:
 ''''

取得できる文字はシングルクォーテーションで囲まれて返されます。
SQLインジェクション問題への対策で有効そうです。

以上です。