-
Notifications
You must be signed in to change notification settings - Fork 0
[1] Core
The Core module is the type-safe AST behind DOPE. It contains all Clauses, Expressions, Operators, and ValidTypes that replicate SQL++/N1QL language constructs. Core on its own does not render a query string — pair it with a QueryResolver (for Couchbase: the couchbase module's CouchbaseResolver) to get a concrete query.
With DOPE Core, you build an AST consisting of Clauses and Expressions. The AST is rendered by calling build(resolver), which returns a DopeQuery. For Couchbase, this is a CouchbaseDopeQuery with:
-
queryString— the rendered SQL++/N1QL string. -
parameters— aDopeParametersvalue holdingnamedParameters(Map<String, Any>) andpositionalParameters(List<Any>).
import ch.ergon.dope.QueryBuilder
import ch.ergon.dope.couchbase.CouchbaseResolver
val query = QueryBuilder.select(personName, personAge).build(CouchbaseResolver())
query.queryString // "SELECT person.name, person.age"
query.parameters // DopeParameters(namedParameters = {}, positionalParameters = [])QueryBuilder is a Kotlin object (singleton), so entry points such as QueryBuilder.select(...), QueryBuilder.selectAsterisk(), QueryBuilder.selectDistinct(...), QueryBuilder.selectRaw(...), QueryBuilder.selectFrom(...), QueryBuilder.with(...), QueryBuilder.update(...), and QueryBuilder.deleteFrom(...) are called without parentheses on QueryBuilder itself.
NumberExpressions produce a numeric result. Use them for arithmetic within a query — add a constant to a field, multiply a field, or combine operations.
personAge.add(3) // -> "person.age + 3"
2.mul(personAge).sub(5) // -> "2 * person.age - 5"StringExpressions produce a string result. Handy for string manipulation — concatenation, trimming, padding, etc.
"_".concat(personName, "_") // -> "CONCAT(\"_\", person.name, \"_\")"
personName.ltrim() // -> "LTRIM(person.name)"BooleanExpressions produce a boolean result. Used in WHERE and JOIN conditions for equality, pattern matching, null checks, and logical combinations.
personAge.isEqualTo(18) // -> "person.age = 18"
personName.isLike("A%") // -> "person.name LIKE \"A%\""
personName.isNotNull() // -> "person.name IS NOT NULL"A Field represents a property in a JSON document and is typed with a ValidType (NumberType, StringType, BooleanType, ArrayType<T>, ObjectType, …).
val personName = Field<StringType>(name = "name", path = "person")
val personAge = Field<NumberType>(name = "age", path = "person")
val isPersonEmployed = Field<BooleanType>(name = "isEmployed", path = "person")Here we have a String property person.name, a Number property person.age, and a Boolean property person.isEmployed.
Use toDopeType() to lift a Kotlin primitive into an expression:
import ch.ergon.dope.resolvable.expression.type.toDopeType
val literalNumber = 42.toDopeType()
val literalString = "hello".toDopeType()
val literalBoolean = true.toDopeType()build() returns parameters in a DopeParameters value, split into namedParameters and positionalParameters. Create parameters with the asParameter() extension — it works for Number, String, Boolean, Map<String, Any>, and collections of those types.
Pass a name to get a named parameter ($name).
7.asParameter("number") // -> "$number"Omit the name to get a positional parameter ($1, $2, …).
7.asParameter() // -> "$1"val parameter1 = 2.asParameter()
val parameter2 = 5.asParameter("param")
val parameter3 = "peter".asParameter()
val result = QueryBuilder
.select(parameter1.isEqualTo(parameter2))
.where(personName.isNotEqualTo(parameter3))
.build(CouchbaseResolver())
// result.queryString -> "SELECT $1 = $param WHERE person.name != $2"
// result.parameters -> DopeParameters(
// namedParameters = { "param" to 5 },
// positionalParameters = [2, "peter"]
// )A Bucket is any table-like source you can read from. DOPE ships two:
import ch.ergon.dope.resolvable.bucket.UnaliasedBucket
val personBucket = UnaliasedBucket("person") // -> "person"
val personAlias = personBucket.alias("p") // -> "person AS p"Buckets also support scope and collection paths:
UnaliasedBucket("person").withScope("hr") // -> "hr.person"
UnaliasedBucket("person").withScopeAndCollection("hr", "employees") // -> "hr.employees.person"DOPE has several SELECT entry points, all on QueryBuilder. The basic SELECT takes one or more selectables:
val result = QueryBuilder
.select(personName, personAge)
.build(CouchbaseResolver())
// result.queryString -> "SELECT person.name, person.age"Other SELECT variants: selectAsterisk(), selectDistinct(...), selectRaw(expression), and the shortcut selectFrom(bucket) (which is equivalent to selectAsterisk().from(bucket)).
Clauses can be chained:
val personBucket = UnaliasedBucket("person")
val result = QueryBuilder
.select(personBucket.asterisk())
.from(personBucket)
.join(UnaliasedBucket("city"), condition = personCityId.isEqualTo(cityId))
.where(personAge.isGreaterThan(14))
.groupBy(personName)
.orderBy(personAge)
.limit(10)
.offset(5)
.build(CouchbaseResolver())
// result.queryString -> "SELECT person.* FROM person JOIN city ON person.cityId = city.id WHERE person.age > 14 GROUP BY person.name ORDER BY person.age LIMIT 10 OFFSET 5"LET bindings are introduced with withVariables(...) and assignTo(...):
import ch.ergon.dope.resolvable.expression.type.assignTo
val isAdult = "isAdult".assignTo(personAge.isGreaterThan(18))
val result = QueryBuilder
.select(personName)
.from(personBucket)
.withVariables(isAdult)
.where(isAdult)
.build(CouchbaseResolver())QueryBuilder.with(...) starts a CTE:
val sub = QueryBuilder
.select(personName)
.from(personBucket)
val cte = "p".assignTo(sub)
val result = QueryBuilder
.with(cte)
.selectAsterisk()
.from(UnaliasedBucket("p"))
.build(CouchbaseResolver())QueryBuilder.update(bucket) starts an UPDATE, QueryBuilder.deleteFrom(bucket) starts a DELETE. Both support where, limit, and the returning* family.
import ch.ergon.dope.resolvable.clause.model.toNewValue
val update = QueryBuilder
.update(personBucket)
.set(personAge.toNewValue(30))
.where(personAge.isGreaterThan(18))
.build(CouchbaseResolver())
val delete = QueryBuilder
.deleteFrom(personBucket)
.where(personAge.isGreaterThan(18))
.build(CouchbaseResolver())For the full list of supported clauses, operators, and functions, see [[3]-DOPE-functionality].