Skip to content

Clarify the situation when generated extension property causes ClassCast or NPE exceptions #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -9918,6 +9918,11 @@ public final class org/jetbrains/kotlinx/dataframe/exceptions/ColumnNotFoundExce
public fun getMessage ()Ljava/lang/String;
}

public final class org/jetbrains/kotlinx/dataframe/exceptions/ColumnTypeMismatchesColumnValuesException : java/lang/RuntimeException {
public fun <init> (Lorg/jetbrains/kotlinx/dataframe/DataColumn;Ljava/lang/Throwable;)V
public final fun getColumn ()Lorg/jetbrains/kotlinx/dataframe/DataColumn;
}

public final class org/jetbrains/kotlinx/dataframe/exceptions/DuplicateColumnNamesException : java/lang/IllegalArgumentException {
public fun <init> (Ljava/util/List;)V
public final fun getAllColumnNames ()Ljava/util/List;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.jetbrains.kotlinx.dataframe.exceptions

import org.jetbrains.kotlinx.dataframe.AnyCol
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.columns.values
import org.jetbrains.kotlinx.dataframe.type

/**
* Extension properties are generated according to [DataColumn.type] property
* [DataColumn.type] must match types of [DataColumn.values], but it can fail to do so.
* This causes [ClassCastException] or [NullPointerException] when you use extension property and actual value is of different type or is null.
* If generated extension property causes this exception, this is a bug in the library
* You can work around this problem by referring to [column] using String API
*/
public class ColumnTypeMismatchesColumnValuesException(public val column: AnyCol, cause: Throwable) :
RuntimeException("Failed to convert column '${column.name()}'", cause)
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.jetbrains.kotlinx.dataframe.columns.values
import org.jetbrains.kotlinx.dataframe.dataTypes.IFRAME
import org.jetbrains.kotlinx.dataframe.dataTypes.IMG
import org.jetbrains.kotlinx.dataframe.exceptions.CellConversionException
import org.jetbrains.kotlinx.dataframe.exceptions.ColumnTypeMismatchesColumnValuesException
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConversionException
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConverterNotFoundException
import org.jetbrains.kotlinx.dataframe.impl.columns.DataColumnInternal
Expand Down Expand Up @@ -68,7 +69,16 @@ internal fun <T, C, R> Convert<T, C>.withRowCellImpl(
type: KType,
infer: Infer,
rowConverter: RowValueExpression<T, C, R>,
): DataFrame<T> = to { col -> df.newColumn(type, col.name, infer) { rowConverter(it, it[col]) } }
): DataFrame<T> =
to { col ->
try {
df.newColumn(type, col.name, infer) { rowConverter(it, it[col]) }
} catch (e: ClassCastException) {
throw ColumnTypeMismatchesColumnValuesException(col, e)
} catch (e: NullPointerException) {
throw ColumnTypeMismatchesColumnValuesException(col, e)
}
}

@PublishedApi
internal fun <T, C, R> Convert<T, C>.convertRowColumnImpl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import io.kotest.matchers.shouldBe
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalTime
import org.jetbrains.kotlinx.dataframe.ColumnsContainer
import org.jetbrains.kotlinx.dataframe.DataColumn
import org.jetbrains.kotlinx.dataframe.DataFrame
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
import org.jetbrains.kotlinx.dataframe.exceptions.CellConversionException
import org.jetbrains.kotlinx.dataframe.exceptions.ColumnTypeMismatchesColumnValuesException
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConversionException
import org.jetbrains.kotlinx.dataframe.exceptions.TypeConverterNotFoundException
import org.jetbrains.kotlinx.dataframe.hasNulls
Expand Down Expand Up @@ -315,4 +317,15 @@ class ConvertTests {
}
}
}

private interface Marker

private val ColumnsContainer<Marker>.a get() = this["a"] as DataColumn<String>

@Test
fun `convert with buggy extension property`() {
shouldThrow<ColumnTypeMismatchesColumnValuesException> {
dataFrameOf("a")(1, 2, 3).cast<Marker>().convert { a }.with { it }
}
}
}
Loading