Skip to content
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

Nullable accessors and fixed convertTo #175

Merged
merged 8 commits into from
Oct 10, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,58 @@ public sealed interface FieldType {
public class GroupFieldType(public val markerName: String) : FieldType
}

/**
* Returns whether the column type ends with `?` or not.
* NOTE: for [FieldType.FrameFieldType], the `nullable` property indicates the nullability of the frame itself, not the type of the column.
*/
public fun FieldType.isNullable(): Boolean =
when (this) {
is FieldType.FrameFieldType -> markerName.endsWith("?")
is FieldType.GroupFieldType -> markerName.endsWith("?")
is FieldType.ValueFieldType -> typeFqName.endsWith("?")
}

/**
* Returns whether the column type doesn't end with `?` or whether it does.
* NOTE: for [FieldType.FrameFieldType], the `nullable` property indicates the nullability of the frame itself, not the type of the column.
*/
public fun FieldType.isNotNullable(): Boolean = !isNullable()

private fun String.toNullable() = if (this.last() == '?') this else "$this?"

/**
* Returns a new fieldType with the same type but with nullability in the column type.
* NOTE: for [FieldType.FrameFieldType], the `nullable` property indicates the nullability of the frame itself, not the type of the column.
*/
public fun FieldType.toNullable(): FieldType =
if (isNotNullable()) {
when (this) {
is FieldType.FrameFieldType -> FieldType.FrameFieldType(markerName.toNullable(), nullable)
is FieldType.GroupFieldType -> FieldType.GroupFieldType(markerName.toNullable())
is FieldType.ValueFieldType -> FieldType.ValueFieldType(typeFqName.toNullable())
}
} else this

/**
* Returns a new fieldType with the same type but with nullability disabled in the column type.
* NOTE: for [FieldType.FrameFieldType], the `nullable` property indicates the nullability of the frame itself, not the type of the column.
*/
public fun FieldType.toNotNullable(): FieldType =
if (isNullable()) {
when (this) {
is FieldType.FrameFieldType -> FieldType.FrameFieldType(markerName.removeSuffix("?"), nullable)
is FieldType.GroupFieldType -> FieldType.GroupFieldType(markerName.removeSuffix("?"))
is FieldType.ValueFieldType -> FieldType.ValueFieldType(typeFqName.removeSuffix("?"))
}
} else this

public val FieldType.name: String
get() = when (this) {
is FieldType.FrameFieldType -> markerName
is FieldType.GroupFieldType -> markerName
is FieldType.ValueFieldType -> typeFqName
}

public class ValidFieldName private constructor(private val identifier: String, public val needsQuote: Boolean) {
public val unquoted: String get() = identifier
public val quotedIfNeeded: String get() = if (needsQuote) "`$identifier`" else identifier
Expand Down Expand Up @@ -48,6 +100,22 @@ public interface BaseField {
public val fieldType: FieldType
}

public fun BaseField.toNullable(): BaseField =
if (fieldType.isNullable()) this
else object : BaseField {
override val fieldName: ValidFieldName = [email protected]
override val columnName: String = [email protected]
override val fieldType: FieldType = [email protected]()
}

public fun BaseField.toNotNullable(): BaseField =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, I'm not sure. I needed toNullable, but for completeness if we need it in the future I thought it best to also create a reverse option. Might save time to figure out how it works again

if (fieldType.isNotNullable()) this
else object : BaseField {
override val fieldName: ValidFieldName = [email protected]
override val columnName: String = [email protected]
override val fieldType: FieldType = [email protected]()
}

public data class GeneratedField(
override val fieldName: ValidFieldName,
override val columnName: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.superclasses
import kotlin.reflect.full.withNullability
import kotlin.reflect.jvm.jvmErasure

internal fun KType.shouldBeConvertedToFrameColumn(): Boolean = when (jvmErasure) {
Expand All @@ -24,30 +25,32 @@ internal fun KType.shouldBeConvertedToColumnGroup(): Boolean = jvmErasure.let {
it == DataRow::class || it.hasAnnotation<DataSchema>()
}

private fun String.toNullable(): String = if (endsWith("?")) this else "$this?"

internal object MarkersExtractor {

private val cache = mutableMapOf<KClass<*>, Marker>()

inline fun <reified T> get() = get(T::class)

operator fun get(markerClass: KClass<*>): Marker =
fun get(markerClass: KClass<*>, nullableProperties: Boolean = false): Marker =
cache.getOrPut(markerClass) {
val fields = getFields(markerClass)
val fields = getFields(markerClass, nullableProperties)
val isOpen = markerClass.findAnnotation<DataSchema>()?.isOpen ?: false
val baseSchemas = markerClass.superclasses.filter { it != Any::class }.map { get(it) }
val baseSchemas = markerClass.superclasses.filter { it != Any::class }.map { get(it, nullableProperties) }
Marker(
markerClass.qualifiedName ?: markerClass.simpleName!!,
isOpen,
fields,
baseSchemas,
MarkerVisibility.IMPLICIT_PUBLIC,
markerClass
name = markerClass.qualifiedName ?: markerClass.simpleName!!,
isOpen = isOpen,
fields = fields,
superMarkers = baseSchemas,
visibility = MarkerVisibility.IMPLICIT_PUBLIC,
klass = markerClass,
)
}

private fun getFields(markerClass: KClass<*>): List<GeneratedField> {
private fun getFields(markerClass: KClass<*>, nullableProperties: Boolean): List<GeneratedField> {
val order = getPropertiesOrder(markerClass)
return markerClass.declaredMemberProperties.sortedBy { order[it.name] ?: Int.MAX_VALUE }.mapIndexed { index, it ->
return markerClass.declaredMemberProperties.sortedBy { order[it.name] ?: Int.MAX_VALUE }.mapIndexed { _, it ->
val fieldName = ValidFieldName.of(it.name)
val columnName = it.findAnnotation<ColumnName>()?.name ?: fieldName.unquoted
val type = it.returnType
Expand All @@ -56,19 +59,25 @@ internal object MarkersExtractor {
val columnSchema = when {
type.shouldBeConvertedToColumnGroup() -> {
val nestedType = if (clazz == DataRow::class) type.arguments[0].type!! else type
val marker = get(nestedType.jvmErasure)
val marker = get(nestedType.jvmErasure, nullableProperties || type.isMarkedNullable)
fieldType = FieldType.GroupFieldType(marker.name)
ColumnSchema.Group(marker.schema)
}

type.shouldBeConvertedToFrameColumn() -> {
val frameType = type.arguments[0].type!!
val marker = get(frameType.jvmErasure)
fieldType = FieldType.FrameFieldType(marker.name, type.isMarkedNullable)
val marker = get(frameType.jvmErasure, nullableProperties || type.isMarkedNullable)
fieldType = FieldType.FrameFieldType(marker.name, type.isMarkedNullable || nullableProperties)
ColumnSchema.Frame(marker.schema, type.isMarkedNullable)
}

else -> {
fieldType = FieldType.ValueFieldType(type.toString())
ColumnSchema.Value(type)
fieldType = FieldType.ValueFieldType(
if (nullableProperties) type.toString().toNullable() else type.toString()
)
ColumnSchema.Value(
if (nullableProperties) type.withNullability(true) else type
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,6 @@ internal fun AnyFrame.convertToImpl(
}

val clazz = type.jvmErasure
val marker = MarkersExtractor[clazz]
val marker = MarkersExtractor.get(clazz)
return convertToSchema(marker.schema, emptyPath())
}
Loading