Skip to content

Commit 683330a

Browse files
committed
Improve readability in withConnection
1 parent f22db78 commit 683330a

File tree

1 file changed

+36
-51
lines changed

1 file changed

+36
-51
lines changed
Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.github.michaelbull.jdbc
22

33
import com.github.michaelbull.jdbc.context.CoroutineConnection
4-
import com.github.michaelbull.jdbc.context.CoroutineDataSource
54
import com.github.michaelbull.jdbc.context.dataSource
65
import com.github.michaelbull.logging.InlineLogger
76
import kotlinx.coroutines.CoroutineScope
87
import kotlinx.coroutines.withContext
98
import java.sql.Connection
109
import java.sql.SQLException
10+
import javax.sql.DataSource
1111
import kotlin.contracts.InvocationKind
1212
import kotlin.contracts.contract
1313
import 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
*/
3131
suspend 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

Comments
 (0)