com.lightningkite.lightningserver.db.PostgresCollection.kt Maven / Gradle / Ivy
package com.lightningkite.lightningserver.db
import com.lightningkite.lightningdb.*
import com.lightningkite.lightningserver.serialization.Serialization
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils.statementsRequiredToActualizeScheme
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import java.sql.Connection.TRANSACTION_READ_COMMITTED
import java.sql.Connection.TRANSACTION_SERIALIZABLE
import kotlin.reflect.KProperty1
class PostgresCollection(
val db: Database,
val name: String,
val serializer: KSerializer,
) : AbstractSignalFieldCollection() {
companion object {
var format = DbMapLikeFormat(Serialization.module)
}
val table = SerialDescriptorTable(name, serializer.descriptor)
suspend inline fun t(noinline action: suspend Transaction.()->T): T = newSuspendedTransaction(Dispatchers.IO, db = db, transactionIsolation = TRANSACTION_READ_COMMITTED, action)
@OptIn(DelicateCoroutinesApi::class, ExperimentalSerializationApi::class)
val prepare = GlobalScope.async(Dispatchers.Unconfined, start = CoroutineStart.LAZY) {
t {
statementsRequiredToActualizeScheme(table).forEach {
exec(it)
}
}
}
override suspend fun find(
condition: Condition,
orderBy: List>,
skip: Int,
limit: Int,
maxQueryMs: Long,
): Flow {
prepare.await()
val items = t {
table
.select { condition(condition, serializer, table).asOp() }
.orderBy(*orderBy.map { table.col[it.field.property.name]!! to if (it.ascending) SortOrder.ASC else SortOrder.DESC }
.toTypedArray())
.limit(limit, skip.toLong())
// .prep
.map { format.decode(serializer, it) }
}
return items.asFlow()
}
override suspend fun count(condition: Condition): Int {
prepare.await()
return t {
table
.select { condition(condition, serializer, table).asOp() }
.count().toInt()
}
}
override suspend fun groupCount(condition: Condition, groupBy: KProperty1): Map {
prepare.await()
return t {
val groupCol = table.col[groupBy.name] as Column
val count = Count(stringLiteral("*"))
table.slice(groupCol, count)
.select { condition(condition, serializer, table).asOp() }
.groupBy(table.col[groupBy.name]!!).associate { it[groupCol] to it[count].toInt() }
}
}
override suspend fun aggregate(
aggregate: Aggregate,
condition: Condition,
property: KProperty1,
): Double? {
prepare.await()
return t {
val valueCol = table.col[property.name] as Column
val agg = when(aggregate) {
Aggregate.Sum -> Sum(valueCol, DecimalColumnType(Int.MAX_VALUE, 8))
Aggregate.Average -> Avg(valueCol, 8)
Aggregate.StandardDeviationSample -> StdDevSamp(valueCol, 8)
Aggregate.StandardDeviationPopulation -> StdDevPop(valueCol, 8)
}
table.slice(agg)
.select { condition(condition, serializer, table).asOp() }
.firstOrNull()?.get(agg)?.toDouble()
}
}
override suspend fun groupAggregate(
aggregate: Aggregate,
condition: Condition,
groupBy: KProperty1,
property: KProperty1,
): Map {
prepare.await()
return t {
val groupCol = table.col[groupBy.name] as Column
val valueCol = table.col[property.name] as Column
val agg = when(aggregate) {
Aggregate.Sum -> Sum(valueCol, DoubleColumnType())
Aggregate.Average -> Avg(valueCol, 8)
Aggregate.StandardDeviationSample -> StdDevSamp(valueCol, 8)
Aggregate.StandardDeviationPopulation -> StdDevPop(valueCol, 8)
}
table.slice(groupCol, agg)
.select { condition(condition, serializer, table).asOp() }
.groupBy(table.col[groupBy.name]!!).associate { it[groupCol] to it[agg]?.toDouble() }
}
}
override suspend fun insertImpl(models: Iterable): List {
prepare.await()
t {
table.batchInsert(models) {
format.encode(serializer, it, this)
}
}
return models.toList()
}
override suspend fun replaceOneImpl(condition: Condition, model: T, orderBy: List>): EntryChange {
return updateOneImpl(condition, Modification.Assign(model), orderBy)
}
override suspend fun replaceOneIgnoringResultImpl(condition: Condition, model: T, orderBy: List>): Boolean {
return updateOneIgnoringResultImpl(condition, Modification.Assign(model), orderBy)
}
override suspend fun upsertOneImpl(condition: Condition, modification: Modification, model: T): EntryChange {
return newSuspendedTransaction(db = db, transactionIsolation = TRANSACTION_SERIALIZABLE) {
val existing = findOne(condition)
if(existing == null) {
EntryChange(null, insertImpl(listOf(model)).first())
} else
updateOneImpl(condition, modification)
}
}
override suspend fun upsertOneIgnoringResultImpl(
condition: Condition,
modification: Modification,
model: T,
): Boolean {
return newSuspendedTransaction(db = db, transactionIsolation = TRANSACTION_SERIALIZABLE) {
val existing = findOne(condition)
if(existing == null) {
insertImpl(listOf(model))
false
} else
updateOneIgnoringResultImpl(condition, modification, listOf())
}
}
override suspend fun updateOneImpl(
condition: Condition,
modification: Modification,
orderBy: List>
): EntryChange {
if(orderBy.isNotEmpty()) throw UnsupportedOperationException()
return t {
val old = table.updateReturningOld(
where = { condition(condition, serializer, table).asOp() },
limit = 1,
body = {
it.modification(modification, serializer, table)
}
)
old.map { format.decode(serializer, it) }.firstOrNull()?.let {
EntryChange(it, modification(it))
} ?: EntryChange()
}
}
override suspend fun updateOneIgnoringResultImpl(
condition: Condition,
modification: Modification,
orderBy: List>
): Boolean {
if(orderBy.isNotEmpty()) throw UnsupportedOperationException()
return t {
table.update(
where = { condition(condition, serializer, table).asOp() },
limit = null,
body = {
it.modification(modification, serializer, table)
}
)
} > 0
}
override suspend fun updateManyImpl(condition: Condition, modification: Modification): CollectionChanges {
return t {
val old = table.updateReturningOld(
where = { condition(condition, serializer, table).asOp() },
limit = null,
body = {
it.modification(modification, serializer, table)
}
)
CollectionChanges(old.map { format.decode(serializer, it) }.map {
EntryChange(it, modification(it))
})
}
}
override suspend fun updateManyIgnoringResultImpl(condition: Condition, modification: Modification): Int {
return t {
table.update(
where = { condition(condition, serializer, table).asOp() },
limit = null,
body = {
it.modification(modification, serializer, table)
}
)
}
}
override suspend fun deleteOneImpl(condition: Condition, orderBy: List>): T? {
if(orderBy.isNotEmpty()) throw UnsupportedOperationException()
return t {
table.deleteReturningWhere(
limit = 1,
where = { condition(condition, serializer, table).asOp() }
).firstOrNull()?.let { format.decode(serializer, it) }
}
}
override suspend fun deleteOneIgnoringOldImpl(condition: Condition, orderBy: List>): Boolean {
if(orderBy.isNotEmpty()) throw UnsupportedOperationException()
return t {
table.deleteWhere(
limit = 1,
op = { condition(condition, serializer, table).asOp() }
) > 0
}
}
override suspend fun deleteManyImpl(condition: Condition): List {
return t {
table.deleteReturningWhere(
where = { condition(condition, serializer, table).asOp() }
).map { format.decode(serializer, it) }
}
}
override suspend fun deleteManyIgnoringOldImpl(condition: Condition): Int {
return t {
table.deleteWhere(
op = { condition(condition, serializer, table).asOp() }
)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy