Coverage Summary for Class: SelectClause (com.kotlinorm.orm.select)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
SelectClause |
50%
(28/56)
|
46.4%
(39/84)
|
67.2%
(135/201)
|
57.3%
(783/1367)
|
SelectClause$Companion |
0%
(0/18)
|
|
0%
(0/22)
|
0%
(0/242)
|
Total |
37.8%
(28/74)
|
46.4%
(39/84)
|
60.5%
(135/223)
|
48.7%
(783/1609)
|
/**
* 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.select
import com.kotlinorm.beans.dsl.Criteria
import com.kotlinorm.beans.dsl.Field
import com.kotlinorm.beans.dsl.KSelectable
import com.kotlinorm.beans.dsl.KTableForCondition.Companion.afterFilter
import com.kotlinorm.beans.dsl.KTableForReference.Companion.afterReference
import com.kotlinorm.beans.dsl.KTableForSelect.Companion.afterSelect
import com.kotlinorm.beans.dsl.KTableForSort.Companion.afterSort
import com.kotlinorm.beans.task.KronosAtomicBatchTask
import com.kotlinorm.beans.task.KronosAtomicQueryTask
import com.kotlinorm.beans.task.KronosQueryTask
import com.kotlinorm.cache.fieldsMapCache
import com.kotlinorm.cache.kPojoAllColumnsCache
import com.kotlinorm.cache.kPojoAllFieldsCache
import com.kotlinorm.cache.kPojoLogicDeleteCache
import com.kotlinorm.database.SqlManager.getSelectSql
import com.kotlinorm.database.SqlManager.quoted
import com.kotlinorm.enums.KColumnType.CUSTOM_CRITERIA_SQL
import com.kotlinorm.enums.KOperationType
import com.kotlinorm.enums.PessimisticLock
import com.kotlinorm.enums.QueryType.QueryList
import com.kotlinorm.enums.QueryType.QueryOne
import com.kotlinorm.enums.QueryType.QueryOneOrNull
import com.kotlinorm.enums.SortType
import com.kotlinorm.exceptions.EmptyFieldsException
import com.kotlinorm.interfaces.KPojo
import com.kotlinorm.interfaces.KronosDataSourceWrapper
import com.kotlinorm.orm.cascade.CascadeSelectClause
import com.kotlinorm.orm.pagination.PagedClause
import com.kotlinorm.types.ToFilter
import com.kotlinorm.types.ToReference
import com.kotlinorm.types.ToSelect
import com.kotlinorm.types.ToSort
import com.kotlinorm.utils.ConditionSqlBuilder.buildConditionSqlWithParams
import com.kotlinorm.utils.DataSourceUtil.orDefault
import com.kotlinorm.utils.Extensions.asSql
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.logAndReturn
import com.kotlinorm.utils.processParams
import com.kotlinorm.utils.toLinkedSet
class SelectClause<T : KPojo>(
override val pojo: T, setSelectFields: ToSelect<T, Any?> = null
) : KSelectable<T>(pojo) {
private var kClass = pojo.kClass()
private var tableName = pojo.kronosTableName()
internal var paramMap = pojo.toDataMap()
private var logicDeleteStrategy = kPojoLogicDeleteCache[kClass]
private var allFields = kPojoAllFieldsCache[kClass]!!
private var allColumns = kPojoAllColumnsCache[kClass]!!
internal var condition: Criteria? = null
private var havingCondition: Criteria? = null
override var selectFields: LinkedHashSet<Field> = linkedSetOf()
private var groupByFields: LinkedHashSet<Field> = linkedSetOf()
private var orderByFields: LinkedHashSet<Pair<Field, SortType>> = linkedSetOf()
override var limitCapacity = 0
private var distinctEnabled = false
override var pageEnabled = false
private var groupEnabled = false
private var havingEnabled = false
private var orderEnabled = false
private var cascadeEnabled = true
/**
* 级联查询允许的字段,若为空则表示所有字段均可级联查询,优先级高于[com.kotlinorm.annotations.Ignore[com.kotlinorm.enums.IgnoreAction.CASCADE_SELECT]]
* */
internal var cascadeAllowed: Set<Field>? = null
internal var cascadeSelectedProps: Set<Field>? = null
private var lock: PessimisticLock? = null
override var selectAll = true
private var ps = 0
private var pi = 0
private var databaseName: String? = null
internal var operationType = KOperationType.SELECT // 级联操作类型,默认为SELECT
/**
* 初始化函数:用于在对象初始化时配置选择字段。
* 该函数不接受参数,也不返回任何值。
* 它首先检查setSelectFields是否为非空,如果是,则调用pojo.tableRun块,
* 在该块内调用setSelectFields方法来设置选择的字段,并将当前字段集合转换为链接集合后赋值给selectFields属性。
*/
init {
if (setSelectFields != null) {
pojo.afterSelect {
setSelectFields(it) // 设置选择的字段
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
selectFields = fields.toLinkedSet() // 将字段集合转换为不可变的链接集合并赋值给selectFields
if (selectFields.isNotEmpty()) {
selectAll = false
}
}
}
}
fun single(): SelectClause<T> {
limitCapacity = 1
return this
}
fun limit(capacity: Int): SelectClause<T> {
limitCapacity = capacity
return this
}
fun db(databaseName: String): SelectClause<T> {
if (databaseName.isNotBlank()) this.databaseName = databaseName
return this
}
/**
* 根据指定的字段对当前对象进行排序。
*
* @param someFields 可排序字段的集合,这里的字段类型为 [ToSort],单位为 [Unit]。
* 该参数指定了排序时所依据的字段。
* @return 返回 [SelectClause] 对象,允许链式调用。
*/
fun orderBy(someFields: ToSort<T, Any?>): SelectClause<T> {
if (someFields == null) throw EmptyFieldsException()
orderEnabled = true
pojo.afterSort {
someFields(it)// 在这里对排序操作进行封装,为后续的链式调用提供支持。
orderByFields = sortedFields.toLinkedSet()
}
return this // 返回当前对象,允许继续进行其他查询操作。
}
/**
* 根据指定的字段对数据进行分组。
*
* @param someFields 要用于分组的字段,类型为 KTableField<T, Unit>。该字段不能为空。
* @return 返回 SelectClause<T> 实例,允许链式调用。
* @throws EmptyFieldsException 如果 someFields 为空,则抛出此异常。
*/
fun groupBy(someFields: ToSelect<T, Any?>): SelectClause<T> {
groupEnabled = true
// 检查 someFields 参数是否为空,如果为空则抛出异常
someFields ?: throw EmptyFieldsException()
pojo.afterSelect {
someFields(it)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
// 设置分组字段
groupByFields = fields.toLinkedSet()
}
return this
}
/**
* 将当前选择语句设置为Distinct模式,即去除结果中的重复项。
*
* @return [SelectClause<T>] 返回当前选择语句实例,允许链式调用。
*/
fun distinct(): SelectClause<T> {
distinctEnabled = true // 标记为Distinct,去除结果中的重复项
return this
}
/**
* 设置分页信息,用于查询语句的分页操作。
*
* @param pi 当前页码,表示需要获取哪一页的数据。
* @param ps 每页的记录数,指定每页显示的数据量。
* @return 返回 SelectClause<T> 实例,支持链式调用。
*/
fun page(pi: Int, ps: Int): SelectClause<T> {
pageEnabled = true
this.ps = ps
this.pi = pi
return this
}
fun cascade(enabled: Boolean): SelectClause<T> {
cascadeEnabled = enabled
return this
}
fun cascade(someFields: ToReference<T, Any?>): SelectClause<T> {
someFields ?: throw EmptyFieldsException()
cascadeEnabled = true
pojo.afterReference {
someFields(it)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
cascadeAllowed = fields.toSet()
}
return this
}
/**
* 根据指定的字段构建查询条件,并返回SelectClause实例。
*
* @param someFields KTableField类型,表示要用来构建查询条件的字段。
* 不能为空,否则会抛出EmptyFieldsException异常。
* @return 返回当前SelectClause实例,允许链式调用。
*/
fun by(someFields: ToSelect<T, Any?>): SelectClause<T> {
// 检查someFields是否为空,为空则抛出异常
someFields ?: throw EmptyFieldsException()
pojo.afterSelect { t ->
// 执行someFields中定义的查询逻辑
someFields(t)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
// 构建查询条件,将字段名映射到参数值,并转换为查询条件对象
if (condition == null) {
condition = fields.map { it.eq(paramMap[it.name]) }.toCriteria()
} else {
// 如果已有条件,则将新条件添加到现有条件中
condition!!.children.add(fields.map { it.eq(paramMap[it.name]) }.toCriteria())
}
}
return this // 返回当前SelectClause实例,允许链式调用
}
/**
* 根据提供的选择条件构建查询条件。
*
* @param selectCondition 一个函数,用于定义条件查询。该函数接收一个 [ToFilter] 类型的参数,
* 并返回一个 [Boolean]? 类型的值,用于指定条件是否成立。如果为 null,则表示选择所有字段。
* @return [SelectClause] 的实例,代表了一个查询的选择子句。
*/
fun where(selectCondition: ToFilter<T, Boolean?> = null): SelectClause<T> {
selectCondition ?: return this
pojo.afterFilter {
criteriaParamMap = paramMap
selectCondition(it) // 执行用户提供的条件函数
if (criteria == null) return@afterFilter
if (condition == null) {
condition = criteria // 设置查询条件
} else {
condition!!.children.addAll(criteria!!.children) // 将新条件添加到现有条件中
}
}
return this
}
/**
* 设置HAVING条件的函数,用于在查询中添加基于聚合结果的条件限制。
*
* @param selectCondition 一个KTableConditionalField类型的函数参数,表示筛选的条件。该条件是一个函数,
* 它接收当前的参数映射表和执行条件,并设置HAVING子句的条件。
* @return 返回SelectClause类型的实例,允许链式调用。
* @throws EmptyFieldsException 如果selectCondition为null,则抛出此异常,表示需要提供条件字段。
*/
fun having(selectCondition: ToFilter<T, Boolean?> = null): SelectClause<T> {
havingEnabled = true // 标记为HAVING条件
// 检查是否提供了条件,未提供则抛出异常
selectCondition ?: throw EmptyFieldsException()
pojo.afterFilter {
criteriaParamMap = paramMap // 设置属性参数映射
selectCondition(it) // 执行传入的条件函数
if (criteria == null) return@afterFilter
if (havingCondition == null) {
havingCondition = criteria // 如果HAVING条件为空,则直接赋值
} else {
havingCondition!!.children.addAll(criteria!!.children) // 否则将新条件添加到现有HAVING条件中
}
}
return this // 允许链式调用
}
fun withTotal(): PagedClause<T, SelectClause<T>> {
return PagedClause(this)
}
fun patch(vararg pairs: Pair<String, Any?>): SelectClause<T> {
paramMap.putAll(pairs)
return this
}
fun lock(lock: PessimisticLock? = PessimisticLock.X): SelectClause<T> {
this.lock = lock
return this
}
private var buildCondition: Criteria? = null
/**
* 构建一个KronosAtomicTask对象。
*
* 该方法主要用于根据提供的KronosDataSourceWrapper(如果存在)和其他参数构建一个用于执行数据库操作的KronosAtomicTask对象。
* 这包括构建SQL查询语句及其参数映射,配置逻辑删除策略,并根据不同的标志(如分页、去重、分组等)调整查询语句的构造。
*
* @param wrapper 可选的KronosDataSourceWrapper对象,用于提供数据库表信息等。
* @return 构建好的KronosAtomicTask对象,包含了完整的SQL查询语句和对应的参数映射。
*/
override fun build(wrapper: KronosDataSourceWrapper?): KronosQueryTask {
buildCondition = condition
if (selectAll) {
selectFields += allColumns
}
val columns = allColumns
// 如果条件为空,则根据paramMap构建查询条件
if (buildCondition == null) {
buildCondition = paramMap.keys.filter {
paramMap[it] != null
}.mapNotNull { propName ->
columns.find { it.name == propName }?.eq(paramMap[propName])
}.toCriteria()
}
// 设置逻辑删除的条件
logicDeleteStrategy?.execute(defaultValue = getDefaultBoolean(wrapper.orDefault(), false)) { _, value ->
buildCondition = listOfNotNull(
buildCondition,
"${field.quoted(wrapper.orDefault())} = $value".asSql()
).toCriteria()
}
val paramMapNew = mutableMapOf<String, Any?>()
// 构建查询条件SQL
val sql = getSelectSql(wrapper.orDefault(), toSelectClauseInfo(wrapper) {
paramMapNew.putAll(it)
})
val fieldMap = fieldsMapCache[kClass]!!
paramMapNew.forEach { (key, value) ->
val field = fieldMap[key]
if (field != null && value != null) {
paramMapNew[key] = processParams(wrapper.orDefault(), field, value)
} else {
paramMapNew[key] = value
}
}
// 返回构建好的KronosAtomicTask对象
return CascadeSelectClause.build(
cascadeEnabled, cascadeAllowed, pojo, kClass, KronosAtomicQueryTask(
sql, paramMapNew, operationType = KOperationType.SELECT
), if (selectAll) allFields else selectFields,
operationType, cascadeSelectedProps ?: mutableSetOf()
)
}
/**
* 执行Kronos操作的函数。
*
* @param wrapper 可选参数,KronosDataSourceWrapper的实例,用于提供数据源配置和上下文。
* 如果为null,函数将使用默认配置执行操作。
* @return 返回KronosOperationResult对象,包含操作的结果信息。
*/
fun query(wrapper: KronosDataSourceWrapper? = null): List<Map<String, Any>> {
return this.build().query(wrapper)
}
inline fun <reified T> queryList(wrapper: KronosDataSourceWrapper? = null, isKPojo: Boolean = false, superTypes: List<String> = listOf()): List<T> {
return this.build().queryList(wrapper, isKPojo, superTypes)
}
@JvmName("queryForList")
@Suppress("UNCHECKED_CAST")
fun queryList(wrapper: KronosDataSourceWrapper? = null): List<T> {
with(this.build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
wrapper.orDefault().forList(atomicTask, kClass, true, listOf()) as List<T>, QueryList
)
afterQuery?.invoke(result, QueryList, wrapper.orDefault())
return result
}
}
fun queryMap(wrapper: KronosDataSourceWrapper? = null): Map<String, Any> {
limit(1)
return this.build().queryMap(wrapper)
}
fun queryMapOrNull(wrapper: KronosDataSourceWrapper? = null): Map<String, Any>? {
limit(1)
return this.build().queryMapOrNull(wrapper)
}
inline fun <reified T> queryOne(wrapper: KronosDataSourceWrapper? = null, isKPojo: Boolean = false, superTypes: List<String> = listOf()): T {
limit(1)
return this.build().queryOne(wrapper, isKPojo, superTypes)
}
@JvmName("queryForObject")
@Suppress("UNCHECKED_CAST")
fun queryOne(wrapper: KronosDataSourceWrapper? = null): T {
limit(1)
with(build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
(wrapper.orDefault().forObject(atomicTask, kClass, true, listOf())
?: throw NullPointerException("No such record")) as T, QueryOne
)
afterQuery?.invoke(result, QueryOne, wrapper.orDefault())
return result
}
}
inline fun <reified T> queryOneOrNull(wrapper: KronosDataSourceWrapper? = null, isKPojo: Boolean = false, superTypes: List<String> = listOf()): T? {
limit(1)
return this.build().queryOneOrNull(wrapper, isKPojo, superTypes)
}
@JvmName("queryForObjectOrNull")
@Suppress("UNCHECKED_CAST")
fun queryOneOrNull(wrapper: KronosDataSourceWrapper? = null): T? {
limit(1)
with(build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
wrapper.orDefault().forObject(atomicTask, kClass, true, listOf()) as T?, QueryOneOrNull
)
afterQuery?.invoke(result, QueryOneOrNull, wrapper.orDefault())
return result
}
}
companion object {
fun <T : KPojo> Iterable<SelectClause<T>>.by(someFields: ToSelect<T, Any?>): List<SelectClause<T>> {
return map { it.by(someFields) }
}
fun <T : KPojo> Iterable<SelectClause<T>>.cascade(
enabled: Boolean
): List<SelectClause<T>> {
return map { it.cascade(enabled) }
}
fun <T : KPojo> Iterable<SelectClause<T>>.cascade(
someFields: ToReference<T, Any?>
): List<SelectClause<T>> {
return map { it.cascade(someFields) }
}
/**
* Applies the `where` operation to each update clause in the list based on the provided update condition.
*
* @param selectCondition the condition for the update clause. Defaults to null.
* @return a list of UpdateClause objects with the updated condition
*/
fun <T : KPojo> Iterable<SelectClause<T>>.where(selectCondition: ToFilter<T, Boolean?> = null): List<SelectClause<T>> {
return map { it.where(selectCondition) }
}
/**
* Builds a KronosAtomicBatchTask from a list of UpdateClause objects.
*
* @param T The type of KPojo objects in the list.
* @return A KronosAtomicBatchTask object with the SQL and parameter map array from the UpdateClause objects.
*/
fun <T : KPojo> Iterable<SelectClause<T>>.build(): KronosAtomicBatchTask {
val tasks = this.map { it.build() }
return KronosAtomicBatchTask(
sql = tasks.first().atomicTask.sql,
paramMapArr = tasks.map { it.atomicTask.paramMap }.toTypedArray(),
operationType = KOperationType.SELECT
)
}
fun <T : KPojo> Iterable<SelectClause<T>>.query(wrapper: KronosDataSourceWrapper? = null): List<List<Map<String, Any>>> {
return map { it.query(wrapper) }
}
inline fun <reified T : KPojo> Iterable<SelectClause<T>>.queryList(wrapper: KronosDataSourceWrapper? = null): List<List<T>> {
return map { it.queryList<T>(wrapper) }
}
fun <T : KPojo> Iterable<SelectClause<T>>.queryMap(wrapper: KronosDataSourceWrapper? = null): List<Map<String, Any>> {
return map { it.queryMap(wrapper) }
}
fun <T : KPojo> Iterable<SelectClause<T>>.queryMapOrNull(wrapper: KronosDataSourceWrapper? = null): List<Map<String, Any>?> {
return map { it.queryMapOrNull(wrapper) }
}
inline fun <reified T : KPojo> Iterable<SelectClause<T>>.queryOne(wrapper: KronosDataSourceWrapper? = null): List<T> {
return map { it.queryOne(wrapper) }
}
inline fun <reified T : KPojo> Iterable<SelectClause<T>>.queryOneOrNull(wrapper: KronosDataSourceWrapper? = null): List<T?> {
return map { it.queryOneOrNull(wrapper) }
}
}
private fun toSelectClauseInfo(
wrapper: KronosDataSourceWrapper? = null, updateMap: (map: MutableMap<String, Any?>) -> Unit
): SelectClauseInfo {
// 构建带有参数的查询条件SQL
val (whereClauseSql, mapOfWhere) = buildConditionSqlWithParams(KOperationType.SELECT, wrapper, buildCondition).toWhereClause()
val groupByClauseSql =
if (groupEnabled && groupByFields.isNotEmpty()) " GROUP BY " + (groupByFields.joinToString(", ") {
it.quoted(wrapper.orDefault())
}) else null
val orderByClauseSql =
if (orderEnabled && orderByFields.isNotEmpty()) " ORDER BY " + orderByFields.joinToString(", ") {
if (it.first.type == CUSTOM_CRITERIA_SQL) it.first.toString() else it.first.quoted(wrapper.orDefault()) + " " + it.second
} else null
val (havingClauseSql, mapOfHaving) = if (havingEnabled) buildConditionSqlWithParams(
KOperationType.SELECT,
wrapper, havingCondition
).toHavingClause() else null to mutableMapOf()
updateMap(mapOfWhere)
updateMap(mapOfHaving)
return SelectClauseInfo(
databaseName,
tableName,
selectFields.toList(),
distinctEnabled,
pageEnabled,
pi,
ps,
limitCapacity,
lock,
whereClauseSql,
groupByClauseSql,
orderByClauseSql,
havingClauseSql
)
}
}