-
Notifications
You must be signed in to change notification settings - Fork 675
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
feat: Add ALL
and ANY
operators accepting array, subquery, or table parameters
#1886
Changes from 10 commits
6aeed36
be49f65
2c549b1
020313c
6076057
7d0ef40
c03aa1f
93253a3
dd296e6
7157813
eeec230
30d4b85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,7 @@ package org.jetbrains.exposed.sql | |
import org.jetbrains.exposed.dao.id.EntityID | ||
import org.jetbrains.exposed.dao.id.EntityIDFunctionProvider | ||
import org.jetbrains.exposed.dao.id.IdTable | ||
import org.jetbrains.exposed.sql.ops.InListOrNotInListBaseOp | ||
import org.jetbrains.exposed.sql.ops.PairInListOp | ||
import org.jetbrains.exposed.sql.ops.SingleValueInListOp | ||
import org.jetbrains.exposed.sql.ops.TripleInListOp | ||
import org.jetbrains.exposed.sql.ops.* | ||
import org.jetbrains.exposed.sql.vendors.FunctionProvider | ||
import org.jetbrains.exposed.sql.vendors.currentDialect | ||
import java.math.BigDecimal | ||
|
@@ -96,6 +93,37 @@ fun <T : Any?> ExpressionWithColumnType<T>.varPop(scale: Int = 2): VarPop<T> = V | |
*/ | ||
fun <T : Any?> ExpressionWithColumnType<T>.varSamp(scale: Int = 2): VarSamp<T> = VarSamp(this, scale) | ||
|
||
// Array Comparisons | ||
|
||
/** Returns this subquery wrapped in the `ANY` operator. This function is not supported by the SQLite dialect. */ | ||
fun <T> anyFrom(subQuery: Query): Op<T> = AllAnyFromSubQueryOp(true, subQuery) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ShreckYe I forgot to mention something. Please take |
||
|
||
/** Returns this array of data wrapped in the `ANY` operator. This function is only supported by PostgreSQL and H2 dialects. */ | ||
fun <T> anyFrom(array: Array<T>): Op<T> = AllAnyFromArrayOp(true, array) | ||
|
||
/** Returns this table wrapped in the `ANY` operator. This function is only supported by PostgreSQL and H2 dialects. */ | ||
fun <T> anyFrom(table: Table): Op<T> = AllAnyFromTableOp(true, table) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly, The operators with table arguments are supported by PostgreSQL and H2 as tested though they haven't clearly documented about this, but the tests fail on MySQL who claims supporting it. I think it's probably because it's not supported by its JDBC connector. I verified by copying the SQL statement to an online MySQL executor and it works. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there is currently an issue with the mysql8 docker setup in our tests, so the test failing is a false negative. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so shall I allow the tests to run on MySQL related dialects and just let them fail, or skip the tests as how they are now? |
||
|
||
/** Returns this subquery wrapped in the `ALL` operator. This function is not supported by the SQLite dialect. */ | ||
fun <T> allFrom(subQuery: Query): Op<T> = AllAnyFromSubQueryOp(false, subQuery) | ||
|
||
/** Returns this array of data wrapped in the `ALL` operator. This function is only supported by PostgreSQL and H2 dialects. */ | ||
fun <T> allFrom(array: Array<T>): Op<T> = AllAnyFromArrayOp(false, array) | ||
|
||
/** Returns this table wrapped in the `ALL` operator. This function is only supported by PostgreSQL and H2 dialects. */ | ||
fun <T> allFrom(table: Table): Op<T> = AllAnyFromTableOp(false, table) | ||
|
||
// TODO Currently these functions delegate to `anyFrom`. Should the actual `SOME` SQL operator be supported? | ||
|
||
/** An alias for [anyFrom]. */ | ||
fun <T> someFrom(subQuery: Query): Op<T> = anyFrom(subQuery) | ||
|
||
/** An alias for [anyFrom]. */ | ||
fun <T> someFrom(array: Array<T>): Op<T> = anyFrom(array) | ||
|
||
/** An alias for [anyFrom]. */ | ||
fun <T> someFrom(table: Table): Op<T> = anyFrom(table) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ShreckYe There's already quite a bit in this PR. Please remove these aliases and then |
||
// Sequence Manipulation Functions | ||
|
||
/** Advances this sequence and returns the new value. */ | ||
|
@@ -578,7 +606,7 @@ interface ISqlExpressionBuilder { | |
|
||
// Array Comparisons | ||
|
||
/** Checks if this expression is equals to any element from [list]. */ | ||
/** Checks if this expression is equal to any element from [list]. */ | ||
infix fun <T> ExpressionWithColumnType<T>.inList(list: Iterable<T>): InListOrNotInListBaseOp<T> = SingleValueInListOp(this, list, isInList = true) | ||
|
||
/** | ||
|
@@ -635,6 +663,14 @@ interface ISqlExpressionBuilder { | |
return SingleValueInListOp(this, list.map { EntityIDFunctionProvider.createEntityID(it, idTable) }, isInList = false) | ||
} | ||
|
||
// "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 PostgreSQL and H2 dialects. */ | ||
infix fun <T> ExpressionWithColumnType<T>.inTable(table: Table): InTableOp = InTableOp(this, table, true) | ||
|
||
/** 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 PostgreSQL and H2 dialects. */ | ||
infix fun <T> ExpressionWithColumnType<T>.notInTable(table: Table): InTableOp = InTableOp(this, table, false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A "not" is probably missing from the KDocs here. |
||
|
||
// Misc. | ||
|
||
/** Returns the specified [value] as a query parameter of type [T]. */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.jetbrains.exposed.sql.ops | ||
|
||
import org.jetbrains.exposed.sql.Op | ||
import org.jetbrains.exposed.sql.Query | ||
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>() { | ||
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { | ||
+(if (isAny) "ANY" else "ALL") | ||
+" (" | ||
registerSubSearchArgument(subSearch) | ||
+')' | ||
} | ||
|
||
abstract fun QueryBuilder.registerSubSearchArgument(subSearch: SubSearch) | ||
} | ||
|
||
class AllAnyFromSubQueryOp<T>(isAny: Boolean, subQuery: Query) : AllAnyFromBaseOp<T, Query>(isAny, subQuery) { | ||
override fun QueryBuilder.registerSubSearchArgument(subSearch: Query) { | ||
subSearch.prepareSQL(this) | ||
} | ||
} | ||
|
||
/** This function 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. */ | ||
class AllAnyFromTableOp<T>(isAny: Boolean, table: Table) : AllAnyFromBaseOp<T, Table>(isAny, table) { | ||
override fun QueryBuilder.registerSubSearchArgument(subSearch: Table) { | ||
+"TABLE " | ||
+subSearch.tableName | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I appreciate the need for this and for the scope of this PR it's ok. It does have some problems, for example:
But we need to flesh out the ArrayColumnType more thoroughly anyway, so I can work on these problems after this PR is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't remove this because the implemented
Op
s still depend on it. As you commented this will be fleshed out, so to not keep these new definitions in the PR, one way I can think of is to replace its usage with a temporary inlined anonymousColumnType
with itssqlType
being the empty string or just "ARRAY" and the tests still pass.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that sounds like a good temporary option to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so shall I remove it and adopt this approach in the next commit?