Skip to content

Commit c7afcbb

Browse files
committed
Merge branch 'master' into mean
# Conflicts: # core/api/core.api
2 parents ca73fbc + 140432f commit c7afcbb

File tree

50 files changed

+510
-159
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+510
-159
lines changed

CONTRIBUTING.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ so do familiarize yourself with the following guidelines.
9090
* Note, any version above 21 should work in theory, but JDK 21 is the only version we test with,
9191
so it is the recommended version.
9292

93-
* JDK == 11 referred to by the `JDK_11_0` environment variable or `gradle.properties`/`local.properties`.
94-
* This is used for testing our compiler plugins.
95-
9693
* We recommend using [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) as the IDE. This
9794
has the best support for Kotlin, compiler plugins, Gradle, and [Kotlin Notebook](https://kotlinlang.org/docs/kotlin-notebook-overview.html) of course.
9895

build.gradle.kts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,46 @@ tasks.named<DependencyUpdatesTask>("dependencyUpdates").configure {
136136
kotlin {
137137
jvmToolchain(21)
138138
compilerOptions {
139-
jvmTarget = JvmTarget.JVM_11
139+
jvmTarget = JvmTarget.JVM_1_8
140140
}
141141
}
142142

143+
// DataFrame targets Java 8 for maximum compatibility.
144+
// This is, however, not always possible thanks to external dependencies.
145+
// In those cases, we default to Java 11.
146+
val modulesUsingJava11 = with(projects) {
147+
setOf(
148+
dataframeJupyter,
149+
dataframeGeo,
150+
examples.ideaExamples.titanic,
151+
)
152+
}.map { it.path }
153+
143154
allprojects {
144-
tasks.withType<KotlinCompile> {
145-
compilerOptions {
146-
jvmTarget = JvmTarget.JVM_11
147-
freeCompilerArgs.add("-Xjdk-release=11")
155+
if (path in modulesUsingJava11) {
156+
tasks.withType<KotlinCompile> {
157+
compilerOptions {
158+
jvmTarget = JvmTarget.JVM_11
159+
freeCompilerArgs.add("-Xjdk-release=11")
160+
}
161+
}
162+
tasks.withType<JavaCompile> {
163+
sourceCompatibility = JavaVersion.VERSION_11.toString()
164+
targetCompatibility = JavaVersion.VERSION_11.toString()
165+
options.release.set(11)
166+
}
167+
} else {
168+
tasks.withType<KotlinCompile> {
169+
compilerOptions {
170+
jvmTarget = JvmTarget.JVM_1_8
171+
freeCompilerArgs.add("-Xjdk-release=8")
172+
}
173+
}
174+
tasks.withType<JavaCompile> {
175+
sourceCompatibility = JavaVersion.VERSION_1_8.toString()
176+
targetCompatibility = JavaVersion.VERSION_1_8.toString()
177+
options.release.set(8)
148178
}
149-
}
150-
151-
tasks.withType<JavaCompile> {
152-
sourceCompatibility = JavaVersion.VERSION_11.toString()
153-
targetCompatibility = JavaVersion.VERSION_11.toString()
154-
options.release.set(11)
155179
}
156180

157181
// Attempts to configure ktlint for each sub-project that uses the plugin

core/api/core.api

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,13 +1777,12 @@ public final class org/jetbrains/kotlinx/dataframe/api/DataColumnArithmeticsKt {
17771777
}
17781778

17791779
public final class org/jetbrains/kotlinx/dataframe/api/DataColumnTypeKt {
1780+
public static final fun isBigNumber (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17801781
public static final fun isColumnGroup (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17811782
public static final fun isComparable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17821783
public static final fun isFrameColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17831784
public static final fun isList (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17841785
public static final fun isNumber (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
1785-
public static final fun isPrimitive (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
1786-
public static final fun isPrimitiveNumber (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17871786
public static final fun isSubtypeOf (Lorg/jetbrains/kotlinx/dataframe/DataColumn;Lkotlin/reflect/KType;)Z
17881787
public static final fun isValueColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17891788
public static final fun valuesAreComparable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
@@ -2843,6 +2842,8 @@ public final class org/jetbrains/kotlinx/dataframe/api/MeanKt {
28432842
public static synthetic fun meanFor$default (Lorg/jetbrains/kotlinx/dataframe/api/PivotGroupBy;[Ljava/lang/String;ZZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
28442843
public static synthetic fun meanFor$default (Lorg/jetbrains/kotlinx/dataframe/api/PivotGroupBy;[Lkotlin/reflect/KProperty;ZZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
28452844
public static synthetic fun meanFor$default (Lorg/jetbrains/kotlinx/dataframe/api/PivotGroupBy;[Lorg/jetbrains/kotlinx/dataframe/columns/ColumnReference;ZZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
2845+
public static final fun meanOrNull (Lorg/jetbrains/kotlinx/dataframe/DataColumn;Z)Ljava/lang/Double;
2846+
public static synthetic fun meanOrNull$default (Lorg/jetbrains/kotlinx/dataframe/DataColumn;ZILjava/lang/Object;)Ljava/lang/Double;
28462847
public static final fun rowMean (Lorg/jetbrains/kotlinx/dataframe/DataRow;Z)D
28472848
public static synthetic fun rowMean$default (Lorg/jetbrains/kotlinx/dataframe/DataRow;ZILjava/lang/Object;)D
28482849
}
@@ -3966,7 +3967,6 @@ public final class org/jetbrains/kotlinx/dataframe/api/TypeConversionsKt {
39663967
public static final fun asComparable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Lorg/jetbrains/kotlinx/dataframe/DataColumn;
39673968
public static final fun asComparable (Lorg/jetbrains/kotlinx/dataframe/columns/ColumnSet;)Lorg/jetbrains/kotlinx/dataframe/columns/ColumnSet;
39683969
public static final fun asComparable (Lorg/jetbrains/kotlinx/dataframe/columns/SingleColumn;)Lorg/jetbrains/kotlinx/dataframe/columns/SingleColumn;
3969-
public static final fun asComparableNullable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Lorg/jetbrains/kotlinx/dataframe/DataColumn;
39703970
public static final fun asDataColumn (Lorg/jetbrains/kotlinx/dataframe/columns/ColumnGroup;)Lorg/jetbrains/kotlinx/dataframe/DataColumn;
39713971
public static final fun asDataColumn (Lorg/jetbrains/kotlinx/dataframe/columns/FrameColumn;)Lorg/jetbrains/kotlinx/dataframe/DataColumn;
39723972
public static final fun asDataFrame (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
@@ -5103,10 +5103,6 @@ public final class org/jetbrains/kotlinx/dataframe/impl/ExceptionUtilsKt {
51035103
public static final fun suggestIfNull (Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;
51045104
}
51055105

5106-
public final class org/jetbrains/kotlinx/dataframe/impl/NumberTypeUtilsKt {
5107-
public static final fun getPrimitiveNumberTypes ()Ljava/util/Set;
5108-
}
5109-
51105106
public final class org/jetbrains/kotlinx/dataframe/impl/TypeUtilsKt {
51115107
public static final fun getValuesType (Ljava/util/List;Lkotlin/reflect/KType;Lorg/jetbrains/kotlinx/dataframe/api/Infer;)Lkotlin/reflect/KType;
51125108
public static final synthetic fun guessValueType (Lkotlin/sequences/Sequence;Lkotlin/reflect/KType;Z)Lkotlin/reflect/KType;
@@ -5214,10 +5210,6 @@ public final class org/jetbrains/kotlinx/dataframe/impl/aggregation/modes/OfRowE
52145210
public static final fun aggregateOfDelegated (Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator;Lorg/jetbrains/kotlinx/dataframe/api/Grouped;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
52155211
}
52165212

5217-
public final class org/jetbrains/kotlinx/dataframe/impl/aggregation/modes/RowKt {
5218-
public static final fun aggregateOfRow (Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator;Lorg/jetbrains/kotlinx/dataframe/DataRow;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
5219-
}
5220-
52215213
public final class org/jetbrains/kotlinx/dataframe/impl/aggregation/modes/WithinAllColumnsKt {
52225214
public static final fun aggregateAll (Lorg/jetbrains/kotlinx/dataframe/impl/aggregation/aggregators/Aggregator;Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
52235215
}
@@ -5261,6 +5253,7 @@ public final class org/jetbrains/kotlinx/dataframe/impl/api/SchemaKt {
52615253
public final class org/jetbrains/kotlinx/dataframe/impl/api/ToDataFrameKt {
52625254
public static final fun convertToDataFrame (Ljava/lang/Iterable;Lkotlin/reflect/KClass;Ljava/util/List;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;I)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
52635255
public static final fun createDataFrameImpl (Ljava/lang/Iterable;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
5256+
public static final fun getCanBeUnfolded (Lkotlin/reflect/KClass;)Z
52645257
public static final fun getHasProperties (Lkotlin/reflect/KClass;)Z
52655258
public static final fun isValueType (Lkotlin/reflect/KClass;)Z
52665259
}

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ import org.jetbrains.kotlinx.dataframe.util.IS_COMPARABLE_REPLACE
1515
import org.jetbrains.kotlinx.dataframe.util.IS_INTER_COMPARABLE_IMPORT
1616
import kotlin.contracts.ExperimentalContracts
1717
import kotlin.contracts.contract
18-
import kotlin.reflect.KClass
1918
import kotlin.reflect.KType
2019
import kotlin.reflect.KTypeProjection
2120
import kotlin.reflect.KVariance
2221
import kotlin.reflect.full.createType
23-
import kotlin.reflect.full.isSubclassOf
2422
import kotlin.reflect.full.isSubtypeOf
2523
import kotlin.reflect.full.withNullability
2624
import kotlin.reflect.typeOf
@@ -91,13 +89,3 @@ public fun AnyCol.valuesAreComparable(): Boolean =
9189
nullable = hasNulls(),
9290
),
9391
)
94-
95-
@PublishedApi
96-
internal fun AnyCol.isPrimitive(): Boolean = typeClass.isPrimitive()
97-
98-
internal fun KClass<*>.isPrimitive(): Boolean =
99-
isSubclassOf(Number::class) ||
100-
this == String::class ||
101-
this == Char::class ||
102-
this == Array::class ||
103-
isSubclassOf(Collection::class)

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public fun <T> InsertClause<T>.under(column: String): DataFrame<T> = under(pathO
7979

8080
// region after
8181

82+
@Refine
83+
@Interpretable("InsertAfter0")
8284
public fun <T> InsertClause<T>.after(column: ColumnSelector<T, *>): DataFrame<T> = after(df.getColumnPath(column))
8385

8486
public fun <T> InsertClause<T>.after(column: String): DataFrame<T> = df.add(this.column).move(this.column).after(column)

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/toDataFrame.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import org.jetbrains.kotlinx.dataframe.annotations.Interpretable
1111
import org.jetbrains.kotlinx.dataframe.annotations.Refine
1212
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
1313
import org.jetbrains.kotlinx.dataframe.impl.ColumnNameGenerator
14+
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
1415
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
15-
import org.jetbrains.kotlinx.dataframe.impl.api.hasProperties
16-
import org.jetbrains.kotlinx.dataframe.impl.api.isValueType
1716
import org.jetbrains.kotlinx.dataframe.impl.asList
1817
import org.jetbrains.kotlinx.dataframe.impl.columnName
1918
import org.jetbrains.kotlinx.dataframe.impl.columns.createColumnGuessingType
@@ -30,7 +29,7 @@ public inline fun <reified T> Iterable<T>.toDataFrame(): DataFrame<T> =
3029
toDataFrame {
3130
// check if type is value: primitives, primitive arrays, datetime types etc.,
3231
// or has no properties
33-
if (T::class.isValueType || !T::class.hasProperties) {
32+
if (!T::class.canBeUnfolded) {
3433
// create a single `value` column
3534
ValueProperty<T>::value from { it }
3635
} else {

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/unfold.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
88
import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload
99
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
1010
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
11+
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
1112
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
1213
import org.jetbrains.kotlinx.dataframe.typeClass
1314
import kotlin.reflect.KProperty
@@ -17,7 +18,7 @@ public inline fun <reified T> DataColumn<T>.unfold(): AnyCol =
1718
ColumnKind.Group, ColumnKind.Frame -> this
1819

1920
else -> when {
20-
isPrimitive() -> this
21+
!typeClass.canBeUnfolded -> this
2122

2223
else -> values()
2324
.createDataFrameImpl(typeClass) { (this as CreateDataFrameDsl<T>).properties() }

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/move.kt

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import org.jetbrains.kotlinx.dataframe.api.getColumn
1111
import org.jetbrains.kotlinx.dataframe.api.getColumnGroup
1212
import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath
1313
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
14+
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
1415
import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
1516
import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy
1617
import org.jetbrains.kotlinx.dataframe.impl.DataFrameReceiver
18+
import org.jetbrains.kotlinx.dataframe.impl.asList
1719
import org.jetbrains.kotlinx.dataframe.impl.columns.toColumnWithPath
1820
import org.jetbrains.kotlinx.dataframe.impl.columns.tree.ColumnPosition
1921
import org.jetbrains.kotlinx.dataframe.impl.columns.tree.getOrPut
@@ -23,20 +25,58 @@ internal fun <T, C> MoveClause<T, C>.afterOrBefore(column: ColumnSelector<T, *>,
2325
val removeResult = df.removeImpl(columns = columns)
2426

2527
val targetPath = df.getColumnWithPath(column).path
28+
val sourcePaths = removeResult.removedColumns.map { it.toColumnWithPath<C>().path }
29+
30+
// Check if any source path is a prefix of the target path
31+
sourcePaths.forEach { sourcePath ->
32+
val sourceSegments = sourcePath.toList()
33+
val targetSegments = targetPath.toList()
34+
35+
if (sourceSegments.size <= targetSegments.size &&
36+
sourceSegments.indices.all { targetSegments[it] == sourceSegments[it] }
37+
) {
38+
throw IllegalArgumentException(
39+
"Cannot move column '${sourcePath.joinToString()}' after its own child column '${targetPath.joinToString()}'",
40+
)
41+
}
42+
}
43+
2644
val removeRoot = removeResult.removedColumns.first().getRoot()
2745

2846
val refNode = removeRoot.getOrPut(targetPath) {
29-
val parentPath = it.dropLast(1)
30-
val parent = if (parentPath.isEmpty()) df else df.getColumnGroup(parentPath)
31-
val index = parent.getColumnIndex(it.last())
32-
val col = df.getColumn(index)
47+
val path = it.asList()
48+
49+
// Get parent of a target path
50+
val effectivePath = path.dropLast(1)
51+
52+
// Get column name (last segment)
53+
val columnName = path.last()
54+
55+
// Get the parent
56+
val parent = if (effectivePath.isEmpty()) {
57+
df
58+
} else {
59+
df.getColumnGroup(ColumnPath(effectivePath))
60+
}
61+
62+
// Get the column index and the column itself
63+
val index = parent.getColumnIndex(columnName)
64+
val col = parent.getColumn(index)
65+
3366
ColumnPosition(index, false, col)
3467
}
3568

3669
val parentPath = targetPath.dropLast(1)
3770
val toInsert = removeResult.removedColumns.map {
38-
val path = parentPath + it.name
39-
ColumnToInsert(path, it.toColumnWithPath<C>().data, refNode)
71+
val sourceCol = it.toColumnWithPath<C>()
72+
val sourcePath = sourceCol.path
73+
val path = if (sourcePath.size > 1) {
74+
// If source is nested, preserve its structure under the target parent
75+
parentPath + sourcePath.last()
76+
} else {
77+
parentPath + sourceCol.name()
78+
}
79+
ColumnToInsert(path, sourceCol.data, refNode)
4080
}
4181
return removeResult.df.insertImpl(toInsert)
4282
}

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/toDataFrame.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,24 @@ private val valueTypes = setOf(
5656
kotlinx.datetime.TimeZone::class,
5757
kotlinx.datetime.DateTimePeriod::class,
5858
kotlinx.datetime.DateTimeUnit::class,
59+
Map::class,
60+
MutableMap::class,
5961
)
6062

63+
/**
64+
* Determines whether a class can be unfolded into its properties.
65+
*
66+
* A class is considered **unfoldable** if it has at least one public property
67+
* or getter-like function. This excludes:
68+
* - **Value types** such as primitives, enums, and date-time types.
69+
* - **Classes without properties**, including empty or marker classes.
70+
*
71+
* @return `true` if the class has unfoldable properties, `false` otherwise.
72+
*/
73+
@PublishedApi
74+
internal val KClass<*>.canBeUnfolded: Boolean
75+
get() = (!this.isValueType) && this.hasProperties
76+
6177
/**
6278
* Checks if `KClass` is a value type (number, datetime, string, etc.)
6379
* Should be aligned with `ConeKotlinType.isValueType()` in

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.jetbrains.kotlinx.dataframe.api
22

33
import io.kotest.assertions.throwables.shouldNotThrowAny
4+
import io.kotest.assertions.throwables.shouldThrow
45
import io.kotest.matchers.shouldBe
56
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
67
import org.junit.Test
@@ -91,4 +92,64 @@ class MoveTests {
9192
df.move("1").after("2") shouldBe dataFrameOf("2", "1")(2, 1)
9293
}
9394
}
95+
96+
@Test
97+
fun `move after in nested structure`() {
98+
val df = grouped.move { "a"["b"] }
99+
.after { "a"["c"]["d"] }
100+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
101+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
102+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("d", "b")
103+
}
104+
105+
@Test
106+
fun `move after multiple columns`() {
107+
val df = grouped.move { "a"["b"] and "b"["c"] }
108+
.after { "a"["c"]["d"] }
109+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
110+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
111+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("d", "b", "c")
112+
df["b"].asColumnGroup().columnNames() shouldBe listOf("d")
113+
}
114+
115+
@Test
116+
fun `move after with column selector`() {
117+
val df = grouped.move { colsAtAnyDepth { it.name == "r" || it.name == "w" } }
118+
.after { "a"["c"]["d"] }
119+
df.columnNames() shouldBe listOf("q", "a", "b", "e")
120+
df["a"]["c"].asColumnGroup().columnNames() shouldBe listOf("d", "w", "r")
121+
}
122+
123+
@Test
124+
fun `move after between groups`() {
125+
val df = grouped.move { "a"["b"] }.after { "b"["c"] }
126+
df.columnNames() shouldBe listOf("q", "a", "b", "w", "e", "r")
127+
df["a"].asColumnGroup().columnNames() shouldBe listOf("c")
128+
df["b"].asColumnGroup().columnNames() shouldBe listOf("c", "b", "d")
129+
}
130+
131+
@Test
132+
fun `should throw when moving parent after child`() {
133+
// Simple case: direct parent-child relationship
134+
shouldThrow<IllegalArgumentException> {
135+
grouped.move("a").after { "a"["b"] }
136+
}.message shouldBe "Cannot move column 'a' after its own child column 'a/b'"
137+
138+
// Nested case: deeper parent-child relationship
139+
shouldThrow<IllegalArgumentException> {
140+
grouped.move("a").after { "a"["c"]["d"] }
141+
}.message shouldBe "Cannot move column 'a' after its own child column 'a/c/d'"
142+
143+
// Group case: moving group after its nested column
144+
shouldThrow<IllegalArgumentException> {
145+
grouped.move { "a"["c"] }.after { "a"["c"]["d"] }
146+
}.message shouldBe "Cannot move column 'a/c' after its own child column 'a/c/d'"
147+
}
148+
149+
@Test
150+
fun `should throw when moving column after itself`() {
151+
shouldThrow<IllegalArgumentException> {
152+
grouped.move { "a"["b"] }.after { "a"["b"] }
153+
}.message shouldBe "Cannot move column 'a/b' after its own child column 'a/b'"
154+
}
94155
}

0 commit comments

Comments
 (0)