Skip to content

Commit

Permalink
docs: Add missing KDocs for exposed-core statements API (#1893)
Browse files Browse the repository at this point in the history
Add KDocs to statements package, but not to nested statements.api.
  • Loading branch information
bog-walk authored Nov 24, 2023
1 parent 1c5c5e3 commit e32506d
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import org.jetbrains.exposed.sql.isAutoInc
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import org.jetbrains.exposed.sql.transactions.TransactionManager

/**
* Base class representing the SQL statement that batch inserts new rows into a table.
*
* @param shouldReturnGeneratedValues Specifies whether newly generated values (for example, auto-incremented IDs)
* should be returned. See [Batch Insert](https://github.com/JetBrains/Exposed/wiki/DSL#batch-insert) for more details.
*/
abstract class BaseBatchInsertStatement(
table: Table,
ignore: Boolean,
Expand All @@ -28,6 +34,12 @@ abstract class BaseBatchInsertStatement(
super.set(column, value)
}

/**
* Adds the most recent batch to the current list of insert statements.
*
* This function uses the mapping of columns scheduled for change with their new values, which is
* provided by the implementing `BatchInsertStatement` instance.
*/
fun addBatch() {
if (data.isNotEmpty()) {
validateLastBatch()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import java.sql.ResultSet

/** An exception thrown when the provided data cannot be validated or processed to prepare a batch statement. */
class BatchDataInconsistentException(message: String) : Exception(message)

/** Represents the SQL statement that batch inserts new rows into a table. */
open class BatchInsertStatement(
table: Table,
ignore: Boolean = false,
Expand All @@ -14,8 +16,17 @@ open class BatchInsertStatement(

private const val OUTPUT_ROW_LIMIT = 1000

open class SQLServerBatchInsertStatement(table: Table, ignore: Boolean = false, shouldReturnGeneratedValues: Boolean = true) :
BatchInsertStatement(table, ignore, shouldReturnGeneratedValues) {
/**
* Represents the SQL statement that batch inserts new rows into a table, specifically for the SQL Server database.
*
* Before adding each new batch, the class validates that the database's maximum number of inserted rows (1000)
* is not being exceeded.
*/
open class SQLServerBatchInsertStatement(
table: Table,
ignore: Boolean = false,
shouldReturnGeneratedValues: Boolean = true
) : BatchInsertStatement(table, ignore, shouldReturnGeneratedValues) {
override val isAlwaysBatch: Boolean = false

override fun validateLastBatch() {
Expand All @@ -25,7 +36,9 @@ open class SQLServerBatchInsertStatement(table: Table, ignore: Boolean = false,
}
}

private val columnToReturnValue = table.autoIncColumn?.takeIf { shouldReturnGeneratedValues && it.autoIncColumnType?.nextValExpression == null }
private val columnToReturnValue = table.autoIncColumn?.takeIf {
shouldReturnGeneratedValues && it.autoIncColumnType?.nextValExpression == null
}

override fun prepareSQL(transaction: Transaction, prepared: Boolean): String {
val values = arguments!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.jetbrains.exposed.sql.vendors.MysqlFunctionProvider
import org.jetbrains.exposed.sql.vendors.h2Mode

/**
* Represents the SQL command that either batch inserts new rows into a table, or, if insertions violate unique constraints,
* Represents the SQL statement that either batch inserts new rows into a table, or, if insertions violate unique constraints,
* first deletes the existing rows before inserting new rows.
*
* @param table Table to either insert values into or delete values from then insert into.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import java.util.*

/**
* Represents the SQL statement that batch updates rows of a table.
*
* @param table Identity table to update values from.
*/
open class BatchUpdateStatement(val table: IdTable<*>) : UpdateStatement(table, null) {
/** The mappings of columns to update with their updated values for each entity in the batch. */
val data = ArrayList<Pair<EntityID<*>, Map<Column<*>, Any?>>>()
override val firstDataSet: List<Pair<Column<*>, Any?>> get() = data.first().second.toList()

/**
* Adds the specified entity [id] to the current list of update statements, using the mapping of columns to update
* provided for this `BatchUpdateStatement`.
*/
fun addBatch(id: EntityID<*>) {
val lastBatch = data.lastOrNull()
val different by lazy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.jetbrains.exposed.sql.vendors.H2FunctionProvider
import org.jetbrains.exposed.sql.vendors.MysqlFunctionProvider

/**
* Represents the SQL command that either batch inserts new rows into a table, or updates the existing rows if insertions violate unique constraints.
* Represents the SQL statement that either batch inserts new rows into a table, or updates the existing rows if insertions violate unique constraints.
*
* **Note**: Unlike `UpsertStatement`, `BatchUpsertStatement` does not include a `where` parameter. Please log a feature request
* on [YouTrack](https://youtrack.jetbrains.com/newIssue?project=EXPOSED&c=Type%20Feature&draftId=25-4449790) if a use-case requires inclusion of a `where` clause.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi

/**
* Represents the SQL statement that deletes one or more rows of a table.
*
* @param table Table to delete rows from.
* @param where Condition that determines which rows to delete.
* @param isIgnore Whether to ignore errors or not.
* **Note** [isIgnore] is not supported by all vendors. Please check the documentation.
* @param limit Maximum number of rows to delete.
* @param offset The number of rows to skip.
*/
open class DeleteStatement(
val table: Table,
val where: Op<Boolean>? = null,
Expand All @@ -28,9 +38,19 @@ open class DeleteStatement(
}

companion object {
/**
* Creates a [DeleteStatement] that deletes only rows in [table] that match the provided [op].
*
* @return Count of deleted rows.
*/
fun where(transaction: Transaction, table: Table, op: Op<Boolean>, isIgnore: Boolean = false, limit: Int? = null, offset: Long? = null): Int =
DeleteStatement(table, op, isIgnore, limit, offset).execute(transaction) ?: 0

/**
* Creates a [DeleteStatement] that deletes all rows in [table].
*
* @return Count of deleted rows.
*/
fun all(transaction: Transaction, table: Table): Int = DeleteStatement(table).execute(transaction) ?: 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi

open class InsertSelectStatement(val columns: List<Column<*>>, val selectQuery: AbstractQuery<*>, val isIgnore: Boolean = false) :
Statement<Int>(StatementType.INSERT, listOf(columns.first().table)) {
/**
* Represents the SQL statement that uses data retrieved from a [selectQuery] to insert new rows into a table.
*
* @param columns Columns to insert the values into.
* @param selectQuery Source SELECT query that provides the values to insert.
* @param isIgnore Whether to ignore errors or not.
* **Note** [isIgnore] is not supported by all vendors. Please check the documentation.
*/
open class InsertSelectStatement(
val columns: List<Column<*>>,
val selectQuery: AbstractQuery<*>,
val isIgnore: Boolean = false
) : Statement<Int>(StatementType.INSERT, listOf(columns.first().table)) {

init {
if (columns.isEmpty()) error("Can't insert without provided columns")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ import java.sql.ResultSet
import java.sql.SQLException
import kotlin.properties.Delegates

open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean = false) : UpdateBuilder<Int>(StatementType.INSERT, listOf(table)) {
/**
* Represents the SQL statement that inserts a new row into a table.
*
* @param table Table to insert the new row into.
* @param isIgnore Whether to ignore errors or not.
* **Note** [isIgnore] is not supported by all vendors. Please check the documentation.
*/
open class InsertStatement<Key : Any>(
val table: Table,
val isIgnore: Boolean = false
) : UpdateBuilder<Int>(StatementType.INSERT, listOf(table)) {

/**
* Returns the number of rows affected by the insert operation.
* The number of rows affected by the insert operation.
*
* When returned by a `BatchInsertStatement` or `BatchUpsertStatement`, the returned value is calculated using the
* sum of the individual values generated by each statement.
Expand All @@ -21,6 +31,7 @@ open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean =
*/
var insertedCount: Int by Delegates.notNull()

/** The [ResultRow]s generated by processing the database result set retrieved after executing the statement. */
var resultedValues: List<ResultRow>? = null
private set

Expand All @@ -34,6 +45,10 @@ open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean =
return row[column]
}

/**
* Returns the value of a given [column] from the first stored [ResultRow], or `null` if either no results were
* retrieved from the database or if the column cannot be found in the row.
*/
fun <T> getOrNull(column: Column<T>): T? = resultedValues?.firstOrNull()?.getOrNull(column)

@Suppress("NestedBlockDepth", "ComplexMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.jetbrains.exposed.sql.vendors.MysqlFunctionProvider
import org.jetbrains.exposed.sql.vendors.h2Mode

/**
* Represents the SQL command that either inserts a new row into a table, or, if insertion would violate a unique constraint,
* Represents the SQL statement that either inserts a new row into a table, or, if insertion would violate a unique constraint,
* first deletes the existing row before inserting a new row.
*
* @param table Table to either insert values into or delete values from then insert into.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,45 @@ internal object DefaultValueMarker {
override fun toString(): String = "DEFAULT"
}

/**
* Base class representing an SQL statement that can be executed.
*
* @param type The specific [StatementType], usually represented by the leading word in the command syntax.
* @param targets Tables on which to perform the SQL statement.
*/
abstract class Statement<out T>(val type: StatementType, val targets: List<Table>) {

/**
* Determines the exact way that an SQL statement is executed in a [transaction] and applies any necessary
* logic before returning the result generated by the executed statement.
*/
abstract fun PreparedStatementApi.executeInternal(transaction: Transaction): T?

/**
* Returns the string representation of an SQL statement.
*
* If necessary, [transaction] can be used to ensure that database-specific syntax is used to generate the string.
* To return a non-parameterized string, set [prepared] to `false`.
*/
abstract fun prepareSQL(transaction: Transaction, prepared: Boolean = true): String

/** Returns all mappings of columns and expression types to their values needed to prepare an SQL statement. */
abstract fun arguments(): Iterable<Iterable<Pair<IColumnType, Any?>>>

/**
* Uses a [transaction] connection and an [sql] string representation to return a precompiled SQL statement,
* stored as an implementation of [PreparedStatementApi].
*/
open fun prepared(transaction: Transaction, sql: String): PreparedStatementApi =
transaction.connection.prepareStatement(sql, false)

/** Whether the SQL statement is meant to be performed as part of a batch execution. */
open val isAlwaysBatch: Boolean = false

/**
* Executes the SQL statement directly in the provided [transaction] and returns the generated result,
* or `null` if no result was retrieved.
*/
fun execute(transaction: Transaction): T? = transaction.exec(this)

internal fun executeIn(transaction: Transaction): Pair<T?, List<StatementContext>> {
Expand Down Expand Up @@ -76,10 +102,16 @@ abstract class Statement<out T>(val type: StatementType, val targets: List<Table
}
}

/** Holds information related to a particular [statement] and the [args] needed to prepare it for execution. */
class StatementContext(val statement: Statement<*>, val args: Iterable<Pair<IColumnType, Any?>>) {
/** Returns the string representation of the SQL statement associated with this [StatementContext]. */
fun sql(transaction: Transaction) = statement.prepareSQL(transaction)
}

/**
* Returns the string representation of [this] context's [Statement] with its argument values included
* directly instead of parameter placeholders.
*/
fun StatementContext.expandArgs(transaction: Transaction): String {
val sql = sql(transaction)
val iterator = args.iterator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,46 @@ import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi

/**
* In general, statement execution flow works in a following way :
* 1) beforeExecution
* 2) PreparedStatement is created
* 3) afterStatementPrepared with prepared statement, which was created at phase 2
* 4) Execute SQL query
* 5) afterExecution
* Represents the processes that should be performed during a statement's lifecycle events in a transaction.
*
* In general, statement execution flow works in the following way:
* 1) [beforeExecution] of the statement
* 2) Creation of the prepared statement
* 3) [afterStatementPrepared] using the prepared statement from step 2
* 4) Execution of the SQL query
* 5) [afterExecution]
*/

interface StatementInterceptor {
/** Performs steps before a statement, from the provided [context], is executed in a [transaction]. */
fun beforeExecution(transaction: Transaction, context: StatementContext) {}

/**
* Performs steps after [preparedStatement] has been created in a [transaction], but before the statement
* has been executed.
**/
fun afterStatementPrepared(transaction: Transaction, preparedStatement: PreparedStatementApi) {}

/** Performs steps after an [executedStatement], from the provided [contexts], is complete in [transaction]. */
fun afterExecution(transaction: Transaction, contexts: List<StatementContext>, executedStatement: PreparedStatementApi) {}

/** Performs steps before a [transaction] is committed. */
fun beforeCommit(transaction: Transaction) {}

/** Performs steps after a [transaction] is committed. */
fun afterCommit(transaction: Transaction) {}

/** Performs steps before a rollback operation is issued on a [transaction]. */
fun beforeRollback(transaction: Transaction) {}

/** Performs steps after a rollback operation is issued on a [transaction]. */
fun afterRollback(transaction: Transaction) {}

/**
* Returns a mapping of [userData] that ensures required information is not lost from the transaction scope
* once the transaction is committed.
*/
fun keepUserDataInTransactionStoreOnCommit(userData: Map<Key<*>, Any?>): Map<Key<*>, Any?> = emptyMap()
}

/** Represents a [StatementInterceptor] that is loaded whenever a [Transaction] instance is initialized. */
interface GlobalStatementInterceptor : StatementInterceptor
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ import org.jetbrains.exposed.sql.wrapAsExpression
import kotlin.internal.LowPriorityInOverloadResolution

/**
* @author max
* Represents the underlying mapping of columns scheduled for change along with their new values.
*/

abstract class UpdateBuilder<out T>(type: StatementType, targets: List<Table>) : Statement<T>(type, targets) {
/** The mapping of columns scheduled for change with their new values. */
protected val values: MutableMap<Column<*>, Any?> = LinkedHashMap()

open operator fun contains(column: Column<*>): Boolean = values.contains(column)

/** Whether the underlying mapping has at least one stored value that is a batched statement. */
protected var hasBatchedValues: Boolean = false

private fun checkThatExpressionWasNotSetInPreviousBatch(column: Column<*>) {
require(!(values.containsKey(column) && hasBatchedValues)) { "$column is already initialized in a batch" }
}
Expand Down Expand Up @@ -67,15 +69,22 @@ abstract class UpdateBuilder<out T>(type: StatementType, targets: List<Table>) :
open operator fun <S> set(column: Column<S>, value: Query) = update(column, wrapAsExpression(value))

open operator fun <S> set(column: CompositeColumn<S>, value: S) {
column.getRealColumnsWithValues(value).forEach { (realColumn, itsValue) -> set(realColumn as Column<Any?>, itsValue) }
column.getRealColumnsWithValues(value).forEach { (realColumn, itsValue) ->
set(realColumn as Column<Any?>, itsValue)
}
}

/**
* Updates the mapping of the specified [column] with the specified [value] if [column] has not been previously
* set up for a change and if [value] is of a valid type.
**/
open fun <T, S : T?> update(column: Column<T>, value: Expression<S>) {
checkThatExpressionWasNotSetInPreviousBatch(column)
column.columnType.validateValueBeforeUpdate(value)
values[column] = value
}

/** Updates the mapping of the specified [column] with the value of the provided expression. */
open fun <T, S : T?> update(column: Column<T>, value: SqlExpressionBuilder.() -> Expression<S>) {
update(column, SqlExpressionBuilder.value())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ import org.jetbrains.exposed.sql.vendors.OracleDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
import org.jetbrains.exposed.sql.vendors.h2Mode

/**
* Represents the SQL statement that updates rows of a table.
*
* @param targetsSet Column set to update rows from. This may be a [Table] or a [Join] instance.
* @param limit Maximum number of rows to update.
* @param where Condition that determines which rows to update.
*/
open class UpdateStatement(val targetsSet: ColumnSet, val limit: Int?, val where: Op<Boolean>? = null) :
UpdateBuilder<Int>(StatementType.UPDATE, targetsSet.targetTables()) {

/** The initial list of columns to update with their updated values. */
open val firstDataSet: List<Pair<Column<*>, Any?>> get() = values.toList()

override fun PreparedStatementApi.executeInternal(transaction: Transaction): Int {
Expand Down
Loading

0 comments on commit e32506d

Please sign in to comment.