From b0733dcd51df213a9959b1c4467f1b3e397c61aa Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 10 Nov 2023 08:27:27 -0800 Subject: [PATCH] Reduce transactions during getAndPossiblyMerge. --- .../securesms/database/RecipientTable.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index 9d75591fe7..aa2f2e0436 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -446,6 +446,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da fun getAndPossiblyMerge(aci: ACI?, pni: PNI?, e164: String?, pniVerified: Boolean = false, changeSelf: Boolean = false): RecipientId { require(aci != null || pni != null || e164 != null) { "Must provide an ACI, PNI, or E164!" } + // To avoid opening a transaction and doing extra reads, we start with a single read that checks if all of the fields already match a single recipient + val singleMatch: RecipientId? = getRecipientIdIfAllFieldsMatch(aci, pni, e164) + if (singleMatch != null) { + return singleMatch + } + + Log.d(TAG, "[getAndPossiblyMerge] Requires a transaction.") + val db = writableDatabase var transactionSuccessful = false lateinit var result: ProcessPnpTupleResult @@ -2618,6 +2626,40 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da ) } + /** + * If all of the non-null fields match a single recipient, return it. Otherwise null. + */ + private fun getRecipientIdIfAllFieldsMatch(aci: ACI?, pni: PNI?, e164: String?): RecipientId? { + if (aci == null && pni == null && e164 == null) { + return null + } + + val columns = listOf( + ACI_COLUMN to aci?.toString(), + PNI_COLUMN to pni?.toString(), + E164 to e164 + ).filter { it.second != null } + + val query = columns + .map { "${it.first} = ?" } + .joinToString(separator = " AND ") + + val args: Array = columns.map { it.second!! }.toTypedArray() + + val ids: List = readableDatabase + .select(ID) + .from(TABLE_NAME) + .where(query, args) + .run() + .readToList { it.requireLong(ID) } + + return if (ids.size == 1) { + RecipientId.from(ids[0]) + } else { + null + } + } + /** * A session switchover event indicates a situation where we start communicating with a different session that we were before. * If a switchover is "verified" (i.e. proven safe cryptographically by the sender), then this doesn't require a user-visible event.