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

net.dzikoysk.exposed.upsert.UpsertStatement.kt Maven / Gradle / Ivy

There is a newer version: 1.2.2
Show newest version
package net.dzikoysk.exposed.upsert

import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.Index
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.transactions.TransactionManager

/**
 * Upsert statement for Exposed.
 */
internal class UpsertStatement(
    table: Table,
    conflictColumn: Column<*>? = null,
    conflictIndex: Index? = null,
    private val updateStatement: UpsertInsertStatement
) : InsertStatement(table, false) {

    private val indexName: String
    private val indexColumns: List>
    private val index: Boolean

    init {
        when {
            conflictIndex != null -> {
                index = true
                indexName = conflictIndex.indexName
                indexColumns = conflictIndex.columns
            }
            conflictColumn != null -> {
                index = false
                indexName = TransactionManager.current().db.identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(conflictColumn.name)
                indexColumns = listOf(conflictColumn)
            }
            else -> throw IllegalArgumentException()
        }
    }

    // Merge arguments from INSERT + UPDATE statements in sublist
    override fun arguments(): List>> {
        val updateArgs = updateStatement.arguments()

        val result = super.arguments().mapIndexed { index, iterable ->
            val list = iterable.toList()
            list + (updateArgs.getOrNull(index) ?: return@mapIndexed list)
        }

        return result
    }

    // @Suppress("UNCHECKED_CAST")
    override fun prepareSQL(transaction: Transaction) = buildString {
        append(super.prepareSQL(transaction))

        val updateValues = updateStatement.builderValues

        if (updateValues.isEmpty()) {
            return@buildString
        }

        val upsert = when (val dialect = transaction.db.vendor) {
            "mysql", "mariadb", "h2" -> onDuplicateKeyUpdate(transaction, updateValues)
            "postgresql" -> {
                if (index) {
                    append(" ON CONFLICT ON CONSTRAINT $indexName")
                } else {
                    append(" ON CONFLICT($indexName)")
                }

                doUpdateSet(transaction, updateValues)
            }
            "sqlite" -> {
                append(" ON CONFLICT(")

                append(indexColumns.joinToString(",") { '"' + it.name + '"' })

                append(")")
                doUpdateSet(transaction, updateValues)
            }
            else -> throw UnsupportedOperationException("Unsupported SQL dialect: $dialect")
        }

        append(upsert)
    }

    private fun doUpdateSet(transaction: Transaction, updateValues: Map, Any?>): String = buildString {
        append(" DO UPDATE SET ")
        append(appendUpdateSet(transaction, updateValues))
    }

    private fun onDuplicateKeyUpdate(transaction: Transaction, updateValues: Map, Any?>): String = buildString {
        append(" ON DUPLICATE KEY UPDATE ")
        append(appendUpdateSet(transaction, updateValues))
    }
    private fun appendUpdateSet(transaction: Transaction, updateValues: Map, Any?>): String = buildString {
        updateValues.entries
            .filter { it.key !in indexColumns }
            .sortedBy { it.key }
            .joinTo(this) { (column, value) ->
                when (value) {
                    is Expression<*> -> {
                        val queryBuilder = QueryBuilder(true)
                        value.toQueryBuilder(queryBuilder)
                        "${transaction.identity(column)}=${queryBuilder}"
                    }
                    else -> "${transaction.identity(column)}=?"
                }
            }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy