Coverage Summary for Class: CascadeDeleteClause (com.kotlinorm.orm.cascade)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
CascadeDeleteClause |
50%
(2/4)
|
5.9%
(3/51)
|
22.8%
(21/92)
|
15.8%
(86/545)
|
CascadeDeleteClause$WhenMappings |
|
Total |
50%
(2/4)
|
5.9%
(3/51)
|
22.8%
(21/92)
|
15.8%
(86/545)
|
/**
* 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.annotations.Cascade.Companion.RESERVED
import com.kotlinorm.beans.dsl.Field
import com.kotlinorm.interfaces.KPojo
import com.kotlinorm.beans.task.KronosActionTask
import com.kotlinorm.beans.task.KronosActionTask.Companion.toKronosActionTask
import com.kotlinorm.beans.task.KronosAtomicActionTask
import com.kotlinorm.enums.CascadeDeleteAction.*
import com.kotlinorm.enums.KOperationType
import com.kotlinorm.orm.cascade.NodeOfKPojo.Companion.toTreeNode
import com.kotlinorm.orm.delete.delete
import com.kotlinorm.orm.select.select
import com.kotlinorm.orm.update.update
import com.kotlinorm.utils.KStack
import com.kotlinorm.utils.pop
import com.kotlinorm.utils.push
import kotlin.reflect.KClass
/**
* Used to build a cascade delete clause.
*
* 构建级联删除子句。
*
* This object is used to construct a cascade delete clause for a database operation.
* It contains a nested Counter class for counting operations, a nested ValidCascade data class for storing cascades and cascaded POJOs,
* and several functions for building the cascade delete clause and generating SQL statements.
*
* The main function is build, which takes a POJO, a SQL where clause, a logic flag, a parameter map, and a delete task,
* and returns an array of KronosAtomicActionTask objects representing the cascade delete operations.
*
* The other functions are helper functions used by build. They include findValidRefs, which finds valid cascades in a list of fields,
* generateCascadeDeleteSql, which generates a delete SQL statement for a cascaded POJO, and getDefaultUpdates, which generates a default update SQL clause.
*
*/
object CascadeDeleteClause {
/**
* Build a cascade delete clause.
* 构建级联删除子句。
*
* @param cascade Whether the cascade is enabled.
* @param cascadeAllowed The properties that are allowed to cascade.
* @param pojo The pojo to be deleted.
* @param whereClauseSql The condition to be met.
* @param logic The logic to be used.
* @param rootTask The delete task.
* @return The list of atomic tasks.
*/
fun <T : KPojo> build(
cascade: Boolean,
cascadeAllowed: Set<Field>?,
kClass: KClass<KPojo>,
pojo: T,
whereClauseSql: String?,
paramMap: Map<String, Any?>,
logic: Boolean,
rootTask: KronosAtomicActionTask
) =
if (cascade) generateTask(
cascadeAllowed,
kClass,
pojo,
whereClauseSql,
paramMap,
pojo.kronosColumns(),
logic,
rootTask
) else rootTask.toKronosActionTask()
/**
* Generate a task for a cascade delete operation.
*
* @param cascadeAllowed The properties that are allowed to cascade.
* @param pojo The pojo to be deleted.
* @param whereClauseSql The condition to be met.
* @param paramMap The parameter map.
* @param columns The columns of the pojo.
* @param logic The logic to be used.
* @param rootTask The delete task.
*
* **/
private fun <T : KPojo> generateTask(
cascadeAllowed: Set<Field>?,
kClass: KClass<KPojo>,
pojo: T,
whereClauseSql: String?,
paramMap: Map<String, Any?>,
columns: List<Field>,
logic: Boolean,
rootTask: KronosAtomicActionTask
): KronosActionTask {
val tableName = pojo.kronosTableName()
val validCascades = findValidRefs( // 获取有效的引用
kClass,
columns,
KOperationType.DELETE,
cascadeAllowed?.filter { it.tableName == tableName }?.map { it.name }?.toSet(), // 获取当前Pojo内允许级联的属性
cascadeAllowed.isNullOrEmpty() // 是否允许所有属性级联
).filter { !it.mapperByThis }
return rootTask.toKronosActionTask().apply {
doBeforeExecute { wrapper -> // 在执行前检查是否有引用
if (validCascades.isEmpty()) return@doBeforeExecute // 如果没有级联,直接返回
val toDeleteRecords =
pojo.select().where { whereClauseSql.asSql() }.patch(*paramMap.toList().toTypedArray())
.apply {
this.cascadeAllowed = cascadeAllowed
this.operationType = KOperationType.DELETE
}
.queryList(wrapper) //先查询出要删除的记录
if (toDeleteRecords.isEmpty()) return@doBeforeExecute // 如果没有要删除的记录,直接返回
// 检查限制级联的引用,如果有相关的级联引用数据,那么此次删除操作将被拒绝
val restrictCascades =
validCascades.filter { it.kCascade.onDelete == RESTRICT }
toDeleteRecords.forEach { record ->
restrictCascades.forEach { cascade ->
val valueOfPojo = record.toDataMap()[cascade.field.name]
if (valueOfPojo != null && !(valueOfPojo is Collection<*> && valueOfPojo.isEmpty())) {
throw UnsupportedOperationException(
"The record cannot be deleted because it is restricted by a cascade." +
"${record.kronosTableName()}.${cascade.kCascade.properties} is restricted by ${cascade.kCascade.targetProperties}, " +
"and the value is ${valueOfPojo}."
)
}
}
}
// 生成树结构,后序遍历所有的子节点,将所有的子节点压入list,最后由子到父执行删除操作
val forestOfKPojo = toDeleteRecords.map {
it.toTreeNode(
operationType = KOperationType.DELETE,
cascadeAllowed = cascadeAllowed
)
}
if (forestOfKPojo.any { it.children.isNotEmpty() }) {
this.atomicTasks.clear() // 清空原有的任务
val list = mutableListOf<NodeOfKPojo>()
forestOfKPojo.forEach { tree ->
val stack = KStack<NodeOfKPojo>() // 用于深度优先遍历
val all = KStack<NodeOfKPojo>() // 用于存储所有的节点
stack.push(tree) // 将根节点压入栈
var tmp: NodeOfKPojo
while (!stack.isEmpty()) { // 深度优先遍历
tmp = stack.pop()
all.push(tmp)
tmp.children.forEach {
stack.push(it) // 将子节点压入栈
}
}
while (!all.isEmpty()) {
list.add(all.pop()) // 将所有节点压入list
}
}
atomicTasks.addAll(list.mapNotNull {
when (it.data?.kCascade?.onDelete) {
NO_ACTION, RESTRICT -> null
CASCADE, null -> it.kPojo.delete().logic(logic).cascade(enabled = false).build().atomicTasks
SET_NULL -> it.kPojo.update().apply {
val listOfValidCascade = it.data.parent?.validCascades?.filter { cascade-> cascade.field == it.data.fieldOfParent }
listOfValidCascade?.forEach { validCascade->
validCascade.kCascade.properties.forEach{ property ->
val field = allFields.first { f -> f.name == property }
toUpdateFields += field
paramMapNew[field + "New"] = null
}
}
}.build().atomicTasks
SET_DEFAULT -> it.kPojo.update().apply {
val listOfValidCascade = it.data.parent?.validCascades?.filter { cascade-> cascade.field == it.data.fieldOfParent }
listOfValidCascade?.forEach { validCascade->
validCascade.kCascade.properties.forEachIndexed{ index, property ->
val field = allFields.first { f -> f.name == property }
val defaultValue = validCascade.kCascade.defaultValue.getOrNull(index)
if(defaultValue != null && defaultValue != RESERVED) {
toUpdateFields += field
paramMapNew[field + "New"] = defaultValue
}
}
}
}.build().atomicTasks
}
}.flatten()) // 生成删除任务
}
}
}
}
}