feat: add database indices and testing

This commit is contained in:
2026-06-08 15:01:06 +02:00
parent 2574a6e1e6
commit a232d4bfde
11 changed files with 289 additions and 7 deletions
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
@@ -0,0 +1,164 @@
package com.bitcointxoko.gudariwallet.benchmark
import android.content.Context
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.bitcointxoko.gudariwallet.data.db.AppDatabase
import com.bitcointxoko.gudariwallet.data.db.PaymentDao
import com.bitcointxoko.gudariwallet.data.db.PaymentRecordEntity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PaymentDaoBenchmark {
@get:Rule
val benchmarkRule = BenchmarkRule()
private lateinit var db: AppDatabase
private lateinit var dao: PaymentDao
@Before
fun setup() {
val context = ApplicationProvider.getApplicationContext<Context>()
db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.allowMainThreadQueries()
.build()
dao = db.paymentDao()
val now = System.currentTimeMillis()
val fakePayments = (1..5_000).map { i ->
PaymentRecordEntity(
checkingId = "checking_$i",
paymentHash = "hash_$i",
amountMsat = if (i % 2 == 0) (i * 1000L) else -(i * 1000L),
feeMsat = 1000L,
memo = if (i % 3 == 0) "Coffee payment $i" else null,
time = (now - i * 60_000L).toString(),
createdAt = (now / 1000) - (i * 60L),
status = when (i % 4) {
0 -> "complete"
1 -> "paid"
2 -> "pending"
else -> "failed"
},
bolt11 = "lnbc${i}n1...",
pending = i % 4 == 2,
preimage = "preimage_$i",
extra = null,
savedAt = now - (i * 60_000L),
pubkey = if (i % 5 == 0) "pubkey_$i" else null,
alias = if (i % 5 == 0) "alias_$i" else null
)
}
runBlocking { dao.insertAll(fakePayments) }
}
@After
fun teardown() {
db.close()
}
@Test
fun getAll_baseline() = benchmarkRule.measureRepeated {
runBlocking { dao.getAll() }
}
@Test
fun getPage_30rows() = benchmarkRule.measureRepeated {
runBlocking { dao.getPage(limit = 30, offset = 0) }
}
@Test
fun getPage_30rows_deepOffset() = benchmarkRule.measureRepeated {
runBlocking { dao.getPage(limit = 30, offset = 2_000) }
}
@Test
fun queryAll_noFilters() = benchmarkRule.measureRepeated {
runBlocking {
dao.queryAll(null, null, null, null, null, null)
}
}
@Test
fun queryAll_withSearchQuery() = benchmarkRule.measureRepeated {
runBlocking {
dao.queryAll("Coffee", null, null, null, null, null)
}
}
@Test
fun queryAll_statusFilter() = benchmarkRule.measureRepeated {
runBlocking {
dao.queryAll(null, "complete,paid", null, null, null, null)
}
}
@Test
fun queryAll_combinedFilters() = benchmarkRule.measureRepeated {
val now = System.currentTimeMillis() / 1000
runBlocking {
dao.queryAll(
searchQuery = "Coffee",
statusPattern = "complete,paid",
minAmountMsat = 10_000L,
maxAmountMsat = 5_000_000L,
minCreatedAt = now - 86_400 * 30,
maxCreatedAt = now
)
}
}
@Test
fun getByCheckingId_lookup() = benchmarkRule.measureRepeated {
runBlocking { dao.getByCheckingId("checking_2500") }
}
@Test
fun count_rows() = benchmarkRule.measureRepeated {
runBlocking { dao.count() }
}
@Test
fun concurrentReadWrite_walBenefit() = benchmarkRule.measureRepeated {
// Simulate a write happening concurrently with a read
// (WAL allows these to proceed in parallel; default journal blocks)
val writeJob = CoroutineScope(Dispatchers.IO).launch {
dao.insert(
PaymentRecordEntity(
checkingId = "concurrent_${System.nanoTime()}",
paymentHash = "hash_concurrent",
amountMsat = 1000L,
feeMsat = 10L,
memo = "concurrent write",
time = System.currentTimeMillis().toString(),
createdAt = System.currentTimeMillis() / 1000,
status = "complete",
bolt11 = "lnbc...",
pending = false,
preimage = "preimage",
extra = null,
savedAt = System.currentTimeMillis(),
pubkey = null,
alias = null
)
)
}
// Read while write is in-flight
runBlocking {
dao.getPage(limit = 30, offset = 0)
writeJob.join()
}
}
}