diff options
author | Ta-wei Yen <twyen@google.com> | 2016-01-26 18:24:34 -0800 |
---|---|---|
committer | Ta-wei Yen <twyen@google.com> | 2016-01-26 18:24:34 -0800 |
commit | 155ff44c5809da846b841011923b7e81dede303d (patch) | |
tree | fc8656e38d62478c4db1d8b70a76c962e1110db3 /src/com | |
parent | 7d8c2d4c22024478a36cc57b9288ca5e7b3fb7e6 (diff) | |
download | ContactsProvider-155ff44c5809da846b841011923b7e81dede303d.tar.gz |
Implement UPSERT for VoicemailStatusTable#insert
When putting data into the voicemail status table we used to have 2
separate database operations:
1) check if the index exists
2) insert into the database if not exist, or update if exists.
This is a potential race condition if 2 threads try to write at the
same time, pointed out in ag/838999
This CL solve it by changing the semantic of INSERT to "UPSERT",
"Update or insert", which is a single database operation of
"insert into the database if not exist, or update if exists"
SQLite does not directly support UPSERT, and doing so in a single
query is complicated. Instead insert, query, update, and delete in
VoicemailStatusTable is changed to be synchronized with a singleton, so there can only
be one thread doing a set of operations on the table at once.
Large traffic on VoicemailStatusTable is unlikely so the synchronization
cost is acceptable.
Bug:26451384
Change-Id: I6c03bd2720cbb05738cb3059fd19d65156244e20
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/providers/contacts/VoicemailStatusTable.java | 80 |
1 files changed, 52 insertions, 28 deletions
diff --git a/src/com/android/providers/contacts/VoicemailStatusTable.java b/src/com/android/providers/contacts/VoicemailStatusTable.java index b34c4819..c07d4b16 100644 --- a/src/com/android/providers/contacts/VoicemailStatusTable.java +++ b/src/com/android/providers/contacts/VoicemailStatusTable.java @@ -33,6 +33,9 @@ import com.android.providers.contacts.VoicemailContentProvider.UriData; /** * Implementation of {@link VoicemailTable.Delegate} for the voicemail status table. + * + * Public methods of this class are thread-safe as it is used in a content provider, which should + * be thread-safe. */ public class VoicemailStatusTable implements VoicemailTable.Delegate { @@ -48,6 +51,8 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate { .add(Status.QUOTA_TOTAL) .build(); + private static final Object DATABASE_LOCK = new Object(); + private final String mTableName; private final Context mContext; private final CallLogDatabaseHelper mDbHelper; @@ -63,50 +68,69 @@ public class VoicemailStatusTable implements VoicemailTable.Delegate { @Override public Uri insert(UriData uriData, ContentValues values) { - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - ContentValues copiedValues = new ContentValues(values); - mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues); - long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues); - if (rowId > 0) { - Uri newUri = ContentUris.withAppendedId(uriData.getUri(), rowId); - return newUri; - } else { - return null; + synchronized (DATABASE_LOCK) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + // Try to update before insert. + String combinedClause = uriData.getWhereClause(); + int rowsChanged = getDatabaseModifier(db) + .update(mTableName, values, combinedClause, null); + if (rowsChanged != 0) { + final String[] selection = new String[] {Status._ID}; + Cursor c = db.query(mTableName, selection, combinedClause, null, null, null, null); + c.moveToFirst(); + int rowId = c.getInt(0); + return ContentUris.withAppendedId(uriData.getUri(), rowId); + } + ContentValues copiedValues = new ContentValues(values); + mDelegateHelper.checkAndAddSourcePackageIntoValues(uriData, copiedValues); + long rowId = getDatabaseModifier(db).insert(mTableName, null, copiedValues); + if (rowId > 0) { + return ContentUris.withAppendedId(uriData.getUri(), rowId); + } else { + return null; + } } } @Override public int delete(UriData uriData, String selection, String[] selectionArgs) { - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); - return getDatabaseModifier(db).delete(mTableName, combinedClause, - selectionArgs); + synchronized (DATABASE_LOCK) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + return getDatabaseModifier(db).delete(mTableName, combinedClause, + selectionArgs); + } } @Override public Cursor query(UriData uriData, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - qb.setTables(mTableName); - qb.setProjectionMap(sStatusProjectionMap); - qb.setStrict(true); - - String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); - SQLiteDatabase db = mDbHelper.getReadableDatabase(); - Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder); - if (c != null) { - c.setNotificationUri(mContext.getContentResolver(), Status.CONTENT_URI); + synchronized (DATABASE_LOCK) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(mTableName); + qb.setProjectionMap(sStatusProjectionMap); + qb.setStrict(true); + + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + Cursor c = qb + .query(db, projection, combinedClause, selectionArgs, null, null, sortOrder); + if (c != null) { + c.setNotificationUri(mContext.getContentResolver(), Status.CONTENT_URI); + } + return c; } - return c; } @Override public int update(UriData uriData, ContentValues values, String selection, String[] selectionArgs) { - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); - return getDatabaseModifier(db).update(mTableName, values, combinedClause, - selectionArgs); + synchronized (DATABASE_LOCK) { + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + String combinedClause = concatenateClauses(selection, uriData.getWhereClause()); + return getDatabaseModifier(db).update(mTableName, values, combinedClause, + selectionArgs); + } } @Override |