diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index de922f9b4f..92a13afa8c 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -925,6 +925,7 @@ public abstract interface class org/jetbrains/exposed/sql/ISqlExpressionBuilder public abstract fun inList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun inListIds (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun inSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/InSubQueryOp; + public abstract fun inTable (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public abstract fun intToDecimal (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/NoOpConversion; public abstract fun isDistinctFrom (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; public abstract fun isDistinctFrom (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; @@ -967,6 +968,7 @@ public abstract interface class org/jetbrains/exposed/sql/ISqlExpressionBuilder public abstract fun notInList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun notInListIds (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public abstract fun notInSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotInSubQueryOp; + public abstract fun notInTable (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public abstract fun notLike (Lorg/jetbrains/exposed/sql/Expression;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public abstract fun notLike (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public abstract fun notLike (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/LikePattern;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; @@ -1030,6 +1032,7 @@ public final class org/jetbrains/exposed/sql/ISqlExpressionBuilder$DefaultImpls public static fun inList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun inListIds (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun inSubQuery (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/InSubQueryOp; + public static fun inTable (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public static fun intToDecimal (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/NoOpConversion; public static fun isDistinctFrom (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; public static fun isDistinctFrom (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; @@ -1074,6 +1077,7 @@ public final class org/jetbrains/exposed/sql/ISqlExpressionBuilder$DefaultImpls public static fun notInList (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun notInListIds (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public static fun notInSubQuery (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotInSubQueryOp; + public static fun notInTable (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public static fun notLike (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public static fun notLike (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public static fun notLike (Lorg/jetbrains/exposed/sql/ISqlExpressionBuilder;Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/LikePattern;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; @@ -1734,6 +1738,12 @@ public final class org/jetbrains/exposed/sql/RowNumber : org/jetbrains/exposed/s public final class org/jetbrains/exposed/sql/SQLExpressionBuilderKt { public static final fun CustomLongFunction (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/CustomFunction; public static final fun CustomStringFunction (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/CustomFunction; + public static final fun allFrom (Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/Op; + public static final fun allFrom (Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/Op; + public static final fun allFrom ([Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; + public static final fun anyFrom (Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/Op; + public static final fun anyFrom (Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/Op; + public static final fun anyFrom ([Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Op; public static final fun avg (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;I)Lorg/jetbrains/exposed/sql/Avg; public static synthetic fun avg$default (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;IILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Avg; public static final fun castTo (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/IColumnType;)Lorg/jetbrains/exposed/sql/ExpressionWithColumnType; @@ -1974,6 +1984,7 @@ public final class org/jetbrains/exposed/sql/SqlExpressionBuilder : org/jetbrain public fun inList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun inListIds (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun inSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/InSubQueryOp; + public fun inTable (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public fun intToDecimal (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/NoOpConversion; public fun isDistinctFrom (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Expression;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; public fun isDistinctFrom (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Comparable;)Lorg/jetbrains/exposed/sql/IsDistinctFromOp; @@ -2016,6 +2027,7 @@ public final class org/jetbrains/exposed/sql/SqlExpressionBuilder : org/jetbrain public fun notInList (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun notInListIds (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Iterable;)Lorg/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp; public fun notInSubQuery (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/AbstractQuery;)Lorg/jetbrains/exposed/sql/NotInSubQueryOp; + public fun notInTable (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/ops/InTableOp; public fun notLike (Lorg/jetbrains/exposed/sql/Expression;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public fun notLike (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; public fun notLike (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/LikePattern;)Lorg/jetbrains/exposed/sql/LikeEscapeOp; @@ -2391,6 +2403,11 @@ 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 (Lorg/jetbrains/exposed/sql/Expression;)V public final fun getExpr ()Lorg/jetbrains/exposed/sql/Expression; @@ -2572,6 +2589,32 @@ public final class org/jetbrains/exposed/sql/functions/math/TanFunction : org/je public fun (Lorg/jetbrains/exposed/sql/ExpressionWithColumnType;)V } +public final class org/jetbrains/exposed/sql/ops/AllAnyFromArrayOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp { + public fun (Z[Ljava/lang/Object;)V + public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;[Ljava/lang/Object;)V +} + +public abstract class org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp : org/jetbrains/exposed/sql/Op { + public fun (ZLjava/lang/Object;)V + public final fun getSubSearch ()Ljava/lang/Object; + public final fun isAny ()Z + public abstract fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V +} + +public final class org/jetbrains/exposed/sql/ops/AllAnyFromSubQueryOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp { + public fun (ZLorg/jetbrains/exposed/sql/AbstractQuery;)V + public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Lorg/jetbrains/exposed/sql/AbstractQuery;)V +} + +public final class org/jetbrains/exposed/sql/ops/AllAnyFromTableOp : org/jetbrains/exposed/sql/ops/AllAnyFromBaseOp { + public fun (ZLorg/jetbrains/exposed/sql/Table;)V + public synthetic fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Ljava/lang/Object;)V + public fun registerSubSearchArgument (Lorg/jetbrains/exposed/sql/QueryBuilder;Lorg/jetbrains/exposed/sql/Table;)V +} + public abstract class org/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp : org/jetbrains/exposed/sql/Op, org/jetbrains/exposed/sql/ComplexExpression { public fun (Ljava/lang/Object;Ljava/lang/Iterable;Z)V public synthetic fun (Ljava/lang/Object;Ljava/lang/Iterable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -2583,6 +2626,15 @@ public abstract class org/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp : or public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V } +public final class org/jetbrains/exposed/sql/ops/InTableOp : org/jetbrains/exposed/sql/Op, org/jetbrains/exposed/sql/ComplexExpression { + public fun (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Table;Z)V + public synthetic fun (Lorg/jetbrains/exposed/sql/Expression;Lorg/jetbrains/exposed/sql/Table;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getExpr ()Lorg/jetbrains/exposed/sql/Expression; + public final fun getTable ()Lorg/jetbrains/exposed/sql/Table; + public final fun isInTable ()Z + public fun toQueryBuilder (Lorg/jetbrains/exposed/sql/QueryBuilder;)V +} + public final class org/jetbrains/exposed/sql/ops/PairInListOp : org/jetbrains/exposed/sql/ops/InListOrNotInListBaseOp { public fun (Lkotlin/Pair;Ljava/lang/Iterable;Z)V public synthetic fun (Lkotlin/Pair;Ljava/lang/Iterable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3150,6 +3202,7 @@ public abstract class org/jetbrains/exposed/sql/vendors/DataTypeProvider { public fun ubyteType ()Ljava/lang/String; public fun uintegerType ()Ljava/lang/String; public fun ulongType ()Ljava/lang/String; + public fun untypedAndUnsizedArrayType ()Ljava/lang/String; public fun ushortType ()Ljava/lang/String; public fun uuidToDB (Ljava/util/UUID;)Ljava/lang/Object; public fun uuidType ()Ljava/lang/String; diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt index dd333e2e96..cb51e5a8d0 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ColumnType.kt @@ -997,6 +997,11 @@ class CustomEnumerationColumnType>( override fun nonNullValueToString(value: Any): String = super.nonNullValueToString(notNullValueToDB(value)) } +object UntypedAndUnsizedArrayColumnType : ColumnType() { + override fun sqlType(): String = + currentDialect.dataTypeProvider.untypedAndUnsizedArrayType() +} + // Date/Time columns /** diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt index 951b750bf4..98e663935d 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/SQLExpressionBuilder.kt @@ -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 @@ -112,6 +109,26 @@ fun ExpressionWithColumnType.varPop(scale: Int = 2): VarPop = V */ fun ExpressionWithColumnType.varSamp(scale: Int = 2): VarSamp = VarSamp(this, scale) +// Array Comparisons + +/** Returns this subquery wrapped in the `ANY` operator. This function is not supported by the SQLite dialect. */ +fun anyFrom(subQuery: AbstractQuery<*>): Op = AllAnyFromSubQueryOp(true, subQuery) + +/** Returns this array of data wrapped in the `ANY` operator. This function is only supported by PostgreSQL and H2 dialects. */ +fun anyFrom(array: Array): Op = AllAnyFromArrayOp(true, array) + +/** Returns this table wrapped in the `ANY` operator. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */ +fun anyFrom(table: Table): Op = AllAnyFromTableOp(true, table) + +/** Returns this subquery wrapped in the `ALL` operator. This function is not supported by the SQLite dialect. */ +fun allFrom(subQuery: AbstractQuery<*>): Op = AllAnyFromSubQueryOp(false, subQuery) + +/** Returns this array of data wrapped in the `ALL` operator. This function is only supported by PostgreSQL and H2 dialects. */ +fun allFrom(array: Array): Op = AllAnyFromArrayOp(false, array) + +/** Returns this table wrapped in the `ALL` operator. This function is only supported by MySQL, PostgreSQL, and H2 dialects. */ +fun allFrom(table: Table): Op = AllAnyFromTableOp(false, table) + // Sequence Manipulation Functions /** Advances this sequence and returns the new value. */ @@ -606,7 +623,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 ExpressionWithColumnType.inList(list: Iterable): InListOrNotInListBaseOp = SingleValueInListOp(this, list, isInList = true) /** @@ -663,6 +680,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 MySQL, PostgreSQL, and H2 dialects. */ + infix fun ExpressionWithColumnType.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. */ + infix fun ExpressionWithColumnType.notInTable(table: Table): InTableOp = InTableOp(this, table, false) + // Misc. /** Returns the specified [value] as a query parameter of type [T]. */ diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt new file mode 100644 index 0000000000..e2215cd8af --- /dev/null +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/AllAnyOps.kt @@ -0,0 +1,38 @@ +package org.jetbrains.exposed.sql.ops + +import org.jetbrains.exposed.sql.AbstractQuery +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.QueryBuilder +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.UntypedAndUnsizedArrayColumnType + +abstract class AllAnyFromBaseOp(val isAny: Boolean, val subSearch: SubSearch) : Op() { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + +(if (isAny) "ANY" else "ALL") + +" (" + registerSubSearchArgument(subSearch) + +')' + } + + abstract fun QueryBuilder.registerSubSearchArgument(subSearch: SubSearch) +} + +class AllAnyFromSubQueryOp(isAny: Boolean, subQuery: AbstractQuery<*>) : AllAnyFromBaseOp>(isAny, subQuery) { + override fun QueryBuilder.registerSubSearchArgument(subSearch: AbstractQuery<*>) { + subSearch.prepareSQL(this) + } +} + +/** This function is only supported by PostgreSQL and H2 dialects. */ +class AllAnyFromArrayOp(isAny: Boolean, array: Array) : AllAnyFromBaseOp>(isAny, array) { + override fun QueryBuilder.registerSubSearchArgument(subSearch: Array) = + registerArgument(UntypedAndUnsizedArrayColumnType, subSearch) +} + +/** This function is only supported by PostgreSQL and H2 dialects. */ +class AllAnyFromTableOp(isAny: Boolean, table: Table) : AllAnyFromBaseOp(isAny, table) { + override fun QueryBuilder.registerSubSearchArgument(subSearch: Table) { + +"TABLE " + +subSearch.tableName + } +} diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InTableOp.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InTableOp.kt new file mode 100644 index 0000000000..f7e6cbf19d --- /dev/null +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/ops/InTableOp.kt @@ -0,0 +1,26 @@ +package org.jetbrains.exposed.sql.ops + +import org.jetbrains.exposed.sql.ComplexExpression +import org.jetbrains.exposed.sql.Expression +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. */ +class InTableOp( + val expr: Expression<*>, + /** the table to check against. */ + val table: Table, + /** Returns `true` if the check is inverted, `false` otherwise. */ + val isInTable: Boolean = true +) : Op(), ComplexExpression { + override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder { + +expr + +" " + +if (isInTable) "" else "NOT " + +"IN (" + +"TABLE " + +table.tableName + +')' + } +} diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt index 9d5c5e2241..941e34e2ca 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/DataTypeProvider.kt @@ -128,6 +128,11 @@ abstract class DataTypeProvider { open fun jsonBType(): String = throw UnsupportedByDialectException("This vendor does not support binary JSON data type", currentDialect) + /** Data type for arrays with no specified size or element type, used only as types of [QueryParameter]s for PostgreSQL and H2. + * An array with no element type cannot be used for storing arrays in a column in either PostgreSQL or H2. */ + open fun untypedAndUnsizedArrayType(): String = + throw UnsupportedByDialectException("This vendor does not support array data type", currentDialect) + // Misc. /** Returns the SQL representation of the specified expression, for it to be used as a column default value. */ diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index 6c4d85f8d5..7698ac77c6 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -19,6 +19,7 @@ internal object H2DataTypeProvider : DataTypeProvider() { override fun timestampWithTimeZoneType(): String = "TIMESTAMP(9) WITH TIME ZONE" override fun jsonBType(): String = "JSON" + override fun untypedAndUnsizedArrayType(): String = "ARRAY[]" override fun hexToDb(hexString: String): String = "X'$hexString'" } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index 63fefcb0fa..6cc822805e 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -21,6 +21,8 @@ internal object PostgreSQLDataTypeProvider : DataTypeProvider() { override fun dateTimeType(): String = "TIMESTAMP" override fun jsonBType(): String = "JSONB" + override fun untypedAndUnsizedArrayType(): String = "ARRAY" + override fun processForDefaultValue(e: Expression<*>): String = when { e is LiteralOp<*> && e.columnType is JsonColumnMarker && (currentDialect as? H2Dialect) == null -> { val cast = if (e.columnType.usesBinaryFormat) "::jsonb" else "::json" diff --git a/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/TestDB.kt b/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/TestDB.kt index 265e7059e0..099897d8a4 100644 --- a/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/TestDB.kt +++ b/exposed-tests/src/main/kotlin/org/jetbrains/exposed/sql/tests/TestDB.kt @@ -111,6 +111,7 @@ enum class TestDB( companion object { val allH2TestDB = listOf(H2, H2_MYSQL, H2_PSQL, H2_MARIADB, H2_ORACLE, H2_SQLSERVER) val mySqlRelatedDB = listOf(MYSQL, MARIADB, H2_MYSQL, H2_MARIADB) + val postgreSQLRelatedDB = listOf(POSTGRESQL, POSTGRESQLNG) fun enabledDialects(): Set { if (TEST_DIALECTS.isEmpty()) { diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/DMLTestData.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/DMLTestData.kt index e3490ee97d..9ee9a05f1b 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/DMLTestData.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/DMLTestData.kt @@ -46,6 +46,10 @@ object DMLTestsData { val product: Column = varchar("product", 30).nullable() val amount: Column = decimal("amount", 8, 2) } + + object SomeAmounts : Table() { + val amount: Column = decimal("amount", 8, 2) + } } @Suppress("LongMethod") @@ -165,6 +169,31 @@ private fun insertSale(year: Int, month: Int, product: String?, amount: String) } } +fun DatabaseTestsBase.withSomeAmounts( + statement: Transaction.(testDb: TestDB, someAmounts: DMLTestsData.SomeAmounts) -> Unit +) { + val someAmounts = DMLTestsData.SomeAmounts + + withTables(someAmounts) { + fun insertAmount(amount: BigDecimal) = + someAmounts.insert { it[someAmounts.amount] = amount } + insertAmount(BigDecimal("650.70")) + insertAmount(BigDecimal("1500.25")) + insertAmount(BigDecimal(1000)) + + statement(it, someAmounts) + } +} + +fun DatabaseTestsBase.withSalesAndSomeAmounts( + statement: Transaction.(testDb: TestDB, sales: DMLTestsData.Sales, someAmounts: DMLTestsData.SomeAmounts) -> Unit +) = + withSales { testDb, sales -> + withSomeAmounts { _, someAmounts -> + statement(testDb, sales, someAmounts) + } + } + object OrgMemberships : IntIdTable() { val orgId = reference("org", Orgs.uid) } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt index 3de4456e03..069c9c9ee0 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/dml/SelectTests.kt @@ -264,6 +264,158 @@ class SelectTests : DatabaseTestsBase() { } } + val testDBsSupportingInAnyAllFromTables = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB + + @Test + fun testInTable() { + withDb(testDBsSupportingInAnyAllFromTables) { + withSalesAndSomeAmounts { _, sales, someAmounts -> + val r = sales.select { sales.amount inTable someAmounts } + assertEquals(2, r.count()) + } + } + } + + @Test + fun testNotInTable() { + withDb(testDBsSupportingInAnyAllFromTables) { + withSalesAndSomeAmounts { _, sales, someAmounts -> + val r = sales.select { sales.amount notInTable someAmounts } + assertEquals(5, r.count()) + } + } + } + + val testDBsSupportingAnyAndAllFromSubQuries = TestDB.values().asList() - TestDB.SQLITE + val testDBsSupportingAnyAndAllFromArrays = TestDB.postgreSQLRelatedDB + TestDB.allH2TestDB + + /** Adapted from [testInSubQuery01]. */ + @Test + fun testEqAnyFromSubQuery() { + withDb(testDBsSupportingAnyAndAllFromSubQuries) { + withCitiesAndUsers { cities, _, _ -> + val r = cities.select { cities.id eq anyFrom(cities.slice(cities.id).select { cities.id eq 2 }) } + assertEquals(1L, r.count()) + } + } + } + + @Test + fun testNeqAnyFromSubQuery() { + withDb(testDBsSupportingAnyAndAllFromSubQuries) { + withCitiesAndUsers { cities, _, _ -> + val r = cities.select { cities.id neq anyFrom(cities.slice(cities.id).select { 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() + + assertEquals(2, r.size) + assertEquals("Alex", r[0][users.name]) + assertEquals("Andrey", r[1][users.name]) + } + } + } + + @Test + fun testNeqAnyFromArray() { + withDb(testDBsSupportingAnyAndAllFromArrays) { + withCitiesAndUsers { _, users, _ -> + val r = users.select { users.id neq anyFrom(arrayOf("andrey")) }.orderBy(users.name) + assertEquals(4, r.count()) + } + } + } + + @Test + fun testNeqAnyFromEmptyArray() { + withDb(testDBsSupportingAnyAndAllFromArrays) { + withCitiesAndUsers { _, users, _ -> + val r = users.select { users.id neq anyFrom(emptyArray()) }.orderBy(users.name) + assert(r.empty()) + } + } + } + + @Test + fun testGreaterEqAnyFromArray() { + 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) + .map { it[sales.product] } + assertEquals(6, r.size) + r.subList(0, 3).forEach { assertEquals("tea", it) } + r.subList(3, 6).forEach { assertEquals("coffee", it) } + } + } + } + + /** Adapted from [testInTable]. */ + @Test + fun testEqAnyFromTable() { + withDb(testDBsSupportingInAnyAllFromTables) { + withSalesAndSomeAmounts { testDb, sales, someAmounts -> + val r = sales.select { 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) } + assertEquals(5, r.count()) + } + } + } + + @Test + fun testGreaterEqAllFromSubQuery() { + withDb(testDBsSupportingAnyAndAllFromSubQuries) { + withSales { _, sales -> + val r = sales.select { sales.amount greaterEq allFrom(sales.slice(sales.amount).select { sales.product eq "tea" }) } + .orderBy(sales.amount).map { it[sales.product] } + assertEquals(4, r.size) + assertEquals("tea", r.first()) + r.drop(1).forEach { assertEquals("coffee", it) } + } + } + } + + @Test + fun testGreaterEqAllFromArray() { + withDb(testDBsSupportingAnyAndAllFromArrays) { + withSales { _, sales -> + val amounts = arrayOf(100, 1000).map { it.toBigDecimal() }.toTypedArray() + val r = sales.select { sales.amount greaterEq allFrom(amounts) }.toList() + assertEquals(3, r.size) + r.forEach { assertEquals("coffee", it[sales.product]) } + } + } + } + + @Test + fun testGreaterEqAllFromTable() { + withDb(testDBsSupportingInAnyAllFromTables) { + withSalesAndSomeAmounts { _, sales, someAmounts -> + val r = sales.select { sales.amount greaterEq allFrom(someAmounts) }.toList() + assertEquals(3, r.size) + r.forEach { assertEquals("coffee", it[sales.product]) } + } + } + } + @Test fun testSelectDistinct() { val tbl = DMLTestsData.Cities