-
Notifications
You must be signed in to change notification settings - Fork 250
Kotlin DSL #3914
vanillajonathan
started this conversation in
Show and tell
Kotlin DSL
#3914
-
Kotlin is a language with some interesting features which lets you write domain-specific languages (DSL), a language within the language.
It lets you have strongly-typed arguments and lets the IDE provide you with auto-completion for aggregates.
This just scratches the surface of what is possible, it is possible to have strongly typed columns, and use infix functions, operator overloading and other stuff to implement custom operators and keywords to have it more strongly typed with compiler help and less strings.
You could use it like this.
import org.prql_lang.dsl fun main() { val prql = prql { from("invoices") derive("foo", "bar") filter("income > 1") select("customer_id", "first_name", "last_name") sort("-sum_income") take(10..20) } println(prql) }
File: prql.kt
package org.prql_lang.dsl @DslMarker annotation class PrqlDsl fun prql(init: PrqlQueryBuilder.() -> Unit): String { val builder = PrqlQueryBuilder() builder.init() return builder.build() } /** * Exposes methods to build a PRQL query. */ @PrqlDsl class PrqlQueryBuilder { private lateinit var _from: String private lateinit var _sort: String private var skip: Int = 0 private var take: Int = 0 private val columns = mutableListOf<String>() private val filters = mutableListOf<String>() private val derives = mutableMapOf<String, String>() /** * Adds a derive. * * @param alias The alias. * @param expression The expression. */ fun derive(alias: String, expression: String) { this.derives.put(alias, expression) } fun derive(map: Map<String, String>) { this.derives.putAll(map) } /** * Adds a filter predicate. * * @param filter The filter predicate. */ fun filter(filter: String) { this.filters.add(filter) } fun from(from: String) { if (::_from.isInitialized) { throw IllegalStateException("Table already defined") } this._from = from } fun select(vararg columns: String) { require(columns.isNotEmpty()) { "At least one column should be defined" } this.columns.addAll(columns) } fun select(columns: List<String>) { this.columns.addAll(columns) } /** * Sorts the elements of a sequence. * * @param sort The key to sort by. * @throws IllegalStateException */ fun sort(sort: String) { if (::_sort.isInitialized) { throw IllegalStateException("Already sorted") } this._sort = sort } /** * Returns a specified number of contiguous elements from the start of a sequence. * * @param take The number of elements to return. * @throws IllegalArgumentException */ fun take(take: Int) { require(take > 0) { "Take needs to be greater than zero, was $take" } this.take = take } fun take(range: Iterable<Int>) { this.skip = range.first() this.take = range.last() } /** * Builds a PRQL query string. * * @return A PRQL query. * @throws IllegalStateException */ fun build(): String { if (!::_from.isInitialized) { throw IllegalStateException("Need call 'from' first") } val stringBuilder = StringBuilder() stringBuilder.appendLine("from $_from") if (!this.derives.isEmpty()) for ((key, value) in derives) stringBuilder.appendLine("derive $key = $value") if (!this.filters.isEmpty()) for (filter in filters) stringBuilder.appendLine("filter $filter") if (!this.columns.isEmpty()) stringBuilder.appendLine("select { ${this.columns.joinToString(", ")} }") if (::_sort.isInitialized) stringBuilder.appendLine("sort $_sort") if (this.take != 0) if (this.skip != 0) stringBuilder.appendLine("take $skip..$take") else stringBuilder.appendLine("take $take") return stringBuilder.toString() } override fun toString(): String { return build() } }
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment