From 8faf8c6c70b60c77aad8e4405a34fa82062107fc Mon Sep 17 00:00:00 2001 From: Oleg Babichev Date: Tue, 8 Oct 2024 13:21:31 +0200 Subject: [PATCH] Review issues: rename functions (arrayN -> array, arrayNLiteral -> arrayLiteral, arrayNParam -> arrayParam), fix breaking change in ArrayColumnType constructor, remove array2(), array3() functions, update documentation. Fix GetFunctions for multi-dimensional arrays, add tests for GetFunction and SliceFunction --- .../Writerside/topics/Breaking-Changes.md | 7 +- .../Writerside/topics/Data-Types.topic | 91 ++++++++++++------- exposed-core/api/exposed-core.api | 10 +- .../org/jetbrains/exposed/sql/ColumnType.kt | 16 +++- .../kotlin/org/jetbrains/exposed/sql/Op.kt | 16 ++-- .../exposed/sql/SQLExpressionBuilder.kt | 8 +- .../kotlin/org/jetbrains/exposed/sql/Table.kt | 52 ++--------- .../jetbrains/exposed/sql/ops/AllAnyOps.kt | 2 +- .../shared/types/MultiArrayColumnTypeTests.kt | 63 ++++++++++++- 9 files changed, 162 insertions(+), 103 deletions(-) diff --git a/documentation-website/Writerside/topics/Breaking-Changes.md b/documentation-website/Writerside/topics/Breaking-Changes.md index 5bb164689d..ee3e5cefd8 100644 --- a/documentation-website/Writerside/topics/Breaking-Changes.md +++ b/documentation-website/Writerside/topics/Breaking-Changes.md @@ -8,10 +8,9 @@ * In Oracle and H2 Oracle, the `uinteger()` column now maps to data type `NUMBER(10)` instead of `NUMBER(13)`. * In Oracle and H2 Oracle, the `integer()` column now maps to data type `NUMBER(10)` and `INTEGER` respectively, instead of `NUMBER(12)`. In Oracle and SQLite, using the integer column in a table now also creates a CHECK constraint to ensure that no out-of-range values are inserted. -* `ArrayColumnType` supports multidimensional arrays now and has one more generic parameter. - If the was used directly for one dimensional arrays with parameter `T` like `ArrayColumnType`, now it should - be defined as `ArrayColumnType>`, for example `ArrayColumnType` -> `ArrayColumnType>` - +* `ArrayColumnType` now supports multidimensional arrays and includes an additional generic parameter. + If it was previously used for one-dimensional arrays with the parameter `T` like `ArrayColumnType`, + it should now be defined as `ArrayColumnType>`. For instance, `ArrayColumnType` should now be `ArrayColumnType>`. ## 0.55.0 * The `DeleteStatement` property `table` is now deprecated in favor of `targetsSet`, which holds a `ColumnSet` that may be a `Table` or `Join`. diff --git a/documentation-website/Writerside/topics/Data-Types.topic b/documentation-website/Writerside/topics/Data-Types.topic index 039c3c2ae1..60aae6f513 100644 --- a/documentation-website/Writerside/topics/Data-Types.topic +++ b/documentation-website/Writerside/topics/Data-Types.topic @@ -290,6 +290,50 @@ it[budgets] = listOf(9999.0) } + +

PostgreSQL database supports the explicit ARRAY data type, which includes support for multi-dimensional arrays.

+

Exposed supports columns defined as multi-dimensional arrays, with the stored contents being any + out-of-the-box or custom data type. + If the contents are of a type with a supported ColumnType in the exposed-core + module, the column can be simply defined with that type:

+ + + object Teams : Table("teams") { + val nestedMemberIds = array<UUID, List<List<UUID>>>( + "nested_member_ids", dimensions = 2 + ) + val hierarchicalMemberNames = array<String, List<List<List<String>>>>( + "member_names", dimensions = 3 + ) + } + +

If more control is needed over the base content type, or if the latter is user-defined or from a non-core + module, the explicit type should be provided to the function:

+ + + object Teams : Table("teams") { + val nestedMemberIds = array<UUID, List<List<UUID>>>( + "nested_member_ids", dimensions = 2 + ) + val hierarchicalMemberNames = array<String, List<List<List<String>>>>( + "hierarchical_member_names", + VarCharColumnType(colLength = 32), + dimensions = 3 + ) + } + + +

A multi-dimensional array column accepts inserts and retrieves stored array contents as a Kotlin nested List:

+ + + Teams.insert { + it[nestedMemberIds] = List(5) { List(5) { UUID.randomUUID() } } + it[hierarchicalMemberNames] = List(3) { List(3) { List(3) { + i -> "Member ${'A' + i}" + } } } + } + +

A single element in a stored array can be accessed using the index reference get() operator:

@@ -300,6 +344,14 @@ .select(firstMember) .where { Teams.expenses[1] greater Teams.budgets[1] } + +

This also applies to multidimensional arrays:

+ + Teams + .selectAll() + .where { Teams.hierarchicalMemberNames[1][1] eq "Mr. Smith" } + + Both PostgreSQL and H2 use a one-based indexing convention, so the first element is retrieved by using index 1. @@ -310,6 +362,11 @@ Teams.select(Teams.deadlines.slice(1, 3)) + +

In the case of multidimensional arrays, the slice() calls can be nested:

+ + Teams.select(Teams.hierarchicalMemberNames.slice(1, 2).slice(3, 4)) +

Both arguments for these bounds are optional if using PostgreSQL.

An array column can also be used as an argument for the ANY and ALL SQL operators, either by providing the entire column or a new array expression via slice():

@@ -325,40 +382,6 @@
- -

PostgreSQL database supports the explicit ARRAY data type, which includes support for multi-dimensional arrays.

-

Exposed supports columns defined as multi-dimensional arrays, with the stored contents being any - out-of-the-box or custom data type. - If the contents are of a type with a supported ColumnType in the exposed-core - module, the column can be simply defined with that type:

- - - object Teams : Table("teams") { - val memberIds = array2("member_ids") - val memberNames = array3("member_names") - val budgets = array2("budgets") - } - -

If more control is needed over the base content type, or if the latter is user-defined or from a non-core - module, the explicit type should be provided to the function:

- - - object Teams : Table("teams") { - val memberIds = array2("member_ids") - val memberNames = array3("member_names", VarCharColumnType(colLength = 32)) - } - - -

A multi-dimensional array column accepts inserts and retrieves stored array contents as a Kotlin nested List:

- - - Teams.insert { - it[memberIds] = List(5) { List(5) { UUID.randomUUID() } } - it[memberNames] = List(3) { List(3) { List(3) { i -> "Member ${'A' + i}" } } } - it[budgets] = listOf(listOf(9999.0, 8888.0)) - } - -

If a database-specific data type is not immediately supported by Exposed, any existing and open column type class can be extended or diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index f48a72397a..a237eaed2e 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -203,8 +203,10 @@ public final class org/jetbrains/exposed/sql/AndOp : org/jetbrains/exposed/sql/C } public final class org/jetbrains/exposed/sql/ArrayColumnType : org/jetbrains/exposed/sql/ColumnType { - public fun (Lorg/jetbrains/exposed/sql/ColumnType;ILjava/util/List;)V - public synthetic fun (Lorg/jetbrains/exposed/sql/ColumnType;ILjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/jetbrains/exposed/sql/ColumnType;Ljava/lang/Integer;)V + public synthetic fun (Lorg/jetbrains/exposed/sql/ColumnType;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/jetbrains/exposed/sql/ColumnType;Ljava/util/List;I)V + public synthetic fun (Lorg/jetbrains/exposed/sql/ColumnType;Ljava/util/List;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDelegate ()Lorg/jetbrains/exposed/sql/ColumnType; public final fun getDelegateType ()Ljava/lang/String; public final fun getDimensions ()I @@ -2464,9 +2466,9 @@ public class org/jetbrains/exposed/sql/Table : org/jetbrains/exposed/sql/ColumnS public fun (Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun array (Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;Ljava/lang/Integer;)Lorg/jetbrains/exposed/sql/Column; + public final fun array (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;Ljava/util/List;I)Lorg/jetbrains/exposed/sql/Column; public static synthetic fun array$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;Ljava/lang/Integer;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column; - public final fun arrayN (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;ILjava/util/List;)Lorg/jetbrains/exposed/sql/Column; - public static synthetic fun arrayN$default (Lorg/jetbrains/exposed/sql/Table;Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;ILjava/util/List;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column; + public static synthetic fun array$default (Lorg/jetbrains/exposed/sql/Table;Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Lorg/jetbrains/exposed/sql/ColumnType;Ljava/util/List;IILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column; public final fun autoGenerate (Lorg/jetbrains/exposed/sql/Column;)Lorg/jetbrains/exposed/sql/Column; public final fun autoIncrement (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column; public final fun autoIncrement (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/Sequence;)Lorg/jetbrains/exposed/sql/Column; 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 a60b82cadb..1109ed272e 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 @@ -1240,13 +1240,23 @@ class CustomEnumerationColumnType>( * @property maximumCardinality The maximum cardinality (number of allowed elements) for each dimension of the array. * * **Note:** The maximum cardinality is considered for each dimension, but it is ignored by the PostgreSQL database. - * Validation is performed on the client side. */ class ArrayColumnType>( val delegate: ColumnType, - val dimensions: Int, - val maximumCardinality: List? = null + val maximumCardinality: List? = null, + val dimensions: Int = 1 ) : ColumnType() { + /** + * Constructor with maximum cardinality for a single dimension. + * + * @param delegate The base column type associated with this array column's individual elements. + * @param maximumCardinality The maximum cardinality (number of allowed elements) for the array. + */ + constructor(delegate: ColumnType, maximumCardinality: Int? = null) : this(delegate, maximumCardinality?.let { listOf(it) }) + + /** + * The SQL type definition of the delegate column type without any potential array dimensions. + */ val delegateType: String get() = delegate.sqlType().substringBefore('(') diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt index 5e45dfef39..8ef2135bdf 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Op.kt @@ -688,7 +688,7 @@ fun decimalLiteral(value: BigDecimal): LiteralOp = LiteralOp(Decimal * @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided. */ inline fun arrayLiteral(value: List, delegateType: ColumnType? = null): LiteralOp> = - arrayNLiteral(value, delegateType, dimensions = 1) + arrayLiteral(value, 1, delegateType) /** * Returns the specified [value] as an array literal, with elements parsed by the [delegateType] if provided. @@ -698,13 +698,13 @@ inline fun arrayLiteral(value: List, delegateType: ColumnTy * * **Note:** Because arrays can have varying dimensions, you must specify the type of elements * and the number of dimensions when using array literals. - * For example, use `arrayNLiteral>>(list, dimensions = 2)`. + * For example, use `arrayLiteral>>(list, dimensions = 2)`. * * @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided. */ -inline fun > arrayNLiteral(value: R, delegateType: ColumnType? = null, dimensions: Int): LiteralOp { +inline fun > arrayLiteral(value: R, dimensions: Int, delegateType: ColumnType? = null): LiteralOp { @OptIn(InternalApi::class) - return LiteralOp(ArrayColumnType(delegateType ?: resolveColumnType(T::class), dimensions), value) + return LiteralOp(ArrayColumnType(delegateType ?: resolveColumnType(T::class), dimensions = dimensions), value) } // Query Parameters @@ -792,7 +792,7 @@ fun blobParam(value: ExposedBlob, useObjectIdentifier: Boolean = false): Express * @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided. */ inline fun arrayParam(value: List, delegateType: ColumnType? = null): Expression> = - arrayNParam(value, delegateType, dimensions = 1) + arrayParam(value, 1, delegateType) /** * Returns the specified [value] as an array query parameter, with elements parsed by the [delegateType] if provided. @@ -802,13 +802,13 @@ inline fun arrayParam(value: List, delegateType: ColumnType * * **Note:** Because arrays can have varying dimensions, you must specify the type of elements * and the number of dimensions when using array literals. - * For example, use `arrayNParam>>(list, dimensions = 2)`. + * For example, use `arrayParam>>(list, dimensions = 2)`. * * @throws IllegalStateException If no column type mapping is found and a [delegateType] is not provided. */ -inline fun > arrayNParam(value: R, delegateType: ColumnType? = null, dimensions: Int): Expression { +inline fun > arrayParam(value: R, dimensions: Int, delegateType: ColumnType? = null): Expression { @OptIn(InternalApi::class) - return QueryParameter(value, ArrayColumnType(delegateType ?: resolveColumnType(T::class), dimensions)) + return QueryParameter(value, ArrayColumnType(delegateType ?: resolveColumnType(T::class), dimensions = dimensions)) } // Misc. 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 1098ab5771..7b7d10109c 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 @@ -198,8 +198,12 @@ fun ?> allFrom(expression: Expression): Op = AllAnyFromExpr * * @sample org.jetbrains.exposed.sql.tests.shared.types.ArrayColumnTypeTests.testSelectUsingArrayGet */ -infix operator fun ?> ExpressionWithColumnType.get(index: Int): ArrayGet = - ArrayGet(this, index, (this.columnType as ArrayColumnType>).delegate) +infix operator fun ?> ExpressionWithColumnType.get(index: Int): ArrayGet { + return when (this) { + is ArrayGet<*, *> -> ArrayGet(this as Expression, index, this.columnType as IColumnType) as ArrayGet + else -> ArrayGet(this, index, (this.columnType as ArrayColumnType>).delegate) + } +} /** * Returns a subarray of elements stored from between [lower] and [upper] bounds (inclusive), diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt index 6df9446720..f7db121e86 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt @@ -922,7 +922,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { * when using the PostgreSQL dialect is allowed, but this value will be ignored by the database. */ fun array(name: String, columnType: ColumnType, maximumCardinality: Int? = null): Column> = - arrayN>(name, columnType, dimensions = 1, maximumCardinality = maximumCardinality?.let { listOf(it) }) + array>(name, columnType, dimensions = 1, maximumCardinality = maximumCardinality?.let { listOf(it) }) /** * Creates an array column, with the specified [name], for storing elements of a `List`. @@ -940,41 +940,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { * @throws IllegalStateException If no column type mapping is found. */ inline fun array(name: String, maximumCardinality: Int? = null): Column> = - arrayN>(name, dimensions = 1, maximumCardinality?.let { listOf(it) }) - - /** - * Creates a 3-dimensional array column, with the specified [name], for storing elements of a nested `List`. - * - * **Note:** This column type is only supported by PostgreSQL dialect. - * - * @param name Name of the column. - * @param maximumCardinality The maximum cardinality (number of allowed elements) for each dimension in the array. - * - * **Note:** Providing an array size limit when using the PostgreSQL dialect is allowed, but this value will be ignored by the database. - * The whole validation is performed on the client side. - * - * @return A column instance that represents a 3-dimensional list of elements of type [T]. - * @throws IllegalStateException If no column type mapping is found. - */ - inline fun Table.array3(name: String, maximumCardinality: List? = null): Column>>> = - arrayN>>>(name, dimensions = 3, maximumCardinality) - - /** - * Creates a 2-dimensional array column, with the specified [name], for storing elements of a nested `List`. - * - * **Note:** This column type is only supported by PostgreSQL dialect. - * - * @param name Name of the column. - * @param maximumCardinality The maximum cardinality (number of allowed elements) for each dimension in the array. - * - * **Note:** Providing an array size limit when using the PostgreSQL dialect is allowed, but this value will be ignored by the database. - * The whole validation is performed on the client side. - * - * @return A column instance that represents a 2-dimensional list of elements of type [T]. - * @throws IllegalStateException If no column type mapping is found. - */ - inline fun Table.array2(name: String, maximumCardinality: List? = null): Column>> = - arrayN>>(name, dimensions = 2, maximumCardinality) + array>(name, maximumCardinality?.let { listOf(it) }, dimensions = 1) /** * Creates a multi-dimensional array column, with the specified [name], for storing elements of a nested `List`. @@ -983,19 +949,18 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { * **Note:** This column type is only supported by PostgreSQL dialect. * * @param name Name of the column. - * @param dimensions The number of dimensions of the array. * @param maximumCardinality The maximum cardinality (number of allowed elements) for each dimension in the array. + * @param dimensions The number of dimensions of the array. * * **Note:** Providing an array size limit when using the PostgreSQL dialect is allowed, but this value will be ignored by the database. - * The whole validation is performed on the client side. * * @return A column instance that represents a multi-dimensional list of elements of type [T]. * @throws IllegalArgumentException If [dimensions] is less than or equal to 1. * @throws IllegalStateException If no column type mapping is found. */ - inline fun > Table.arrayN(name: String, dimensions: Int, maximumCardinality: List? = null): Column { + inline fun > Table.array(name: String, maximumCardinality: List? = null, dimensions: Int): Column { @OptIn(InternalApi::class) - return arrayN(name, resolveColumnType(T::class), dimensions, maximumCardinality) + return array(name, resolveColumnType(T::class), maximumCardinality, dimensions) } /** @@ -1005,18 +970,17 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { * **Note:** This column type is only supported by PostgreSQL dialect. * * @param name Name of the column. - * @param dimensions The number of dimensions of the array. * @param maximumCardinality The maximum cardinality (number of allowed elements) for each dimension in the array. + * @param dimensions The number of dimensions of the array. * * **Note:** Providing an array size limit when using the PostgreSQL dialect is allowed, but this value will be ignored by the database. - * The whole validation is performed on the client side. * * @return A column instance that represents a multi-dimensional list of elements of type [E]. * @throws IllegalArgumentException If [dimensions] is less than or equal to 1. * @throws IllegalStateException If no column type mapping is found. */ - fun > Table.arrayN(name: String, columnType: ColumnType, dimensions: Int, maximumCardinality: List? = null): Column = - registerColumn(name, ArrayColumnType(columnType, dimensions, maximumCardinality)) + fun > Table.array(name: String, columnType: ColumnType, maximumCardinality: List? = null, dimensions: Int): Column = + registerColumn(name, ArrayColumnType(columnType, maximumCardinality, dimensions)) // Auto-generated values 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 index 79ce3a7af7..2d44ab8133 100644 --- 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 @@ -50,7 +50,7 @@ class AllAnyFromArrayOp( private val delegateType: ColumnType ) : AllAnyFromBaseOp>(isAny, array) { override fun QueryBuilder.registerSubSearchArgument(subSearch: List) { - registerArgument(ArrayColumnType>(delegateType, dimensions = 1), subSearch) + registerArgument(ArrayColumnType>(delegateType), subSearch) } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/MultiArrayColumnTypeTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/MultiArrayColumnTypeTests.kt index 746edca3e7..ff0d61cec6 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/MultiArrayColumnTypeTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/types/MultiArrayColumnTypeTests.kt @@ -12,6 +12,12 @@ import org.jetbrains.exposed.sql.tests.shared.assertEquals import org.junit.Test import kotlin.test.assertNull +private inline fun Table.array3(name: String, maximumCardinality: List? = null): Column>>> = + array>>>(name, maximumCardinality, dimensions = 3) + +private inline fun Table.array2(name: String, maximumCardinality: List? = null): Column>> = + array>>(name, maximumCardinality, dimensions = 2) + class MultiArrayColumnTypeTests : DatabaseTestsBase() { private val multiArrayTypeUnsupportedDb = TestDB.ALL - TestDB.ALL_POSTGRES.toSet() @@ -57,7 +63,7 @@ class MultiArrayColumnTypeTests : DatabaseTestsBase() { @Test fun test5xMultiArray() { val tester = object : IntIdTable("test_table") { - val multiArray = arrayN>>>>>("multi_array", 5) + val multiArray = array>>>>>("multi_array", dimensions = 5) } withTables(excludeSettings = multiArrayTypeUnsupportedDb, tester) { @@ -140,7 +146,7 @@ class MultiArrayColumnTypeTests : DatabaseTestsBase() { val list = listOf(listOf(1, 2), listOf(3, 4)) tester.insert { - it[multiArray] = arrayNLiteral>>(list, dimensions = 2) + it[multiArray] = arrayLiteral>>(list, dimensions = 2) } val value = tester.selectAll().first()[tester.multiArray] @@ -158,7 +164,7 @@ class MultiArrayColumnTypeTests : DatabaseTestsBase() { val list = listOf(listOf(1, 2), listOf(3, 4)) tester.insert { - it[multiArray] = arrayNParam>>(list, dimensions = 2) + it[multiArray] = arrayParam>>(list, dimensions = 2) } val value = tester.selectAll().first()[tester.multiArray] @@ -228,6 +234,57 @@ class MultiArrayColumnTypeTests : DatabaseTestsBase() { } } + @Test + fun testMultiArrayGetFunction() { + val tester = object : IntIdTable("test_table") { + val multiArray = array2("multi_array") + } + + withTables(excludeSettings = multiArrayTypeUnsupportedDb, tester) { + tester.batchInsert( + listOf( + listOf(listOf(1, 1), listOf(1, 4)), + listOf(listOf(1, 1), listOf(2, 4)), + listOf(listOf(1, 1), listOf(1, 6)), + ) + ) { + this[tester.multiArray] = it + } + + val values = tester.selectAll().where { tester.multiArray[2][2] eq 4 }.map { it[tester.multiArray] } + assertEquals(2, values.size) + assertEqualLists( + listOf( + listOf(listOf(1, 1), listOf(1, 4)), + listOf(listOf(1, 1), listOf(2, 4)), + ), + values + ) + + assertEquals(0, tester.selectAll().where { tester.multiArray[2][2] greater 10 }.map { it[tester.multiArray] }.size) + } + } + + @Test + fun testMultiArraySliceFunction() { + val tester = object : IntIdTable("test_table") { + val multiArray = array2("multi_array") + } + + withTables(excludeSettings = multiArrayTypeUnsupportedDb, tester) { + tester.insert { + it[multiArray] = listOf( + listOf(1, 2, 3, 4), listOf(5, 6, 7, 8), listOf(9, 10, 11, 12), listOf(13, 14, 15, 16) + ) + } + + val alias = tester.multiArray.slice(1, 2).slice(2, 3) + + val query = tester.select(alias).first() + assertEqualLists(listOf(2, 3, 6, 7), query[alias].flatten()) + } + } + object MultiArrayTable : IntIdTable() { val multiArray = array2("multi_array") }