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

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