Is the execution order of two consecutive calls to a content provider guaranteed (within one thread)?

StackOverflow https://stackoverflow.com/questions/23388057

سؤال

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)
هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top