android database improvements

This commit is contained in:
InsanusMokrassar 2021-03-05 16:15:21 +06:00
parent 5f38d9635d
commit d1c6c7696a
6 changed files with 59 additions and 37 deletions

View File

@ -4,6 +4,11 @@
* `Versions`: * `Versions`:
* `Coroutines`: `1.4.2` -> `1.4.3` * `Coroutines`: `1.4.2` -> `1.4.3`
* `Repos`:
* `Common`
* `Android`:
* New `blockingReadableTransaction`/`blockingWritableTransaction`
* Android databases realizations now use blocking transactions where it is possible
## 0.4.28 ## 0.4.28

View File

@ -32,7 +32,9 @@ private object ContextsPool {
suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let { suspend fun <T> use(block: suspend (CoroutineContext) -> T): T = acquireContext().let {
try { try {
safely {
block(it) block(it)
}
} finally { } finally {
freeContext(it) freeContext(it)
} }
@ -48,11 +50,12 @@ class TransactionContext(
} }
suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T { suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T): T {
return coroutineContext[TransactionContext] ?.let { coroutineContext[TransactionContext] ?.let {
withContext(it.databaseContext) { return withContext(it.databaseContext) {
block() block()
} }
} ?: ContextsPool.use { context -> }
return ContextsPool.use { context ->
withContext(TransactionContext(context) + context) { withContext(TransactionContext(context) + context) {
beginTransaction() beginTransaction()
safely( safely(
@ -70,18 +73,18 @@ suspend fun <T> SQLiteDatabase.transaction(block: suspend SQLiteDatabase.() -> T
} }
} }
inline fun <T> SQLiteDatabase.inlineTransaction(block: SQLiteDatabase.() -> T): T { inline fun <T> SQLiteDatabase.inlineTransaction(crossinline block: SQLiteDatabase.() -> T): T {
return when { return when {
inTransaction() -> block() inTransaction() -> block()
else -> { else -> {
beginTransaction() beginTransaction()
try { try {
block().also { block().also { setTransactionSuccessful() }
setTransactionSuccessful()
}
} finally { } finally {
endTransaction() endTransaction()
} }
} }
} }
} }
fun <T> SQLiteDatabase.blockingTransaction(block: SQLiteDatabase.() -> T): T = inlineTransaction(block)

View File

@ -31,6 +31,20 @@ class StandardSQLHelper(
suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block) suspend fun <T> readableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.readableTransaction(block)
} }
fun <T> SQLiteOpenHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return writableDatabase.blockingTransaction(block)
}
fun <T> SQLiteOpenHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return readableDatabase.blockingTransaction(block)
}
fun <T> StandardSQLHelper.blockingWritableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingWritableTransaction(block)
}
fun <T> StandardSQLHelper.blockingReadableTransaction(block: SQLiteDatabase.() -> T): T {
return sqlOpenHelper.blockingReadableTransaction(block)
}
suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T { suspend fun <T> SQLiteOpenHelper.writableTransaction(block: suspend SQLiteDatabase.() -> T): T {
return writableDatabase.transaction(block) return writableDatabase.transaction(block)
} }

View File

@ -20,7 +20,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
it.count it.count
}.toLong() }.toLong()
override suspend fun contains(id: IdType): Boolean = helper.readableTransaction { override suspend fun contains(id: IdType): Boolean = helper.blockingReadableTransaction {
select( select(
tableName, tableName,
null, null,

View File

@ -19,9 +19,10 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
override suspend fun create(values: List<InputValueType>): List<ObjectType> { override suspend fun create(values: List<InputValueType>): List<ObjectType> {
val indexes = helper.writableTransaction { val valuesContentValues = values.map { it.asContentValues() }
values.map { val indexes = helper.blockingWritableTransaction {
insert(tableName, null, it.asContentValues()) valuesContentValues.map {
insert(tableName, null, it)
} }
} }
return helper.readableTransaction { return helper.readableTransaction {
@ -47,7 +48,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
override suspend fun deleteById(ids: List<IdType>) { override suspend fun deleteById(ids: List<IdType>) {
val deleted = mutableListOf<IdType>() val deleted = mutableListOf<IdType>()
helper.writableTransaction { helper.blockingWritableTransaction {
ids.forEach { id -> ids.forEach { id ->
delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also { delete(tableName, "$idColumnName=?", arrayOf(id.asId)).also {
if (it > 0) { if (it > 0) {
@ -64,7 +65,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
override suspend fun update(id: IdType, value: InputValueType): ObjectType? { override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
val asContentValues = value.asContentValues(id) val asContentValues = value.asContentValues(id)
if (asContentValues.keySet().isNotEmpty()) { if (asContentValues.keySet().isNotEmpty()) {
helper.writableTransaction { helper.blockingWritableTransaction {
update( update(
tableName, tableName,
asContentValues, asContentValues,
@ -79,11 +80,12 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
} }
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> { override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
val contentValues = values.map { (id, value) -> id to value.asContentValues(id) }
helper.writableTransaction { helper.writableTransaction {
values.forEach { (id, value) -> contentValues.forEach { (id, contentValues) ->
update( update(
tableName, tableName,
value.asContentValues(id), contentValues,
"$idColumnName=?", "$idColumnName=?",
arrayOf(id.asId) arrayOf(id.asId)
) )
@ -98,5 +100,5 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
} }
} }
override suspend fun count(): Long = helper.readableTransaction { select(tableName).use { it.count.toLong() } } override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
} }

View File

@ -42,8 +42,7 @@ class OneToManyAndroidRepo<Key, Value>(
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this) private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
init { init {
runBlocking(DatabaseCoroutineContext) { helper.blockingWritableTransaction {
helper.writableTransaction {
createTable( createTable(
tableName, tableName,
internalId to internalIdType, internalId to internalIdType,
@ -52,11 +51,10 @@ class OneToManyAndroidRepo<Key, Value>(
) )
} }
} }
}
override suspend fun add(toAdd: Map<Key, List<Value>>) { override suspend fun add(toAdd: Map<Key, List<Value>>) {
val added = mutableListOf<Pair<Key, Value>>() val added = mutableListOf<Pair<Key, Value>>()
helper.writableTransaction { helper.blockingWritableTransaction {
toAdd.forEach { (k, values) -> toAdd.forEach { (k, values) ->
values.forEach { v -> values.forEach { v ->
insert( insert(
@ -78,7 +76,7 @@ class OneToManyAndroidRepo<Key, Value>(
} }
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
helper.writableTransaction { helper.blockingWritableTransaction {
delete(tableName, "$idColumnName=?", arrayOf(k.asId())) delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
}.also { }.also {
if (it > 0) { if (it > 0) {
@ -88,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
} }
override suspend fun set(toSet: Map<Key, List<Value>>) { override suspend fun set(toSet: Map<Key, List<Value>>) {
val (clearedKeys, inserted) = helper.writableTransaction { val (clearedKeys, inserted) = helper.blockingWritableTransaction {
toSet.mapNotNull { (k, _) -> toSet.mapNotNull { (k, _) ->
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) { if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
k k
@ -110,13 +108,13 @@ class OneToManyAndroidRepo<Key, Value>(
inserted.forEach { newPair -> _onNewValue.emit(newPair) } inserted.forEach { newPair -> _onNewValue.emit(newPair) }
} }
override suspend fun contains(k: Key): Boolean = helper.readableTransaction { override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use { select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
it.count > 0 it.count > 0
} }
} }
override suspend fun contains(k: Key, v: Value): Boolean = helper.readableTransaction { override suspend fun contains(k: Key, v: Value): Boolean = helper.blockingReadableTransaction {
select( select(
tableName, tableName,
selection = "$idColumnName=? AND $valueColumnName=?", selection = "$idColumnName=? AND $valueColumnName=?",
@ -127,7 +125,7 @@ class OneToManyAndroidRepo<Key, Value>(
} }
} }
override suspend fun count(): Long =helper.readableTransaction { override suspend fun count(): Long =helper.blockingReadableTransaction {
select( select(
tableName tableName
).use { ).use {
@ -135,7 +133,7 @@ class OneToManyAndroidRepo<Key, Value>(
} }
}.toLong() }.toLong()
override suspend fun count(k: Key): Long = helper.readableTransaction { override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use { select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
it.count it.count
} }
@ -147,7 +145,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean reversed: Boolean
): PaginationResult<Value> = count(k).let { count -> ): PaginationResult<Value> = count(k).let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction { helper.blockingReadableTransaction {
select( select(
tableName, tableName,
selection = "$idColumnName=?", selection = "$idColumnName=?",
@ -173,7 +171,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean reversed: Boolean
): PaginationResult<Key> = count().let { count -> ): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction { helper.blockingReadableTransaction {
select( select(
tableName, tableName,
limit = resultPagination.limitClause() limit = resultPagination.limitClause()
@ -198,7 +196,7 @@ class OneToManyAndroidRepo<Key, Value>(
reversed: Boolean reversed: Boolean
): PaginationResult<Key> = count().let { count -> ): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination } val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
helper.readableTransaction { helper.blockingReadableTransaction {
select( select(
tableName, tableName,
selection = "$valueColumnName=?", selection = "$valueColumnName=?",
@ -220,7 +218,7 @@ class OneToManyAndroidRepo<Key, Value>(
} }
override suspend fun remove(toRemove: Map<Key, List<Value>>) { override suspend fun remove(toRemove: Map<Key, List<Value>>) {
helper.writableTransaction { helper.blockingWritableTransaction {
toRemove.flatMap { (k, vs) -> toRemove.flatMap { (k, vs) ->
vs.mapNotNullA { v -> vs.mapNotNullA { v ->
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) { if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {