BAEL-2582 Building DSLs in Kotlin
* added basic SQL DSL * basic SQL DSL tests
This commit is contained in:
parent
46bad01ab9
commit
29c13ca801
|
@ -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…
Reference in New Issue