diff --git a/CHANGELOG.md b/CHANGELOG.md index 801f8f0164e..ccb6108563b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.12.3 +* `Repos`: + * `Exposed`: + * Add abstract exposed variants of `KeyValue` and `KeyValues` repos + * Add new extension `Query#selectPaginated` + ## 0.12.2 * `Versions`: diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/AbstractExposedWriteCRUDRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/AbstractExposedWriteCRUDRepo.kt index 5b6588880b7..6bb44a53052 100644 --- a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/AbstractExposedWriteCRUDRepo.kt +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/AbstractExposedWriteCRUDRepo.kt @@ -26,7 +26,6 @@ abstract class AbstractExposedWriteCRUDRepo( override val deletedObjectsIdsFlow: Flow = _deletedObjectsIdsFlow.asSharedFlow() protected abstract fun InsertStatement.asObject(value: InputValueType): ObjectType - abstract val selectByIds: SqlExpressionBuilder.(List) -> Op protected abstract fun insert(value: InputValueType, it: InsertStatement) protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement) diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt new file mode 100644 index 00000000000..f30da0c9eda --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt @@ -0,0 +1,9 @@ +package dev.inmo.micro_utils.repos.exposed + +import org.jetbrains.exposed.sql.* + +interface CommonExposedRepo : ExposedRepo { + val ResultRow.asObject: ObjectType + val selectById: SqlExpressionBuilder.(IdType) -> Op + val selectByIds: SqlExpressionBuilder.(List) -> Op +} diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/ExposedCRUDRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/ExposedCRUDRepo.kt index a7505f5e727..258e62b7950 100644 --- a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/ExposedCRUDRepo.kt +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/ExposedCRUDRepo.kt @@ -2,7 +2,4 @@ package dev.inmo.micro_utils.repos.exposed import org.jetbrains.exposed.sql.* -interface ExposedCRUDRepo : ExposedRepo { - val ResultRow.asObject: ObjectType - val selectById: SqlExpressionBuilder.(IdType) -> Op -} +interface ExposedCRUDRepo : CommonExposedRepo diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedKeyValueRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedKeyValueRepo.kt new file mode 100644 index 00000000000..528c669ec8b --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedKeyValueRepo.kt @@ -0,0 +1,72 @@ +package dev.inmo.micro_utils.repos.exposed.keyvalue + +import dev.inmo.micro_utils.repos.KeyValueRepo +import kotlinx.coroutines.flow.* +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.statements.UpdateStatement +import org.jetbrains.exposed.sql.transactions.transaction + +abstract class AbstractExposedKeyValueRepo( + override val database: Database, + tableName: String? = null +) : KeyValueRepo, AbstractExposedReadKeyValueRepo( + database, + tableName +) { + protected val _onNewValue = MutableSharedFlow>() + protected val _onValueRemoved = MutableSharedFlow() + + override val onNewValue: Flow> = _onNewValue.asSharedFlow() + override val onValueRemoved: Flow = _onValueRemoved.asSharedFlow() + + protected abstract fun update(k: Key, v: Value, it: UpdateStatement) + protected abstract fun insert(k: Key, v: Value, it: InsertStatement) + + override suspend fun set(toSet: Map) { + transaction(database) { + toSet.mapNotNull { (k, v) -> + if (update({ selectById(k) }) { update(k, v, it) } > 0) { + k to v + } else { + val inserted = insert { + insert(k, v, it) + }.getOrNull(keyColumn) != null + if (inserted) { + k to v + } else { + null + } + } + } + }.forEach { + _onNewValue.emit(it) + } + } + + override suspend fun unset(toUnset: List) { + transaction(database) { + toUnset.mapNotNull { + if (deleteWhere { selectById(it) } > 0) { + it + } else { + null + } + } + }.forEach { + _onValueRemoved.emit(it) + } + } + + override suspend fun unsetWithValues(toUnset: List) { + transaction(database) { + toUnset.flatMap { + val keys = select { selectByValue(it) }.mapNotNull { it.asKey } + deleteWhere { selectByIds(keys) } + keys + } + }.distinct().forEach { + _onValueRemoved.emit(it) + } + } +} diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedReadKeyValueRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedReadKeyValueRepo.kt new file mode 100644 index 00000000000..60c142f7a8f --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/AbstractExposedReadKeyValueRepo.kt @@ -0,0 +1,60 @@ +package dev.inmo.micro_utils.repos.exposed.keyvalue + +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.repos.ReadKeyValueRepo +import dev.inmo.micro_utils.repos.exposed.* +import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction + +abstract class AbstractExposedReadKeyValueRepo( + override val database: Database, + tableName: String? = null +) : ReadKeyValueRepo, + CommonExposedRepo, + Table(tableName ?: "") { + abstract val keyColumn: Column<*> + abstract val ResultRow.asKey: Key + abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op + + override suspend fun get(k: Key): Value? = transaction(database) { + select { selectById(k) }.limit(1).firstOrNull() ?.asObject + } + + override suspend fun contains(key: Key): Boolean = transaction(database) { + select { selectById(key) }.limit(1).any() + } + + override suspend fun count(): Long = transaction(database) { selectAll().count() } + + override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult = transaction(database) { + selectAll().selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asKey + } + } + + override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult = transaction(database) { + select { selectByValue(v) }.selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asKey + } + } + + override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult = transaction(database) { + selectAll().selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asObject + } + } +} diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/ExposedReadKeyValueRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/ExposedReadKeyValueRepo.kt index 7fa979d4a26..fe52ac4de69 100644 --- a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/ExposedReadKeyValueRepo.kt +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/keyvalue/ExposedReadKeyValueRepo.kt @@ -3,7 +3,9 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.repos.ReadKeyValueRepo import dev.inmo.micro_utils.repos.exposed.* +import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction open class ExposedReadKeyValueRepo( diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedKeyValueRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedKeyValueRepo.kt new file mode 100644 index 00000000000..dd7b319cf9b --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedKeyValueRepo.kt @@ -0,0 +1,69 @@ +package dev.inmo.micro_utils.repos.exposed.onetomany + +import dev.inmo.micro_utils.repos.KeyValuesRepo +import kotlinx.coroutines.flow.* +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.InsertStatement +import org.jetbrains.exposed.sql.transactions.transaction + +abstract class AbstractExposedKeyValuesRepo( + override val database: Database, + tableName: String? = null +) : KeyValuesRepo, AbstractExposedReadKeyValuesRepo( + database, + tableName +) { + protected val _onNewValue: MutableSharedFlow> = MutableSharedFlow() + override val onNewValue: Flow> + get() = _onNewValue + protected val _onValueRemoved: MutableSharedFlow> = MutableSharedFlow() + override val onValueRemoved: Flow> + get() = _onValueRemoved + protected val _onDataCleared: MutableSharedFlow = MutableSharedFlow() + override val onDataCleared: Flow + get() = _onDataCleared + + protected abstract fun insert(k: Key, v: Value, it: InsertStatement) + + override suspend fun add(toAdd: Map>) { + transaction(database) { + toAdd.keys.flatMap { k -> + toAdd[k] ?.mapNotNull { v -> + if (select { selectById(k).and(selectByValue(v)) }.limit(1).any()) { + return@mapNotNull null + } + val insertResult = insert { + insert(k, v, it) + } + if (insertResult.insertedCount > 0) { + k to v + } else { + null + } + } ?: emptyList() + } + }.forEach { _onNewValue.emit(it) } + } + + override suspend fun remove(toRemove: Map>) { + transaction(database) { + toRemove.keys.flatMap { k -> + toRemove[k] ?.mapNotNull { v -> + if (deleteWhere { selectById(k).and(selectByValue(v)) } > 0 ) { + k to v + } else { + null + } + } ?: emptyList() + } + }.forEach { + _onValueRemoved.emit(it) + } + } + + override suspend fun clear(k: Key) { + transaction(database) { + deleteWhere { selectById(k) } + }.also { _onDataCleared.emit(k) } + } +} diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedReadKeyValueRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedReadKeyValueRepo.kt new file mode 100644 index 00000000000..1575c9b0d04 --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany/AbstractExposedReadKeyValueRepo.kt @@ -0,0 +1,74 @@ +package dev.inmo.micro_utils.repos.exposed.onetomany + +import dev.inmo.micro_utils.pagination.* +import dev.inmo.micro_utils.repos.ReadKeyValueRepo +import dev.inmo.micro_utils.repos.ReadKeyValuesRepo +import dev.inmo.micro_utils.repos.exposed.* +import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.transactions.transaction + +abstract class AbstractExposedReadKeyValuesRepo( + override val database: Database, + tableName: String? = null +) : ReadKeyValuesRepo, + CommonExposedRepo, + Table(tableName ?: "") { + abstract val keyColumn: Column<*> + abstract val ResultRow.asKey: Key + abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op + + override suspend fun count(k: Key): Long = transaction(database) { select { selectById(k) }.count() } + + override suspend fun count(): Long = transaction(database) { selectAll().count() } + + override suspend fun get( + k: Key, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = transaction(database) { + select { selectById(k) }.selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asObject + } + } + + override suspend fun keys( + pagination: Pagination, + reversed: Boolean + ): PaginationResult = transaction(database) { + selectAll().selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asKey + } + } + + override suspend fun keys( + v: Value, + pagination: Pagination, + reversed: Boolean + ): PaginationResult = transaction(database) { + select { selectByValue(v) }.selectPaginated( + pagination, + keyColumn, + reversed + ) { + it.asKey + } + } + + override suspend fun contains(k: Key): Boolean = transaction(database) { + select { selectById(k) }.limit(1).any() + } + + override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) { + select { selectById(k).and(selectByValue(v)) }.limit(1).any() + } +} diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/utils/PaginatedSelect.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/utils/PaginatedSelect.kt new file mode 100644 index 00000000000..a1a327e4e07 --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/utils/PaginatedSelect.kt @@ -0,0 +1,21 @@ +package dev.inmo.micro_utils.repos.exposed.utils + +import dev.inmo.micro_utils.pagination.* +import org.jetbrains.exposed.sql.* + +fun Query.selectPaginated( + pagination: Pagination, + orderBy: Pair, SortOrder>? = null, + createResult: (ResultRow) -> T +): PaginationResult { + val count = count() + val list = paginate(pagination, orderBy).map(createResult) + return list.createPaginationResult(pagination, count) +} + +fun Query.selectPaginated( + pagination: Pagination, + orderBy: Expression<*>?, + reversed: Boolean = false, + createResult: (ResultRow) -> T +) = selectPaginated(pagination, orderBy ?.let { it to if (reversed) SortOrder.DESC else SortOrder.ASC }, createResult)