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

com.dbobjekts.codegen.writer.TableDetailsSourceBuilder.kt Maven / Gradle / Ivy

There is a newer version: 0.6.0-RC2
Show newest version
package com.dbobjekts.codegen.writer

import com.dbobjekts.api.PackageName
import com.dbobjekts.statement.WriteQueryAccessors
import com.dbobjekts.codegen.metadata.*
import com.dbobjekts.codegen.metadata.DBGeneratedPrimaryKey
import com.dbobjekts.metadata.column.NullableColumn
import com.dbobjekts.statement.insert.InsertBuilderBase
import com.dbobjekts.statement.update.UpdateBuilderBase
import com.dbobjekts.util.StringUtil

data class FieldData(
    val field: String,
    val columnName: String,
    val fieldType: String,
    val defaultClause: String,
    val nullable: Boolean,
    val autoGenPK: Boolean,
    val singlePrimaryKey: Boolean,
    val compositePrimaryKey: Boolean
)

class TableDetailsSourceBuilder(val tableDefinition: DBTableDefinition) {

    private val tableName = tableDefinition.asClassName()

    val fields: List
    val allFieldsExceptAutoPK: List
    val nonNullFields: List
    val singlePrimaryKey: FieldData?
    val allPrimaryKeys: List
    val autoPrimaryKey: FieldData?

    init {
        fields = tableDefinition.columns.map { colDef ->
            val fieldName = colDef.columnName.fieldName
            val isNullable = colDef.column is NullableColumn<*>
            val dataType = StringUtil.classToString(colDef.column.valueClass) + (if (isNullable) "?" else "")
            val defaultClause: String = getDefaultValue(colDef)?.let { " = $it" } ?: ""
            val autoGenPk = colDef is DBGeneratedPrimaryKey
            FieldData(
                fieldName,
                colDef.columnName.value,
                dataType,
                defaultClause,
                isNullable,
                autoGenPk,
                colDef.isSinglePrimaryKey,
                colDef.isCompositePrimaryKey
            )
        }
        allPrimaryKeys = fields.filter { it.singlePrimaryKey || it.compositePrimaryKey }
        allFieldsExceptAutoPK = fields.filterNot { it.autoGenPK }
        nonNullFields = allFieldsExceptAutoPK.filterNot { it.nullable || it.autoGenPK }
        singlePrimaryKey = fields.filter { it.singlePrimaryKey }.firstOrNull()
        autoPrimaryKey = fields.filter { it.autoGenPK }.firstOrNull()
    }

    fun sourceForTableComment(): String {
        val (foreignKeys, references) = tableDefinition.linkedTables.partition { (s, t) -> s == tableDefinition.schema && t == tableDefinition.tableName }
        val composite = tableDefinition.columns.filter { it.isCompositePrimaryKey }
        val pks = if (composite.isEmpty()) (singlePrimaryKey?.columnName ?: "none") else composite.map { it.columnName.value }
        return """
            /**           
             * Auto-generated metadata object for db table ${tableDefinition.schema.value}.${tableDefinition.tableName}.
             *
             * Do not edit this file manually! Always use [com.dbobjekts.codegen.CodeGenerator] when the metadata model is no longer in sync with the database.           
             *
             * Primary keys: $pks
             *
             * Foreign keys to: ${foreignKeys.map { it.first.value + "." + it.second.value }.joinToString(",")}
             * References by: ${references.map { it.first.value + "." + it.second.value }.joinToString(",")}
             */
        """.trimIndent()
    }

    fun sourceForToValue(): String {
        val elements = fields.mapIndexed { i, field ->
            "values[$i] as ${field.fieldType}"
        }.joinToString(",")
        return "    override fun toValue(values: List) = ${tableName}Row($elements)"
    }

    fun sourceForUpdateRowMethod(): String {
        if (allPrimaryKeys.isEmpty())
            return """
    /**
     * Warning: this method will throw a StatementBuilderException at runtime because $tableName does not have a primary key.
     */
    override fun updateRow(rowData: TableRowData<*, *>): Long = 
      throw com.dbobjekts.api.exception.StatementBuilderException("Sorry, but you cannot use row-based updates for table ${tableName}. At least one column must be marked as primary key.")                
            """

        val elements = fields.mapIndexed { _, field ->
            "      add($tableName.${field.field}, rowData.${field.field})"
        }.joinToString("\n")
        val pk1 = allPrimaryKeys[0].field
        val whereclause = StringBuilder("${tableName}.$pk1.eq(rowData.$pk1)")
        allPrimaryKeys.forEachIndexed { i, r ->
            if (i > 0) {
                val field = r.field
                whereclause.append(".and(${tableName}.$field.eq(rowData.$field))")
            }
        }
        val source = """    
    /**
     * FOR INTERNAL USE ONLY
     */
    override fun updateRow(rowData: TableRowData<*, *>): Long {
      rowData as ${tableName}Row
$elements
      return where($whereclause)
    }    
        """
        return source
    }

    fun sourceForRowDataClass(): String {
        val elements = mutableListOf()
        if (autoPrimaryKey != null)
            elements += "val ${autoPrimaryKey.field}: ${autoPrimaryKey.fieldType} = 0"

        elements.addAll(allFieldsExceptAutoPK.map { f ->
            "  val ${f.field}: ${f.fieldType}"
        })
        val pks = allPrimaryKeys.map { "Pair($tableName.${it.field}, ${it.field})" }.joinToString(",")
        val fieldStr = elements.joinToString(",\n")
        val source = """
data class ${tableName}Row(
$fieldStr    
) : TableRowData<${tableName}UpdateBuilder, ${tableName}InsertBuilder>(${tableName}.metadata()){
     override val primaryKeys = listOf>($pks)
}
        """
        return source
    }

    private fun sourceForInsertRowMethod(): String {
        val elements = allFieldsExceptAutoPK.mapIndexed { _, field ->
            "      add($tableName.${field.field}, rowData.${field.field})"
        }.joinToString("\n")
        val source = """
    override fun insertRow(rowData: TableRowData<*, *>): Long {
      rowData as ${tableName}Row
$elements
      return execute()
    }    
        """
        return source
    }

    private fun sourceForMandatoryColumnsMethod(): String {
        return if (nonNullFields.isEmpty()) "" else
            """
    fun mandatoryColumns(${nonNullFields.map { f -> "${f.field}: ${f.fieldType}" }.joinToString(", ")}) : ${tableName}InsertBuilder {
${nonNullFields.map { f -> "      mandatory($tableName.${f.field}, ${f.field})" }.joinToString("\n")}
      return this
    }
"""
    }

    fun sourceForMetaDataVal(): String {
        val accessorClass = WriteQueryAccessors::class.java.simpleName
        val updateBuilder = "${tableName}UpdateBuilder"
        val insertBuilder = "${tableName}InsertBuilder"
        return "    override fun metadata(): $accessorClass<$updateBuilder, $insertBuilder> = $accessorClass($updateBuilder(), $insertBuilder())"
    }

    fun sourceForBuilderClasses(): String {
        val updateBuilderBase = UpdateBuilderBase::class.java.simpleName
        val insertBuilderBase = InsertBuilderBase::class.java.simpleName

        fun writeMethod(data: FieldData, returnType: String) =
            "    fun ${data.field}(value: ${data.fieldType}): $returnType = put($tableName.${data.field}, value)"

        val updateRowMethodSource = sourceForUpdateRowMethod()
        val insertRowMethodSource = sourceForInsertRowMethod()
        val mandatoryColumnsMethod = sourceForMandatoryColumnsMethod()

        val updateBuilder = "${tableName}UpdateBuilder"
        val insertBuilder = "${tableName}InsertBuilder"

        return """
class $updateBuilder() : $updateBuilderBase($tableName) {
${allFieldsExceptAutoPK.map { d -> writeMethod(d, updateBuilder) }.joinToString("\n")}
$updateRowMethodSource
}

class $insertBuilder():$insertBuilderBase(){
${allFieldsExceptAutoPK.map { d -> writeMethod(d, insertBuilder) }.joinToString("\n")}
$mandatoryColumnsMethod
$insertRowMethodSource
}
"""
    }


    private fun getDefaultValue(colDef: DBColumnDefinition): String? {
        if (!(colDef.column is NullableColumn<*>))
            return null
        val clz = colDef.column.valueClass
        return if (!clz.isPrimitive) "null"
        else {
            when (clz.simpleName) {
                "byte" -> "0"
                "short" -> "0"
                "int" -> "0"
                "float" -> "0f"
                "long" -> "0l"
                "double" -> "0d"
                "boolean" -> "false"
                else -> null
            }
        }
    }



}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy