All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dbobjekts.metadata.joins.DerivedJoinChainBuilder.kt Maven / Gradle / Ivy

There is a newer version: 0.6.0-RC2
Show newest version
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