com.dbobjekts.metadata.joins.DerivedJoinChainBuilder.kt Maven / Gradle / Ivy
package com.dbobjekts.metadata.joins
import com.dbobjekts.api.AnyTable
import com.dbobjekts.api.exception.StatementBuilderException
import com.dbobjekts.metadata.Catalog
import com.dbobjekts.metadata.Table
import com.dbobjekts.metadata.column.IsForeignKey
import com.dbobjekts.util.StringUtil
class DerivedJoinChainBuilder(
val catalog: Catalog,
val drivingTable: AnyTable,
val tables: List,
private val useOuterJoins: Boolean = false
) {
init {
require(tables.any { it == drivingTable }, { "Table ${drivingTable.tableName} should be part of List $tables" })
}
fun build(): DerivedJoin {
fun extractJoins(tablePairs: List>): Pair, List> =
tablePairs.map { createPairWithOptionalJoin(it) }.partition { it.isJoined() }
fun createProperties(tbls: List): JoinProperties {
val pairings: List> = getTablePairings(tbls)
val (joined, unjoined) = extractJoins(pairings)
return JoinProperties(tbls, joined, unjoined)
}
if (tables.isEmpty())
throw StatementBuilderException("Need at least one table")
if (tables.size == 1)
return DerivedJoin(tables[0])
val joinProperties: JoinProperties = createProperties(tables)
val manyToManyTables =
joinProperties.unJoinedPairs.map { findJoinTableForUnjoinedPair(it.pair) }.filterNotNull()
val joinPropsWithMtoN = if (manyToManyTables.isNotEmpty()) createProperties(
StringUtil.concatLists(
tables,
manyToManyTables
)
) else joinProperties
return buildChain(joinPropsWithMtoN)
}
fun getTablePairings(tbs: List): List> {
val l = tbs.sortedBy { it.tableName.value }
return l.flatMap { t -> l.slice(l.indexOf(t) + 1..l.size - 1).map { Pair(t, it) } }
}
internal fun findJoin(pair: Pair): Boolean =
areJoined(pair.first, pair.second) && areJoined(pair.second, pair.first)
private fun createPairWithOptionalJoin(pair: Pair): TablePair = TablePair(pair, findJoin(pair))
internal fun findJoinTableForUnjoinedPair(pair: Pair): AnyTable? {
fun getForeignKeyFor(child: AnyTable, parent: AnyTable): IsForeignKey<*, *>? =
child.foreignKeys.find { it.parentColumn.table == parent }
if (pair.first == pair.second) {
throw StatementBuilderException("Pair elements must not refer to the same object")
}
return catalog.tables.map { Pair(getForeignKeyFor(it, pair.first), getForeignKeyFor(it, pair.second)) }
.find { it.first != null && it.second != null }?.first?.column?.table
}
internal fun areJoined(parent: AnyTable, child: AnyTable): Boolean =
child.getForeignKeysToParent(parent).filter {
it.parentColumn.table == parent
}.isNotEmpty()
internal fun buildChain(props: JoinProperties): DerivedJoin {
require(props.tables.isNotEmpty(), { "No tables to build join query" })
fun sort(sorted: List, toSort: List): List {
val maybeFound = sorted
.flatMap { t -> toSort.map { Pair(t, it) } }
.find { areJoined(it.first, it.second) || areJoined(it.second, it.first) }
return maybeFound?.let {
val m = it
sort(StringUtil.concatLists(sorted, listOf(m.second)), toSort.filterNot { it == m.second })
} ?: sorted
}
val tablesToJoin = props.tables.filterNot { it == drivingTable }
val sortedTables: List> = sort(listOf(drivingTable), tablesToJoin.sortedBy { it.tableName.value })
val sortedSet = sortedTables.toHashSet()
val unUsed = tablesToJoin.filterNot { sortedSet.contains(it) }
if (unUsed.isNotEmpty())
throw StatementBuilderException("The following table(s) could not be joined: ${StringUtil.joinBy(unUsed, { it.dbName })}")
var chain = DerivedJoin(sortedTables.first())
sortedTables.drop(1).forEach { chain = if (useOuterJoins) chain.join(it, JoinType.LEFT) else chain.join(it, JoinType.INNER) }
return chain
}
}
internal data class JoinProperties(
val tables: List,
val joinedPairs: List,
val unJoinedPairs: List
)
internal data class TablePair(val pair: Pair, val joinOpt: Boolean) {
fun isJoined(): Boolean = joinOpt
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy