BAEL-2582 Building DSLs in Kotlin

* added basic SQL DSL
* basic SQL DSL tests
This commit is contained in:
Denis Zhdanov 2019-02-06 11:12:14 +08:00
parent 46bad01ab9
commit 29c13ca801
2 changed files with 188 additions and 0 deletions

View 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)
}

View File

@ -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)
}
}