Skip to content

Commit 4c160c8

Browse files
authored
fix: Fix No transaction in context for ktor (#2483)
1 parent 750d653 commit 4c160c8

File tree

4 files changed

+47
-9
lines changed

4 files changed

+47
-9
lines changed

exposed-dao/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,5 @@ kotlin {
1414
dependencies {
1515
api(project(":exposed-core"))
1616

17-
// TODO change dependency level (use api or at minimum implementation dep)
18-
compileOnly(project(":exposed-jdbc"))
17+
implementation(project(":exposed-jdbc"))
1918
}

exposed-dao/src/main/kotlin/org/jetbrains/exposed/v1/dao/EntityHook.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.jetbrains.exposed.v1.dao
22

3+
import org.jetbrains.exposed.v1.core.InternalApi
34
import org.jetbrains.exposed.v1.core.Transaction
45
import org.jetbrains.exposed.v1.core.dao.id.EntityID
6+
import org.jetbrains.exposed.v1.core.transactions.CoreTransactionManager
57
import org.jetbrains.exposed.v1.core.transactions.transactionScope
6-
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
8+
import org.jetbrains.exposed.v1.jdbc.JdbcTransaction
79
import java.util.Deque
810
import java.util.concurrent.ConcurrentLinkedDeque
911
import java.util.concurrent.ConcurrentLinkedQueue
@@ -115,13 +117,12 @@ fun Transaction.registeredChanges() = entityEvents.toList()
115117
*
116118
* The [action] will be unregistered at the end of the call to the [body] block.
117119
*/
118-
// TODO add tests, at the current moment it's not used anywhere
119120
fun <T> withHook(action: (EntityChange) -> Unit, body: () -> T): T {
120121
EntityHook.subscribe(action)
121122
try {
122123
return body().apply {
123-
// TODO Could it be replaces with CoreTransactionsManager?
124-
TransactionManager.current().commit()
124+
@OptIn(InternalApi::class)
125+
(CoreTransactionManager.currentTransactionOrNull() as? JdbcTransaction?)?.commit()
125126
}
126127
} finally {
127128
EntityHook.unsubscribe(action)

exposed-r2dbc/src/main/kotlin/org/jetbrains/exposed/v1/r2dbc/statements/R2dbcConnectionImpl.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import kotlinx.coroutines.reactive.awaitFirst
1212
import kotlinx.coroutines.reactive.awaitFirstOrNull
1313
import kotlinx.coroutines.reactive.awaitSingle
1414
import kotlinx.coroutines.reactive.collect
15-
import kotlinx.coroutines.withContext
1615
import org.jetbrains.exposed.v1.core.statements.StatementType
1716
import org.jetbrains.exposed.v1.core.statements.api.ExposedSavepoint
1817
import org.jetbrains.exposed.v1.core.vendors.MysqlDialect
@@ -216,14 +215,26 @@ class R2dbcConnectionImpl(
216215

217216
private var localConnection: Connection? = null
218217

219-
private suspend fun <T> withConnection(body: suspend Connection.() -> T): T = withContext(scope.coroutineContext) {
218+
// TODO Recheck the reason of creating new context with `scope.coroutineContext`
219+
// It couses the issues `No transaction in context` if Exposed is used inside ktor server
220+
// To reproduce the problem it's enough to run the following script with any table Customers inside the server code
221+
// runBlocking {
222+
// val database = R2dbcDatabase.connect("r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1")
223+
// suspendTransaction(db = database) {
224+
// SchemaUtils.create(Customers)
225+
// }
226+
// }
227+
//
228+
// Old function definition
229+
// private suspend fun <T> withConnection(body: suspend Connection.() -> T): T = withContext(scope.coroutineContext) {
230+
private suspend fun <T> withConnection(body: suspend Connection.() -> T): T {
220231
if (localConnection == null) {
221232
localConnection = connection.awaitFirst().also {
222233
// this starts an explicit transaction with autoCommit mode off
223234
it.beginTransaction().awaitFirstOrNull()
224235
}
225236
}
226-
localConnection!!.body()
237+
return localConnection!!.body()
227238
}
228239
}
229240

exposed-tests/src/test/kotlin/org/jetbrains/exposed/v1/tests/shared/entities/EntityHookTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package org.jetbrains.exposed.v1.tests.shared.entities
2+
23
import org.jetbrains.exposed.v1.core.*
34
import org.jetbrains.exposed.v1.core.ReferenceOption
45
import org.jetbrains.exposed.v1.core.dao.id.EntityID
@@ -332,4 +333,30 @@ class EntityHookTest : DatabaseTestsBase() {
332333
assertEquals(2, hookCalls)
333334
}
334335
}
336+
337+
@Test
338+
fun testWithHook() {
339+
withTables(EntityHookTestData.User.table) {
340+
var hookCalls = 0
341+
342+
withHook({ hookCalls++ }) {
343+
val user = EntityHookTestData.User.new {
344+
name = "name 1"
345+
age = 25
346+
}
347+
user.flush()
348+
349+
user.name = "name 2"
350+
}
351+
352+
assertEquals(2, hookCalls)
353+
354+
// Change value outside the 'withHook'
355+
val user = EntityHookTestData.User.all().first()
356+
user.name = "name 3"
357+
user.flush()
358+
359+
assertEquals(2, hookCalls)
360+
}
361+
}
335362
}

0 commit comments

Comments
 (0)