Skip to content

Commit 0642818

Browse files
committed
Fix KSP type extraction for Comparable and Number types with @convert
Remove annotation-based type detection that prevented proper type hierarchy analysis. Types implementing Comparable or extending Number now correctly generate ComparablePath and NumberPath respectively, matching Java APT behavior. - Remove userType() method that checked for @convert, @type, @JdbcTypeCode - Enhance fallbackType() to detect Comparable and Number via type hierarchy - Add test cases for YearMonth (Comparable) and Money (Number+Comparable)
1 parent 45ef040 commit 0642818

File tree

7 files changed

+135
-42
lines changed

7 files changed

+135
-42
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.querydsl.example.ksp
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.Convert
5+
import jakarta.persistence.Entity
6+
import jakarta.persistence.Id
7+
import java.time.YearMonth
8+
import java.util.UUID
9+
10+
@Entity
11+
class Invoice(
12+
@Id
13+
val id: UUID,
14+
15+
@Column(nullable = false, length = 7)
16+
@Convert(converter = YearMonthConverter::class)
17+
val month: YearMonth?,
18+
19+
@Column(nullable = false, precision = 30, scale = 10)
20+
@Convert(converter = MoneyConverter::class)
21+
val amount: Money?,
22+
)
23+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.querydsl.example.ksp
2+
3+
import java.math.BigDecimal
4+
5+
data class Money(val value: BigDecimal) : Number(), Comparable<Money> {
6+
7+
override fun toByte(): Byte = value.toByte()
8+
override fun toDouble(): Double = value.toDouble()
9+
override fun toFloat(): Float = value.toFloat()
10+
override fun toInt(): Int = value.toInt()
11+
override fun toLong(): Long = value.toLong()
12+
override fun toShort(): Short = value.toShort()
13+
14+
override fun compareTo(other: Money): Int = this.value.compareTo(other.value)
15+
}
16+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.querydsl.example.ksp
2+
3+
import jakarta.persistence.AttributeConverter
4+
import jakarta.persistence.Converter
5+
import java.math.BigDecimal
6+
7+
@Converter
8+
class MoneyConverter : AttributeConverter<Money, BigDecimal> {
9+
10+
override fun convertToDatabaseColumn(attribute: Money?): BigDecimal? {
11+
return attribute?.value
12+
}
13+
14+
override fun convertToEntityAttribute(dbData: BigDecimal?): Money? {
15+
return dbData?.let { Money(it) }
16+
}
17+
}
18+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.querydsl.example.ksp
2+
3+
import jakarta.persistence.AttributeConverter
4+
import jakarta.persistence.Converter
5+
import java.time.YearMonth
6+
7+
@Converter
8+
class YearMonthConverter : AttributeConverter<YearMonth, String> {
9+
10+
override fun convertToDatabaseColumn(attribute: YearMonth?): String? {
11+
return attribute?.toString()
12+
}
13+
14+
override fun convertToEntityAttribute(dbData: String?): YearMonth? {
15+
return dbData?.let { YearMonth.parse(it) }
16+
}
17+
}
18+

querydsl-examples/querydsl-example-ksp-codegen/src/test/kotlin/Tests.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.querydsl.example.ksp.QBear
99
import com.querydsl.example.ksp.QBearSimplifiedProjection
1010
import com.querydsl.example.ksp.QCat
1111
import com.querydsl.example.ksp.QDog
12+
import com.querydsl.example.ksp.QInvoice
1213
import com.querydsl.example.ksp.QMyShape
1314
import com.querydsl.example.ksp.QPerson
1415
import com.querydsl.example.ksp.QPersonClassDTO
@@ -290,6 +291,18 @@ class Tests {
290291
assertThat(departureProperty.returnType.arguments.single().type!!.jvmErasure.qualifiedName!!).isEqualTo("org.locationtech.jts.geom.Geometry")
291292
}
292293

294+
@Test
295+
fun `Invoice month and amount generate correct path types`() {
296+
val monthProperty = QInvoice::class.memberProperties.single { it.name == "month" }
297+
val amountProperty = QInvoice::class.memberProperties.single { it.name == "amount" }
298+
299+
assertThat(monthProperty.returnType.jvmErasure.qualifiedName!!).isEqualTo("com.querydsl.core.types.dsl.ComparablePath")
300+
assertThat(monthProperty.returnType.arguments.single().type!!.jvmErasure.qualifiedName!!).isEqualTo("java.time.YearMonth")
301+
302+
assertThat(amountProperty.returnType.jvmErasure.qualifiedName!!).isEqualTo("com.querydsl.core.types.dsl.NumberPath")
303+
assertThat(amountProperty.returnType.arguments.single().type!!.jvmErasure.qualifiedName!!).isEqualTo("com.querydsl.example.ksp.Money")
304+
}
305+
293306
private fun initialize(): EntityManagerFactory {
294307
val configuration = Configuration()
295308
.setProperty(AvailableSettings.JAKARTA_JDBC_DRIVER, org.h2.Driver::class.qualifiedName!!)

querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelExtractor.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ class QueryModelExtractor(
9090
val extractor = TypeExtractor(
9191
settings,
9292
"${declaration.parent?.location} - $paramName",
93-
parameter.annotations
9493
)
9594
val type = extractor.extract(parameter.type.resolve())
9695
QProperty(paramName, type)
@@ -109,7 +108,6 @@ class QueryModelExtractor(
109108
val extractor = TypeExtractor(
110109
settings,
111110
property.simpleName.asString(),
112-
property.annotations
113111
)
114112
val type = extractor.extract(property.type.resolve())
115113
QProperty(propName, type)

querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/TypeExtractor.kt

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import com.squareup.kotlinpoet.ClassName
66
import com.squareup.kotlinpoet.asClassName
77
import com.squareup.kotlinpoet.ksp.toClassName
88
import com.squareup.kotlinpoet.ksp.toTypeName
9-
import jakarta.persistence.Convert
109

1110
class TypeExtractor(
1211
private val settings: KspSettings,
1312
private val fullPathName: String,
14-
private val annotations: Sequence<KSAnnotation>
1513
) {
1614
fun extract(type: KSType): QPropertyType {
1715
when (val declaration = type.declaration) {
@@ -31,8 +29,7 @@ class TypeExtractor(
3129
return extract(innerType)
3230
}
3331
else -> {
34-
return userType(type)
35-
?: parameterType(type)
32+
return parameterType(type)
3633
?: simpleType(type)
3734
?: referenceType(type)
3835
?: collectionType(type)
@@ -58,24 +55,21 @@ class TypeExtractor(
5855
}
5956
}
6057

61-
private fun fallbackType(type: KSType): QPropertyType.Simple {
58+
private fun fallbackType(type: KSType): QPropertyType {
6259
val declaration = type.declaration
63-
val isComparable = if (declaration is KSClassDeclaration) {
64-
val comparableNames = listOfNotNull(
65-
Comparable::class.java.canonicalName,
66-
java.lang.Comparable::class.qualifiedName
67-
)
68-
declaration.getAllSuperTypes().any {
69-
comparableNames.contains(it.toClassName().canonicalName)
70-
}
71-
} else {
72-
false
73-
}
7460
val className = type.toClassNameSimple()
75-
if (isComparable) {
76-
return QPropertyType.Simple(SimpleType.Comparable(className))
77-
} else {
78-
return QPropertyType.Simple(SimpleType.Simple(className))
61+
62+
if (declaration !is KSClassDeclaration) {
63+
return QPropertyType.Unknown(className, type.toTypeName())
64+
}
65+
66+
val isComparable = declaration.isComparable()
67+
val isNumber = declaration.isNumber()
68+
69+
return when {
70+
isComparable && isNumber -> QPropertyType.Simple(SimpleType.QNumber(className))
71+
isComparable -> QPropertyType.Simple(SimpleType.Comparable(className))
72+
else -> QPropertyType.Unknown(className, type.toTypeName())
7973
}
8074
}
8175

@@ -144,23 +138,6 @@ class TypeExtractor(
144138
return QueryModelType.autodetect(classDeclaration) != null
145139
}
146140

147-
private fun userType(type: KSType): QPropertyType.Unknown? {
148-
if (type.isEnum()) {
149-
return null
150-
}
151-
152-
val userTypeAnnotations = listOf(
153-
ClassName("org.hibernate.annotations", "Type"),
154-
ClassName("org.hibernate.annotations", "JdbcTypeCode"),
155-
Convert::class.asClassName()
156-
)
157-
if (annotations.any { userTypeAnnotations.contains(it.annotationType.resolve().toClassName()) }) {
158-
return QPropertyType.Unknown(type.toClassNameSimple(), type.toTypeName())
159-
} else {
160-
return null
161-
}
162-
}
163-
164141
private fun assertTypeArgCount(parentType: KSType, collectionTypeName: String, count: Int) {
165142
if (parentType.arguments.size != count) {
166143
throwError("Type looks like a $collectionTypeName so expected $count type arguments, but got ${parentType.arguments.size}")
@@ -209,8 +186,38 @@ private fun KSType.toClassNameSimple(): ClassName {
209186
}
210187
}
211188

212-
private fun KSType.isEnum(): Boolean {
213-
val referencedDeclaration = declaration
189+
private fun KSClassDeclaration.hasSuperType(predicate: (ClassName) -> Boolean): Boolean {
190+
return getAllSuperTypes().any { superType ->
191+
val superDeclaration = superType.declaration
192+
if (superDeclaration is KSClassDeclaration) {
193+
predicate(superDeclaration.toClassName())
194+
} else {
195+
false
196+
}
197+
}
198+
}
199+
200+
private fun KSClassDeclaration.isComparable(): Boolean {
201+
val comparableNames = listOfNotNull(
202+
Comparable::class.java.canonicalName,
203+
java.lang.Comparable::class.qualifiedName
204+
)
205+
return hasSuperType { className ->
206+
comparableNames.contains(className.canonicalName)
207+
}
208+
}
214209

215-
return referencedDeclaration is KSClassDeclaration && referencedDeclaration.classKind == ClassKind.ENUM_CLASS
210+
private fun KSClassDeclaration.isNumber(): Boolean {
211+
val numberQualifiedNames = listOf(
212+
"kotlin.Number",
213+
"java.lang.Number"
214+
)
215+
216+
if (qualifiedName?.asString() in numberQualifiedNames) {
217+
return true
218+
}
219+
220+
return getAllSuperTypes().any { superType ->
221+
superType.declaration.qualifiedName?.asString() in numberQualifiedNames
222+
}
216223
}

0 commit comments

Comments
 (0)