Skip to content

Commit 3f33063

Browse files
Automated commit of generated code
1 parent c02fe6b commit 3f33063

3 files changed

Lines changed: 121 additions & 3 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.jetbrains.kotlinx.dataframe.api
2+
3+
import org.jetbrains.kotlinx.dataframe.ColumnSelector
4+
import org.jetbrains.kotlinx.dataframe.DataFrame
5+
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
6+
import org.jetbrains.kotlinx.dataframe.annotations.Interpretable
7+
import org.jetbrains.kotlinx.dataframe.annotations.Refine
8+
import org.jetbrains.kotlinx.dataframe.impl.api.requireImpl
9+
import kotlin.reflect.typeOf
10+
11+
/**
12+
* Resolves [column] in this [DataFrame] and checks that its runtime type is a subtype of [C].
13+
* Throws if the column can't be resolved or if its type doesn't match.
14+
*
15+
* From the compiler plugin perspective, a new column will appear in the compile-time schema as a result of this operation.
16+
*
17+
* The aim here is to help incrementally migrate workflows to extension properties API.
18+
*
19+
* We recommend considering declaring a [DataSchema] and use [cast] or [convertTo] if you end up with more than a few `requireColumn` calls.
20+
*
21+
* Example:
22+
*
23+
* ```kotlin
24+
* val repos = DataFrame
25+
* .readCsv("https://raw.githubusercontent.com/Kotlin/dataframe/master/data/jetbrains_repositories.csv")
26+
*
27+
* repos
28+
* .filter { "stargazers_count"<Int>() > 100 }
29+
* .sortByDesc("stargazers_count")
30+
* .select("full_name", "stargazers_count")
31+
* ```
32+
*
33+
* Notice how `stargazers_count` String is repeated three times. We can refactor this code using `requireColumn`:
34+
*
35+
* ```
36+
* val repos = DataFrame
37+
* .readCsv("https://raw.githubusercontent.com/Kotlin/dataframe/master/data/jetbrains_repositories.csv")
38+
* .requireColumn { "stargazers_count"<Int>() }
39+
*
40+
* repos
41+
* .filter { stargazers_count > 100 }
42+
* .sortByDesc { stargazers_count }
43+
* .select { "full_name" and stargazers_count }
44+
* ```
45+
*
46+
* This way code becomes a bit more robust. For example, usages of a renamed column will become compile time errors that are easy to spot and update:
47+
* ```kotlin
48+
* val repos = DataFrame
49+
* .readCsv("https://raw.githubusercontent.com/Kotlin/dataframe/master/data/jetbrains_repositories.csv")
50+
* .requireColumn { "stargazers_count"<Int>() }
51+
* .rename { stargazers_count }.into("stars")
52+
*
53+
* repos
54+
* .filter { stars > 100 }
55+
* .sortByDesc { stars }
56+
* .select { "full_name" and stars }
57+
* ```
58+
*
59+
*/
60+
@Refine
61+
@Interpretable("Require0")
62+
public inline fun <T, reified C> DataFrame<T>.requireColumn(noinline column: ColumnSelector<T, C>): DataFrame<T> =
63+
requireImpl(column, typeOf<C>())

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

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.dataframe.api.asDataColumn
88
import org.jetbrains.kotlinx.dataframe.api.cast
99
import org.jetbrains.kotlinx.dataframe.api.isColumnGroup
1010
import org.jetbrains.kotlinx.dataframe.api.pathOf
11+
import org.jetbrains.kotlinx.dataframe.api.toPath
1112
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
1213
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
1314
import org.jetbrains.kotlinx.dataframe.columns.ColumnReference
@@ -21,6 +22,7 @@ import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
2122
import org.jetbrains.kotlinx.dataframe.impl.columns.missing.MissingColumnGroup
2223
import org.jetbrains.kotlinx.dataframe.impl.columns.missing.MissingDataColumn
2324
import org.jetbrains.kotlinx.dataframe.nrow
25+
import kotlin.collections.map
2426

2527
private fun <T> DataFrame<T>.unbox(): DataFrame<T> =
2628
when (this) {
@@ -47,9 +49,7 @@ internal open class DataFrameReceiver<T>(
4749
host = this@DataFrameReceiver,
4850
).asDataColumn().cast()
4951

50-
UnresolvedColumnsPolicy.Fail -> error(
51-
"Column '${path.joinToString()}' not found among ${df.columnNames()}.",
52-
)
52+
UnresolvedColumnsPolicy.Fail -> error(formatMissingColumnMessage(path))
5353
}
5454

5555
is MissingDataColumn -> this
@@ -59,6 +59,43 @@ internal open class DataFrameReceiver<T>(
5959
else -> this
6060
}
6161

62+
// Context:
63+
// it's strange that we have to reverse-search why the column is missing
64+
// would be nice to "fail fast" exactly where resolve failed, knowing the current path and parent.
65+
// but it's very unclear what to do with resolveSingle.
66+
// at first glance: a lot of changes.
67+
@Suppress("FoldInitializerAndIfToElvis")
68+
private fun formatMissingColumnMessage(path: ColumnPath): String {
69+
val fullPath = path.joinToString()
70+
71+
for (depth in path.indices) {
72+
val currentPath = path.slice(0..depth).toPath()
73+
val currentPathString = currentPath.joinToString()
74+
val column = df.getColumnOrNull(currentPath)
75+
if (column == null) {
76+
return if (depth == 0) {
77+
"Column '$currentPathString' not found among ${df.columnNames()}."
78+
} else {
79+
val parentPath = currentPath.dropLast()
80+
val parentPathString = parentPath.joinToString()
81+
val parentColumn = df.getColumnOrNull(parentPath)
82+
if (parentColumn != null && parentColumn.isColumnGroup()) {
83+
"Column '$currentPathString' not found among columns of '$parentPathString': ${parentColumn.columnNames()}."
84+
} else {
85+
"Column '$currentPathString' not found among ${df.columnNames()}."
86+
}
87+
}
88+
}
89+
90+
if (depth != path.lastIndex) {
91+
if (!column.isColumnGroup()) {
92+
return "Column '$fullPath' cannot be resolved: '$currentPathString' is not a column group."
93+
}
94+
}
95+
}
96+
return "Column '$fullPath' not found among ${df.columnNames()}."
97+
}
98+
6299
override fun getColumnOrNull(name: String) = df.getColumnOrNull(name).check(pathOf(name))
63100

64101
override fun getColumnOrNull(index: Int) = df.getColumnOrNull(index).check(pathOf(""))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.jetbrains.kotlinx.dataframe.impl.api
2+
3+
import org.jetbrains.kotlinx.dataframe.ColumnSelector
4+
import org.jetbrains.kotlinx.dataframe.DataFrame
5+
import org.jetbrains.kotlinx.dataframe.api.getColumnWithPath
6+
import org.jetbrains.kotlinx.dataframe.api.isSubtypeOf
7+
import org.jetbrains.kotlinx.dataframe.type
8+
import kotlin.reflect.KType
9+
10+
@PublishedApi
11+
internal fun <T, C> DataFrame<T>.requireImpl(column: ColumnSelector<T, C>, type: KType): DataFrame<T> {
12+
val resolvedColumn = getColumnWithPath(column)
13+
val actualType = resolvedColumn.data.type
14+
require(resolvedColumn.data.isSubtypeOf(type)) {
15+
"Column '${resolvedColumn.path.joinToString()}' has type '$actualType', which is not subtype of required '$type' type."
16+
}
17+
return this
18+
}

0 commit comments

Comments
 (0)