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

chore: Add Kdocs and update DSL for AllAnyFromBaseOp feature #1960

Merged
merged 1 commit into from
Jan 5, 2024
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
5 changes: 0 additions & 5 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2404,11 +2404,6 @@ public final class org/jetbrains/exposed/sql/UnionAll : org/jetbrains/exposed/sq
public fun withDistinct (Z)Lorg/jetbrains/exposed/sql/SetOperation;
}

public final class org/jetbrains/exposed/sql/UntypedAndUnsizedArrayColumnType : org/jetbrains/exposed/sql/ColumnType {
public static final field INSTANCE Lorg/jetbrains/exposed/sql/UntypedAndUnsizedArrayColumnType;
public fun sqlType ()Ljava/lang/String;
}

public final class org/jetbrains/exposed/sql/UpperCase : org/jetbrains/exposed/sql/Function {
public fun <init> (Lorg/jetbrains/exposed/sql/Expression;)V
public final fun getExpr ()Lorg/jetbrains/exposed/sql/Expression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,14 @@ class CustomEnumerationColumnType<T : Enum<T>>(
override fun nonNullValueToString(value: Any): String = super.nonNullValueToString(notNullValueToDB(value))
}

object UntypedAndUnsizedArrayColumnType : ColumnType() {
/**
* Array column for storing arrays of any size and type.
*
* This column type only exists to allow registering an array as a valid SQL type for statement clauses generated
* using `anyFrom(array)` and `allFrom(array)`. It does not correctly process arrays for use in `nonNullValueToString()`
* and will be replaced with a full implementation of ArrayColumnType.
*/
internal object UntypedAndUnsizedArrayColumnType : ColumnType() {
Comment on lines -1006 to +1013
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A YT issue has been created for the proper implementation of an array column type.

override fun sqlType(): String =
currentDialect.dataTypeProvider.untypedAndUnsizedArrayType()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,18 @@ interface ISqlExpressionBuilder {

// "IN (TABLE ...)" comparisons

/** Checks if this expression is equal to any element from the column of [table] with only a single column. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */
/**
* Checks if this expression is equal to any element from the column of [table] with only a single column.
*
* **Note** This function is only supported by MySQL, PostgreSQL, and H2 dialects.
*/
infix fun <T> ExpressionWithColumnType<T>.inTable(table: Table): InTableOp = InTableOp(this, table, true)

/** Checks if this expression is not equal to any element from the column of [table] with only a single column. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */
/**
* Checks if this expression is **not** equal to any element from the column of [table] with only a single column.
*
* **Note** This function is only supported by MySQL, PostgreSQL, and H2 dialects.
*/
infix fun <T> ExpressionWithColumnType<T>.notInTable(table: Table): InTableOp = InTableOp(this, table, false)

// Misc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,57 @@ import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.UntypedAndUnsizedArrayColumnType

abstract class AllAnyFromBaseOp<T, SubSearch>(val isAny: Boolean, val subSearch: SubSearch) : Op<T>() {
/**
* Represents an SQL operator that checks a value, based on the preceding comparison operator,
* against elements returned by [subSearch].
*/
abstract class AllAnyFromBaseOp<T, SubSearch>(
/** Returns `true` if at least 1 comparison must evaluate to `true`, or `false` if all comparisons must be `true`. **/
val isAny: Boolean,
/** Returns the source of elements to be compared against. */
val subSearch: SubSearch
) : Op<T>() {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
+(if (isAny) "ANY" else "ALL")
+" ("
registerSubSearchArgument(subSearch)
+')'
}

/** Processes the [subSearch] value for inclusion in the generated query. */
abstract fun QueryBuilder.registerSubSearchArgument(subSearch: SubSearch)
}

class AllAnyFromSubQueryOp<T>(isAny: Boolean, subQuery: AbstractQuery<*>) : AllAnyFromBaseOp<T, AbstractQuery<*>>(isAny, subQuery) {
/**
* Represents an SQL operator that checks a value, based on the preceding comparison operator,
* against results returned by a query.
*/
class AllAnyFromSubQueryOp<T>(
isAny: Boolean,
subQuery: AbstractQuery<*>
) : AllAnyFromBaseOp<T, AbstractQuery<*>>(isAny, subQuery) {
override fun QueryBuilder.registerSubSearchArgument(subSearch: AbstractQuery<*>) {
subSearch.prepareSQL(this)
}
}

/** This function is only supported by PostgreSQL and H2 dialects. */
/**
* Represents an SQL operator that checks a value, based on the preceding comparison operator,
* against an array of values.
*
* **Note** This operation is only supported by PostgreSQL and H2 dialects.
*/
class AllAnyFromArrayOp<T>(isAny: Boolean, array: Array<T>) : AllAnyFromBaseOp<T, Array<T>>(isAny, array) {
override fun QueryBuilder.registerSubSearchArgument(subSearch: Array<T>) =
registerArgument(UntypedAndUnsizedArrayColumnType, subSearch)
}

/** This function is only supported by PostgreSQL and H2 dialects. */
/**
* Represents an SQL operator that checks a value, based on the preceding comparison operator,
* against elements in a single-column table.
*
* **Note** This operation is only supported by MySQL, PostgreSQL, and H2 dialects.
*/
class AllAnyFromTableOp<T>(isAny: Boolean, table: Table) : AllAnyFromBaseOp<T, Table>(isAny, table) {
override fun QueryBuilder.registerSubSearchArgument(subSearch: Table) {
+"TABLE "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table

/** This function is only supported by PostgreSQL and H2 dialects. */
/**
* Represents an SQL operator that checks if [expr] is equal to any element from a single-column [table].
*
* **Note** This operation is only supported by MySQL, PostgreSQL, and H2 dialects.
*/
class InTableOp(
/** Returns the expression compared to each element in the table's column. */
val expr: Expression<*>,
/** the table to check against. */
/** Returns the single-column table to check against. */
val table: Table,
/** Returns `true` if the check is inverted, `false` otherwise. */
/** Returns `false` if the check is inverted, `true` otherwise. */
val isInTable: Boolean = true
) : Op<Boolean>(), ComplexExpression {
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testSelectAnd() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { _, users, _ ->
users.selectAll().where { users.id.eq("andrey") and users.name.eq("Andrey") }.forEach {
val userId = it[users.id]
val userName = it[users.name]
Expand All @@ -97,7 +97,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testSelectOr() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { _, users, _ ->
users.selectAll().where { users.id.eq("andrey") or users.name.eq("Andrey") }.forEach {
val userId = it[users.id]
val userName = it[users.name]
Expand All @@ -111,7 +111,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testSelectNot() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { _, users, _ ->
users.selectAll().where { not(users.id.eq("andrey")) }.forEach {
val userId = it[users.id]
val userName = it[users.name]
Expand All @@ -124,7 +124,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testSizedIterable() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { cities, users, _ ->
assertEquals(false, cities.selectAll().empty())
assertEquals(true, cities.selectAll().where { cities.name eq "Qwertt" }.empty())
assertEquals(0L, cities.selectAll().where { cities.name eq "Qwertt" }.count())
Expand All @@ -136,7 +136,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testInList01() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { _, users, _ ->
val r = users.selectAll().where {
users.id inList listOf("andrey", "alex")
}.orderBy(users.name).toList()
Expand All @@ -149,7 +149,7 @@ class SelectTests : DatabaseTestsBase() {

@Test
fun testInList02() {
withCitiesAndUsers { cities, users, userData ->
withCitiesAndUsers { cities, _, _ ->
val cityIds = cities.selectAll().map { it[cities.id] }.take(2)
val r = cities.selectAll().where { cities.id inList cityIds }

Expand Down Expand Up @@ -264,13 +264,13 @@ class SelectTests : DatabaseTestsBase() {
}
}

val testDBsSupportingInAnyAllFromTables = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB
private val testDBsSupportingInAnyAllFromTables = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include MySQL8 as well, but tests will fail due to the current issue with gradle task testMySql8. A YT issue has been created to add the dialect to the tests once that issue is resolved.


@Test
fun testInTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
withSalesAndSomeAmounts { _, sales, someAmounts ->
val r = sales.select { sales.amount inTable someAmounts }
val r = sales.selectAll().where { sales.amount inTable someAmounts }
assertEquals(2, r.count())
}
}
Expand All @@ -280,42 +280,46 @@ class SelectTests : DatabaseTestsBase() {
fun testNotInTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
withSalesAndSomeAmounts { _, sales, someAmounts ->
val r = sales.select { sales.amount notInTable someAmounts }
val r = sales.selectAll().where { sales.amount notInTable someAmounts }
assertEquals(5, r.count())
}
}
}

val testDBsSupportingAnyAndAllFromSubQuries = TestDB.values().asList() - TestDB.SQLITE
val testDBsSupportingAnyAndAllFromArrays = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB
private val testDBsSupportingAnyAndAllFromSubQueries = TestDB.entries - TestDB.SQLITE
private val testDBsSupportingAnyAndAllFromArrays = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB

/** Adapted from [testInSubQuery01]. */
@Test
fun testEqAnyFromSubQuery() {
withDb(testDBsSupportingAnyAndAllFromSubQuries) {
withDb(testDBsSupportingAnyAndAllFromSubQueries) {
withCitiesAndUsers { cities, _, _ ->
val r = cities.select { cities.id eq anyFrom(cities.slice(cities.id).select { cities.id eq 2 }) }
val r = cities.selectAll().where {
cities.id eq anyFrom(cities.select(cities.id).where { cities.id eq 2 })
}
assertEquals(1L, r.count())
}
}
}

@Test
fun testNeqAnyFromSubQuery() {
withDb(testDBsSupportingAnyAndAllFromSubQuries) {
withDb(testDBsSupportingAnyAndAllFromSubQueries) {
withCitiesAndUsers { cities, _, _ ->
val r = cities.select { cities.id neq anyFrom(cities.slice(cities.id).select { cities.id eq 2 }) }
val r = cities.selectAll().where {
cities.id neq anyFrom(cities.select(cities.id).where { cities.id eq 2 })
}
assertEquals(2, r.count())
}
}
}

/** Adapted from [testInList01]. */
@Test
fun testEqAnyFromArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.select { users.id eq anyFrom(arrayOf("andrey", "alex")) }.orderBy(users.name).toList()
val r = users.selectAll().where {
users.id eq anyFrom(arrayOf("andrey", "alex"))
}.orderBy(users.name).toList()

assertEquals(2, r.size)
assertEquals("Alex", r[0][users.name])
Expand All @@ -328,7 +332,9 @@ class SelectTests : DatabaseTestsBase() {
fun testNeqAnyFromArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.select { users.id neq anyFrom(arrayOf("andrey")) }.orderBy(users.name)
val r = users.selectAll().where {
users.id neq anyFrom(arrayOf("andrey"))
}.orderBy(users.name)
assertEquals(4, r.count())
}
}
Expand All @@ -338,7 +344,7 @@ class SelectTests : DatabaseTestsBase() {
fun testNeqAnyFromEmptyArray() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withCitiesAndUsers { _, users, _ ->
val r = users.select { users.id neq anyFrom(emptyArray()) }.orderBy(users.name)
val r = users.selectAll().where { users.id neq anyFrom(emptyArray()) }.orderBy(users.name)
assert(r.empty())
}
}
Expand All @@ -349,7 +355,8 @@ class SelectTests : DatabaseTestsBase() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withSales { _, sales ->
val amounts = arrayOf(100, 1000).map { it.toBigDecimal() }.toTypedArray()
val r = sales.select { sales.amount greaterEq anyFrom(amounts) }.orderBy(sales.amount)
val r = sales.selectAll().where { sales.amount greaterEq anyFrom(amounts) }
.orderBy(sales.amount)
.map { it[sales.product] }
assertEquals(6, r.size)
r.subList(0, 3).forEach { assertEquals("tea", it) }
Expand All @@ -358,33 +365,33 @@ class SelectTests : DatabaseTestsBase() {
}
}

/** Adapted from [testInTable]. */
@Test
fun testEqAnyFromTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
withSalesAndSomeAmounts { testDb, sales, someAmounts ->
val r = sales.select { sales.amount eq anyFrom(someAmounts) }
withSalesAndSomeAmounts { _, sales, someAmounts ->
val r = sales.selectAll().where { sales.amount eq anyFrom(someAmounts) }
assertEquals(2, r.count())
}
}
}

/** Adapted from [testNotInTable]. */
@Test
fun testNeqAllFromTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
withSalesAndSomeAmounts { testDb, sales, someAmounts ->
val r = sales.select { sales.amount neq allFrom(someAmounts) }
withSalesAndSomeAmounts { _, sales, someAmounts ->
val r = sales.selectAll().where { sales.amount neq allFrom(someAmounts) }
assertEquals(5, r.count())
}
}
}

@Test
fun testGreaterEqAllFromSubQuery() {
withDb(testDBsSupportingAnyAndAllFromSubQuries) {
withDb(testDBsSupportingAnyAndAllFromSubQueries) {
withSales { _, sales ->
val r = sales.select { sales.amount greaterEq allFrom(sales.slice(sales.amount).select { sales.product eq "tea" }) }
val r = sales.selectAll().where {
sales.amount greaterEq allFrom(sales.select(sales.amount).where { sales.product eq "tea" })
}
.orderBy(sales.amount).map { it[sales.product] }
assertEquals(4, r.size)
assertEquals("tea", r.first())
Expand All @@ -398,7 +405,7 @@ class SelectTests : DatabaseTestsBase() {
withDb(testDBsSupportingAnyAndAllFromArrays) {
withSales { _, sales ->
val amounts = arrayOf(100, 1000).map { it.toBigDecimal() }.toTypedArray()
val r = sales.select { sales.amount greaterEq allFrom(amounts) }.toList()
val r = sales.selectAll().where { sales.amount greaterEq allFrom(amounts) }.toList()
assertEquals(3, r.size)
r.forEach { assertEquals("coffee", it[sales.product]) }
}
Expand All @@ -409,7 +416,7 @@ class SelectTests : DatabaseTestsBase() {
fun testGreaterEqAllFromTable() {
withDb(testDBsSupportingInAnyAllFromTables) {
withSalesAndSomeAmounts { _, sales, someAmounts ->
val r = sales.select { sales.amount greaterEq allFrom(someAmounts) }.toList()
val r = sales.selectAll().where { sales.amount greaterEq allFrom(someAmounts) }.toList()
assertEquals(3, r.size)
r.forEach { assertEquals("coffee", it[sales.product]) }
}
Expand Down
Loading