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

com.lightningkite.lightningdb.InMemoryFieldCollection.kt Maven / Gradle / Ivy

The newest version!
package com.lightningkite.lightningdb

import com.lightningkite.lightningserver.exceptions.BadRequestException
import io.ktor.util.reflect.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.serialization.KSerializer
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

/**
 * A FieldCollection who's underlying implementation is actually manipulating a MutableList.
 * This is useful for times that an actual database is not needed, and you need to move fast, such as during Unit Tests.
 */
open class InMemoryFieldCollection(
    val data: MutableList = ArrayList(),
    val serializer: KSerializer
) :
    AbstractSignalFieldCollection() {

    private val lock = ReentrantLock()

    private val uniqueIndexChecks = ConcurrentLinkedQueue<(List>) -> Unit>()

    private fun uniqueCheck(changed: EntryChange) = uniqueCheck(listOf(changed))
    private fun uniqueCheck(changes: List>) = uniqueIndexChecks.forEach { it(changes) }

    init {
        serializer.descriptor.indexes().forEach { index: NeededIndex ->
            if (index.unique) {
                val fields =
                    serializer.attemptGrabFields().filterKeys { index.fields.contains(it) }.values
                uniqueIndexChecks.add { changes: List> ->
                    val fieldChanges = changes.mapNotNull { entryChange ->
                        if (
                            (entryChange.old == null && entryChange.new != null) ||
                            (entryChange.old != null &&
                                    entryChange.new != null &&
                                    fields.any { it.get(entryChange.old!!) != it.get(entryChange.new!!) })
                        )
                            fields.map { it to it.get(entryChange.new!!) }
                        else
                            null
                    }
                    fieldChanges.forEach { fieldValues ->
                        if (data.any { fromDb -> fieldValues.all { (property, value) -> property.get(fromDb) == value } }) {
                            throw BadRequestException("Unique Index Violation. The following fields are already in the database: ${fieldValues.joinToString { (property, value) -> "${property.name}: $value" }}")
                        }
                    }
                }
            }
        }
    }

    override suspend fun find(
        condition: Condition,
        orderBy: List>,
        skip: Int,
        limit: Int,
        maxQueryMs: Long,
    ): Flow = flow {
        val result = lock.withLock {
            data.asSequence()
                .filter { condition(it) }
                .let {
                    orderBy.comparator?.let { c ->
                        it.sortedWith(c)
                    } ?: it
                }
                .drop(skip)
                .take(limit)
                .toList()
        }
        result
            .forEach {
                emit(it)
            }
    }

    override suspend fun count(condition: Condition): Int = data.count { condition(it) }

    @Suppress("UNCHECKED_CAST")
    override suspend fun  groupCount(
        condition: Condition,
        groupBy: DataClassPath,
    ): Map = data.filter { condition(it) }.groupingBy { groupBy.get(it) }.eachCount().minus(null) as Map

    override suspend fun  aggregate(
        aggregate: Aggregate,
        condition: Condition,
        property: DataClassPath,
    ): Double? =
        data.asSequence().filter { condition(it) }.mapNotNull { property.get(it)?.toDouble() }.aggregate(aggregate)

    override suspend fun  groupAggregate(
        aggregate: Aggregate,
        condition: Condition,
        groupBy: DataClassPath,
        property: DataClassPath,
    ): Map = data.asSequence().filter { condition(it) }
        .mapNotNull { (groupBy.get(it) ?: return@mapNotNull null) to (property.get(it)?.toDouble() ?: return@mapNotNull null) }.aggregate(aggregate)

    override suspend fun insertImpl(models: Iterable): List = lock.withLock {
        uniqueCheck(models.map { EntryChange(null, it) })
        data.addAll(models)
        return models.toList()
    }

    override suspend fun replaceOneImpl(
        condition: Condition,
        model: Model,
        orderBy: List>,
    ): EntryChange = lock.withLock {
        for (it in sortIndices(orderBy)) {
            val old = data[it]
            if (condition(old)) {
                val changed = EntryChange(old, model)
                uniqueCheck(changed)
                data[it] = model
                return changed
            }
        }
        return EntryChange(null, null)
    }

    private fun sortIndices(orderBy: List>): Iterable {
        return data.indices.let {
            orderBy.comparator?.let { c ->
                it.sortedWith { a, b -> c.compare(data[a], data[b]) }
            } ?: it
        }
    }

    override suspend fun upsertOneImpl(
        condition: Condition,
        modification: Modification,
        model: Model,
    ): EntryChange = lock.withLock {
        for (it in data.indices) {
            val old = data[it]
            if (condition(old)) {
                val new = modification(old)
                val changed = EntryChange(old, new)
                uniqueCheck(changed)
                data[it] = new
                return changed
            }
        }
        data.add(model)
        return EntryChange(null, model)
    }

    override suspend fun updateOneImpl(
        condition: Condition,
        modification: Modification,
        orderBy: List>,
    ): EntryChange = lock.withLock {
        for (it in sortIndices(orderBy)) {
            val old = data[it]
            if (condition(old)) {
                val new = modification(old)
                val changed = EntryChange(old, new)
                uniqueCheck(changed)
                data[it] = new
                return changed
            }
        }
        return EntryChange(null, null)
    }

    override suspend fun updateManyImpl(
        condition: Condition,
        modification: Modification,
    ): CollectionChanges = lock.withLock {
        return data.indices
            .mapNotNull {
                val old = data[it]
                if (condition(old)) {
                    val new = modification(old)
                    it to EntryChange(old, new)
                } else null
            }
            .let {
                val changes = it.map { it.second }
                uniqueCheck(changes)
                it.forEach { (index, change) -> data[index] = change.new!! }
                CollectionChanges(changes = changes)
            }
    }

    override suspend fun deleteOneImpl(condition: Condition, orderBy: List>): Model? =
        lock.withLock {
            for (it in sortIndices(orderBy)) {
                val old = data[it]
                if (condition(old)) {
                    data.removeAt(it)
                    return old
                }
            }
            return null
        }

    override suspend fun deleteManyImpl(condition: Condition): List = lock.withLock {
        val removed = ArrayList()
        data.removeAll {
            if (condition(it)) {
                removed.add(it)
                true
            } else {
                false
            }
        }
        return removed
    }

    override suspend fun replaceOneIgnoringResultImpl(
        condition: Condition,
        model: Model,
        orderBy: List>,
    ): Boolean = replaceOne(
        condition,
        model,
        orderBy
    ).new != null

    override suspend fun upsertOneIgnoringResultImpl(
        condition: Condition,
        modification: Modification,
        model: Model,
    ): Boolean = upsertOne(condition, modification, model).old != null

    override suspend fun updateOneIgnoringResultImpl(
        condition: Condition,
        modification: Modification,
        orderBy: List>,
    ): Boolean = updateOne(condition, modification, orderBy).new != null

    override suspend fun updateManyIgnoringResultImpl(
        condition: Condition,
        modification: Modification,
    ): Int = updateMany(condition, modification).changes.size

    override suspend fun deleteOneIgnoringOldImpl(
        condition: Condition,
        orderBy: List>,
    ): Boolean = deleteOne(condition, orderBy) != null

    override suspend fun deleteManyIgnoringOldImpl(condition: Condition): Int = deleteMany(condition).size

    fun drop() {
        data.clear()
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy