BAEL-2582 Building DSLs in Kotlin
* added basic SQL DSL * basic SQL DSL tests
This commit is contained in:
parent
46bad01ab9
commit
29c13ca801
114
core-kotlin/src/main/kotlin/com/baeldung/kotlin/dsl/SqlDsl.kt
Normal file
114
core-kotlin/src/main/kotlin/com/baeldung/kotlin/dsl/SqlDsl.kt
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package com.baeldung.kotlin.dsl
|
||||||
|
|
||||||
|
abstract class Condition {
|
||||||
|
|
||||||
|
fun and(initializer: Condition.() -> Unit) {
|
||||||
|
addCondition(And().apply(initializer))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun or(initializer: Condition.() -> Unit) {
|
||||||
|
addCondition(Or().apply(initializer))
|
||||||
|
}
|
||||||
|
|
||||||
|
infix fun String.eq(value: Any?) {
|
||||||
|
addCondition(Eq(this, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun addCondition(condition: Condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class CompositeCondition(private val sqlOperator: String) : Condition() {
|
||||||
|
private val conditions = mutableListOf<Condition>()
|
||||||
|
|
||||||
|
override fun addCondition(condition: Condition) {
|
||||||
|
conditions += condition
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return if (conditions.size == 1) {
|
||||||
|
conditions.first().toString()
|
||||||
|
} else {
|
||||||
|
conditions.joinToString(prefix = "(", postfix = ")", separator = " $sqlOperator ") {
|
||||||
|
"$it"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class And : CompositeCondition("and")
|
||||||
|
|
||||||
|
class Or : CompositeCondition("or")
|
||||||
|
|
||||||
|
class Eq(private val column: String, private val value: Any?) : Condition() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (value != null && value !is Number && value !is String) {
|
||||||
|
throw IllegalArgumentException("Only <null>, numbers and strings values can be used in the 'where' clause")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addCondition(condition: Condition) {
|
||||||
|
throw IllegalStateException("Can't add a nested condition to the sql 'eq'")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (value) {
|
||||||
|
null -> "$column is null"
|
||||||
|
is String -> "$column = '$value'"
|
||||||
|
else -> "$column = $value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SqlSelectBuilder {
|
||||||
|
|
||||||
|
private val columns = mutableListOf<String>()
|
||||||
|
private lateinit var table: String
|
||||||
|
private var condition: Condition? = null
|
||||||
|
|
||||||
|
fun select(vararg columns: String) {
|
||||||
|
if (columns.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("At least one column should be defined")
|
||||||
|
}
|
||||||
|
if (this.columns.isNotEmpty()) {
|
||||||
|
throw IllegalStateException("Detected an attempt to re-define columns to fetch. Current columns list: "
|
||||||
|
+ "${this.columns}, new columns list: $columns")
|
||||||
|
}
|
||||||
|
this.columns.addAll(columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun from(table: String) {
|
||||||
|
this.table = table
|
||||||
|
}
|
||||||
|
|
||||||
|
fun where(initializer: Condition.() -> Unit) {
|
||||||
|
condition = And().apply(initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): String {
|
||||||
|
if (!::table.isInitialized) {
|
||||||
|
throw IllegalStateException("Failed to build an sql select - target table is undefined")
|
||||||
|
}
|
||||||
|
return toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val columnsToFetch =
|
||||||
|
if (columns.isEmpty()) {
|
||||||
|
"*"
|
||||||
|
} else {
|
||||||
|
columns.joinToString(", ")
|
||||||
|
}
|
||||||
|
val conditionString =
|
||||||
|
if (condition == null) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
" where $condition"
|
||||||
|
}
|
||||||
|
return "select $columnsToFetch from $table$conditionString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun query(initializer: SqlSelectBuilder.() -> Unit): SqlSelectBuilder {
|
||||||
|
return SqlSelectBuilder().apply(initializer)
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.baeldung.kotlin.dsl
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
class SqlDslTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when no columns are specified then star is used`() {
|
||||||
|
doTest("select * from table1") {
|
||||||
|
from ("table1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when no condition is specified then correct query is built`() {
|
||||||
|
doTest("select column1, column2 from table1") {
|
||||||
|
select("column1", "column2")
|
||||||
|
from ("table1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = Exception::class)
|
||||||
|
fun `when no table is specified then an exception is thrown`() {
|
||||||
|
query {
|
||||||
|
select("column1")
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when a list of conditions is specified then it's respected`() {
|
||||||
|
doTest("select * from table1 where (column3 = 4 and column4 is null)") {
|
||||||
|
from ("table1")
|
||||||
|
where {
|
||||||
|
"column3" eq 4
|
||||||
|
"column4" eq null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when 'or' conditions are specified then they are respected`() {
|
||||||
|
doTest("select * from table1 where (column3 = 4 or column4 is null)") {
|
||||||
|
from ("table1")
|
||||||
|
where {
|
||||||
|
or {
|
||||||
|
"column3" eq 4
|
||||||
|
"column4" eq null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when either 'and' or 'or' conditions are specified then they are respected`() {
|
||||||
|
doTest("select * from table1 where ((column3 = 4 or column4 is null) and column5 = 42)") {
|
||||||
|
from ("table1")
|
||||||
|
where {
|
||||||
|
and {
|
||||||
|
or {
|
||||||
|
"column3" eq 4
|
||||||
|
"column4" eq null
|
||||||
|
}
|
||||||
|
"column5" eq 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTest(expected: String, sql: SqlSelectBuilder.() -> Unit) {
|
||||||
|
assertThat(query(sql).build()).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user