Coverage Summary for Class: SelectFrom (com.kotlinorm.orm.join)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
SelectFrom |
38.5%
(25/65)
|
37.7%
(49/130)
|
59.5%
(182/306)
|
48.8%
(1016/2082)
|
SelectFrom$crossJoin$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/34)
|
SelectFrom$fullJoin$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/34)
|
SelectFrom$innerJoin$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/34)
|
SelectFrom$leftJoin$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/34)
|
SelectFrom$rightJoin$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/34)
|
Total |
35.7%
(25/70)
|
37.7%
(49/130)
|
56.7%
(182/321)
|
45.1%
(1016/2252)
|
/**
* 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.join
import com.kotlinorm.beans.dsl.Criteria
import com.kotlinorm.beans.dsl.Field
import com.kotlinorm.beans.dsl.KJoinable
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.KronosAtomicQueryTask
import com.kotlinorm.beans.task.KronosQueryTask
import com.kotlinorm.cache.fieldsMapCache
import com.kotlinorm.cache.kPojoAllColumnsCache
import com.kotlinorm.cache.kPojoLogicDeleteCache
import com.kotlinorm.database.SqlManager.getJoinSql
import com.kotlinorm.database.SqlManager.quote
import com.kotlinorm.enums.JoinType
import com.kotlinorm.enums.KColumnType.CUSTOM_CRITERIA_SQL
import com.kotlinorm.enums.KOperationType
import com.kotlinorm.enums.QueryType
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.CascadeJoinClause
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
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.KStack
import com.kotlinorm.utils.execute
import com.kotlinorm.utils.getDefaultBoolean
import com.kotlinorm.utils.logAndReturn
import com.kotlinorm.utils.pop
import com.kotlinorm.utils.processParams
import com.kotlinorm.utils.push
import com.kotlinorm.utils.toLinkedSet
import kotlin.reflect.KClass
/**
* Select From
*
* Create a joint clause for the given pojos
*
* @param T1 the type of the first pojo
*
* @property t1 the instance of the first pojo
*/
open class SelectFrom<T1 : KPojo>(open val t1: T1) : KSelectable<T1>(t1) {
open lateinit var tableName: String
open lateinit var paramMap: MutableMap<String, Any?>
private var kClass = pojo.kClass()
open var logicDeleteStrategy = kPojoLogicDeleteCache[kClass]
open var allFields = kPojoAllColumnsCache[kClass]!!
open lateinit var listOfPojo: MutableList<Pair<KClass<KPojo>, KPojo>>
private var condition: Criteria? = null
private var havingCondition: Criteria? = null
override var selectFields: LinkedHashSet<Field> = linkedSetOf()
override var selectAll: Boolean = false
private var selectFieldsWithNames: MutableMap<String, Field> = mutableMapOf()
private var keyCounters: ConditionSqlBuilder.KeyCounter = ConditionSqlBuilder.KeyCounter()
val listOfJoinable: MutableList<KJoinable> = mutableListOf()
private var groupByFields: LinkedHashSet<Field> = linkedSetOf()
private var orderByFields: LinkedHashSet<Pair<Field, SortType>> = linkedSetOf()
private var distinctEnabled = false
private var groupEnabled = false
private var havingEnabled = false
private var orderEnabled = false
override var pageEnabled = false
override var limitCapacity = 0
private var cascadeEnabled = true
private var cascadeAllowed: Set<Field>? = null
private var cascadeSelectedProps: Set<Field>? = null
private var pi = 0
private var ps = 0
private val databaseOfTable: MutableMap<String, String> = mutableMapOf()
internal var operationType = KOperationType.SELECT
fun on(on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val criteriaMap = mutableMapOf<String, MutableList<Criteria>>()
val constMap = mutableMapOf<String, MutableList<Criteria>>()
val repeatList = mutableListOf<Triple<Criteria, String, String>>()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
val stack = KStack<Criteria>()
var cur = criteria
var prev = criteria
while (null != cur || !stack.isEmpty()) {
while (null != cur) {
stack.push(cur)
cur = if (cur.children.isNotEmpty()) cur.children.first() else null
}
val top = stack.pop()
if (top.children.size <= 1 || top.children[1] == prev) {
prev = top
val topTableName = top.tableName
if (!topTableName.isNullOrEmpty()) {
val fieldTableName = top.field.tableName
val valueTableName = if (top.value is Field) (top.value as Field).tableName else null
if (null == valueTableName)
setInMap(top, fieldTableName, constMap)
else if (valueTableName == tableName || (fieldTableName != tableName && !criteriaMap.contains(
fieldTableName
) && criteriaMap.contains(valueTableName))
) //value侧为主表或目前field侧无条件而value侧有条件,直接将条件放入field侧
setInMap(top, fieldTableName, criteriaMap)
else if (fieldTableName == tableName || (valueTableName != tableName && criteriaMap.contains(
fieldTableName
))
) //field侧为主表或目前value侧无条件而field侧有条件或两侧都有条件,可直接将条件放入vaule侧
setInMap(top, valueTableName, criteriaMap)
else { // 条件两侧均未出现过,将条件放入两侧,后期再根据两端条件数量删除一侧
setInMap(top, valueTableName, criteriaMap)
setInMap(top, fieldTableName, criteriaMap)
repeatList.add(Triple(top, fieldTableName, valueTableName))
}
}
if (stack.isNotEmpty()) stack.pop()
} else cur = top.children[1]
}
repeatList.forEach {
val (repeatCriteria, fieldTableName, valueTableName) = it
if (null != criteriaMap[fieldTableName] && criteriaMap[fieldTableName]!!.size == 1)
removeInMap(repeatCriteria, valueTableName, criteriaMap)
else removeInMap(repeatCriteria, fieldTableName, criteriaMap)
}
criteriaMap.putAll(constMap)
criteriaMap.keys.forEach { tableName ->
val (kClass, kPojo) = listOfPojo.first { it.second.kronosTableName() == tableName }
listOfJoinable.add(
KJoinable(
tableName,
JoinType.LEFT_JOIN,
criteriaMap[tableName]!!.toCriteria(),
kClass,
kPojo
)
)
}
}
}
private fun setInMap(
criteria: Criteria,
criteriaTableName: String,
map: MutableMap<String, MutableList<Criteria>>
) {
val criteriaList = map.getOrDefault(criteriaTableName, mutableListOf())
criteriaList.add(criteria)
map[criteriaTableName] = criteriaList
}
private fun removeInMap(
criteria: Criteria,
criteriaTableName: String,
map: MutableMap<String, MutableList<Criteria>>
) {
val criteriaList = map[criteriaTableName]!!
criteriaList.remove(criteria)
map[criteriaTableName] = criteriaList
}
/**
* Performs a left join operation between two tables.
*
* @param another The table to join with.
* @param on The condition for the join.
* @throws EmptyFieldsException If the `on` parameter is null.
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : KPojo> leftJoin(another: T, noinline on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val tableName = another.kronosTableName()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
listOfJoinable.add(KJoinable(tableName, JoinType.LEFT_JOIN, criteria, T::class as KClass<KPojo>, another))
}
}
/**
* Performs a right join operation between two tables.
*
* @param another The table to join with.
* @param on The condition for the join.
* @throws EmptyFieldsException If the `on` parameter is null.
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : KPojo> rightJoin(another: T, noinline on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val tableName = another.kronosTableName()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
listOfJoinable.add(KJoinable(tableName, JoinType.RIGHT_JOIN, criteria, T::class as KClass<KPojo>, another))
}
}
/**
* Performs a cross join operation between two tables.
*
* @param another The table to join with.
* @param on The condition for the join.
* @throws EmptyFieldsException If the `on` parameter is null.
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : KPojo> crossJoin(another: T, noinline on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val tableName = another.kronosTableName()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
listOfJoinable.add(KJoinable(tableName, JoinType.CROSS_JOIN, criteria, T::class as KClass<KPojo>, another))
}
}
/**
* Performs an inner join operation between two tables.
*
* @param another The table to join with.
* @param on The condition for the join.
* @throws EmptyFieldsException If the `on` parameter is null.
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : KPojo> innerJoin(another: T, noinline on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val tableName = another.kronosTableName()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
listOfJoinable.add(KJoinable(tableName, JoinType.INNER_JOIN, criteria, T::class as KClass<KPojo>, another))
}
}
/**
* Performs a full join operation between two tables.
*
* @param another The table to join with.
* @param on The condition for the join.
* @throws EmptyFieldsException If the `on` parameter is null.
*/
@Suppress("UNCHECKED_CAST")
inline fun <reified T : KPojo> fullJoin(another: T, noinline on: ToFilter<T1, Boolean?>) {
if (null == on) throw EmptyFieldsException()
val tableName = another.kronosTableName()
t1.afterFilter {
criteriaParamMap = paramMap
on(t1)
listOfJoinable.add(KJoinable(tableName, JoinType.FULL_JOIN, criteria, T::class as KClass<KPojo>, another))
}
}
/**
* Selects the specified fields from the table associated with the given KTableField.
*
* @param someFields The KTableField representing the fields to be selected.
*/
@Suppress("UNCHECKED_CAST")
fun select(someFields: ToSelect<T1, Any?>) {
if (null == someFields) return
pojo.afterSelect {
someFields(t1)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
selectFields += fields
fields.forEach { field ->
val safeKey = ConditionSqlBuilder.getSafeKey(
field.name,
keyCounters,
selectFieldsWithNames as MutableMap<String, Any?>,
field
)
selectFieldsWithNames[safeKey] = field
}
}
}
fun db(vararg databaseOfTables: Pair<KPojo, String>) {
databaseOfTables.forEach {
databaseOfTable[it.first.kronosTableName()] = it.second
}
}
fun cascade(enabled: Boolean) {
cascadeEnabled = enabled
}
fun cascade(someFields: ToReference<T1, Any?>) {
if (someFields == null) throw EmptyFieldsException()
cascadeEnabled = true
pojo.afterReference {
someFields(t1)
if (fields.isEmpty()) throw EmptyFieldsException()
cascadeAllowed = fields.toSet()
}
}
/**
* Orders the result set by the specified fields.
*
* @param someFields The fields to order the result set by.
* @throws EmptyFieldsException If the `someFields` parameter is null.
*/
fun orderBy(someFields: ToSort<T1, Any?>) {
if (someFields == null) throw EmptyFieldsException()
orderEnabled = true
pojo.afterSort {
someFields(t1)// 在这里对排序操作进行封装,为后续的链式调用提供支持。
orderByFields = sortedFields.toLinkedSet()
}
}
/**
* Sets the groupBy flag to true and checks if the `someFields` parameter is null.
* If it is null, throws a EmptyFieldsException.
*
* @param someFields The fields to group the result set by.
* @throws EmptyFieldsException If the `someFields` parameter is null.
*/
fun groupBy(someFields: ToSelect<T1, Any?>) {
groupEnabled = true
// 检查 someFields 参数是否为空,如果为空则抛出异常
if (null == someFields) throw EmptyFieldsException()
pojo.afterSelect {
someFields(t1)
if (fields.isEmpty()) {
throw EmptyFieldsException()
}
// 设置分组字段
groupByFields = fields.toLinkedSet()
}
}
/**
* Sets the distinctEnabled flag to true, indicating that the result set should be distinct.
*/
fun distinct() {
this.distinctEnabled = true
}
/**
* Sets the limit flag to true and sets the limit capacity to the specified number.
*
* @param num the number of records to limit the result set to
*/
fun limit(num: Int) {
this.limitCapacity = num
}
/**
* Sets the page information for the query, enabling pagination.
*
* @param pi the current page number, indicating which page of data to retrieve
* @param ps the number of records per page, specifying the number of records to display per page
*/
fun page(pi: Int, ps: Int) {
this.pageEnabled = true
this.ps = ps
this.pi = pi
}
/**
* Executes the query logic defined in [someFields] and builds the query condition.
*
* @param someFields the fields to be queried
* @throws EmptyFieldsException if [someFields] is null
*/
fun by(someFields: ToSelect<T1, Any?>) {
// 检查someFields是否为空,为空则抛出异常
if (null == someFields) throw EmptyFieldsException()
pojo.afterSelect {
// 执行someFields中定义的查询逻辑
someFields(t1)
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())
}
}
}
/**
* Sets the condition for the query based on the provided select condition.
*
* @param selectCondition the conditional field representing the query condition. Defaults to null.
* If null, a condition is built to query all fields. Otherwise, the provided select condition is executed
* and the resulting condition is set.
*/
fun where(selectCondition: ToFilter<T1, Boolean?> = null) {
if (selectCondition == null) {
// 当没有提供选择条件时,构建一个查询所有字段的条件
condition = paramMap.keys.map { propName ->
allFields.first { it.name == propName }.eq(paramMap[propName])
}.toCriteria()
} else {
pojo.afterFilter {
criteriaParamMap = paramMap
selectCondition(t1) // 执行用户提供的条件函数
if (criteria == null) return@afterFilter
if (condition == null) {
condition = criteria // 设置查询条件
} else {
condition!!.children.addAll(criteria!!.children) // 将新条件添加到现有条件中
}
}
}
}
/**
* Sets the condition for the HAVING clause based on the provided select condition.
*
* @param selectCondition the conditional field representing the HAVING condition. Defaults to null.
* If null, a condition is built to query all fields. Otherwise, the provided select condition is executed
* and the resulting condition is set.
*
* @throws EmptyFieldsException if the selectCondition parameter is null.
*/
fun having(selectCondition: ToFilter<T1, Boolean?> = null) {
// 检查是否提供了条件,未提供则抛出异常
if (selectCondition == null) throw EmptyFieldsException()
havingEnabled = true // 标记为HAVING条件
pojo.afterFilter {
criteriaParamMap = paramMap // 设置属性参数映射
selectCondition(t1) // 执行传入的条件函数
if (criteria == null) return@afterFilter
if (havingCondition == null) {
havingCondition = criteria // 如果HAVING条件为空,则直接赋值
} else {
havingCondition!!.children.addAll(criteria!!.children) // 否则将新条件添加到现有HAVING条件中
}
}
}
fun patch(vararg pairs: Pair<String, Any?>) {
paramMap.putAll(pairs)
}
/**
* Queries the data source using the provided data source wrapper and returns a list of maps representing the results.
*
* @param wrapper the data source wrapper to use for the query. Defaults to null. If null, the default data source wrapper is used.
* @return a list of maps representing the results of the query.
*/
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<T1> {
with(this.build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
wrapper.orDefault().forList(atomicTask, pojo::class, true, listOf()) as List<T1>,
QueryType.QueryList
)
afterQuery?.invoke(result, QueryType.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): T1 {
limit(1)
with(this.build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
(wrapper.orDefault().forObject(atomicTask, pojo::class, true, listOf())
?: throw NullPointerException("No such record")) as T1,
QueryType.QueryOne
)
afterQuery?.invoke(result, QueryType.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): T1? {
limit(1)
with(this.build()) {
beforeQuery?.invoke(this)
val result = atomicTask.logAndReturn(
wrapper.orDefault().forObject(atomicTask, pojo::class, true, listOf()) as T1?,
QueryType.QueryOneOrNull
)
afterQuery?.invoke(result, QueryType.QueryOneOrNull, wrapper.orDefault())
return result
}
}
/**
* Builds and returns a KronosAtomicQueryTask object based on the provided data source wrapper.
*
* @param wrapper the data source wrapper to use for the query. Defaults to null. If null, the default data source wrapper is used.
* @return a KronosAtomicQueryTask object representing the query.
*/
override fun build(wrapper: KronosDataSourceWrapper?): KronosQueryTask {
var buildCondition = condition
// 初始化所有字段集合
if (selectFields.isEmpty()) {
selectFields += allFields
}
// 如果条件为空,则根据paramMap构建查询条件
if (buildCondition == null) {
buildCondition = paramMap.keys.filter {
paramMap[it] != null
}.mapNotNull { propName ->
allFields.firstOrNull { it.name == propName }?.eq(paramMap[propName])
}.toCriteria()
}
// 设置逻辑删除的条件
logicDeleteStrategy?.execute(defaultValue = getDefaultBoolean(wrapper.orDefault(), false)) { _, value ->
buildCondition = listOfNotNull(
buildCondition,
"${quote(wrapper.orDefault(), logicDeleteStrategy!!.field, true, databaseOfTable)} = $value".asSql()
).toCriteria()
}
val paramMapNew = mutableMapOf<String, Any?>()
val sql = getJoinSql(wrapper.orDefault(), toJoinClauseInfo(wrapper, buildCondition) {
paramMapNew.putAll(it.filter { entry -> null != entry.value })
})
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 CascadeJoinClause.build(
cascadeEnabled, cascadeAllowed, listOfPojo, KronosAtomicQueryTask(
sql, paramMapNew, operationType = KOperationType.SELECT
), operationType, selectFieldsWithNames, cascadeSelectedProps ?: mutableSetOf()
)
}
private fun toJoinClauseInfo(
wrapper: KronosDataSourceWrapper? = null,
buildCondition: Criteria?,
updateMap: (map: MutableMap<String, Any?>) -> Unit
): JoinClauseInfo {
val (whereClauseSql, mapOfWhere) = buildConditionSqlWithParams(
KOperationType.SELECT,
wrapper,
buildCondition,
showTable = true,
databaseOfTable = databaseOfTable
).toWhereClause()
val joinSql = " " + listOfJoinable.joinToString(" ") {
var joinCondition = it.condition
val logicDeleteStrategy = kPojoLogicDeleteCache[it.kClass]
logicDeleteStrategy?.execute(defaultValue = getDefaultBoolean(wrapper.orDefault(), false)) { _, value ->
joinCondition = listOfNotNull(
joinCondition,
"${
quote(
wrapper.orDefault(),
logicDeleteStrategy.field,
true,
databaseOfTable
)
} = $value".asSql()
).toCriteria()
}
val (onSql, mapOfOn) = buildConditionSqlWithParams(
KOperationType.SELECT,
wrapper,
joinCondition,
paramMap,
showTable = true,
databaseOfTable = databaseOfTable
)
.toOnClause()
updateMap(mapOfOn)
it.joinType.value + " " + quote(wrapper.orDefault(), it.tableName, true, map = databaseOfTable) + onSql
}
val groupByClauseSql =
if (groupEnabled && groupByFields.isNotEmpty()) " GROUP BY " + (groupByFields.joinToString(", ") {
quote(wrapper.orDefault(), it, true, databaseOfTable)
}) else null
val orderByClauseSql =
if (orderEnabled && orderByFields.isNotEmpty()) " ORDER BY " + orderByFields.joinToString(", ") {
if (it.first.type == CUSTOM_CRITERIA_SQL) it.first.toString() else quote(
wrapper.orDefault(),
it.first,
true,
databaseOfTable
) + " " + it.second
} else null
val (havingClauseSql, mapOfHaving) = if (havingEnabled) buildConditionSqlWithParams(
KOperationType.SELECT, wrapper, havingCondition, showTable = true, databaseOfTable = databaseOfTable
).toHavingClause() else null to mutableMapOf()
updateMap(mapOfWhere)
updateMap(mapOfHaving)
return JoinClauseInfo(
tableName,
selectFieldsWithNames.toList(),
distinctEnabled,
pageEnabled,
pi,
ps,
limitCapacity,
databaseOfTable,
whereClauseSql,
groupByClauseSql,
orderByClauseSql,
havingClauseSql,
joinSql
)
}
}