com.vladsch.kotlin.jdbc.SqlQueryBase.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-jdbc Show documentation
Show all versions of kotlin-jdbc Show documentation
A thin library that exposes JDBC API with the convenience of Kotlin and gets out of the way when not needed.
The newest version!
package com.vladsch.kotlin.jdbc
import org.jetbrains.annotations.TestOnly
import java.sql.PreparedStatement
abstract class SqlQueryBase>(
val statement: String,
params: List = listOf(),
namedParams: Map = mapOf()
) {
protected val params = ArrayList(params.asParamList())
protected val namedParams: HashMap> = HashMap(namedParams.asParamMap())
private var _queryDetails: Details? = null
private val queryDetails: Details get() = finalizedQuery()
val replacementMap get() = queryDetails.replacementMap
val cleanStatement get() = queryDetails.cleanStatement
val inputParams: Map> get() = this.namedParams.filter { it.value.inOut.isIn }
val outputParams: Map> get() = this.namedParams.filter { it.value.inOut.isOut }
data class Details(
val listParamsMap: Map,
val replacementMap: Map>,
val cleanStatement: String,
val paramCount: Int
)
protected fun resetDetails() {
_queryDetails = null
}
private fun finalizedQuery(): Details {
if (_queryDetails == null) {
// called when parameters are defined and have request for clean statement or populate params
val listParamsMap: HashMap = HashMap()
var idxOffset = 0
val findAll = regex.findAll(statement)
val replacementMap = findAll.filter { group ->
if (!group.value.startsWith(":")) {
// not a parameter
false
} else {
// filter out commented lines
val pos = statement.lastIndexOf('\n', group.range.first)
val lineStart = if (pos == -1) 0 else pos + 1;
!regexSqlComment.containsMatchIn(statement.subSequence(lineStart, statement.length))
}
}.map { group ->
val paramName = group.value.substring(1);
val paramValue = namedParams[paramName]
val pair = Pair(paramName, idxOffset)
if (paramValue?.value is Collection<*>) {
val size = paramValue.value.size
listParamsMap[paramName] = "?,".repeat(size).substring(0, size * 2 - 1)
idxOffset += size - 1
}
idxOffset++
pair
}.groupBy({ it.first }, { it.second })
val cleanStatement = regex.replace(statement) { matchResult ->
if (!matchResult.value.startsWith(":")) {
// not a parameter, leave as is
matchResult.value
} else {
val paramName = matchResult.value.substring(1);
listParamsMap[paramName] ?: "?"
}
}
_queryDetails = Details(listParamsMap, replacementMap, cleanStatement, idxOffset)
}
return _queryDetails!!
}
fun populateParams(stmt: PreparedStatement) {
// TODO: handle mix of ? and named params, by computing indices for ? params and populating based on index
if (replacementMap.isNotEmpty()) {
populateNamedParams(stmt)
} else {
params.forEachIndexed { index, value ->
stmt.setTypedParam(index + 1, value.param())
}
}
}
protected fun forEachNamedParam(inOut: InOut, action: (paramName: String, param: Parameter<*>, occurrences: List) -> Unit) {
replacementMap.forEach { (paramName, occurrences) ->
val param = namedParams[paramName] ?: NULL_PARAMETER
if (param.inOut.isOf(inOut)) action.invoke(paramName, param, occurrences)
}
}
protected open fun populateNamedParams(stmt: PreparedStatement) {
forEachNamedParam(InOut.IN) { _, param, occurrences ->
if (param.value is Collection<*>) {
param.value.forEachIndexed { idx, paramItem ->
occurrences.forEach {
when (paramItem) {
is Parameter<*> -> stmt.setTypedParam(it + idx + 1, param)
else -> stmt.setParam(it + idx + 1, paramItem)
}
}
}
} else {
occurrences.forEach {
stmt.setTypedParam(it + 1, param)
}
}
}
}
@TestOnly
fun getParams(): List {
return if (replacementMap.isNotEmpty()) {
val sqlParams = ArrayList(queryDetails.paramCount)
for (i in 0 until queryDetails.paramCount) {
sqlParams.add(null)
}
forEachNamedParam(InOut.IN) { _, param, occurrences ->
occurrences.forEach {
if (param.value is Collection<*>) {
param.value.forEachIndexed { idx, paramItem ->
sqlParams[it + idx] = paramItem
}
} else {
sqlParams[it] = param.value
}
}
}
sqlParams
} else {
params
}
}
fun params(vararg params: Any?): T {
this.params.addAll(params.asParamList())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
fun paramsArray(params: Array): T {
this.params.addAll(params.asParamList())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
fun paramsList(params: Collection): T {
this.params.addAll(params.asParamList())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
fun params(params: Map): T {
namedParams.putAll(params.asParamMap())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
fun params(vararg params: Pair): T {
namedParams.putAll(params.asParamMap())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
fun paramsArray(params: Array>): T {
namedParams.putAll(params.asParamMap())
this._queryDetails = null
@Suppress("UNCHECKED_CAST")
return this as T
}
@Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
fun inParams(params: Map): T {
return params(params.asParamMap(InOut.IN))
}
@Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("params"))
fun inParams(vararg params: Pair): T {
return params(params.asParamMap(InOut.IN))
}
@Deprecated(message = "Use directional parameter construction with to/inTo, outTo, inOutTo infix functions", replaceWith = ReplaceWith("paramsArray"))
fun inParamsArray(params: Array>): T {
return paramsArray(params)
}
override fun toString(): String {
return "SqlQueryBase(statement='$statement', params=$params, namedParams=$namedParams, replacementMap=$replacementMap, cleanStatement='$cleanStatement')"
}
companion object {
// must begin with
private val NULL_PARAMETER = Parameter(null, Any::class.java, InOut.IN)
private val regex = Regex(":\\w+|'(?:[^']|'')*'|`(?:[^`])*`|\"(?:[^\"])*\"")
private val regexSqlComment = Regex("""^\s*(?:--\s|#)""")
}
}