Reputation: 1832
I have a content provider backed by an SQLiteDatabase
. Consider the following two calls:
//the following call results in "DELETE FROM DETAILS":
context.getContentResolver().delete(getContentUriOfDetailTable(), null, null); //line 1
//the following call results in "DELETE FROM MASTER":
context.getContentResolver().delete(getContentUriOfMasterTable(), null, null); //line 2
As I know content providers are thread safe, when backed by an SQLiteDatabase. But is there a guarantee, that the execution of the SQL triggered by line 1 is finished before the SQL triggered by line 2 will be executed?
Background: There is a foreign key reference from table DETAIL to MASTER and I got a bugreport
SQLiteConstraintException: foreign key constraint failed (code 19)
from line 2, though I cannot reproduce it.
Tables:
CREATE TABLE MASTER
(_id PRIMARY KEY autoincrement,
VALUE text
);
CREATE TABLE DETAILS (
_id PRIMARY KEY autoincrement,
MASTERIDX integer not null,
VALUE text,
FOREIGN KEY(MASTERIDX) REFERENCES MASTER(_id)
);
My ContentProvider is nothing special:
public class MyDbContentProvider extends ContentProvider {
private MyDbOpenHelper mDbHelper;
//...
//...
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
int rowsDeleted = 0;
String where;
MyUriMatcher.MatchResult matchRes = mURIMatcher.matchTable(uri);
TableDef tableDef = matchRes.tableDef;
switch (matchRes.uriType) {
case ALL_ITEMS:
where = selection;
break;
case SINGLE_ITEM:
String idstr = uri.getLastPathSegment();
where = TableDef._ID + " = " + idstr;
if (!TextUtils.isEmpty(selection)) {
where += " AND (" + selection + ")";
}
break;
default:
throw new IllegalArgumentException("Unsupported URI for delete: " + uri);
}
try {
rowsDeleted = db.delete(tableDef.getTableName(), where, selectionArgs);
} catch (SQLException e) {
throw createCustomSqlException(e, "DELETE", uri, null, where, selectionArgs);
}
// notify all listeners of changes:
if (rowsDeleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;
}
}
Here is the stack trace:
android.database.SQLException: SQLiteConstraintException: foreign key constraint failed (code 19) for DELETE for following URI: content://de.myapp.mobil.myappDbProvider/master and this selection: null
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:858)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
at android.database.sqlite.SQLiteDatabase.delete(SQLiteDatabase.java:1494)
at de.myapp.mobil.MyDbContentProvider.delete(MyDbContentProvider.java:267)
at android.content.ContentProvider$Transport.delete(ContentProvider.java:228)
at android.content.ContentResolver.delete(ContentResolver.java:958)
at de.myapp.CommonDbContract$TableDef.deleteAll(CommonDbContract.java:119)
at de.myapp.mobil.MyDbContract.deleteAll(MyDbContract.java:1730)
at de.myapp.mobil.MyDbContract.recreateDbOrDeleteAll(MyDbContract.java:1761)
at de.myapp.mobil.SettingsActivity$ResetAllCommand.execute(SettingsActivity.java:77)
at de.myapp.mobil.SettingsActivity$ResetAllCommand.execute(SettingsActivity.java:1)
at de.myapp.DlgUtils$DataCommand.execute(DlgUtils.java:54)
at de.myapp.DlgUtils$CombinedCommand.execute(DlgUtils.java:116)
at de.myapp.DlgUtils$CommandWrapper.onClick(DlgUtils.java:157)
Upvotes: 0
Views: 400
Reputation: 654
Instead of this
CREATE TABLE MASTER
(_id PRIMARY KEY autoincrement,
VALUE text
);
CREATE TABLE DETAILS (
_id PRIMARY KEY autoincrement,
MASTERIDX integer not null,
VALUE text,
FOREIGN KEY(MASTERIDX) REFERENCES MASTER(_id)
);
Just update this
CREATE TABLE MASTER
(_id PRIMARY KEY autoincrement,
VALUE text
);
CREATE TABLE DETAILS (
_id PRIMARY KEY autoincrement,
MASTERIDX integer not null,
VALUE text,
FOREIGN KEY(MASTERIDX) REFERENCES MASTER(_id) ON DELETE CASCADE
);
Now this line alone is enough to delete data in both master table and details table:
//the following call results in "DELETE FROM MASTER":
context.getContentResolver().delete(getContentUriOfMasterTable(), null, null); //line 2
For more info : http://www.sqlite.org/foreignkeys.html#fk_actions
Upvotes: 0
Reputation: 15775
ContentResolver
objects use Binder
(IBinder
) interfaces behind the scenes to communicate with ContentProvider
instances. The Binder
calls are blocking, so they will execute sequentially within your app. Check the AOSP sources: ContentResolver and IContentProvider. The IContentProvider
is a synchronous Binder interface, there is no async
keyword applied to the AIDL defined interface.
Also, check the SQLite documentation on foreign keys. If you attempt to delete all rows from the MASTER table, but there still exists a row in the DETAIL table which refers to a MASTER table ID then it will fail: sqlite.org/foreignkeys.html.
Upvotes: 4