2023-09-03 13:18:48 -07:00
|
|
|
@file:Suppress("ktlint:standard:filename")
|
2022-10-19 13:52:54 -07:00
|
|
|
|
|
|
|
package cash.z.ecc.android.sdk.internal.db
|
|
|
|
|
|
|
|
import android.database.sqlite.SQLiteDatabase
|
|
|
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
|
|
|
import androidx.sqlite.db.SupportSQLiteQueryBuilder
|
2024-04-17 00:24:49 -07:00
|
|
|
import cash.z.ecc.android.sdk.internal.SdkDispatchers
|
2022-10-19 13:52:54 -07:00
|
|
|
import kotlinx.coroutines.flow.flow
|
|
|
|
import kotlinx.coroutines.flow.flowOn
|
|
|
|
import java.util.Locale
|
|
|
|
|
|
|
|
/**
|
2024-04-17 00:24:49 -07:00
|
|
|
* Performs a query on a background thread using the dedicated [SdkDispatchers.DATABASE_IO] dispatcher.
|
2022-10-19 13:52:54 -07:00
|
|
|
*
|
|
|
|
* Note that this method is best for small queries, as Cursor has an in-memory window of cached data. If iterating
|
|
|
|
* through a large number of items that exceeds the window, the Cursor may perform additional IO.
|
|
|
|
*/
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
internal fun <T> SQLiteDatabase.queryAndMap(
|
|
|
|
table: String,
|
|
|
|
columns: Array<String>? = null,
|
|
|
|
selection: String? = null,
|
|
|
|
selectionArgs: Array<Any>? = null,
|
|
|
|
groupBy: String? = null,
|
|
|
|
having: String? = null,
|
|
|
|
orderBy: String? = null,
|
|
|
|
limit: String? = null,
|
|
|
|
offset: String? = null,
|
|
|
|
cursorParser: CursorParser<T>
|
|
|
|
) = flow<T> {
|
|
|
|
// TODO [#703]: Support blobs for argument binding
|
2022-12-18 23:08:25 -08:00
|
|
|
// TODO [#703]: https://github.com/zcash/zcash-android-wallet-sdk/issues/703
|
2024-01-04 12:21:32 -08:00
|
|
|
val mappedSelectionArgs =
|
|
|
|
selectionArgs?.onEach {
|
|
|
|
require(it !is ByteArray) {
|
|
|
|
"ByteArray is not supported"
|
|
|
|
}
|
|
|
|
}?.map { it.toString() }?.toTypedArray()
|
2022-10-19 13:52:54 -07:00
|
|
|
|
|
|
|
// Counterintuitive but correct. When using the comma syntax, offset comes first.
|
|
|
|
// When using the keyword syntax, "LIMIT 1 OFFSET 2" then the offset comes second.
|
2024-01-04 12:21:32 -08:00
|
|
|
val limitAndOffset =
|
|
|
|
if (null == offset) {
|
|
|
|
limit
|
|
|
|
} else {
|
|
|
|
String.format(Locale.ROOT, "%s,%s", offset, limit) // NON-NLS
|
|
|
|
}
|
2022-10-19 13:52:54 -07:00
|
|
|
|
|
|
|
query(
|
|
|
|
table,
|
|
|
|
columns,
|
|
|
|
selection,
|
|
|
|
mappedSelectionArgs,
|
|
|
|
groupBy,
|
|
|
|
having,
|
|
|
|
orderBy,
|
|
|
|
limitAndOffset
|
|
|
|
).use {
|
|
|
|
it.moveToPosition(-1)
|
|
|
|
while (it.moveToNext()) {
|
|
|
|
emit(cursorParser.newObject(it))
|
|
|
|
}
|
|
|
|
}
|
2024-04-17 00:24:49 -07:00
|
|
|
}.flowOn(SdkDispatchers.DATABASE_IO)
|
2022-10-19 13:52:54 -07:00
|
|
|
|
|
|
|
/**
|
2024-04-17 00:24:49 -07:00
|
|
|
* Performs a query on a background thread using the dedicated [SdkDispatchers.DATABASE_IO] dispatcher.
|
2022-10-19 13:52:54 -07:00
|
|
|
*
|
|
|
|
* Note that this method is best for small queries, as Cursor has an in-memory window of cached data. If iterating
|
|
|
|
* through a large number of items that exceeds the window, the Cursor may perform additional IO.
|
|
|
|
*/
|
|
|
|
@Suppress("LongParameterList")
|
|
|
|
internal fun <T> SupportSQLiteDatabase.queryAndMap(
|
|
|
|
table: String,
|
|
|
|
columns: Array<String>? = null,
|
|
|
|
selection: String? = null,
|
|
|
|
selectionArgs: Array<Any>? = null,
|
|
|
|
groupBy: String? = null,
|
|
|
|
having: String? = null,
|
|
|
|
orderBy: String? = null,
|
|
|
|
limit: String? = null,
|
|
|
|
offset: String? = null,
|
|
|
|
cursorParser: CursorParser<T>
|
|
|
|
) = flow<T> {
|
2024-01-04 12:21:32 -08:00
|
|
|
val qb =
|
|
|
|
SupportSQLiteQueryBuilder.builder(table).apply {
|
|
|
|
columns(columns)
|
|
|
|
selection(selection, selectionArgs)
|
|
|
|
having(having)
|
|
|
|
groupBy(groupBy)
|
|
|
|
orderBy(orderBy)
|
2022-10-19 13:52:54 -07:00
|
|
|
|
2024-01-04 12:21:32 -08:00
|
|
|
if (null != limit) {
|
|
|
|
// Counterintuitive but correct. When using the comma syntax, offset comes first.
|
|
|
|
// When using the keyword syntax, "LIMIT 1 OFFSET 2" then the offset comes second.
|
|
|
|
if (null == offset) {
|
|
|
|
limit(limit)
|
|
|
|
} else {
|
|
|
|
limit(String.format(Locale.ROOT, "%s,%s", offset, limit)) // NON-NLS
|
|
|
|
}
|
2023-01-18 03:26:12 -08:00
|
|
|
}
|
2022-10-19 13:52:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
query(qb.create()).use {
|
|
|
|
it.moveToPosition(-1)
|
|
|
|
while (it.moveToNext()) {
|
|
|
|
emit(cursorParser.newObject(it))
|
|
|
|
}
|
|
|
|
}
|
2024-04-17 00:24:49 -07:00
|
|
|
}.flowOn(SdkDispatchers.DATABASE_IO)
|