Skip to content

Commit

Permalink
Make PreparedStatementApi methods suspend & extract vendor exec() cal…
Browse files Browse the repository at this point in the history
…ls to jdbc module

- executeQuery(), executeBatch(), and executeMultiple() now suspend, as do many
internal or private functions
- Transaction.exec() variants should also suspend as they are used frequently, but
this is problematic for jdbc and trickling up
- Any use of exec() or Statement.execute() in vendor dialects has been moved to jdbc module
- Since Statement.execute() is used heavily in Queries.kt, creation of each statement
has been extracted to a common StatementBuilder object. That way execution can be
separated by introducing suspend variants of insert/update/delete etc.
- A decision needs to be made about query statements and SchemaUtils and all other
internal uses.
- Marked all potential breaking changes and places that may need suspend
  • Loading branch information
bog-walk committed Oct 7, 2024
1 parent dee4f2a commit b5331f1
Show file tree
Hide file tree
Showing 43 changed files with 751 additions and 308 deletions.
140 changes: 109 additions & 31 deletions exposed-core/api/exposed-core.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ open class ExplainQuery(
private val transaction
get() = TransactionManager.current()

override fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi = executeQuery()
override suspend fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi = executeQuery()

override fun arguments(): Iterable<Iterable<Pair<IColumnType<*>, Any?>>> = internalStatement.arguments()

Expand Down
107 changes: 43 additions & 64 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Queries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.statements.*
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.SQLServerDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
import kotlin.sequences.Sequence

Expand Down Expand Up @@ -119,7 +118,7 @@ fun <T : Table> T.deleteWhere(limit: Int? = null, offset: Long? = null, op: T.(I
* @sample org.jetbrains.exposed.sql.tests.shared.dml.DeleteTests.testDelete01
*/
fun <T : Table> T.deleteWhere(limit: Int? = null, op: T.(ISqlExpressionBuilder) -> Op<Boolean>): Int =
DeleteStatement.where(TransactionManager.current(), this@deleteWhere, op(SqlExpressionBuilder), false, limit)
StatementBuilder { deleteWhere(this@deleteWhere, limit, op) }.execute(TransactionManager.current()) ?: 0

@Deprecated(
"This `offset` parameter is not being used and will be removed in future releases. Please leave a comment on " +
Expand All @@ -143,7 +142,7 @@ fun <T : Table> T.deleteIgnoreWhere(limit: Int? = null, offset: Long? = null, op
* @return Count of deleted rows.
*/
fun <T : Table> T.deleteIgnoreWhere(limit: Int? = null, op: T.(ISqlExpressionBuilder) -> Op<Boolean>): Int =
DeleteStatement.where(TransactionManager.current(), this@deleteIgnoreWhere, op(SqlExpressionBuilder), true, limit)
StatementBuilder { deleteIgnoreWhere(this@deleteIgnoreWhere, limit, op) }.execute(TransactionManager.current()) ?: 0

/**
* Represents the SQL statement that deletes all rows in a table.
Expand All @@ -152,7 +151,7 @@ fun <T : Table> T.deleteIgnoreWhere(limit: Int? = null, op: T.(ISqlExpressionBui
* @sample org.jetbrains.exposed.sql.tests.shared.dml.DeleteTests.testDelete01
*/
fun Table.deleteAll(): Int =
DeleteStatement.all(TransactionManager.current(), this@deleteAll)
StatementBuilder { StatementBuilder.deleteAll(this@deleteAll) }.execute(TransactionManager.current()) ?: 0

/**
* Represents the SQL statement that deletes rows in a table and returns specified data from the deleted rows.
Expand All @@ -167,8 +166,7 @@ fun <T : Table> T.deleteReturning(
returning: List<Expression<*>> = columns,
where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null
): ReturningStatement {
val delete = DeleteStatement(this, where?.let { SqlExpressionBuilder.it() }, false, null)
return ReturningStatement(this, returning, delete)
return StatementBuilder { deleteReturning(this@deleteReturning, returning, where) }
}

/**
Expand All @@ -192,8 +190,7 @@ fun Join.delete(
limit: Int? = null,
where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null
): Int {
val targets = listOf(targetTable) + targetTables
val delete = DeleteStatement(this, where?.let { SqlExpressionBuilder.it() }, ignore, limit, targets)
val delete = StatementBuilder { delete(join = this@delete, targetTable, targetTables = targetTables, ignore, limit, where) }
return delete.execute(TransactionManager.current()) ?: 0
}

Expand All @@ -202,10 +199,9 @@ fun Join.delete(
*
* @sample org.jetbrains.exposed.sql.tests.h2.H2Tests.insertInH2
*/
fun <T : Table> T.insert(body: T.(InsertStatement<Number>) -> Unit): InsertStatement<Number> = InsertStatement<Number>(this).apply {
body(this)
execute(TransactionManager.current())
}
fun <T : Table> T.insert(body: T.(InsertStatement<Number>) -> Unit): InsertStatement<Number> = StatementBuilder {
insert(this@insert, body)
}.apply { execute(TransactionManager.current()) }

/**
* Represents the SQL statement that inserts a new row into a table.
Expand All @@ -214,8 +210,7 @@ fun <T : Table> T.insert(body: T.(InsertStatement<Number>) -> Unit): InsertState
* @sample org.jetbrains.exposed.sql.tests.shared.dml.InsertTests.testGeneratedKey04
*/
fun <Key : Comparable<Key>, T : IdTable<Key>> T.insertAndGetId(body: T.(InsertStatement<EntityID<Key>>) -> Unit): EntityID<Key> =
InsertStatement<EntityID<Key>>(this, false).run {
body(this)
StatementBuilder { insertAndGetId(this@insertAndGetId, body) }.run {
execute(TransactionManager.current())
get(id)
}
Expand Down Expand Up @@ -262,11 +257,7 @@ private fun <T : Table, E> T.batchInsert(
shouldReturnGeneratedValues: Boolean = true,
body: BatchInsertStatement.(E) -> Unit
): List<ResultRow> = executeBatch(data, body) {
if (currentDialect is SQLServerDialect && this.autoIncColumn != null) {
SQLServerBatchInsertStatement(this, ignoreErrors, shouldReturnGeneratedValues)
} else {
BatchInsertStatement(this, ignoreErrors, shouldReturnGeneratedValues)
}
StatementBuilder { batchInsert(this@batchInsert, ignoreErrors, shouldReturnGeneratedValues) }
}

/**
Expand Down Expand Up @@ -308,9 +299,10 @@ private fun <T : Table, E> T.batchReplace(
shouldReturnGeneratedValues: Boolean = true,
body: BatchReplaceStatement.(E) -> Unit
): List<ResultRow> = executeBatch(data, body) {
BatchReplaceStatement(this, shouldReturnGeneratedValues)
StatementBuilder { batchReplace(this@batchReplace, shouldReturnGeneratedValues) }
}

// this may also need to suspend
private fun <E, S : BaseBatchInsertStatement> executeBatch(
data: Iterator<E>,
body: S.(E) -> Unit,
Expand Down Expand Up @@ -366,9 +358,8 @@ private fun <E, S : BaseBatchInsertStatement> executeBatch(
*
* @sample org.jetbrains.exposed.sql.tests.shared.dml.InsertTests.testInsertIgnoreAndGetIdWithPredefinedId
*/
fun <T : Table> T.insertIgnore(body: T.(UpdateBuilder<*>) -> Unit): InsertStatement<Long> = InsertStatement<Long>(this, isIgnore = true).apply {
body(this)
execute(TransactionManager.current())
fun <T : Table> T.insertIgnore(body: T.(UpdateBuilder<*>) -> Unit): InsertStatement<Long> = StatementBuilder {
insertIgnore(this@insertIgnore, body).apply { execute(TransactionManager.current()) }
}

/**
Expand All @@ -382,8 +373,7 @@ fun <T : Table> T.insertIgnore(body: T.(UpdateBuilder<*>) -> Unit): InsertStatem
* @sample org.jetbrains.exposed.sql.tests.shared.dml.InsertTests.testInsertIgnoreAndGetId01
*/
fun <Key : Comparable<Key>, T : IdTable<Key>> T.insertIgnoreAndGetId(body: T.(UpdateBuilder<*>) -> Unit): EntityID<Key>? =
InsertStatement<EntityID<Key>>(this, isIgnore = true).run {
body(this)
StatementBuilder { insertIgnoreAndGetId(this@insertIgnoreAndGetId, body) }.run {
when (execute(TransactionManager.current())) {
null, 0 -> null
else -> getOrNull(id)
Expand All @@ -398,9 +388,8 @@ fun <Key : Comparable<Key>, T : IdTable<Key>> T.insertIgnoreAndGetId(body: T.(Up
*
* @sample org.jetbrains.exposed.sql.tests.shared.dml.ReplaceTests.testReplaceWithExpression
*/
fun <T : Table> T.replace(body: T.(UpdateBuilder<*>) -> Unit): ReplaceStatement<Long> = ReplaceStatement<Long>(this).apply {
body(this)
execute(TransactionManager.current())
fun <T : Table> T.replace(body: T.(UpdateBuilder<*>) -> Unit): ReplaceStatement<Long> = StatementBuilder {
replace(this@replace, body).apply { execute(TransactionManager.current()) }
}

/**
Expand All @@ -418,7 +407,9 @@ fun <T : Table> T.replace(body: T.(UpdateBuilder<*>) -> Unit): ReplaceStatement<
fun <T : Table> T.replace(
selectQuery: AbstractQuery<*>,
columns: List<Column<*>> = this.columns.filter { it.isValidIfAutoIncrement() }
): Int? = ReplaceSelectStatement(columns, selectQuery).execute(TransactionManager.current())
): Int? = StatementBuilder {
replace(selectQuery, columns)
}.execute(TransactionManager.current())

/**
* Represents the SQL statement that uses data retrieved from a [selectQuery] to insert new rows into a table.
Expand All @@ -432,7 +423,9 @@ fun <T : Table> T.replace(
fun <T : Table> T.insert(
selectQuery: AbstractQuery<*>,
columns: List<Column<*>> = this.columns.filter { it.isValidIfAutoIncrement() }
): Int? = InsertSelectStatement(columns, selectQuery).execute(TransactionManager.current())
): Int? = StatementBuilder {
insert(selectQuery, columns)
}.execute(TransactionManager.current())

/**
* Represents the SQL statement that uses data retrieved from a [selectQuery] to insert new rows into a table,
Expand All @@ -448,7 +441,9 @@ fun <T : Table> T.insert(
fun <T : Table> T.insertIgnore(
selectQuery: AbstractQuery<*>,
columns: List<Column<*>> = this.columns.filter { it.isValidIfAutoIncrement() }
): Int? = InsertSelectStatement(columns, selectQuery, true).execute(TransactionManager.current())
): Int? = StatementBuilder {
insertIgnore(selectQuery, columns)
}.execute(TransactionManager.current())

private fun Column<*>.isValidIfAutoIncrement(): Boolean =
!columnType.isAutoInc || autoIncColumnType?.nextValExpression != null
Expand All @@ -468,9 +463,7 @@ fun <T : Table> T.insertReturning(
ignoreErrors: Boolean = false,
body: T.(InsertStatement<Number>) -> Unit
): ReturningStatement {
val insert = InsertStatement<Number>(this, ignoreErrors)
body(insert)
return ReturningStatement(this, returning, insert)
return StatementBuilder { insertReturning(this@insertReturning, returning, ignoreErrors, body) }
}

/**
Expand All @@ -482,9 +475,9 @@ fun <T : Table> T.insertReturning(
* @sample org.jetbrains.exposed.sql.tests.shared.dml.UpdateTests.testUpdate01
*/
fun <T : Table> T.update(where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null, limit: Int? = null, body: T.(UpdateStatement) -> Unit): Int {
val query = UpdateStatement(this, limit, where?.let { SqlExpressionBuilder.it() })
body(query)
return query.execute(TransactionManager.current()) ?: 0
return StatementBuilder {
update(this@update, where, limit, body)
}.execute(TransactionManager.current()) ?: 0
}

/**
Expand All @@ -496,9 +489,9 @@ fun <T : Table> T.update(where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null
* @sample org.jetbrains.exposed.sql.tests.shared.dml.UpdateTests.testUpdateWithSingleJoin
*/
fun Join.update(where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null, limit: Int? = null, body: (UpdateStatement) -> Unit): Int {
val query = UpdateStatement(this, limit, where?.let { SqlExpressionBuilder.it() })
body(query)
return query.execute(TransactionManager.current()) ?: 0
return StatementBuilder {
update(this@update, where, limit, body)
}.execute(TransactionManager.current()) ?: 0
}

/**
Expand All @@ -514,9 +507,7 @@ fun <T : Table> T.updateReturning(
where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
body: T.(UpdateStatement) -> Unit
): ReturningStatement {
val update = UpdateStatement(this, null, where?.let { SqlExpressionBuilder.it() })
body(update)
return ReturningStatement(this, returning, update)
return StatementBuilder { updateReturning(this@updateReturning, returning, where, body) }
}

/**
Expand Down Expand Up @@ -545,10 +536,10 @@ fun <T : Table> T.upsert(
onUpdateExclude: List<Column<*>>? = null,
where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
body: T.(UpsertStatement<Long>) -> Unit
) = UpsertStatement<Long>(this, keys = keys, onUpdateExclude = onUpdateExclude, where = where?.let { SqlExpressionBuilder.it() }).apply {
onUpdate?.let { storeUpdateValues(it) }
body(this)
execute(TransactionManager.current())
): UpsertStatement<Long> {
return StatementBuilder {
upsert(this@upsert, keys = keys, onUpdate, onUpdateExclude, where, body)
}.apply { execute(TransactionManager.current()) }
}

@Deprecated(
Expand Down Expand Up @@ -596,10 +587,7 @@ fun <T : Table> T.upsertReturning(
where: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
body: T.(UpsertStatement<Long>) -> Unit
): ReturningStatement {
val upsert = UpsertStatement<Long>(this, keys = keys, onUpdateExclude, where?.let { SqlExpressionBuilder.it() })
onUpdate?.let { upsert.storeUpdateValues(it) }
body(upsert)
return ReturningStatement(this, returning, upsert)
return StatementBuilder { upsertReturning(this@upsertReturning, keys = keys, returning, onUpdate, onUpdateExclude, where, body) }
}

@Deprecated(
Expand Down Expand Up @@ -724,15 +712,8 @@ private fun <T : Table, E> T.batchUpsert(
vararg keys: Column<*>,
body: BatchUpsertStatement.(E) -> Unit
): List<ResultRow> = executeBatch(data, body) {
BatchUpsertStatement(
this,
keys = keys,
onUpdateExclude = onUpdateExclude,
where = where?.let { SqlExpressionBuilder.it() },
shouldReturnGeneratedValues = shouldReturnGeneratedValues
).apply {
onUpdate?.let { storeUpdateValues(it) }
?: onUpdateList?.let { updateValues.putAll(it) }
StatementBuilder {
batchUpsert(this@batchUpsert, data, onUpdateList, onUpdate, onUpdateExclude, where, shouldReturnGeneratedValues, keys = keys, body)
}
}

Expand Down Expand Up @@ -761,8 +742,7 @@ fun <D : Table, S : Table> D.mergeFrom(
on: (SqlExpressionBuilder.() -> Op<Boolean>)? = null,
body: MergeTableStatement.() -> Unit
): MergeTableStatement {
return MergeTableStatement(this, source, on = on?.invoke(SqlExpressionBuilder)).apply {
body(this)
return StatementBuilder { mergeFrom(this@mergeFrom, source, on, body) }.apply {
execute(TransactionManager.current())
}
}
Expand All @@ -784,8 +764,7 @@ fun <T : Table> T.mergeFrom(
on: SqlExpressionBuilder.() -> Op<Boolean>,
body: MergeSelectStatement.() -> Unit
): MergeSelectStatement {
return MergeSelectStatement(this, selectQuery, SqlExpressionBuilder.on()).apply {
body(this)
return StatementBuilder { mergeFrom(this@mergeFrom, selectQuery, on, body) }.apply {
execute(TransactionManager.current())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ open class Query(override var set: FieldSet, where: Op<Boolean>?) : AbstractQuer
*/
fun isForUpdate() = (forUpdate?.let { it != ForUpdateOption.NoForUpdateOption } ?: false) && currentDialect.supportsSelectForUpdate()

override fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi? {
override suspend fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi? {
val fetchSize = this@Query.fetchSize ?: transaction.db.defaultFetchSize
if (fetchSize != null) {
this.fetchSize = fetchSize
Expand Down Expand Up @@ -300,6 +300,7 @@ open class Query(override var set: FieldSet, where: Op<Boolean>?) : AbstractQuer
* @return Retrieved results as a collection of batched [ResultRow] sub-collections.
* @sample org.jetbrains.exposed.sql.tests.shared.dml.FetchBatchedResultsTests.testFetchBatchedResultsWithWhereAndSetBatchSize
*/
// this will most likely need to be SUSPEND
fun fetchBatchedResults(batchSize: Int = 1000, sortOrder: SortOrder = SortOrder.ASC): Iterable<Iterable<ResultRow>> {
require(batchSize > 0) { "Batch size should be greater than 0." }
require(limit == null) { "A manual `LIMIT` clause should not be set. By default, `batchSize` will be used." }
Expand Down Expand Up @@ -376,6 +377,7 @@ open class Query(override var set: FieldSet, where: Op<Boolean>?) : AbstractQuer
*
* @sample org.jetbrains.exposed.sql.tests.shared.dml.InsertSelectTests.testInsertSelect02
*/
// this will most likely need to be SUSPEND
override fun count(): Long {
return if (distinct || groupedByColumns.isNotEmpty() || limit != null) {
fun Column<*>.makeAlias() =
Expand Down Expand Up @@ -416,6 +418,7 @@ open class Query(override var set: FieldSet, where: Op<Boolean>?) : AbstractQuer
*
* @sample org.jetbrains.exposed.sql.tests.shared.dml.SelectTests.testSizedIterable
*/
// this will most likely need to be SUSPEND
override fun empty(): Boolean {
val oldLimit = limit
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ object SchemaUtils {
return statements
}

// this will most likely need to be SUSPEND - so will everything in SchemaUtils???
private fun Transaction.execStatements(inBatch: Boolean, statements: List<String>) {
if (inBatch) {
execInBatch(statements)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ sealed class SetOperation(
open val operationName = operationName

/** Returns the number of results retrieved after query execution. */
// this will likely need to SUSPEND
override fun count(): Long {
try {
count = true
Expand All @@ -72,6 +73,7 @@ sealed class SetOperation(
}

/** Returns whether any results were retrieved by query execution. */
// this will likely need to SUSPEND
override fun empty(): Boolean {
val oldLimit = limit
try {
Expand All @@ -83,7 +85,7 @@ sealed class SetOperation(
}
}

override fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi = executeQuery()
override suspend fun PreparedStatementApi.executeInternal(transaction: Transaction): ResultApi = executeQuery()

override fun prepareSQL(builder: QueryBuilder): String {
builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,7 @@ fun ColumnSet.targetTables(): List<Table> = when (this) {
else -> error("No target provided for update")
}

internal fun String.isAlreadyQuoted(): Boolean =
private fun String.isAlreadyQuoted(): Boolean =
listOf("\"", "'", "`").any { quoteString ->
startsWith(quoteString) && endsWith(quoteString)
}
Expand Down
Loading

0 comments on commit b5331f1

Please sign in to comment.