sqldelight
sqldelight copied to clipboard
Add support for embedded sql
Fixes #3440
The compiler supports now full embedded sql. See the queries and its usage for a sample:
selectWithBinding:
SELECT avg(id, :FOO), id INTO :FOO, :BAR FROM foo WHERE id = :A;
results into:
public fun <T : Any> selectWithBinding(
FOO: Double?,
A: Long?,
mapper: (FOO: Double?, BAR: Long?) -> T,
): Query<T> = SelectWithBindingQuery(value, id) { cursor ->
mapper(
cursor.getDouble(0),
cursor.getLong(1)
)
}
private inner class SelectWithBindingQuery<out T : Any>(
public val FOO: Double?,
public val A: Long?,
mapper: (SqlCursor) -> T,
) : Query<T>(mapper) {
public override fun addListener(listener: Query.Listener): Unit {
driver.addListener(listener, arrayOf("foo"))
}
public override fun removeListener(listener: Query.Listener): Unit {
driver.removeListener(listener, arrayOf("foo"))
}
public override fun <R> execute(mapper: (SqlCursor) -> R): QueryResult<R> =
driver.executeQuery(null,
"""SELECT avg(id, ?), id FROM foo WHERE id ${ if (id == null) "IS" else "=" } ?""",
mapper, 2) {
bindDouble(0, FOO)
bindLong(1, A)
}
public override fun toString(): String = "Foo.sq:selectWithBinding"
}
set:
SET :FOO, :BAR = avg(42, :A), 42;
results into this code
public fun <T : Any> `set`(A: Double?, mapper: (FOO: Double?, BAR: Long) -> T):
ExecutableQuery<T> = SetQuery(value) { cursor ->
mapper(
cursor.getDouble(0),
cursor.getLong(1)!!
)
}
private inner class SetQuery<out T : Any>(
public val A: Double?,
mapper: (SqlCursor) -> T,
) : ExecutableQuery<T>(mapper) {
public override fun <R> execute(mapper: (SqlCursor) -> R): QueryResult<R> =
driver.executeQuery(-1248256249, """SELECT avg(42, ?), 42""", mapper, 1) {
bindDouble(0, A)
}
public override fun toString(): String = "Foo.sq:set"
}
To use embeddedSql in your code, all you have to do is to extract your embedded sql queries into a .sq
file and simply use the generated kotlin query classes. To map the previous host variables, use the mapper function or the generated data classes.
The implementation requires these PRs in this order:
- https://github.com/AlecStrong/sql-psi/pull/424
- https://github.com/AlecStrong/sql-psi/pull/421
- sql-psi release
- #3454
To test it: apply the patches in sql-psi and copy the publications into build/localMaven
. Then build this branch.
At the moment, sqldelight does not map the names of the bind parameter to the class names:
data class Select(val expr: Long, val expr_: Long)
val result = SqlQueries(driver).fooQueries.select().executeAsOne()
val foo: Long = result.expr
val bar: Long = result.expr_
Reason is in sqldelight the bind parameter is ignored and even if we use them, it is not enforced to have a valid Kotlin name. A dialect could overwrite the bind parameter bind_parameter ::= '?'{id} | '?'{string}
and even the string could be a non valid Kotlin name.
Current workaround is to use componentN()
functions:
data class Select(val expr: Long, val expr_: Long)
val result = SqlQueries(driver).fooQueries.select().executeAsOne()
val foo: Long = result.component1()
val bar: Long = result.component2()
I dislike the usage of componentN()
functions. And I think, the use case for embedded sql is quite limited. So I am okay to close this PR and I would instead rewrite the sql queries during converting the language.
The PRs in sql-psi are still required!
Fixed the data class property names with the new nice hostvariable rule 🎉
Closing this PR because this very rare feature should not be part of sqldelight.