Coverage Summary for Class: CascadeSelectClause (com.kotlinorm.orm.cascade)

Class Method, % Branch, % Line, % Instruction, %
CascadeSelectClause 40% (2/5) 12.8% (5/39) 23.4% (18/77) 19.9% (83/418)
CascadeSelectClause$WhenMappings
Total 40% (2/5) 12.8% (5/39) 23.4% (18/77) 19.9% (83/418)


 /**
  * 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.orm.cascade
 
 import com.kotlinorm.beans.dsl.Field
 import com.kotlinorm.beans.task.KronosAtomicQueryTask
 import com.kotlinorm.beans.task.KronosQueryTask
 import com.kotlinorm.beans.task.KronosQueryTask.Companion.toKronosQueryTask
 import com.kotlinorm.cache.kPojoAllFieldsCache
 import com.kotlinorm.enums.KOperationType
 import com.kotlinorm.enums.QueryType.QueryList
 import com.kotlinorm.enums.QueryType.QueryOne
 import com.kotlinorm.enums.QueryType.QueryOneOrNull
 import com.kotlinorm.interfaces.KPojo
 import com.kotlinorm.interfaces.KronosDataSourceWrapper
 import com.kotlinorm.orm.select.select
 import com.kotlinorm.utils.Extensions.patchTo
 import kotlin.reflect.KClass
 
 /**
  * Used to build a cascade select clause.
  *
  * This object is used to construct a cascade select clause for a database operation.
  *
  * 构建级联查询子句
  *
  * 该对象用于为数据库操作构建级联查询子句
  */
 object CascadeSelectClause {
     /**
      * Build a cascade select clause.
      *
      * 构建级联查询子句
      *
      * @param cascade Whether the cascade is enabled.
      * @param cascadeAllowed The properties that are allowed to cascade.
      * @param pojo The POJO to select.
      * @param rootTask The root task.
      * @param selectFields The fields to select.
      * @param operationType The operation type, the cascade operation type, may be Query, Delete, Update(Delete and Update operations need to cascade query)
      * @param cascadeSelectedProps The properties that have been selected for cascading, preventing infinite recursion.
      * @return A KronosQueryTask object representing the cascade select operation.
      */
     fun <T : KPojo> build(
         cascade: Boolean,
         cascadeAllowed: Set<Field>? = null,
         pojo: T,
         kClass: KClass<KPojo>,
         rootTask: KronosAtomicQueryTask,
         selectFields: LinkedHashSet<Field>,
         operationType: KOperationType,
         cascadeSelectedProps: Set<Field>
     ) = if (cascade) generateTask(
         cascadeAllowed,
         pojo,
         kClass,
         kPojoAllFieldsCache[kClass]!!.filter { selectFields.contains(it) },
         operationType,
         rootTask,
         cascadeSelectedProps
     ) else rootTask.toKronosQueryTask()
 
     /**
      * Generate a task for the cascade select operation.
      *
      * 为级联查询操作生成任务
      *
      * @param cascadeAllowed The properties that are allowed to cascade.
      * @param pojo The POJO to select.
      * @param columns The columns to select.
      * @param operationType The operation type.
      * @param prevTask The previous task.
      * @param cascadeSelectedProps The properties that have been selected for cascading, preventing infinite recursion.
      * @return A KronosQueryTask object representing the cascade select operation.
      */
     @Suppress("UNCHECKED_CAST")
     private fun generateTask(
         cascadeAllowed: Set<Field>?,
         pojo: KPojo,
         kClass: KClass<KPojo>,
         columns: List<Field>,
         operationType: KOperationType,
         prevTask: KronosAtomicQueryTask,
         cascadeSelectedProps: Set<Field>
     ): KronosQueryTask {
         val tableName = pojo.kronosTableName()
         val validCascades = findValidRefs(
             kClass,
             columns,
             operationType,
             cascadeAllowed?.filter { it.tableName == tableName }?.map { it.name }?.toSet(), // 获取当前Pojo内允许级联的属性
             cascadeAllowed.isNullOrEmpty() // 是否允许所有属性级联
         ) // 获取所有的非数据库列、有关联注解且用于删除操作
         return prevTask.toKronosQueryTask().apply {
             // 若没有关联信息,返回空(在deleteClause的build中,有对null值的判断和默认值处理)
             // 为何不直接返回deleteTask: 因为此处的deleteTask构建sql语句时带有表名,而普通的deleteTask不带表名,因此需要重新构建
             if (validCascades.isEmpty()) return@apply
             doAfterQuery { queryType, wrapper ->
                 validCascades.forEach { validRef ->
                     when (queryType) {
                         QueryList -> { // 若是查询KPojo列表
                             val lastStepResult = this as List<KPojo> // this为主表查询的结果
                             if (lastStepResult.isEmpty()) return@forEach // 若该级联属性查询结果为空,不进行级联查询
                             val prop = validRef.field // 获取级联字段的属性如:GroupClass.students
                             if (cascadeSelectedProps.contains(validRef.field)) return@forEach // 若该级联属性未被select,不进行级联查询
                             if (!cascadeAllowed.isNullOrEmpty() && prop !in cascadeAllowed) return@forEach // 若设置了级联忽略,且该属性不在白名单内,不进行级联查询
                             lastStepResult.forEach rowMapper@{
                                 setValues(
                                     it,
                                     prop.name,
                                     validRef,
                                     cascadeAllowed,
                                     mutableSetOf(
                                         *cascadeSelectedProps.toTypedArray(),
                                         *validCascades.map { cascade -> cascade.field }.toTypedArray()
                                     ),
                                     operationType,
                                     wrapper
                                 )
                             }
                         }
 
                         QueryOne, QueryOneOrNull -> {
                             val lastStepResult = this as KPojo? // this为主表查询的结果
                             if (lastStepResult == null) return@forEach // 若该级联属性查询结果为空,不进行级联查询
                             val prop = validRef.field // 获取级联字段的属性如:GroupClass.students
                             if (cascadeSelectedProps.contains(validRef.field)) return@forEach // 若该级联属性未被select,不进行级联查询
                             if (!cascadeAllowed.isNullOrEmpty() && prop !in cascadeAllowed) return@forEach // 若设置了级联忽略,且该属性不在白名单内,不进行级联查询
                             setValues(
                                 lastStepResult,
                                 prop.name,
                                 validRef,
                                 cascadeAllowed,
                                 mutableSetOf(
                                     *cascadeSelectedProps.toTypedArray(),
                                     *validCascades.map { cascade -> cascade.field }.toTypedArray()
                                 ),
                                 operationType,
                                 wrapper
                             )
                         }
 
                         else -> {}
                     }
                 }
             }
         }
     }
 
     fun setValues(
         pojo: KPojo,
         prop: String,
         validRef: ValidCascade,
         cascadeAllowed: Set<Field>?,
         cascadeSelectedProps: Set<Field>,
         operationType: KOperationType,
         wrapper: KronosDataSourceWrapper
     ) {
         // 将KPojo转为Map,该map将用于级联查询
         val dataMap = pojo.toDataMap()
         // 获取KPojo对应的表名
         val tableName = pojo.kronosTableName()
 
         // 获取Pair列表,用于将Map内的值填充到引用的类的POJO中
         // Pair的构建需要判断KPojo对象是ValidRef所在的表还是引用的表,然后根据不同的情况填充Pair
         val listOfPair = validRef.kCascade.targetProperties.mapIndexed { index, targetProperty ->
             if (tableName == validRef.tableName) {
                 targetProperty to (dataMap[validRef.kCascade.properties[index]] ?: return)
             } else {
                 validRef.kCascade.properties[index] to (dataMap[targetProperty] ?: return)
             }
         }
 
         // 通过反射创建引用的类的POJO,支持类型为KPojo/Collections<KPojo>,将级联需要用到的字段填充
         val refPojo = validRef.refPojo.patchTo(
             validRef.refPojo::class,
             *listOfPair.toTypedArray()
         )
 
         pojo[prop] = if (pojo.kronosColumns().first { it.name == prop }.cascadeIsCollectionOrArray) { // 判断属性是否为集合
             refPojo.select().apply {
                 this.operationType = operationType
                 this.cascadeAllowed = cascadeAllowed
                 this.cascadeSelectedProps = cascadeSelectedProps
             }.queryList(wrapper) // 查询级联的POJO
         } else {
             refPojo.select().apply {
                 this.operationType = operationType
                 this.cascadeAllowed = cascadeAllowed
                 this.cascadeSelectedProps = cascadeSelectedProps
             }.queryOneOrNull(wrapper) // 查询级联的POJO
         }
     }
 }