diff --git a/runtime/src/main/kotlin/norm/DataSoruceExtensions.kt b/runtime/src/main/kotlin/norm/DataSourceExtensions.kt similarity index 100% rename from runtime/src/main/kotlin/norm/DataSoruceExtensions.kt rename to runtime/src/main/kotlin/norm/DataSourceExtensions.kt diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt new file mode 100644 index 0000000..d5bef54 --- /dev/null +++ b/runtime/src/main/kotlin/norm/transaction/TransactionResult.kt @@ -0,0 +1,31 @@ +package norm.transaction + +/** + * A result model which will be returned on execution on [executeTransaction]. + */ +sealed class TransactionResult { + data class Success(val data: R) : TransactionResult() + data class Error(val error: Throwable) : TransactionResult() + + /** + * Returns `true` if transaction is successful. Else `false`. + */ + val isSuccessful: Boolean = this is Success + + /** + * Forcefully tries to provide a successful result data. + * If transaction is not successful, it throws [IllegalStateException]. + */ + fun get(): R = runCatching { (this as Success).data } + .getOrElse { throw IllegalStateException("Transaction is not successful") } + + /** + * @return a result if transaction is successful otherwise null. + */ + fun getOrNull(): R? = if (this is Success) data else null + + /** + * @return an error if transaction is failed otherwise null. + */ + fun errorOrNull(): Throwable? = if (this is Error) error else null +} diff --git a/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt new file mode 100644 index 0000000..6318768 --- /dev/null +++ b/runtime/src/main/kotlin/norm/transaction/TransactionUtil.kt @@ -0,0 +1,46 @@ +package norm.transaction + +import java.sql.Connection +import java.sql.SQLException +import javax.sql.DataSource + +/** + * Executes queries specified by [block] in the transaction. + * + * @param block Lambda block with connection on which transaction queries will be executed. + * @return The transaction result. + * + * Example: + * + * ``` + * val result = dataSource.executeTransaction { + * val user = FindUserByIdQuery().query(it, FindUserByIdParams(id)) + * AddUserItemOneCommand().command(it, AddUserItemOneParams(item1, user.id)) + * AddUserItemTwoCommand().command(it, AddUserItemTwoParams(item2, user.id)) + * } + * + * // Check whether transaction is successful + * val isSuccessful = result.isSuccessful + * + * // Retrieve transaction result + * val viewCount = result.get() // or `result.getOrNull()` to retrieve safely. + * + * // Retrieve exception (if it's failed) + * val error = result.errorOrNull() + * ``` + */ +fun DataSource.executeTransaction(block: (Connection) -> R): TransactionResult { + return connection.use { connection -> + connection.autoCommit = false + try { + block(connection) + .also { connection.commit() } + .let { result -> TransactionResult.Success(result) } + } catch (exception: SQLException) { + exception.printStackTrace() + + val rollbackError = runCatching { connection.rollback() }.exceptionOrNull() + TransactionResult.Error(rollbackError ?: exception) + } + } +}