Coverage Summary for Class: UpsertClause (com.kotlinorm.orm.upsert)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
UpsertClause |
68.4%
(13/19)
|
43.8%
(21/48)
|
71.7%
(86/120)
|
68%
(534/785)
|
UpsertClause$Companion |
57.1%
(4/7)
|
|
57.1%
(4/7)
|
58%
(40/69)
|
Total |
65.4%
(17/26)
|
43.8%
(21/48)
|
70.9%
(90/127)
|
67.2%
(574/854)
|
/**
* 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.upsert
import com.kotlinorm.beans.dsl.Field
import com.kotlinorm.beans.dsl.KTableForReference.Companion.afterReference
import com.kotlinorm.beans.dsl.KTableForSelect.Companion.afterSelect
import com.kotlinorm.beans.task.KronosActionTask
import com.kotlinorm.beans.task.KronosActionTask.Companion.merge
import com.kotlinorm.beans.task.KronosActionTask.Companion.toKronosActionTask
import com.kotlinorm.beans.task.KronosAtomicActionTask
import com.kotlinorm.beans.task.KronosOperationResult
import com.kotlinorm.cache.fieldsMapCache
import com.kotlinorm.cache.kPojoAllFieldsCache
import com.kotlinorm.cache.kPojoCreateTimeCache
import com.kotlinorm.cache.kPojoLogicDeleteCache
import com.kotlinorm.cache.kPojoOptimisticLockCache
import com.kotlinorm.cache.kPojoUpdateTimeCache
import com.kotlinorm.database.ConflictResolver
import com.kotlinorm.database.SqlManager
import com.kotlinorm.enums.KColumnType
import com.kotlinorm.enums.KOperationType
import com.kotlinorm.enums.PessimisticLock
import com.kotlinorm.exceptions.EmptyFieldsException
import com.kotlinorm.interfaces.KPojo
import com.kotlinorm.interfaces.KronosDataSourceWrapper
import com.kotlinorm.orm.insert.insert
import com.kotlinorm.orm.select.select
import com.kotlinorm.orm.update.update
import com.kotlinorm.types.ToReference
import com.kotlinorm.types.ToSelect
import com.kotlinorm.utils.DataSourceUtil.orDefault
import com.kotlinorm.utils.Extensions.eq
import com.kotlinorm.utils.Extensions.toCriteria
import com.kotlinorm.utils.execute
import com.kotlinorm.utils.getDefaultBoolean
import com.kotlinorm.utils.processParams
import com.kotlinorm.utils.toLinkedSet
/**
* Update Clause
*
* Creates an update clause for the given pojo.
*
* @param T the type of the pojo
*
* @property pojo the pojo for the update
* @property isExcept whether to exclude the fields from the update
* @param setUpsertFields the fields to update
* @author Jieyao Lu, OUSC
*/
class UpsertClause<T : KPojo>(
private val pojo: T,
private var setUpsertFields: ToSelect<T, Any?> = null
) {
private var paramMap = pojo.toDataMap()
private var tableName = pojo.kronosTableName()
private var kClass = pojo.kClass()
private var createTimeStrategy = kPojoCreateTimeCache[kClass]
private var updateTimeStrategy = kPojoUpdateTimeCache[kClass]
private var logicDeleteStrategy = kPojoLogicDeleteCache[kClass]
private var optimisticStrategy = kPojoOptimisticLockCache[kClass]
internal var allFields = kPojoAllFieldsCache[kClass]!!
private var onConflict = false
private var toInsertFields = linkedSetOf<Field>()
private var toUpdateFields = linkedSetOf<Field>()
private var onFields = linkedSetOf<Field>()
private var cascadeEnabled = true
private var cascadeAllowed: Set<Field>? = null
private var lock: PessimisticLock? = null
private var paramMapNew = mutableMapOf<Field, Any?>()
init {
if (setUpsertFields != null) {
pojo.afterSelect {
setUpsertFields!!(it)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
toUpdateFields += fields
}
}
}
/**
* Set the fields on which the update clause will be applied.
*
* @param someFields on which the update clause will be applied
* @throws EmptyFieldsException if the new value is null
* @return the upsert UpdateClause object
*/
fun on(someFields: ToSelect<T, Any?>): UpsertClause<T> {
if (null == someFields) throw EmptyFieldsException()
pojo.afterSelect {
someFields(it)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
onFields += fields.toSet()
}
return this
}
/**
* On duplicate key update
*
* **Please define constraints before using onConflict**
*
* @return the upsert UpdateClause object
*/
fun onConflict(): UpsertClause<T> {
onConflict = true
return this
}
fun cascade(enabled: Boolean): UpsertClause<T> {
this.cascadeEnabled = enabled
return this
}
fun cascade(someFields: ToReference<T, Any?>): UpsertClause<T> {
if (someFields == null) throw EmptyFieldsException()
cascadeEnabled = true
pojo.afterReference {
someFields(it)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
cascadeAllowed = fields.toSet()
}
return this
}
fun lock(lock: PessimisticLock = PessimisticLock.X): UpsertClause<T> {
optimisticStrategy?.enabled = false
this.lock = lock
return this
}
fun patch(vararg pairs: Pair<String, Any?>): UpsertClause<T> {
paramMapNew.putAll(pairs.map { Field(it.first) to it.second })
return this
}
fun execute(wrapper: KronosDataSourceWrapper? = null): KronosOperationResult {
return build(wrapper).execute(wrapper)
}
fun build(wrapper: KronosDataSourceWrapper? = null): KronosActionTask {
val dataSource = wrapper.orDefault()
if (toInsertFields.isEmpty()) {
toInsertFields = allFields.filter { null != paramMap[it.name] }.toLinkedSet()
}
if (toUpdateFields.isEmpty()) {
toUpdateFields = allFields
}
// 合并参数映射,准备执行SQL所需的参数
val fieldMap = fieldsMapCache[kClass]!!
paramMapNew.forEach { (key, value) ->
val field = fieldMap[key.name]
if (field != null && value != null) {
paramMap[key.name] = processParams(wrapper.orDefault(), field, value)
} else {
paramMap[key.name] = value
}
}
val paramMap = (paramMap.filter { it -> it.key in (toUpdateFields + toInsertFields + onFields).map { it.name } }).toMutableMap()
if (onConflict) {
onFields += toUpdateFields
// 设置逻辑删除策略,将被逻辑删除的字段从更新字段中移除,并更新条件语句
logicDeleteStrategy?.execute(defaultValue = getDefaultBoolean(wrapper.orDefault(), false)) { field, value ->
toInsertFields += field
paramMap[field.name] = value
}
createTimeStrategy?.execute{ field, value ->
onFields -= field
toInsertFields += field
paramMap[field.name] = value
}
// 设置更新时间策略,将更新时间字段添加到更新字段列表,并更新参数映射
updateTimeStrategy?.execute(true) { field, value ->
onFields -= field
toInsertFields += field
toUpdateFields += field
paramMap[field.name] = value
}
return KronosAtomicActionTask(
SqlManager.getOnConflictSql(
dataSource, ConflictResolver(
tableName,
onFields,
toUpdateFields,
toInsertFields
)
),
paramMap,
operationType = KOperationType.UPSERT
).toKronosActionTask()
} else {
return listOf<KronosAtomicActionTask>().toKronosActionTask().doBeforeExecute {
lock = lock ?: PessimisticLock.X.takeIf { optimisticStrategy?.enabled != true }
if ((pojo.select()
.cascade(enabled = false)
.lock(lock)
.apply {
selectFields =
linkedSetOf(Field("COUNT(1)", "COUNT(1)", type = KColumnType.CUSTOM_CRITERIA_SQL))
selectAll = false
condition = onFields.filter { it.isColumn && it.name in paramMap.keys }
.map {
it.eq(paramMap[it.name])
}.toCriteria()
}
.queryOneOrNull<Int>() ?: 0)
> 0
) {
pojo.update().cascade(cascadeEnabled)
.apply {
[email protected] = [email protected]
[email protected] = [email protected]
[email protected] {
[email protected][it + "New"] = paramMap[it.name]
}
condition = onFields.filter { it.isColumn && it.name in paramMap.keys }
.map { it.eq(paramMap[it.name]) }.toCriteria()
}
.execute(wrapper)
} else {
pojo.insert().cascade(cascadeEnabled)
.apply {
[email protected] = [email protected]
}
.execute(wrapper)
}
}
}
}
companion object {
fun <T : KPojo> List<UpsertClause<T>>.on(someFields: ToSelect<T, Any?>): List<UpsertClause<T>> {
return map { it.on(someFields) }
}
fun <T : KPojo> List<UpsertClause<T>>.onConflict(): List<UpsertClause<T>> {
return map { it.onConflict() }
}
fun <T : KPojo> List<UpsertClause<T>>.cascade(
enabled: Boolean
): List<UpsertClause<T>> {
return map { it.cascade(enabled) }
}
fun <T : KPojo> List<UpsertClause<T>>.cascade(
someFields: ToReference<T, Any?>
): List<UpsertClause<T>> {
return map { it.cascade(someFields) }
}
fun <T : KPojo> List<UpsertClause<T>>.build(): KronosActionTask {
return map { it.build() }.merge()
}
fun <T : KPojo> List<UpsertClause<T>>.execute(wrapper: KronosDataSourceWrapper? = null): KronosOperationResult {
return build().execute(wrapper)
}
}
}