
com.lightningkite.lightningserver.db.SerialDescriptorTable.kt Maven / Gradle / Ivy
package com.lightningkite.lightningserver.db
import com.lightningkite.lightningdb.*
import com.lightningkite.lightningdb.Index
import com.lightningkite.lightningserver.serialization.Serialization
import io.ktor.websocket.Serializer
import kotlinx.coroutines.launch
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.AbstractEncoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.internal.GeneratedSerializer
import kotlinx.serialization.modules.SerializersModule
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.javatime.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.*
class SerialDescriptorTable(name: String, val descriptor: SerialDescriptor) : Table(name.replace(".", "__")) {
val columnsByDotPath = HashMap, ArrayList>>()
init {
descriptor.columnType()
.forEach {
val path = buildList {
var current = descriptor
for (index in it.descriptorPath) {
if (current.kind == StructureKind.CLASS) {
add(current.getElementName(index))
}
current = current.getElementDescriptor(index)
}
}
val col = registerColumn(it.key.joinToString("__"), it.type)
for(partialSize in 1..path.size)
columnsByDotPath.getOrPut(path.subList(0, partialSize)) { ArrayList() }.add(col)
}
}
override val primaryKey: PrimaryKey? = columns.find { it.name =="_id" }?.let { PrimaryKey(it) }
val col = columns.associateBy { it.name }
init {
val seen = HashSet()
fun handleDescriptor(descriptor: SerialDescriptor) {
if (!seen.add(descriptor)) return
descriptor.annotations.forEach {
when (it) {
is UniqueSet -> index(
isUnique = true,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
is UniqueSetJankPatch -> {
val sets: MutableList> = mutableListOf()
var current = mutableListOf()
it.fields.forEach { value ->
if (value == ":") {
sets.add(current)
current = mutableListOf()
} else {
current.add(value)
}
}
sets.add(current)
sets.forEach { set ->
index(
isUnique = true,
columns = set.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
}
}
is IndexSet -> index(
isUnique = false,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
is IndexSetJankPatch -> {
val sets: MutableList> = mutableListOf()
var current = mutableListOf()
it.fields.forEach { value ->
if (value == ":") {
sets.add(current)
current = mutableListOf()
} else {
current.add(value)
}
}
sets.add(current)
sets.forEach { set ->
index(
isUnique = true,
columns = set.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
}
}
is TextIndex -> {
// TODO
}
is NamedUniqueSet -> index(
customIndexName = it.indexName,
isUnique = true,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
is NamedUniqueSetJankPatch -> {
val sets: MutableList> = mutableListOf()
var current = mutableListOf()
it.fields.forEach { value ->
if (value == ":") {
sets.add(current)
current = mutableListOf()
} else {
current.add(value)
}
}
sets.add(current)
val names = it.indexNames.split(":").map { it.trim() }
sets.forEachIndexed { index, set ->
index(
customIndexName = names.getOrNull(index),
isUnique = true,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
}
}
is NamedIndexSet -> index(
customIndexName = it.indexName,
isUnique = false,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
is NamedIndexSetJankPatch -> {
val sets: MutableList> = mutableListOf()
var current = mutableListOf()
it.fields.forEach { value ->
if (value == ":") {
sets.add(current)
current = mutableListOf()
} else {
current.add(value)
}
}
sets.add(current)
val names = it.indexNames.split(":").map { it.trim() }
sets.forEachIndexed { index, set ->
index(
customIndexName = names.getOrNull(index),
isUnique = true,
columns = it.fields.flatMap { columnsByDotPath[it.split('.')]!! }.toTypedArray()
)
}
}
}
}
(0 until descriptor.elementsCount).forEach { index ->
val sub = descriptor.getElementDescriptor(index)
if (sub.kind == StructureKind.CLASS) handleDescriptor(sub)
descriptor.getElementAnnotations(index).forEach {
when (it) {
is Unique -> index(
isUnique = true,
columns = columnsByDotPath[listOf(descriptor.getElementName(index))]!!.toTypedArray()
)
is Index -> index(
isUnique = false,
columns = columnsByDotPath[listOf(descriptor.getElementName(index))]!!.toTypedArray()
)
is NamedUnique -> index(
customIndexName = it.indexName,
isUnique = true,
columns = columnsByDotPath[listOf(descriptor.getElementName(index))]!!.toTypedArray()
)
is NamedIndex -> index(
customIndexName = it.indexName,
isUnique = false,
columns = columnsByDotPath[listOf(descriptor.getElementName(index))]!!.toTypedArray()
)
}
}
}
}
handleDescriptor(descriptor)
}
}
/**
* TABLE FORMAT
*
* Lists become Structure of Arrays (SOA)
* Maps become Structure of Arrays (SOA) as well
* Classes have an additional not null field if needed
*
*/
data class SerialDescriptorColumns(val descriptor: SerialDescriptor, val columns: List>)
internal data class ColumnTypeInfo(val key: List, val type: ColumnType, val descriptorPath: List)
internal fun SerialDescriptor.columnType(): List = when (this.unnull()) {
UUIDSerializer.descriptor -> listOf(
ColumnTypeInfo(
listOf(),
UUIDColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
LocalDateSerializer.descriptor -> listOf(
ColumnTypeInfo(
listOf(),
JavaLocalDateColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
InstantSerializer.descriptor -> listOf(
ColumnTypeInfo(
listOf(),
JavaInstantColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
DurationSerializer.descriptor -> listOf(
ColumnTypeInfo(
listOf(),
JavaDurationColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
//LocalDateTimeSerializer.descriptor -> listOf(listOf() to JavaLocalDateTimeColumnType().also { it.nullable = this.isNullable })
LocalTimeSerializer.descriptor -> listOf(
ColumnTypeInfo(
listOf(),
JavaLocalTimeColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
ZonedDateTimeSerializer.descriptor -> listOf(
ColumnTypeInfo(listOf(), JavaInstantColumnType().also { it.nullable = this.isNullable }, listOf()),
ColumnTypeInfo(listOf("zone"), VarCharColumnType(32).also { it.nullable = this.isNullable }, listOf()),
)
else -> when (kind) {
SerialKind.CONTEXTUAL -> throw Error()
PolymorphicKind.OPEN -> throw NotImplementedError()
PolymorphicKind.SEALED -> throw NotImplementedError()
PrimitiveKind.BOOLEAN -> listOf(
ColumnTypeInfo(
listOf(),
BooleanColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.BYTE -> listOf(
ColumnTypeInfo(
listOf(),
ByteColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.CHAR -> listOf(
ColumnTypeInfo(
listOf(),
CharColumnType(1).also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.DOUBLE -> listOf(
ColumnTypeInfo(
listOf(),
DoubleColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.FLOAT -> listOf(
ColumnTypeInfo(
listOf(),
FloatColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.INT -> listOf(
ColumnTypeInfo(
listOf(),
IntegerColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.LONG -> listOf(
ColumnTypeInfo(
listOf(),
LongColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.SHORT -> listOf(
ColumnTypeInfo(
listOf(),
ShortColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
PrimitiveKind.STRING -> listOf(
ColumnTypeInfo(
listOf(),
TextColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
SerialKind.ENUM -> listOf(
ColumnTypeInfo(
listOf(),
TextColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
StructureKind.LIST -> getRealElementDescriptor(0).columnType()
.map {
ColumnTypeInfo(
it.key,
ArrayColumnType(it.type).also { it.nullable = this.isNullable },
listOf(0) + it.descriptorPath
)
}
StructureKind.CLASS -> {
val nullCol = if (isNullable) listOf(
ColumnTypeInfo(
listOf("exists"),
BooleanColumnType(),
listOf()
)
) else listOf()
nullCol + (0 until elementsCount).flatMap { index ->
this.getRealElementDescriptor(index).columnType().map { sub ->
ColumnTypeInfo(
key = (listOf(getElementName(index)) + sub.key),
type = sub.type.also {
it.nullable = it.nullable || isNullable
},
descriptorPath = listOf(index) + sub.descriptorPath
)
}
}
}
StructureKind.MAP -> {
getRealElementDescriptor(0).columnType()
.map {
ColumnTypeInfo(
it.key,
ArrayColumnType(it.type).also { it.nullable = this.isNullable },
listOf(0) + it.descriptorPath
)
}
.plus(
getRealElementDescriptor(1).columnType().map {
ColumnTypeInfo(
it.key + "value",
ArrayColumnType(it.type).also { it.nullable = this.isNullable },
listOf(1) + it.descriptorPath
)
})
}
StructureKind.OBJECT -> listOf(
ColumnTypeInfo(
listOf(),
TextColumnType().also { it.nullable = this.isNullable },
listOf()
)
)
}
}
private fun SerialDescriptor.getRealElementDescriptor(index: Int): SerialDescriptor {
val e = getElementDescriptor(index)
return if (e.kind == SerialKind.CONTEXTUAL) {
if (e.isNullable) {
SerialDescriptorForNullable(Serialization.module.getContextualDescriptor(e)!!)
} else {
Serialization.module.getContextualDescriptor(e)!!
}
} else e
}
private fun SerialDescriptor.unnull(): SerialDescriptor = this.nullElement() ?: this
internal class SerialDescriptorForNullable(
internal val original: SerialDescriptor,
) : SerialDescriptor by original {
override val serialName: String = original.serialName + "?"
override val isNullable: Boolean
get() = true
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SerialDescriptorForNullable) return false
if (original != other.original) return false
return true
}
override fun toString(): String {
return "$original?"
}
override fun hashCode(): Int {
return original.hashCode() * 31
}
}
//interface PerColumnWriter {
// fun handle(column: FutureColumn, value: T)
//}
//
//interface PerColumnReader {
// fun handle(column: FutureColumn): T
//}
//
//data class FutureColumn(val name: String, val type: ColumnType, val table: Table) {
// val column: Column by lazy { table.registerColumn(name, type) }
//}
//
//interface FieldHandler {
// val columns: List>
// fun split(value: T, action: PerColumnWriter)
// fun merge(action: PerColumnReader): T
//}
//
//class ColumnHandler(val column: FutureColumn) : FieldHandler {
// override val columns: List> = listOf(column)
// override fun split(value: T, action: PerColumnWriter) {
// action.handle(column, value)
// }
//
// override fun merge(action: PerColumnReader): T {
// return action.handle(column)
// }
//}
//
//
//class ZonedDateTimeFieldHandler(val prefix: List, table: Table, isNullable: Boolean) :
// FieldHandler {
// val main = ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// JavaInstantColumnType().also { it.nullable = isNullable },
// table
// )
// )
// val zone = ColumnHandler(
// FutureColumn(
// prefix.plus("zone").joinToString("__"),
// TextColumnType().also { it.nullable = isNullable },
// table
// )
// )
// override val columns: List> = listOf(main.column, zone.column)
// override fun split(value: ZonedDateTime, action: PerColumnWriter) {
// action.handle(main.column, value.toInstant())
// action.handle(zone.column, value.zone.id)
// }
//
// override fun merge(action: PerColumnReader): ZonedDateTime {
// return ZonedDateTime.ofInstant(
// action.handle(main.column),
// ZoneId.of(action.handle(zone.column))
// )
// }
//}
//
//class ListFieldHandler(val it: FieldHandler) : FieldHandler> {
// val columnMap: Map, FutureColumn>> = it.columns.associateWith {
// FutureColumn(it.name, ArrayColumnType(it.type), it.table)
// }
// override val columns: List> = columnMap.values.toList()
//
// override fun split(value: List, action: PerColumnWriter) {
// val virtual = HashMap, ArrayList>()
// for (item in value) {
// it.split(item, object : PerColumnWriter {
// override fun handle(column: FutureColumn, value: T) {
// virtual.getOrPut(column) { ArrayList() }.add(value)
// }
// })
// }
// for (entry in columnMap) {
// action.handle(entry.value, virtual[entry.key]!!)
// }
// }
//
// override fun merge(action: PerColumnReader): List {
// val parts = HashMap, List>()
// for (entry in columnMap) {
// parts[entry.key] = action.handle(entry.value)
// }
// return parts.values.first().indices.map { index ->
// it.merge(object : PerColumnReader {
// override fun handle(column: FutureColumn): T = parts[column]!![index] as T
// })
// }
// }
//}
//
//class MapFieldHandler(val keyHandler: FieldHandler, val valueHandler: FieldHandler) :
// FieldHandler> {
// val keyColumnMap: Map, FutureColumn>> = keyHandler.columns.associateWith {
// FutureColumn(it.name, ArrayColumnType(it.type), it.table)
// }
// val valueColumnMap: Map, FutureColumn>> = valueHandler.columns.associateWith {
// FutureColumn(it.name, ArrayColumnType(it.type), it.table)
// }
// override val columns: List> = keyColumnMap.values.toList() + valueColumnMap.values.toList()
//
// override fun split(value: Map, action: PerColumnWriter) {
// val virtualKeys = HashMap, ArrayList>()
// val virtualValues = HashMap, ArrayList>()
// for (item in value) {
// keyHandler.split(item.key, object : PerColumnWriter {
// override fun handle(column: FutureColumn, value: T) {
// virtualKeys.getOrPut(column) { ArrayList() }.add(value)
// }
// })
// valueHandler.split(item.value, object : PerColumnWriter {
// override fun handle(column: FutureColumn, value: T) {
// virtualValues.getOrPut(column) { ArrayList() }.add(value)
// }
// })
// }
// for (entry in keyColumnMap) {
// action.handle(entry.value, virtualKeys[entry.key]!!)
// }
// for (entry in valueColumnMap) {
// action.handle(entry.value, virtualValues[entry.key]!!)
// }
// }
//
// override fun merge(action: PerColumnReader): Map {
// val partsKey = HashMap, List>()
// val partsValue = HashMap, List>()
// for (entry in keyColumnMap) {
// partsKey[entry.key] = action.handle(entry.value)
// }
// for (entry in valueColumnMap) {
// partsValue[entry.key] = action.handle(entry.value)
// }
// return partsKey.values.first().indices.associate { index ->
// keyHandler.merge(object : PerColumnReader {
// override fun handle(column: FutureColumn): T = partsKey[column]!![index] as T
// }) to valueHandler.merge(object : PerColumnReader {
// override fun handle(column: FutureColumn): T = partsValue[column]!![index] as T
// })
// }
// }
//}
//
//@OptIn(InternalSerializationApi::class)
//class ClassFieldHandler(val serializer: KSerializer, table: Table, prefix: List) :
// FieldHandler {
// val serialDescriptor get() = serializer.descriptor
// val parts = (0 until serialDescriptor.elementsCount).map {
// @Suppress("UNCHECKED_CAST")
// ((serializer as GeneratedSerializer<*>).childSerializers().get(it) as KSerializer).fieldHandler(
// table,
// prefix + serialDescriptor.getElementName(it)
// )
// }
//
// override val columns: List> = parts.flatMap { it.columns }
//
// override fun merge(action: PerColumnReader): T {
// TODO("Not yet implemented")
// }
//
// override fun split(value: T, action: PerColumnWriter) {
// TODO("Not yet implemented")
// }
//}
//
//private fun KSerializer.fieldHandler(
// table: Table,
// prefix: List = listOf(),
//): FieldHandler = when (this.descriptor.unnull()) {
// ServerFileSerialization.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// TextColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// UUIDSerializer.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// UUIDColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// LocalDateSerializer.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// JavaLocalDateColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// InstantSerializer.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// JavaInstantColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// DurationSerializer.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// JavaDurationColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
// //LocalDateTimeSerializer.descriptor -> listOf(listOf() to JavaLocalDateTimeColumnType().also { it.nullable = this.isNullable })
// LocalTimeSerializer.descriptor -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// JavaLocalTimeColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// ZonedDateTimeSerializer.descriptor -> ZonedDateTimeFieldHandler(
// prefix,
// table,
// descriptor.isNullable
// ) as FieldHandler
//
// else -> when (descriptor.kind) {
// SerialKind.CONTEXTUAL -> throw Error()
// PolymorphicKind.OPEN -> throw NotImplementedError()
// PolymorphicKind.SEALED -> throw NotImplementedError()
// PrimitiveKind.BOOLEAN -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// BooleanColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.BYTE -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// ByteColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.CHAR -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// CharColumnType(1).also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.DOUBLE -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// DoubleColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.FLOAT -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// FloatColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.INT -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// IntegerColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.LONG -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// LongColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.SHORT -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// ShortColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// PrimitiveKind.STRING -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// TextColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// SerialKind.ENUM -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// TextColumnType().also { it.nullable = this.descriptor.isNullable },
// table
// )
// )
//
// StructureKind.LIST -> {
// @Suppress("UNCHECKED_CAST")
// val it = (listElement() as KSerializer).fieldHandler(table, prefix)
// @Suppress("UNCHECKED_CAST")
// ListFieldHandler(it) as FieldHandler
// }
//
// StructureKind.CLASS -> {
// val nullCol = if (descriptor.isNullable) listOf(listOf() to BooleanColumnType()) else listOf()
// TODO(
// """nullCol + (0 until elementsCount).flatMap {
// this.getRealElementDescriptor(it).columnType().map { sub ->
// (sub.first + listOf(getElementName(it))) to sub.second
// }
// }"""
// )
// }
//
// StructureKind.MAP -> {
// @Suppress("UNCHECKED_CAST")
// val k = (mapKeyElement() as KSerializer).fieldHandler(table, prefix + "key")
//
// @Suppress("UNCHECKED_CAST")
// val v = (mapValueElement() as KSerializer).fieldHandler(table, prefix + "value")
// @Suppress("UNCHECKED_CAST")
// MapFieldHandler(k, v) as FieldHandler
// }
//
// StructureKind.OBJECT -> ColumnHandler(
// FutureColumn(
// prefix.joinToString("__"),
// TextColumnType().also { it.nullable = descriptor.isNullable },
// table
// )
// )
// }
//}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy