Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add missing KDocs for exposed-core queries API #1941

Merged
merged 4 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,30 @@ import org.jetbrains.exposed.sql.statements.StatementType
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.sql.ResultSet

abstract class AbstractQuery<T : AbstractQuery<T>>(targets: List<Table>) : SizedIterable<ResultRow>, Statement<ResultSet>(StatementType.SELECT, targets) {
protected val transaction get() = TransactionManager.current()

/** Base class representing an SQL query that returns a [ResultSet] when executed. */
abstract class AbstractQuery<T : AbstractQuery<T>>(
targets: List<Table>
) : SizedIterable<ResultRow>, Statement<ResultSet>(StatementType.SELECT, targets) {
protected val transaction
get() = TransactionManager.current()

/** The stored list of columns and their [SortOrder] for an `ORDER BY` clause in this query. */
var orderByExpressions: List<Pair<Expression<*>, SortOrder>> = mutableListOf()
private set

/** The stored value for a `LIMIT` clause in this query. */
var limit: Int? = null
protected set

/** The stored value for an `OFFSET` clause in this query. */
var offset: Long = 0
private set

/** The number of results that should be fetched when this query is executed. */
var fetchSize: Int? = null
private set

/** The set of columns on which a query should be executed, contained by a [ColumnSet]. */
abstract val set: FieldSet

protected fun copyTo(other: AbstractQuery<T>) {
Expand All @@ -29,26 +40,32 @@ abstract class AbstractQuery<T : AbstractQuery<T>>(targets: List<Table>) : Sized

override fun prepareSQL(transaction: Transaction, prepared: Boolean) = prepareSQL(QueryBuilder(prepared))

/** Returns the string representation of an SQL query, generated by appending SQL expressions to a [QueryBuilder]. **/
abstract fun prepareSQL(builder: QueryBuilder): String

override fun arguments() = QueryBuilder(true).let {
prepareSQL(it)
if (it.args.isNotEmpty()) listOf(it.args) else emptyList()
}

/** Modifies this query to retrieve only distinct results if [value] is set to `true`. */
abstract fun withDistinct(value: Boolean = true): T

/** Modifies this query to return only [n] results, starting after the specified [offset]. **/
override fun limit(n: Int, offset: Long): T = apply {
limit = n
this.offset = offset
} as T

/** Modifies this query to sort results by the specified [column], according to the provided [order]. **/
fun orderBy(column: Expression<*>, order: SortOrder = SortOrder.ASC): T = orderBy(column to order)

/** Modifies this query to sort results according to the provided [order] of expressions. **/
override fun orderBy(vararg order: Pair<Expression<*>, SortOrder>): T = apply {
(orderByExpressions as MutableList).addAll(order)
} as T

/** Modifies the number of results that should be fetched when this query is executed. */
fun fetchSize(n: Int): T = apply {
fetchSize = n
} as T
Expand Down
46 changes: 44 additions & 2 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Alias.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ class Alias<out T : Table>(val delegate: T, val alias: String) : Table() {
?: error("Column not found in original table")
}

/** Represents a temporary SQL identifier, [alias], for a [delegate] expression. */
class ExpressionAlias<T>(val delegate: Expression<T>, val alias: String) : Expression<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { append(delegate).append(" $alias") }

/** Returns an [Expression] containing only the string representation of this [alias]. */
fun aliasOnlyExpression(): Expression<T> {
return if (delegate is ExpressionWithColumnType<T>) {
object : Function<T>(delegate.columnType) {
Expand All @@ -56,6 +58,7 @@ class ExpressionAlias<T>(val delegate: Expression<T>, val alias: String) : Expre
}
}

/** Represents a temporary SQL identifier, [alias], for a [query]. */
class QueryAlias(val query: AbstractQuery<*>, val alias: String) : ColumnSet() {

override fun describe(s: Transaction, queryBuilder: QueryBuilder) = queryBuilder {
Expand Down Expand Up @@ -106,19 +109,58 @@ class QueryAlias(val query: AbstractQuery<*>, val alias: String) : ColumnSet() {
}

fun <T : Table> T.alias(alias: String) = Alias(this, alias)

/**
* Creates a temporary identifier, [alias], for [this] query.
*
* @sample org.jetbrains.exposed.sql.tests.shared.AliasesTests.testJoinSubQuery01
*/
fun <T : AbstractQuery<*>> T.alias(alias: String) = QueryAlias(this, alias)

/**
* Creates a temporary identifier, [alias], for [this] expression.
*
* @sample org.jetbrains.exposed.sql.tests.shared.AliasesTests.testJoinSubQuery01
*/
fun <T> Expression<T>.alias(alias: String) = ExpressionAlias(this, alias)

/**
* Creates a join relation with a query.
*
* @param on The condition to join that will be placed in the `ON` clause.
* @param joinType The `JOIN` clause type used to combine rows. Defaults to [JoinType.INNER].
* @param joinPart The query to join with.
* @sample org.jetbrains.exposed.sql.tests.shared.AliasesTests.testJoinSubQuery02
*/
fun Join.joinQuery(on: (SqlExpressionBuilder.(QueryAlias) -> Op<Boolean>), joinType: JoinType = JoinType.INNER, joinPart: () -> AbstractQuery<*>): Join {
val qAlias = joinPart().alias("q${joinParts.count { it.joinPart is QueryAlias }}")
return join(qAlias, joinType, additionalConstraint = { on(qAlias) })
}

/**
* Creates a join relation between [this] table and a query.
*
* @param on The condition to join that will be placed in the `ON` clause.
* @param joinType The `JOIN` clause type used to combine rows. Defaults to [JoinType.INNER].
* @param joinPart The query to join with.
*/
fun Table.joinQuery(on: (SqlExpressionBuilder.(QueryAlias) -> Op<Boolean>), joinType: JoinType = JoinType.INNER, joinPart: () -> AbstractQuery<*>) =
Join(this).joinQuery(on, joinType, joinPart)

val Join.lastQueryAlias: QueryAlias? get() = joinParts.map { it.joinPart as? QueryAlias }.firstOrNull()

/**
* Returns the most recent [QueryAlias] instance used to create this join relation, or `null` if a query was not joined.
*
* @sample org.jetbrains.exposed.sql.tests.shared.AliasesTests.testJoinSubQuery02
*/
val Join.lastQueryAlias: QueryAlias?
get() = joinParts.map { it.joinPart as? QueryAlias }.firstOrNull()

/**
* Wraps a [query] as an [Expression] so that it can be used as part of an SQL statement or in another query clause.
*
* @sample org.jetbrains.exposed.sql.tests.shared.dml.OrderByTests.testOrderByExpressions
* @sample org.jetbrains.exposed.sql.tests.shared.dml.InsertTests.testInsertWithColumnExpression
*/
fun <T : Any> wrapAsExpression(query: AbstractQuery<*>) = object : Expression<T?>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
append("(")
Expand Down
31 changes: 29 additions & 2 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Function.kt
Original file line number Diff line number Diff line change
Expand Up @@ -363,26 +363,53 @@ sealed class NextVal<T>(
}

// Conditional Expressions

/**
* Represents an SQL function that allows the comparison of [value] to chained conditional clauses.
*
* If [value] is not provided, each chained conditional will be evaluated independently.
*/
@Suppress("FunctionNaming")
class Case(val value: Expression<*>? = null) {
class Case(
/** The value that is compared against every conditional expression. */
val value: Expression<*>? = null
) {
/** Adds a conditional expression with a [result] if the expression evaluates to `true`. */
fun <T> When(cond: Expression<Boolean>, result: Expression<T>): CaseWhen<T> = CaseWhen<T>(value).When(cond, result)
}

/**
* Represents an SQL function that allows the comparison of [value] to chained conditional clauses.
*
* If [value] is not provided, each chained conditional will be evaluated independently.
*/
@Suppress("FunctionNaming")
class CaseWhen<T>(val value: Expression<*>?) {
class CaseWhen<T>(
/** The value that is compared against every conditional expression. */
val value: Expression<*>?
) {
/** The boolean conditions to check and their resulting expressions if the condition is met. */
val cases: MutableList<Pair<Expression<Boolean>, Expression<out T>>> = mutableListOf()

/** Adds a conditional expression with a [result] if the expression evaluates to `true`. */
@Suppress("UNCHECKED_CAST")
fun <R : T> When(cond: Expression<Boolean>, result: Expression<R>): CaseWhen<R> {
cases.add(cond to result)
return this as CaseWhen<R>
}

/** Adds an expression that will be used as the function result if all [cases] evaluate to `false`. */
fun <R : T> Else(e: Expression<R>): ExpressionWithColumnType<R> = CaseWhenElse(this, e)
}

/**
* Represents an SQL function that steps through conditions, and either returns a value when the first condition is met
* or returns [elseResult] if all conditions are `false`.
*/
class CaseWhenElse<T, R : T>(
/** The conditions to check and their results if met. */
val caseWhen: CaseWhen<T>,
/** The result if none of the conditions checked are found to be `true`. */
val elseResult: Expression<R>
) : ExpressionWithColumnType<R>(), ComplexExpression {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,34 @@ package org.jetbrains.exposed.sql

import org.jetbrains.exposed.sql.vendors.ForUpdateOption

/** Represents the iterable elements of a database result. */
interface SizedIterable<out T> : Iterable<T> {
/** Returns a new [SizedIterable] containing only [n] elements, starting from the specified [offset]. */
fun limit(n: Int, offset: Long = 0): SizedIterable<T>

/** Returns the number of elements stored. */
fun count(): Long

/** Whether there are no elements stored. */
fun empty(): Boolean

/** Returns a new [SizedIterable] with a locking read for the elements according to the rules specified by [option]. */
fun forUpdate(option: ForUpdateOption = ForUpdateOption.ForUpdate): SizedIterable<T> = this

/** Returns a new [SizedIterable] without any locking read for the elements. */
fun notForUpdate(): SizedIterable<T> = this

/** Returns a new [SizedIterable] that is a copy of the original. */
fun copy(): SizedIterable<T>

/** Returns a new [SizedIterable] with the elements sorted according to the specified expression [order]. */
fun orderBy(vararg order: Pair<Expression<*>, SortOrder>): SizedIterable<T>
}

/** Returns an [EmptySizedIterable]. */
fun <T> emptySized(): SizedIterable<T> = EmptySizedIterable()

/** Represents a [SizedIterable] that is empty and cannot be iterated over. */
@Suppress("IteratorNotThrowingNoSuchElementException")
class EmptySizedIterable<out T> : SizedIterable<T>, Iterator<T> {
override fun count(): Long = 0
Expand All @@ -35,6 +51,7 @@ class EmptySizedIterable<out T> : SizedIterable<T>, Iterator<T> {
override fun orderBy(vararg order: Pair<Expression<*>, SortOrder>): SizedIterable<T> = this
}

/** Represents a [SizedIterable] that defers to the specified [delegate] collection. */
class SizedCollection<out T>(val delegate: Collection<T>) : SizedIterable<T> {
constructor(vararg values: T) : this(values.toList())
override fun limit(n: Int, offset: Long): SizedIterable<T> {
Expand All @@ -52,19 +69,22 @@ class SizedCollection<out T>(val delegate: Collection<T>) : SizedIterable<T> {
override fun orderBy(vararg order: Pair<Expression<*>, SortOrder>): SizedIterable<T> = this
}

/** Represents a [SizedIterable] whose elements are only loaded on first access. */
class LazySizedCollection<out T>(_delegate: SizedIterable<T>) : SizedIterable<T> {
private var delegate: SizedIterable<T> = _delegate

private var _wrapper: List<T>? = null
private var _size: Long? = null
private var _empty: Boolean? = null

val wrapper: List<T> get() {
if (_wrapper == null) {
_wrapper = delegate.toList()
/** A list wrapping lazily loaded data. */
val wrapper: List<T>
get() {
if (_wrapper == null) {
_wrapper = delegate.toList()
}
return _wrapper!!
}
return _wrapper!!
}

override fun limit(n: Int, offset: Long): SizedIterable<T> = LazySizedCollection(delegate.limit(n, offset))
override operator fun iterator() = wrapper.iterator()
Expand Down Expand Up @@ -117,9 +137,13 @@ class LazySizedCollection<out T>(_delegate: SizedIterable<T>) : SizedIterable<T>
return this
}

/** Whether the collection already has data loaded by its delegate. */
fun isLoaded(): Boolean = _wrapper != null
}

/**
* Returns a [SizedIterable] containing the lazily evaluated results of applying the function [f] to each original element.
*/
infix fun <T, R> SizedIterable<T>.mapLazy(f: (T) -> R): SizedIterable<R> {
val source = this
return object : SizedIterable<R> {
Expand Down
2 changes: 2 additions & 0 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,9 @@ class NotExists(
/** Returns an SQL operator that checks if [query] doesn't returns any row. */
fun notExists(query: AbstractQuery<*>) = NotExists(query)

/** Represents an SQL operator that compares [expr] to any row returned from [query]. */
sealed class SubQueryOp<T>(
/** Returns the string representation of the operator to use in the comparison. */
val operator: String,
/** Returns the expression compared to each row of the query result. */
val expr: Expression<T>,
Expand Down
Loading
Loading