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

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

package com.lightningkite.lightningdb

import com.github.jershell.kbson.*
import com.lightningkite.khrysalis.IsCodableAndHashable
import com.lightningkite.lightningdb.*
import com.lightningkite.lightningserver.serialization.Serialization
import com.mongodb.client.model.UpdateOptions
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.descriptors.*
import org.bson.BsonDocument
import org.bson.BsonDocumentWriter
import org.bson.BsonNumber
import org.bson.BsonTimestamp
import org.bson.BsonType
import org.bson.BsonValue
import org.bson.Document
import org.bson.types.Binary
import org.bson.types.ObjectId
import java.math.BigDecimal
import java.time.*
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.regex.Pattern
import kotlin.reflect.KProperty1

fun documentOf(): Document {
    return Document()
}
fun documentOf(pair: Pair): Document {
    return Document(pair.first, pair.second)
}
fun documentOf(vararg pairs: Pair): Document {
    return Document().apply {
        for(entry in pairs) {
            this[entry.first] = entry.second
        }
    }
}

// TODO: This whole file is terrible

@Serializable private data class Wrapper(val value: T)
fun  KBson.stringifyAny(serializer: KSerializer, obj: T): BsonValue {
    return stringify(Wrapper.serializer(serializer), Wrapper(obj))["value"]!!
}


private fun  Condition.dump(serializer: KSerializer, into: Document = Document(), key: String?): Document {
    when (this) {
        is Condition.Always -> {}
        is Condition.Never -> into["thisFieldWillNeverExist"] = "no never"
        is Condition.And -> {
            into["\$and"] = conditions.map { it.dump(serializer, key = key)  }
        }
        is Condition.Or -> if(conditions.isEmpty()) into["thisFieldWillNeverExist"] = "no never" else into["\$or"] = conditions.map { it.dump(serializer, key = key)  }
        is Condition.Equal -> into.sub(key)["\$eq"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.NotEqual -> into.sub(key)["\$ne"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.SetAllElements<*> -> (condition as Condition).dump(serializer.listElement()!! as KSerializer, into.sub(key).sub("\$not").sub("\$elemMatch"), key = "\$not")
        is Condition.SetAnyElements<*> -> into.sub(key)["\$elemMatch"] = (condition as Condition).bson(serializer.listElement()!! as KSerializer)
        is Condition.ListAllElements<*> -> (condition as Condition).dump(serializer.listElement()!! as KSerializer, into.sub(key).sub("\$not").sub("\$elemMatch"), key = "\$not")
        is Condition.ListAnyElements<*> -> into.sub(key)["\$elemMatch"] = (condition as Condition).bson(serializer.listElement()!! as KSerializer)
        is Condition.Exists<*> -> into[if (key == null) this.key else "$key.${this.key}"] = documentOf("\$exists" to true)
        is Condition.GreaterThan -> into.sub(key)["\$gt"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.LessThan -> into.sub(key)["\$lt"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.GreaterThanOrEqual -> into.sub(key)["\$gte"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.LessThanOrEqual -> into.sub(key)["\$lte"] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Condition.IfNotNull<*> -> (condition as Condition).dump(serializer.nullElement()!! as KSerializer, into, key)
        is Condition.Inside -> into.sub(key)["\$in"] = values.let { Serialization.Internal.bson.stringifyAny(ListSerializer(serializer), it) }
        is Condition.NotInside -> into.sub(key)["\$nin"] = values.let { Serialization.Internal.bson.stringifyAny(ListSerializer(serializer), it) }
        is Condition.IntBitsAnyClear -> into.sub(key)["\$bitsAllClear"] = mask
        is Condition.IntBitsAnySet -> into.sub(key)["\$bitsAllSet"] = mask
        is Condition.IntBitsClear -> into.sub(key)["\$bitsAnyClear"] = mask
        is Condition.IntBitsSet -> into.sub(key)["\$bitsAnySet"] = mask
        is Condition.Not -> TODO("Condition inversion is not supported yet")
        is Condition.OnKey<*> -> (condition as Condition).dump(serializer.mapValueElement() as KSerializer, into, if (key == null) this.key else "$key.${this.key}")
        is Condition.StringContains -> {
            into.sub(key).also {
                it["\$regex"] = Regex.escape(this.value)
                it["\$options"] = if(this.ignoreCase) "i" else ""
            }
        }
        is Condition.RegexMatches -> {
            into.sub(key).also {
                it["\$regex"] = this.pattern
                it["\$options"] = if(this.ignoreCase) "i" else ""
            }
        }
        is Condition.FullTextSearch -> into["\$text"] = documentOf(
            "\$search" to value,
            "\$caseSensitive" to !this.ignoreCase
        )
        is Condition.SetSizesEquals<*> -> into.sub(key)["\$size"] = count
        is Condition.ListSizesEquals<*> -> into.sub(key)["\$size"] = count
        is Condition.OnField<*, *> -> (condition as Condition).dump((serializer as KSerializer).fieldSerializer(this.key as KProperty1) as KSerializer, into, if (key == null) this.key.name else "$key.${this.key.name}")
    }
    return into
}

private fun  Modification.dump(serializer: KSerializer, update: UpdateWithOptions = UpdateWithOptions(), key: String?): UpdateWithOptions {
    val into = update.document
    when(this) {
        is Modification.Chain -> modifications.forEach { it.dump(serializer, update, key) }
        is Modification.Assign -> into["\$set", key] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Modification.CoerceAtLeast -> into["\$max", key] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Modification.CoerceAtMost -> into["\$min", key] = value.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Modification.Increment -> into["\$inc", key] = by.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Modification.Multiply -> into["\$mul", key] = by.let { Serialization.Internal.bson.stringifyAny(serializer, it) }
        is Modification.AppendString -> TODO("Appending strings is not supported yet")
        is Modification.IfNotNull<*> -> (modification as Modification).dump(serializer.nullElement()!! as KSerializer, update, key)
        is Modification.OnField<*, *> ->
            (modification as Modification).dump((serializer as KSerializer).fieldSerializer(this.key as KProperty1) as KSerializer, update, if (key == null) this.key.name else "$key.${this.key.name}")
        is Modification.ListAppend<*> -> into.sub("\$push").sub(key)["\$each"] = items.let { Serialization.Internal.bson.stringifyAny(serializer as KSerializer>, it) }
        is Modification.ListRemove<*> -> into["\$pull", key] = (condition as Condition).bson(serializer.listElement() as KSerializer)
        is Modification.ListRemoveInstances<*> -> into["\$pullAll", key] = items.let { Serialization.Internal.bson.stringifyAny(serializer as KSerializer>, it) }
        is Modification.ListDropFirst<*> -> into["\$pop", key] = -1
        is Modification.ListDropLast<*> -> into["\$pop", key] = 1
        is Modification.ListPerElement<*> -> {
            val condIdentifier = genName()
            update.options = update.options.arrayFilters(
                (update.options.arrayFilters ?: listOf()) + (condition as Condition).dump(serializer.listElement() as KSerializer, key = condIdentifier)
            )
            (modification as Modification).dump(serializer.listElement() as KSerializer, update, "$key.$[$condIdentifier]")
        }
        is Modification.SetAppend<*> -> into.sub("\$addToSet").sub(key)["\$each"] = items.let { Serialization.Internal.bson.stringifyAny(serializer as KSerializer>, it) }
        is Modification.SetRemove<*> -> into["\$pull", key] = (condition as Condition).bson(serializer.listElement() as KSerializer)
        is Modification.SetRemoveInstances<*> -> into["\$pullAll", key] = items.let { Serialization.Internal.bson.stringifyAny(serializer as KSerializer>, it) }
        is Modification.SetDropFirst<*> -> into["\$pop", key] = -1
        is Modification.SetDropLast<*> -> into["\$pop", key] = 1
        is Modification.SetPerElement<*> -> {
            val condIdentifier = genName()
            update.options = update.options.arrayFilters(
                (update.options.arrayFilters ?: listOf()) + (condition as Condition).dump(serializer.listElement() as KSerializer, key = condIdentifier)
            )
            (modification as Modification).dump(serializer.listElement() as KSerializer, update, "$key.$[$condIdentifier]")
        }
        is Modification.Combine<*> -> map.forEach {
            into.sub("\$set")[if (key == null) it.key else "$key.${it.key}"] = it.value.let { Serialization.Internal.bson.stringifyAny(serializer.mapValueElement() as KSerializer, it) }
        }
        is Modification.ModifyByKey<*> -> map.forEach {
            (it.value as Modification).dump(serializer.mapValueElement() as KSerializer, update, if (key == null) it.key else "$key.${it.key}")
        }
        is Modification.RemoveKeys<*> -> this.fields.forEach {
            into.sub("\$unset")[if (key == null) it else "$key.${it}"] = ""
        }
    }
    return update
}


private fun Document.sub(key: String?): Document = if(key == null) this else getOrPut(key) { Document() } as Document
private operator fun Document.set(owner: String, key: String?, value: Any?) {
    if(key == null) this[owner] = value
    else this.sub(owner)[key] = value
}
private var lastNum = AtomicInteger()
private fun genName(): String {
    val r = 'a' + (lastNum.getAndIncrement() % 26)
    return r.toString()
}

data class UpdateWithOptions(
    val document: Document = Document(),
    var options: UpdateOptions = UpdateOptions()
)

fun  Condition.bson(serializer: KSerializer) = Document().also { dump(serializer, it, null) }
fun  Modification.bson(serializer: KSerializer): UpdateWithOptions = UpdateWithOptions().also { dump(serializer, it, null) }
fun  UpdateWithOptions.upsert(model: T, serializer: KSerializer): Boolean {
    val set: Document? = (document["\$set"] as? Document) ?: (document["\$set"] as? BsonDocument)?.toDocument()
    val inc = (document["\$inc"] as? Document) ?: (document["\$inc"] as? BsonDocument)?.toDocument()
    val restrict = document.entries.asSequence()
        .filter { it.key != "\$set" && it.key != "\$inc" }
        .map { it.value }
        .filterIsInstance()
        .flatMap { it.keys }
        .toSet()
    document["\$setOnInsert"] = Serialization.Internal.bson.stringify(serializer, model).toDocument().also {
        set?.keys?.forEach { k ->
            if(it[k] == set[k]) it.remove(k)
            else {
                return false
            }
        }
        inc?.keys?.forEach { k ->
            if((it[k] as Number).toDouble() == (inc[k] as BsonNumber).doubleValue()) it.remove(k)
            else {
                return false
            }
        }
        restrict.forEach { k ->
            if(it.containsKey(k)) return false
        }
    }
    options = options.upsert(true)
    return true
}

@OptIn(ExperimentalSerializationApi::class)
fun SerialDescriptor.bsonType(): BsonType = when(kind) {
    SerialKind.ENUM -> BsonType.STRING
    SerialKind.CONTEXTUAL -> when(this.capturedKClass){
        ObjectId::class -> BsonType.OBJECT_ID
        BigDecimal::class -> BsonType.DECIMAL128
        ByteArray::class -> BsonType.BINARY
        Date::class -> BsonType.DATE_TIME
        Calendar::class -> BsonType.DATE_TIME
        GregorianCalendar::class -> BsonType.DATE_TIME
        Instant::class -> BsonType.DATE_TIME
        ZonedDateTime::class -> BsonType.DATE_TIME
        OffsetDateTime::class -> BsonType.DATE_TIME
        LocalDate::class -> BsonType.DATE_TIME
        LocalDateTime::class -> BsonType.DATE_TIME
        LocalTime::class -> BsonType.DATE_TIME
        OffsetTime::class -> BsonType.DATE_TIME
        BsonTimestamp::class -> BsonType.DATE_TIME
        Locale::class -> BsonType.STRING
        Binary::class -> BsonType.BINARY
        Pattern::class -> BsonType.DOCUMENT
        Regex::class -> BsonType.DOCUMENT
        UUID::class -> BsonType.BINARY
        else -> Serialization.Internal.bson.serializersModule.getContextualDescriptor(this)!!.bsonType()
    }
    PrimitiveKind.BOOLEAN -> BsonType.BOOLEAN
    PrimitiveKind.BYTE -> BsonType.INT32
    PrimitiveKind.CHAR -> BsonType.SYMBOL
    PrimitiveKind.SHORT -> BsonType.INT32
    PrimitiveKind.INT -> BsonType.INT32
    PrimitiveKind.LONG -> BsonType.INT64
    PrimitiveKind.FLOAT -> BsonType.DOUBLE
    PrimitiveKind.DOUBLE -> BsonType.DOUBLE
    PrimitiveKind.STRING -> BsonType.STRING
    StructureKind.CLASS -> BsonType.DOCUMENT
    StructureKind.LIST -> BsonType.ARRAY
    StructureKind.MAP -> BsonType.DOCUMENT
    StructureKind.OBJECT -> BsonType.STRING
    PolymorphicKind.SEALED -> TODO()
    PolymorphicKind.OPEN -> TODO()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy