diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/AbstractQuery.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/AbstractQuery.kt index fe71d3d20a..bf342ba7ee 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/AbstractQuery.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/AbstractQuery.kt @@ -4,6 +4,7 @@ import org.jetbrains.exposed.sql.statements.Statement import org.jetbrains.exposed.sql.statements.StatementType import org.jetbrains.exposed.sql.transactions.TransactionManager import java.sql.ResultSet +import java.util.concurrent.ConcurrentHashMap abstract class AbstractQuery>(targets: List) : SizedIterable, Statement(StatementType.SELECT, targets) { protected val transaction get() = TransactionManager.current() @@ -59,7 +60,7 @@ abstract class AbstractQuery>(targets: List
) : Sized override fun iterator(): Iterator { val resultIterator = ResultIterator(transaction.exec(queryToExecute)!!) - return if (transaction.db.supportsMultipleResultSets) resultIterator + return if (transaction.db.supportsMultipleResultSets) resultIterator.also { trackStatementOpen(transaction, it) } else { Iterable { resultIterator }.toList().iterator() } @@ -79,8 +80,29 @@ abstract class AbstractQuery>(targets: List
) : Sized override fun hasNext(): Boolean { if (hasNext == null) hasNext = rs.next() - if (hasNext == false) rs.close() + if (hasNext == false) { + rs.close() + untrackStatement(transaction, this) + } return hasNext!! } } + + companion object { + private val trackedOpenResultIterators = ConcurrentHashMap.ResultIterator>>() + + private fun trackStatementOpen(transaction: Transaction, iterator: AbstractQuery<*>.ResultIterator) { + trackedOpenResultIterators.getOrPut(transaction) { ConcurrentHashMap.newKeySet() }.add(iterator) + } + + private fun untrackStatement(transaction: Transaction, iterator: AbstractQuery<*>.ResultIterator) { + trackedOpenResultIterators[transaction]?.remove(iterator) + } + + internal fun closeOpenedStatements(transaction: Transaction) { + trackedOpenResultIterators.remove(transaction)?.forEach { + if (!it.rs.isClosed) it.rs.close() + } + } + } } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt index 1d1c153687..a2de5a589f 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt @@ -60,6 +60,7 @@ open class Transaction(private val transactionImpl: TransactionInterface) : User override fun commit() { globalInterceptors.forEach { it.beforeCommit(this) } interceptors.forEach { it.beforeCommit(this) } + AbstractQuery.closeOpenedStatements(this) transactionImpl.commit() globalInterceptors.forEach { it.afterCommit() } interceptors.forEach { it.afterCommit() } @@ -69,6 +70,7 @@ open class Transaction(private val transactionImpl: TransactionInterface) : User override fun rollback() { globalInterceptors.forEach { it.beforeRollback(this) } interceptors.forEach { it.beforeRollback(this) } + AbstractQuery.closeOpenedStatements(this) transactionImpl.rollback() globalInterceptors.forEach { it.afterRollback() } interceptors.forEach { it.afterRollback() } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/transactions/TransactionApi.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/transactions/TransactionApi.kt index 69b08b5960..6374ae0639 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/transactions/TransactionApi.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/transactions/TransactionApi.kt @@ -100,13 +100,13 @@ interface TransactionManager { manager?.let { currentThreadManager.set(it) } ?: currentThreadManager.remove() } - fun currentOrNew(isolation: Int) = currentOrNull() ?: manager.newTransaction(isolation) + fun currentOrNew(isolation: Int): Transaction = currentOrNull() ?: manager.newTransaction(isolation) - fun currentOrNull() = manager.currentOrNull() + fun currentOrNull(): Transaction? = manager.currentOrNull() - fun current() = currentOrNull() ?: error("No transaction in context.") + fun current(): Transaction = currentOrNull() ?: error("No transaction in context.") - fun isInitialized() = defaultDatabase != null + fun isInitialized(): Boolean = defaultDatabase != null } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateTableTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateTableTests.kt index 31e9d00ac3..6f2d3677ae 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateTableTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateTableTests.kt @@ -22,7 +22,7 @@ class CreateTableTests : DatabaseTestsBase() { fun createTableWithDuplicateColumn() { val assertionFailureMessage = "Can't create a table with multiple columns having the same name" - withDb() { + withDb { assertFails(assertionFailureMessage) { SchemaUtils.create(TableWithDuplicatedColumn) } @@ -43,7 +43,7 @@ class CreateTableTests : DatabaseTestsBase() { override val primaryKey = PrimaryKey(id1, id2) } - withDb() { + withDb { val id1ProperName = account.id1.name.inProperCase() val id2ProperName = account.id2.name.inProperCase() val tableName = account.tableName @@ -63,7 +63,7 @@ class CreateTableTests : DatabaseTestsBase() { val pkConstraintName = "PKConstraintName" // Table with composite primary key - withDb() { + withDb { val id1ProperName = Person.id1.name.inProperCase() val id2ProperName = Person.id2.name.inProperCase() val tableName = Person.tableName @@ -83,7 +83,7 @@ class CreateTableTests : DatabaseTestsBase() { override val primaryKey = PrimaryKey(user_name, name = pkConstraintName) } - withDb() { + withDb { val userNameProperName = user.user_name.name.inProperCase() val tableName = TransactionManager.current().identity(user)