Skip to content

Commit acafb6d

Browse files
Automated commit of generated code
1 parent 6838ecf commit acafb6d

File tree

3 files changed

+70
-31
lines changed

3 files changed

+70
-31
lines changed

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

+7-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ internal fun <T> Iterable<T>.anyNull(): Boolean = any { it == null }
121121
internal fun emptyPath(): ColumnPath = ColumnPath(emptyList())
122122

123123
@PublishedApi
124-
internal fun <T : Number> KClass<T>.zero(): T =
124+
internal fun <T : Number> KClass<T>.zeroOrNull(): T? =
125125
when (this) {
126126
Int::class -> 0 as T
127127
Byte::class -> 0.toByte() as T
@@ -131,10 +131,14 @@ internal fun <T : Number> KClass<T>.zero(): T =
131131
Float::class -> 0.toFloat() as T
132132
BigDecimal::class -> BigDecimal.ZERO as T
133133
BigInteger::class -> BigInteger.ZERO as T
134-
Number::class -> 0 as T
135-
else -> TODO()
134+
Number::class -> 0 as? T
135+
else -> null
136136
}
137137

138+
@PublishedApi
139+
internal fun <T : Number> KClass<T>.zero(): T =
140+
zeroOrNull() ?: throw NotImplementedError("Zero value for $this is not supported")
141+
138142
internal fun <T> catchSilent(body: () -> T): T? =
139143
try {
140144
body()

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

+43-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jetbrains.kotlinx.dataframe.impl.api
22

3+
import io.github.oshai.kotlinlogging.KotlinLogging
34
import org.jetbrains.kotlinx.dataframe.AnyFrame
45
import org.jetbrains.kotlinx.dataframe.AnyRow
56
import org.jetbrains.kotlinx.dataframe.ColumnsSelector
@@ -11,13 +12,13 @@ import org.jetbrains.kotlinx.dataframe.api.ConvertSchemaDsl
1112
import org.jetbrains.kotlinx.dataframe.api.ConverterScope
1213
import org.jetbrains.kotlinx.dataframe.api.ExcessiveColumns
1314
import org.jetbrains.kotlinx.dataframe.api.Infer
15+
import org.jetbrains.kotlinx.dataframe.api.add
1416
import org.jetbrains.kotlinx.dataframe.api.all
1517
import org.jetbrains.kotlinx.dataframe.api.allNulls
1618
import org.jetbrains.kotlinx.dataframe.api.asColumnGroup
1719
import org.jetbrains.kotlinx.dataframe.api.concat
1820
import org.jetbrains.kotlinx.dataframe.api.convertTo
1921
import org.jetbrains.kotlinx.dataframe.api.emptyDataFrame
20-
import org.jetbrains.kotlinx.dataframe.api.getColumnPaths
2122
import org.jetbrains.kotlinx.dataframe.api.isEmpty
2223
import org.jetbrains.kotlinx.dataframe.api.map
2324
import org.jetbrains.kotlinx.dataframe.api.name
@@ -29,12 +30,14 @@ import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
2930
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
3031
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
3132
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
33+
import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy
3234
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
3335
import org.jetbrains.kotlinx.dataframe.exceptions.ExcessiveColumnsException
3436
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConversionException
3537
import org.jetbrains.kotlinx.dataframe.impl.emptyPath
36-
import org.jetbrains.kotlinx.dataframe.impl.schema.createEmptyColumn
38+
import org.jetbrains.kotlinx.dataframe.impl.getColumnPaths
3739
import org.jetbrains.kotlinx.dataframe.impl.schema.createEmptyDataFrame
40+
import org.jetbrains.kotlinx.dataframe.impl.schema.createNullFilledColumn
3841
import org.jetbrains.kotlinx.dataframe.impl.schema.extractSchema
3942
import org.jetbrains.kotlinx.dataframe.impl.schema.render
4043
import org.jetbrains.kotlinx.dataframe.kind
@@ -45,6 +48,8 @@ import kotlin.reflect.KType
4548
import kotlin.reflect.full.withNullability
4649
import kotlin.reflect.jvm.jvmErasure
4750

51+
private val logger = KotlinLogging.logger {}
52+
4853
private open class Converter(val transform: ConverterScope.(Any?) -> Any?, val skipNulls: Boolean)
4954

5055
private class Filler(val columns: ColumnsSelector<*, *>, val expr: RowExpression<*, *>)
@@ -252,22 +257,16 @@ internal fun AnyFrame.convertToImpl(
252257
}
253258
}.toMutableList()
254259

255-
// when the target is nullable but the source does not contain a column, fill it in with nulls / empty dataframes
260+
// when the target is nullable but the source does not contain a column,
261+
// fill it in with nulls / empty dataframes
256262
val size = this.size.nrow
257263
schema.columns.forEach { (name, targetColumn) ->
258-
val isNullable =
259-
// like value column of type Int?
260-
targetColumn.nullable ||
261-
// like value column of type Int? (backup check)
262-
targetColumn.type.isMarkedNullable ||
263-
// like DataRow<Something?> for a group column (all columns in the group will be nullable)
264-
targetColumn.contentType?.isMarkedNullable == true ||
265-
// frame column can be filled with empty dataframes
266-
targetColumn.kind == ColumnKind.Frame
267-
268264
if (name !in visited) {
269-
newColumns += targetColumn.createEmptyColumn(name, size)
270-
if (!isNullable) {
265+
try {
266+
newColumns += targetColumn.createNullFilledColumn(name, size)
267+
} catch (e: IllegalStateException) {
268+
logger.debug(e) { "" }
269+
// if this could not be done automatically, they need to be filled manually
271270
missingPaths.add(path + name)
272271
}
273272
}
@@ -279,14 +278,39 @@ internal fun AnyFrame.convertToImpl(
279278
val marker = MarkersExtractor.get(clazz)
280279
var result = convertToSchema(marker.schema, emptyPath())
281280

281+
/*
282+
* Here we handle all registered fillers of the user.
283+
* Fillers are registered in the DSL like:
284+
* ```kt
285+
* df.convertTo<Target> {
286+
* fill { col1 and col2 }.with { something }
287+
* fill { col3 }.with { somethingElse }
288+
* }
289+
* ```
290+
* Users can use this to fill up any column that was missing during the conversion.
291+
* They can also fill up and thus overwrite any existing column here.
292+
*/
282293
dsl.fillers.forEach { filler ->
283-
val paths = result.getColumnPaths(filler.columns)
284-
missingPaths.removeAll(paths.toSet())
285-
result = result.update { paths.toColumnSet() }.with {
286-
filler.expr(this, this)
294+
// get all paths from the `fill { col1 and col2 }` part
295+
val paths = result.getColumnPaths(UnresolvedColumnsPolicy.Create, filler.columns).toSet()
296+
297+
// split the paths into those that are already in the df and those that are missing
298+
val (newPaths, existingPaths) = paths.partition { it in missingPaths }
299+
300+
// first fill cols that are already in the df using the `with {}` part of the dsl
301+
result = result.update { existingPaths.toColumnSet() }.with { filler.expr(this, this) }
302+
303+
// then create any missing ones by filling using the `with {}` part of the dsl
304+
result = newPaths.fold(result) { df, newPath ->
305+
df.add(newPath, Infer.Type) { filler.expr(this, this) }
287306
}
307+
308+
// remove the paths that are now filled
309+
missingPaths -= paths
288310
}
289311

312+
// Inform the user which target columns could not be created in the conversion
313+
// The user will need to supply extra information for these, like `fill {}` them.
290314
if (missingPaths.isNotEmpty()) {
291315
throw IllegalArgumentException(
292316
"The following columns were not found in DataFrame: ${

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

+20-9
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ internal fun AnyCol.extractSchema(): ColumnSchema =
102102
@PublishedApi
103103
internal fun getSchema(kClass: KClass<*>): DataFrameSchema = MarkersExtractor.get(kClass).schema
104104

105+
/**
106+
* Create "empty" column based on the toplevel of [this] [ColumnSchema].
107+
*/
105108
internal fun ColumnSchema.createEmptyColumn(name: String): AnyCol =
106109
when (this) {
107110
is ColumnSchema.Value -> DataColumn.createValueColumn<Any?>(name, emptyList(), type)
@@ -110,14 +113,22 @@ internal fun ColumnSchema.createEmptyColumn(name: String): AnyCol =
110113
else -> error("Unexpected ColumnSchema: $this")
111114
}
112115

113-
/** Create "empty" column, filled with either null or empty dataframes. */
114-
internal fun ColumnSchema.createEmptyColumn(name: String, numberOfRows: Int): AnyCol =
116+
/**
117+
* Creates a column based on [this] [ColumnSchema] filled with `null` or empty dataframes.
118+
* @throws IllegalStateException if the column is not nullable and [numberOfRows]` > 0`.
119+
*/
120+
internal fun ColumnSchema.createNullFilledColumn(name: String, numberOfRows: Int): AnyCol =
115121
when (this) {
116-
is ColumnSchema.Value -> DataColumn.createValueColumn(
117-
name = name,
118-
values = List(numberOfRows) { null },
119-
type = type,
120-
)
122+
is ColumnSchema.Value -> {
123+
if (!type.isMarkedNullable && numberOfRows > 0) {
124+
error("Cannot create a null-filled value column of type $type as it's not nullable.")
125+
}
126+
DataColumn.createValueColumn(
127+
name = name,
128+
values = List(numberOfRows) { null },
129+
type = type,
130+
)
131+
}
121132

122133
is ColumnSchema.Group -> DataColumn.createColumnGroup(
123134
name = name,
@@ -130,7 +141,7 @@ internal fun ColumnSchema.createEmptyColumn(name: String, numberOfRows: Int): An
130141
schema = lazyOf(schema),
131142
)
132143

133-
else -> error("Unexpected ColumnSchema: $this")
144+
else -> error("Cannot create null-filled column of unexpected ColumnSchema: $this")
134145
}
135146

136147
internal fun DataFrameSchema.createEmptyDataFrame(): AnyFrame =
@@ -143,7 +154,7 @@ internal fun DataFrameSchema.createEmptyDataFrame(numberOfRows: Int): AnyFrame =
143154
DataFrame.empty(numberOfRows)
144155
} else {
145156
columns.map { (name, schema) ->
146-
schema.createEmptyColumn(name, numberOfRows)
157+
schema.createNullFilledColumn(name, numberOfRows)
147158
}.toDataFrame()
148159
}
149160

0 commit comments

Comments
 (0)