Coverage Summary for Class: KronosColumnValueType (com.kotlinorm.compiler.plugin.utils)

Class Class, % Method, % Branch, % Line, % Instruction, %
KronosColumnValueType 100% (1/1) 100% (1/1) 100% (1/1) 100% (24/24)


 /**
  * Copyright 2022-2025 kronos-orm
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 package com.kotlinorm.compiler.plugin.utils
 
 import com.kotlinorm.compiler.helpers.applyIrCall
 import com.kotlinorm.compiler.helpers.createKClassExpr
 import com.kotlinorm.compiler.helpers.dispatchBy
 import com.kotlinorm.compiler.helpers.findByFqName
 import com.kotlinorm.compiler.helpers.irEnum
 import com.kotlinorm.compiler.helpers.irListOf
 import com.kotlinorm.compiler.helpers.irPairOf
 import com.kotlinorm.compiler.helpers.nType
 import com.kotlinorm.compiler.helpers.pairSymbol
 import com.kotlinorm.compiler.helpers.referenceClass
 import com.kotlinorm.compiler.helpers.subType
 import com.kotlinorm.compiler.helpers.valueArguments
 import com.kotlinorm.compiler.plugin.beans.FieldIR
 import com.kotlinorm.compiler.plugin.utils.context.KotlinBuilderContext
 import com.kotlinorm.compiler.plugin.utils.context.withContext
 import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
 import org.jetbrains.kotlin.ir.builders.irGetObject
 import org.jetbrains.kotlin.ir.builders.irNull
 import org.jetbrains.kotlin.ir.builders.irString
 import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.declarations.IrProperty
 import org.jetbrains.kotlin.ir.expressions.IrCall
 import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
 import org.jetbrains.kotlin.ir.expressions.IrExpression
 import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
 import org.jetbrains.kotlin.ir.expressions.IrGetValue
 import org.jetbrains.kotlin.ir.expressions.IrPropertyReference
 import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
 import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
 import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
 import org.jetbrains.kotlin.ir.types.classFqName
 import org.jetbrains.kotlin.ir.types.getClass
 import org.jetbrains.kotlin.ir.util.constructors
 import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
 import org.jetbrains.kotlin.ir.util.getPropertyGetter
 import org.jetbrains.kotlin.ir.util.getSimpleFunction
 import org.jetbrains.kotlin.ir.util.properties
 import org.jetbrains.kotlin.ir.util.superTypes
 import org.jetbrains.kotlin.name.FqName
 
 internal val IrPluginContext.fieldSymbol
     get() = referenceClass("com.kotlinorm.beans.dsl.Field")!!
 
 internal val IrPluginContext.functionSymbol
     get() = referenceClass("com.kotlinorm.beans.dsl.FunctionField")!!
 
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 internal val IrPluginContext.k2dbSymbol
     get() = referenceClass("com.kotlinorm.interfaces.KronosNamingStrategy")!!.getSimpleFunction("k2db")!!
 
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 internal val IrPluginContext.fieldNamingStrategySymbol
     get() =
         KronosSymbol.getPropertyGetter("fieldNamingStrategy")!!
 
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 internal val IrPluginContext.tableNamingStrategySymbol
     get() =
         KronosSymbol.getPropertyGetter("tableNamingStrategy")!!
 
 internal val IrPluginContext.kReferenceSymbol
     get() = referenceClass("com.kotlinorm.beans.dsl.KCascade")!!
 
 val TableAnnotationsFqName = FqName("com.kotlinorm.annotations.Table")
 val TableIndexAnnotationsFqName = FqName("com.kotlinorm.annotations.TableIndex")
 val PrimaryKeyAnnotationsFqName = FqName("com.kotlinorm.annotations.PrimaryKey")
 val ColumnAnnotationsFqName = FqName("com.kotlinorm.annotations.Column")
 val ColumnTypeAnnotationsFqName = FqName("com.kotlinorm.annotations.ColumnType")
 val DateTimeFormatAnnotationsFqName = FqName("com.kotlinorm.annotations.DateTimeFormat")
 val CascadeAnnotationsFqName = FqName("com.kotlinorm.annotations.Cascade")
 val IgnoreAnnotationsFqName = FqName("com.kotlinorm.annotations.Ignore")
 val SerializeAnnotationsFqName = FqName("com.kotlinorm.annotations.Serialize")
 val DefaultValueAnnotationsFqName = FqName("com.kotlinorm.annotations.Default")
 val NecessaryAnnotationsFqName = FqName("com.kotlinorm.annotations.Necessary")
 
 
 /**
  * Returns the column name of the given IrExpression.
  *
  * @param expression the [IrExpression] to get the column name from
  * @return the `IrExpression` representing the column name
  */
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 fun KotlinBuilderContext.getColumnName(expression: IrExpression): IrExpression {
     with(pluginContext){
         with(builder){
             if (!expression.isKPojo()) {
                 return expression
             }
             return when (expression) {
                 is IrCall -> {
                     val propertyName = withContext{ expression.correspondingName!!.asString() }
                     val irProperty =
                         expression.dispatchReceiver!!.type.getClass()!!.properties.first { it.name.asString() == propertyName }
                     getColumnName(irProperty, propertyName)
                 }
 
                 is IrPropertyReference -> {
                     val propertyName = expression.symbol.owner.name.asString()
                     val irProperty = expression.symbol.owner
                     getColumnName(irProperty, propertyName)
                 }
 
                 else -> applyIrCall(fieldSymbol.constructors.first(), irString(""), irString(""))
             }
         }
     }
 }
 
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 fun KotlinBuilderContext.getFunctionName(expression: IrExpression): IrExpression {
     with(pluginContext){
         with(builder){
             return when (expression) {
                 is IrCall -> {
                     val args = mutableListOf<IrExpression>()
                     expression.valueArguments.forEach {
                         if (it is IrVarargImpl) {
                             args.addAll(it.elements.map { element ->
                                 irPairOf(
                                     fieldSymbol.nType,
                                     irBuiltIns.anyNType,
                                     (element as IrExpression).irFieldOrNull() to element
                                 )
                             })
                         } else {
                             args.add(
                                 irPairOf(
                                     fieldSymbol.nType,
                                     irBuiltIns.anyNType,
                                     it.irFieldOrNull() to it
                                 )
                             )
                         }
                     }
                     applyIrCall(
                         functionSymbol.constructors.first(),
                         irString(expression.funcName()),
                         irListOf(
                             pairSymbol.owner.returnType,
                             args
                         ),
                     )
                 }
 
                 else -> {
                     throw IllegalStateException("Unexpected expression type: $expression")
                 }
             }
         }
     }
 }
 
 val ARRAY_OR_COLLECTION_FQ_NAMES = arrayOf(
     FqName("kotlin.collections.Collection"),
     FqName("kotlin.collections.Iterator"),
     FqName("kotlin.Array"),
     FqName("kotlin.IntArray"),
     FqName("kotlin.LongArray"),
     FqName("kotlin.ShortArray"),
     FqName("kotlin.DoubleArray"),
     FqName("kotlin.FloatArray"),
     FqName("kotlin.CharArray"),
     FqName("kotlin.ByteArray"),
     FqName("kotlin.BooleanArray"),
 )
 
 /**
  * Returns the column name of the given IrProperty.
  *
  * @param irProperty the [IrProperty] to get the column name from
  * @param propertyName the name of the property (default: the name of the IrProperty)
  * @return the `IrExpression` representing the column name
  */
 @OptIn(UnsafeDuringIrConstructionAPI::class)
 fun KotlinBuilderContext.getColumnName(
     irProperty: IrProperty, propertyName: String = irProperty.name.asString()
 ): IrExpression {
     with(pluginContext) {
         with(builder) {
             val parent = irProperty.parent as IrClass
             val annotations = irProperty.annotations
 
             // detect annotations
             var columnAnnotation: IrConstructorCall? = null // @Column
             var columnTypeAnnotation: IrConstructorCall? = null // @ColumnType
             var cascadeAnnotation: IrConstructorCall? = null // @Cascade
             var ignoreAnnotation: IrConstructorCall? = null // @Ignore
             var defaultValueAnnotation: IrConstructorCall? = null // @DefaultValue
             var primaryKeyAnnotation: IrConstructorCall? = null // @PrimaryKey
             var dateTimeFormatAnnotation: IrConstructorCall? = null // @DateTimeFormat
             var requiredAnnotation: IrConstructorCall? = null // @Necessary
             var serializeAnnotation: IrConstructorCall? = null // @Serializable
 
             annotations.forEach {
                 when (it.symbol.owner.returnType.getClass()!!.fqNameWhenAvailable) {
                     ColumnTypeAnnotationsFqName -> columnTypeAnnotation = it
                     ColumnAnnotationsFqName -> columnAnnotation = it
                     CascadeAnnotationsFqName -> cascadeAnnotation = it
                     IgnoreAnnotationsFqName -> ignoreAnnotation = it
                     DefaultValueAnnotationsFqName -> defaultValueAnnotation = it
                     PrimaryKeyAnnotationsFqName -> primaryKeyAnnotation = it
                     DateTimeFormatAnnotationsFqName -> dateTimeFormatAnnotation = it
                     NecessaryAnnotationsFqName -> requiredAnnotation = it
                     SerializeAnnotationsFqName -> serializeAnnotation = it
                 }
             }
 
             val columnName = columnAnnotation?.getValueArgument(0) ?: applyIrCall(
                 k2dbSymbol, irString(propertyName)
             ) {
                 dispatchBy(applyIrCall(fieldNamingStrategySymbol) { dispatchBy(irGetObject(KronosSymbol)) })
             }
             val irPropertyType = irProperty.backingField?.type ?: irBuiltIns.anyNType
             val propertyType = irPropertyType.classFqName!!.asString()
             val columnType =
                 columnTypeAnnotation?.getValueArgument(0) ?: irEnum(kColumnTypeSymbol, kotlinTypeToKColumnType(propertyType))
             val tableName = getTableName(parent)
             val propKClass = irPropertyType.getClass()
             val cascadeIsArrayOrCollection = irPropertyType.superTypes().any { it.classFqName in ARRAY_OR_COLLECTION_FQ_NAMES }
             val kClass = if (irProperty.isDelegated) {
                 irNull()
             } else {
                 createKClassExpr(
                     (if (cascadeIsArrayOrCollection) {
                         irPropertyType.subType()!!.getClass()
                     } else {
                         propKClass
                     }!!).symbol
                 )
             }
             val superTypes = propKClass?.superTypes?.mapNotNull {
                 it.classFqName?.asString()?.let { irString(it) }
             } ?: emptyList()
 
             val kCascade = if (cascadeAnnotation != null) {
                 applyIrCall(
                     kReferenceSymbol.constructors.first(), *cascadeAnnotation.valueArguments.toTypedArray()
                 )
             } else {
                 irNull()
             }
 
             val propsOfPrimaryKey = arrayOf(
                 "identity",
                 "uuid",
                 "snowflake",
                 "custom"
             )
 
             val primaryKeyAnnotationIndex =
                 primaryKeyAnnotation?.valueArguments?.indexOfFirst { it is IrConstImpl && it.value == true }
 
             val primaryKey = when {
                 primaryKeyAnnotation == null -> "not"
                 primaryKeyAnnotationIndex == null -> "default"
                 else -> propsOfPrimaryKey.getOrNull(primaryKeyAnnotationIndex) ?: "default"
             }
 
             return FieldIR(
                 columnName = columnName,
                 name = propertyName,
                 type = columnType,
                 primaryKey = primaryKey,
                 dateTimeFormat = dateTimeFormatAnnotation?.getValueArgument(0),
                 tableName = tableName,
                 cascade = kCascade,
                 cascadeIsArrayOrCollection = cascadeIsArrayOrCollection,
                 kClass = kClass,
                 superTypes = superTypes,
                 ignore = ignoreAnnotation?.getValueArgument(0),
                 isColumn = irProperty.isColumn(irPropertyType, ignoreAnnotation),
                 columnTypeLength = columnTypeAnnotation?.getValueArgument(1),
                 columnTypeScale = columnTypeAnnotation?.getValueArgument(2),
                 columnDefaultValue = defaultValueAnnotation?.getValueArgument(0),
                 nullable = requiredAnnotation == null && primaryKeyAnnotation == null,
                 serializable = serializeAnnotation != null,
                 kDoc = irProperty.getKDocString()
             ).build()
         }
     }
 }
 
 /**
  * Kronos Column Value Type
  *
  * Enum class for the kronos column value type
  */
 enum class KronosColumnValueType {
     Value, ColumnName, Function
 }
 
 /**
  * Retrieves the column or value from the given IrExpression.
  *
  * This function checks if the given IrExpression is null. If it is, it returns null.
  * Otherwise, it determines the type and value of the IrExpression using the columnValueGetter function.
  * If the type is Value, it returns the expression itself.
  * If the type is ColumnName, it retrieves the column name from the expression using the getColumnName function.
  *
  * @param expression the IrExpression to retrieve the column or value from. It can be null.
  * @return returns the column or value from the `IrExpression`, or null if the IrExpression is null.
  */
 fun KotlinBuilderContext.getColumnOrValue(expression: IrExpression?): IrExpression? {
     if (expression == null) return null
     val (type, expr) = expression.columnValueGetter()
     return when (type) {
         KronosColumnValueType.Value -> expr
         KronosColumnValueType.ColumnName -> getColumnName(expr)
         KronosColumnValueType.Function -> getFunctionName(expr)
     }
 }
 
 /**
  * Returns the table name associated with the given IrExpression.
  *
  * @param expression the [IrExpression] to retrieve the table name from
  * @return the `IrExpression` representing the table name
  * @throws IllegalStateException if the expression type is unexpected
  */
 fun KotlinBuilderContext.getTableName(expression: IrExpression): IrExpression {
     val irClass = when (expression) {
         is IrGetValue, is IrCall, is IrGetObjectValue -> expression.type.getClass()
         else -> throw IllegalStateException("Unexpected expression type: $expression")
     }!!
     return getTableName(irClass)
 }
 
 /**
  * Returns the table name associated with the given IrClass.
  *
  * @param irClass the [IrClass] to retrieve the table name from
  * @return the `IrExpression` representing the table name
  * @throws IllegalStateException if the table annotation is not found
  */
 fun KotlinBuilderContext.getTableName(irClass: IrClass): IrExpression {
     with(pluginContext){
         with(builder){
             val tableAnnotation = irClass.annotations.findByFqName(TableAnnotationsFqName)
             return tableAnnotation?.getValueArgument(0) ?: applyIrCall(
                 k2dbSymbol, irString(irClass.name.asString())
             ) {
                 dispatchBy(applyIrCall(tableNamingStrategySymbol) { dispatchBy(irGetObject(KronosSymbol)) })
             }
         }
     }
 }