MicroUtils/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/onetomany/OneToManyAndroidRepo.kt

270 lines
9.4 KiB
Kotlin
Raw Normal View History

2020-11-10 09:43:15 +00:00
package dev.inmo.micro_utils.repos.onetomany
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.pagination.*
2020-11-10 09:43:15 +00:00
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
private val internalSerialFormat = Json {
ignoreUnknownKeys = true
}
2021-06-13 05:50:48 +00:00
typealias KeyValuesAndroidRepo<Key, Value> = OneToManyAndroidRepo<Key, Value>
2020-11-10 09:43:15 +00:00
class OneToManyAndroidRepo<Key, Value>(
private val tableName: String,
2021-06-13 05:48:37 +00:00
private val keyAsString: Key.() -> String,
private val valueAsString: Value.() -> String,
private val keyFromString: String.() -> Key,
private val valueFromString: String.() -> Value,
2020-11-10 09:43:15 +00:00
private val helper: SQLiteOpenHelper
) : OneToManyKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
private val _onDataCleared = MutableSharedFlow<Key>()
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
private val idColumnName = "id"
2021-06-15 08:24:00 +00:00
private val idColumnArray = arrayOf(idColumnName)
2020-11-10 09:43:15 +00:00
private val valueColumnName = "value"
2021-06-15 08:24:00 +00:00
private val valueColumnArray = arrayOf(valueColumnName)
2020-11-10 09:43:15 +00:00
init {
2021-03-05 10:15:21 +00:00
helper.blockingWritableTransaction {
createTable(
tableName,
internalId to internalIdType,
idColumnName to ColumnType.Text.NOT_NULLABLE,
valueColumnName to ColumnType.Text.NULLABLE
)
2020-11-10 09:43:15 +00:00
}
}
override suspend fun add(toAdd: Map<Key, List<Value>>) {
val added = mutableListOf<Pair<Key, Value>>()
2021-03-05 10:15:21 +00:00
helper.blockingWritableTransaction {
2021-05-06 11:52:53 +00:00
for ((k, values) in toAdd) {
2020-11-10 09:43:15 +00:00
values.forEach { v ->
insert(
tableName,
null,
contentValuesOf(
2021-06-13 05:48:37 +00:00
idColumnName to k.keyAsString(),
valueColumnName to v.valueAsString()
2020-11-10 09:43:15 +00:00
)
).also {
if (it != -1L) {
added.add(k to v)
}
}
}
}
}
added.forEach { _onNewValue.emit(it) }
}
override suspend fun clear(k: Key) {
2021-03-05 10:15:21 +00:00
helper.blockingWritableTransaction {
2021-06-13 05:48:37 +00:00
delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString()))
2020-11-10 09:43:15 +00:00
}.also {
if (it > 0) {
_onDataCleared.emit(k)
}
}
}
2020-11-18 14:27:29 +00:00
override suspend fun set(toSet: Map<Key, List<Value>>) {
2021-03-05 10:15:21 +00:00
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
2020-11-18 14:27:29 +00:00
toSet.mapNotNull { (k, _) ->
2021-06-13 05:48:37 +00:00
if (delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString())) > 0) {
2020-11-18 14:27:29 +00:00
k
} else {
null
}
} to toSet.flatMap { (k, values) ->
values.map { v ->
insert(
tableName,
null,
2021-06-13 05:48:37 +00:00
contentValuesOf(idColumnName to k.keyAsString(), valueColumnName to v.valueAsString())
2020-11-18 14:27:29 +00:00
)
k to v
}
}
}
clearedKeys.forEach { _onDataCleared.emit(it) }
inserted.forEach { newPair -> _onNewValue.emit(newPair) }
}
2021-03-05 10:15:21 +00:00
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
2021-06-15 08:24:00 +00:00
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = firstPageWithOneElementPagination.limitClause()).use {
2020-11-10 09:43:15 +00:00
it.count > 0
}
}
2021-03-05 10:15:21 +00:00
override suspend fun contains(k: Key, v: Value): Boolean = helper.blockingReadableTransaction {
2020-11-10 09:43:15 +00:00
select(
tableName,
selection = "$idColumnName=? AND $valueColumnName=?",
2021-06-13 05:48:37 +00:00
selectionArgs = arrayOf(k.keyAsString(), v.valueAsString()),
2020-11-10 09:43:15 +00:00
limit = FirstPagePagination(1).limitClause()
).use {
it.count > 0
}
}
2021-06-15 08:24:00 +00:00
override suspend fun count(): Long = helper.blockingReadableTransaction {
2020-11-10 09:43:15 +00:00
select(
tableName
).use {
it.count
}
}.toLong()
2021-03-05 10:15:21 +00:00
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
2021-06-15 08:24:00 +00:00
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
2020-11-10 09:43:15 +00:00
it.count
}
}.toLong()
override suspend fun get(
k: Key,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> = count(k).let { count ->
if (pagination.firstIndex >= count) {
return@let emptyList<Value>().createPaginationResult(
pagination,
count
)
}
2020-11-10 09:43:15 +00:00
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
2021-03-05 10:15:21 +00:00
helper.blockingReadableTransaction {
2020-11-10 09:43:15 +00:00
select(
tableName,
2021-06-15 08:24:00 +00:00
valueColumnArray,
2020-11-10 09:43:15 +00:00
selection = "$idColumnName=?",
2021-06-13 05:48:37 +00:00
selectionArgs = arrayOf(k.keyAsString()),
2020-11-10 09:43:15 +00:00
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Value>().also {
if (c.moveToFirst()) {
do {
2021-06-13 05:48:37 +00:00
it.add(c.getString(valueColumnName).valueFromString())
2020-11-10 09:43:15 +00:00
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
if (pagination.firstIndex >= count) {
return@let emptyList<Key>().createPaginationResult(
pagination,
count
)
}
2020-11-10 09:43:15 +00:00
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
2021-03-05 10:15:21 +00:00
helper.blockingReadableTransaction {
2021-06-15 08:24:00 +00:00
selectDistinct(
2020-11-10 09:43:15 +00:00
tableName,
2021-06-15 08:24:00 +00:00
idColumnArray,
2020-11-10 09:43:15 +00:00
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
2021-06-13 05:48:37 +00:00
it.add(c.getString(idColumnName).keyFromString())
2020-11-10 09:43:15 +00:00
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
2020-11-14 10:44:28 +00:00
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = count().let { count ->
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
2021-03-05 10:15:21 +00:00
helper.blockingReadableTransaction {
2021-06-15 08:24:00 +00:00
selectDistinct(
2020-11-14 10:44:28 +00:00
tableName,
2021-06-15 08:24:00 +00:00
idColumnArray,
2020-11-14 10:44:28 +00:00
selection = "$valueColumnName=?",
2021-06-13 05:48:37 +00:00
selectionArgs = arrayOf(v.valueAsString()),
2020-11-14 10:44:28 +00:00
limit = resultPagination.limitClause()
).use { c ->
mutableListOf<Key>().also {
if (c.moveToFirst()) {
do {
2021-06-13 05:48:37 +00:00
it.add(c.getString(idColumnName).keyFromString())
2020-11-14 10:44:28 +00:00
} while (c.moveToNext())
}
}
}
}.createPaginationResult(
pagination,
count
)
}
2020-11-10 09:43:15 +00:00
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
2021-03-05 10:15:21 +00:00
helper.blockingWritableTransaction {
2020-11-10 09:43:15 +00:00
toRemove.flatMap { (k, vs) ->
vs.mapNotNullA { v ->
2021-06-13 05:48:37 +00:00
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.keyAsString(), v.valueAsString())) > 0) {
2020-11-10 09:43:15 +00:00
k to v
} else {
null
}
}
}
}.forEach { (k, v) ->
_onValueRemoved.emit(k to v)
}
}
}
2021-06-13 05:48:37 +00:00
fun <Key, Value> OneToManyAndroidRepo(
tableName: String,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
helper: SQLiteOpenHelper
) = OneToManyAndroidRepo(
tableName,
{ internalSerialFormat.encodeToString(keySerializer, this) },
{ internalSerialFormat.encodeToString(valueSerializer, this) },
{ internalSerialFormat.decodeFromString(keySerializer, this) },
{ internalSerialFormat.decodeFromString(valueSerializer, this) },
helper
)
2021-06-13 05:50:48 +00:00
fun <Key, Value> KeyValuesAndroidRepo(
tableName: String,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
helper: SQLiteOpenHelper
) = OneToManyAndroidRepo(tableName, keySerializer, valueSerializer, helper)