Android SDKのバージョンによってinsertWithOnConflict()の戻り値が異なる

追記: 2013/08/20
以下の記述はエミュレータで検証した結果です。実機の場合は挿入しようとしたROWIDがそのまま返却されるケースがあるようです。

SQLiteでUNIQUE制約が設定されたカラムに重複データをinsertすれば一意制約違反になるわけですが、
insertWithOnConflict()を使えば、コンフリクト発生時の挙動(アルゴリズム)を指定することができます。

その具体的な挙動については下記Androidデベロッパー公式サイトの通り。
(CONFLICT_XXXのところ)
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html

ところが、insertWithOnConflict()でCONFLICT_IGNOREを指定してコンフリクトを発生させると、想定外の結果が返却されます。たとえば次の様なコードの場合。valuesにはPRIMARY KEY以外にUNIQUE制約が設定されているカラムがあるとします。

int rowId = db.insertWithOnConflict(tableName, hackString,
                values, SQLiteDatabase.CONFLICT_IGNORE);

SDKバージョンによっては、返却値(rowId)が下記のように異なってしまうのです。

[SDKバージョン → rowId]
Android 2.3.4以下 → 0
Android 3.1以上 → -1

なので、例えばContentProviderを利用したデータ挿入で一意制約違反時の処理を考慮する場合は、一例として下記のような細工が必要です。

long rowId = 0;
try {
    rowId = db.insertWithOnConflict(tableName, hackString,
            values, SQLiteDatabase.CONFLICT_IGNORE);
} catch (SQLException e) {
    Log.e(TAG, e.toString());
    return null;
}

if (rowId > 0) {
    Uri insertUri = ContentUris.withAppendedId(contentUri, rowId);
    getContext().getContentResolver().notifyChange(insertUri, null);
    return insertUri;
}

return null;

このように、Android SDKのバージョン違いにより挙動が異なるものがあるので、やっぱり周到なテストが必要ですね。特に回帰テストの重要性を今まで以上にヒシヒシと感じた次第です。