All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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