11package com.github.michaelbull.jdbc
22
33import com.github.michaelbull.jdbc.context.CoroutineConnection
4- import com.github.michaelbull.jdbc.context.CoroutineDataSource
54import com.github.michaelbull.jdbc.context.dataSource
65import com.github.michaelbull.logging.InlineLogger
76import kotlinx.coroutines.CoroutineScope
87import kotlinx.coroutines.withContext
98import java.sql.Connection
109import java.sql.SQLException
10+ import javax.sql.DataSource
1111import kotlin.contracts.InvocationKind
1212import kotlin.contracts.contract
1313import kotlin.coroutines.CoroutineContext
@@ -20,83 +20,68 @@ internal val logger = InlineLogger()
2020 * Calls the specified suspending [block] in the context of a [CoroutineConnection], suspends until it completes, and
2121 * returns the result.
2222 *
23- * When there exists a [CoroutineConnection] in the current [CoroutineContext], the [block] will be immediately invoked
24- * if the [connection is not closed][Connection.isClosed] .
23+ * When the [coroutineContext] has an [open connection][hasOpenConnection] the [block] will be immediately invoked
24+ * within that context .
2525 *
26- * When there exists no [CoroutineConnection] in the current [CoroutineContext ], or when the [CoroutineConnection] in
27- * the current [CoroutineContext] is [closed][ Connection.isClosed], the [block ] will be invoked
28- * [with the context][withContext] of a new [CoroutineConnection] and an attempt will be made to [Connection.close]
29- * it afterwards .
26+ * When the [coroutineContext] has no [Connection], or it [is closed][isClosedCatching ], the [block] will be invoked in
27+ * the context of a new [ Connection]. The new [Connection ] will be created by the [DataSource] in the
28+ * [coroutineContext], throwing an [IllegalStateException] if no such [DataSource] exists. After the [block] is invoked,
29+ * the newly established [Connection] will be [closed][closeCatching] .
3030 */
3131suspend inline fun <T > withConnection (crossinline block : suspend CoroutineScope .() -> T ): T {
3232 contract {
33- callsInPlace(block, InvocationKind .AT_MOST_ONCE )
33+ callsInPlace(block, InvocationKind .EXACTLY_ONCE )
3434 }
3535
36- val connection = coroutineContext[CoroutineConnection ]?.connection
37-
38- return if (connection.isNullOrClosedCatching()) {
39- newConnection(block)
40- } else {
36+ return if (coroutineContext.hasOpenConnection()) {
4137 withContext(coroutineContext) {
4238 block()
4339 }
40+ } else {
41+ val newConnection = coroutineContext.dataSource.connection
42+
43+ withContext(CoroutineConnection (newConnection)) {
44+ try {
45+ block()
46+ } finally {
47+ newConnection.closeCatching()
48+ }
49+ }
4450 }
4551}
4652
4753/* *
48- * Calls the specified suspending [block] with the context of a new [CoroutineConnection], suspends until it completes,
49- * attempts to [close][closeCatching] the [CoroutineConnection], and returns the result.
50- *
51- * If no [CoroutineDataSource] exists in the current [CoroutineContext] from which a [Connection] can be attained, an
52- * [IllegalStateException] is thrown.
54+ * Returns `true` if this [CoroutineContext] container a [Connection] that is not [closed][isClosedCatching],
55+ * otherwise `false`.
5356 */
5457@PublishedApi
55- internal suspend inline fun <T > newConnection (crossinline block : suspend CoroutineScope .() -> T ): T {
56- contract {
57- callsInPlace(block, InvocationKind .AT_MOST_ONCE )
58- }
59-
60- val connection = coroutineContext.dataSource.connection
61-
62- return try {
63- withContext(CoroutineConnection (connection)) {
64- block()
65- }
66- } finally {
67- connection.closeCatching()
68- }
58+ internal fun CoroutineContext.hasOpenConnection (): Boolean {
59+ val connection = get(CoroutineConnection )?.connection
60+ return connection != null && ! connection.isClosedCatching()
6961}
7062
7163/* *
72- * Returns `true` if this nullable [Connection] is either `null` or [isClosed][Connection.isClosed], catching any
73- * [Throwable] exception that was thrown from the call to [Connection.isClosed] and assuming it to be `true`.
64+ * Calls [close][Connection.close] on this [Connection], catching any [SQLException] that was thrown and logging it.
7465 */
7566@PublishedApi
76- internal fun Connection?.isNullOrClosedCatching (): Boolean {
77- contract {
78- returns(false ) implies (this @isNullOrClosedCatching != null )
79- }
80-
81- return if (this == null ) {
82- true
83- } else try {
84- isClosed
67+ internal fun Connection.closeCatching () {
68+ try {
69+ close()
8570 } catch (ex: SQLException ) {
86- logger.warn(ex) { " Connection isNullOrClosed check failed, assuming closed:" }
87- true
71+ logger.warn(ex) { " Failed to close database connection cleanly:" }
8872 }
8973}
9074
9175/* *
92- * Calls [Connection.close ] on this [Connection], catching any [Throwable] exception that was thrown from the call to
93- * [Connection.close] and logging it.
76+ * Calls [isClosed][ Connection.isClosed ] on this [Connection] and returns its result , catching any [SQLException] that
77+ * was thrown then logging it and returning `true` .
9478 */
9579@PublishedApi
96- internal fun Connection.closeCatching () {
97- try {
98- close()
80+ internal fun Connection.isClosedCatching (): Boolean {
81+ return try {
82+ isClosed
9983 } catch (ex: SQLException ) {
100- logger.warn(ex) { " Failed to close database connection cleanly:" }
84+ logger.warn(ex) { " Connection isClosedCatching check failed, assuming closed:" }
85+ true
10186 }
10287}
0 commit comments