Kristian Nedrevold
03/05/2023, 6:22 PMsealed interface R2DBCError {
object Error: R2DBCError
object TransactionError : R2DBCError
}
context (ConnectionFactory)
object R2DBC {
inline fun <T>transaction(crossinline handler: context(Connection) (Handle) -> Effect<R2DBCError, T>): Effect<R2DBCError, T> = effect {
val connection = create().awaitFirst()
val handle = Handle
try {
val value = handler(connection, handle)
connection.commitTransaction()
value.bind()
} catch (e: Exception) {
connection.rollbackTransaction()
if (e is kotlinx.coroutines.CancellationException) {
throw e
}
shift(R2DBCError.TransactionError)
}
}
}
object Handle {
/**
* [execute] executes an insert/update/delete and returns the amount of affected rows
*/
context (Connection)
fun execute(sql: String, vararg params: Any) = effect<R2DBCError, Long> {
val stmt = createStatement(sql)
params.forEachIndexed { idx, param ->
stmt.bind(idx, param)
}
stmt.add().execute().awaitFirst()
.rowsUpdated
.awaitFirst()
}
/**
* [select] Executes a select statement and maps the result into a Flow<T> with a given mapper
*/
context (Connection)
inline fun <reified T: Any>select(sql: String, vararg params: Any, noinline r2dbcMapper: R2DBCMapper<T>) = effect<R2DBCError, Flow<T>> {
val stmt = createStatement(sql)
params.forEachIndexed { idx, param ->
stmt.bind(idx, param)
}
stmt.add().execute().awaitFirst().map(r2dbcMapper).asFlow()
}
}
Kristian Nedrevold
03/05/2023, 6:29 PMinline fun <T>transaction(crossinline handler: context(Connection) (Handle) -> Effect<R2DBCError, T>)
becomes
inline fun <T>transaction(crossinline handler: context(Connection) () -> Effect<R2DBCError, T>)
Kristian Nedrevold
03/05/2023, 7:22 PMsimon.vergauwen
03/06/2023, 8:18 AMsealed interface R2DBCError {
object Error: R2DBCError
data class TransactionError(val cause: Exception) : R2DBCError
}
You're not using R2DBCError.Error
anywhere? ๐คsimon.vergauwen
03/06/2023, 8:18 AMsimon.vergauwen
03/06/2023, 8:23 AMKristian Nedrevold
03/06/2023, 8:41 AMsimon.vergauwen
03/06/2023, 8:51 AMIO
. Even ZIO-sql just uses Exception and Throwable in their error channel..
I've been thinking about writing a super small JDBC wrapper (for Postgres) that translates SQLState into an typed error but even then. No1 is probably going to when
over 100 different errors.Kristian Nedrevold
03/06/2023, 8:55 AMKristian Nedrevold
03/06/2023, 8:57 AMKristian Nedrevold
03/06/2023, 9:04 AMobject R2DBC {
inline fun <E, T>ConnectionFactory.transaction(
crossinline handler: ConnectionHandle<E>.() -> Effect<E, T>): Effect<E, T> = effect {
val connection = create().awaitFirst()
try {
val value = handler(ConnectionHandle(connection, this))
connection.commitTransaction()
value.fold({
connection.rollbackTransaction()
shift(it)
}, { it })
} catch (e: Exception) {
connection.rollbackTransaction()
throw e
}
}
}
class ConnectionHandle<E>(connection: Connection, raise: EffectScope<E>): Connection by connection, EffectScope<E> by raise {
/**
* [select] Executes a select statement and maps the result into a Flow<T> with a given mapper
*/
inline fun <T: Any>select(
noinline r2dbcMapper: R2DBCMapper<T>,
sql: String,
vararg params: Any,
crossinline failWith: (Exception) -> E,
) = effect<E, Flow<T>> {
try {
val stmt = createStatement(sql)
params.forEachIndexed { idx, param ->
stmt.bind(idx, param)
}
stmt.add().execute().awaitFirst()
.map(r2dbcMapper)
.asFlow()
} catch (e: Exception) {
e.nonFatalOrThrow()
shift(failWith(e))
}
}
/**
* [execute] executes an insert/update/delete and returns the amount of affected rows
*/
inline fun <E>execute(
sql: String,
vararg params: Any,
crossinline failWith: (Exception) -> E,
) = effect<E, Long> {
try {
val stmt = createStatement(sql)
params.forEachIndexed { idx, param ->
stmt.bind(idx, param)
}
stmt.add().execute().awaitFirst()
.rowsUpdated
.awaitFirst()
} catch (e: Exception) {
e.nonFatalOrThrow()
shift(failWith(e))
}
}
}
Now I can just add some error handling in the failWith lambda in the callerKristian Nedrevold
03/06/2023, 9:05 AMsimon.vergauwen
03/06/2023, 9:12 AMRDBC2Error
if they don't provide the handler. That way you can enforce error handling, and it exposes best of both worlds without code duplication even ๐Kristian Nedrevold
03/06/2023, 9:13 AM