Skip to content

Commit 0f5227f

Browse files
authored
Incremental processing support for ksp (#669)
1 parent 6765ffe commit 0f5227f

File tree

10 files changed

+189
-108
lines changed

10 files changed

+189
-108
lines changed

querydsl-tooling/querydsl-ksp-codegen/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
<artifactId>kotlin-scripting-jsr223</artifactId>
4848
<scope>test</scope>
4949
</dependency>
50+
<dependency>
51+
<groupId>io.mockk</groupId>
52+
<artifactId>mockk-jvm</artifactId>
53+
<version>1.13.13</version>
54+
<scope>test</scope>
55+
</dependency>
5056
<dependency>
5157
<groupId>org.jetbrains.kotlin</groupId>
5258
<artifactId>kotlin-script-runtime</artifactId>

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,26 @@ sealed interface QPropertyType {
116116
}
117117

118118
class ObjectReference(
119-
val target: QueryModel,
119+
val entityClassName: ClassName,
120+
val queryClassName: ClassName,
120121
val typeArgs: List<TypeName>
121122
) : QPropertyType {
122123
override val originalClassName: ClassName
123-
get() = target.originalClassName
124+
get() = entityClassName
124125

125126
override val originalTypeName: TypeName
126127
get() {
127128
if (typeArgs.isEmpty()) {
128-
return target.originalClassName
129+
return entityClassName
129130
} else {
130-
return target.originalClassName.parameterizedBy(typeArgs)
131+
return entityClassName.parameterizedBy(typeArgs)
131132
}
132133
}
133134

134135
override val pathClassName: ClassName
135-
get() = target.className
136+
get() = queryClassName
136137

137138
override val pathTypeName: TypeName
138-
get() = target.className
139+
get() = queryClassName
139140
}
140141
}

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

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,38 @@ import com.google.devtools.ksp.processing.SymbolProcessor
66
import com.google.devtools.ksp.symbol.*
77
import com.squareup.kotlinpoet.*
88
import com.squareup.kotlinpoet.ksp.writeTo
9-
import jakarta.persistence.Embeddable
10-
import jakarta.persistence.Entity
11-
import jakarta.persistence.MappedSuperclass
129

1310
class QueryDslProcessor(
1411
private val settings: KspSettings,
1512
private val codeGenerator: CodeGenerator
1613
) : SymbolProcessor {
14+
val typeProcessor = QueryModelExtractor(settings)
15+
1716
override fun process(resolver: Resolver): List<KSAnnotated> {
1817
if (settings.enable) {
19-
processImpl(resolver)
18+
QueryModelType.entries.forEach { type ->
19+
resolver.getSymbolsWithAnnotation(type.associatedAnnotation)
20+
.map { it as KSClassDeclaration }
21+
.filter { isIncluded(it) }
22+
.forEach { declaration -> typeProcessor.add(declaration, type) }
23+
}
2024
}
2125
return emptyList()
2226
}
2327

24-
private fun processImpl(resolver: Resolver) {
25-
val declarations = mutableMapOf<KSClassDeclaration, QueryModel.Type>()
26-
27-
resolver.getSymbolsWithAnnotation(Embeddable::class.qualifiedName!!)
28-
.map { it as KSClassDeclaration }
29-
.filter { isIncluded(it) }
30-
.forEach { declarations.putIfAbsent(it, QueryModel.Type.EMBEDDABLE) }
31-
32-
resolver.getSymbolsWithAnnotation(MappedSuperclass::class.qualifiedName!!)
33-
.map { it as KSClassDeclaration }
34-
.filter { isIncluded(it) }
35-
.forEach { declarations.putIfAbsent(it, QueryModel.Type.SUPERCLASS) }
36-
37-
resolver.getSymbolsWithAnnotation(Entity::class.qualifiedName!!)
38-
.map { it as KSClassDeclaration }
39-
.filter { isIncluded(it) }
40-
.forEach { declarations.putIfAbsent(it, QueryModel.Type.ENTITY) }
41-
42-
val allDeclarations = declarations.map { QueryModelExtractor.ModelDeclaration(it.key, it.value) }
43-
val models = QueryModelExtractor.process(settings, allDeclarations)
28+
override fun finish() {
29+
val models = typeProcessor.process()
4430
models.forEach { model ->
4531
val typeSpec = QueryModelRenderer.render(model)
4632
FileSpec.builder(model.className)
4733
.indent(settings.indent)
4834
.addType(typeSpec)
4935
.build()
50-
.writeTo(codeGenerator, false)
36+
.writeTo(
37+
codeGenerator = codeGenerator,
38+
aggregating = false,
39+
originatingKSFiles = listOf(model.originatingFile)
40+
)
5141
}
5242
}
5343

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
package com.querydsl.ksp.codegen
22

3+
import com.google.devtools.ksp.symbol.KSFile
34
import com.squareup.kotlinpoet.ClassName
45

56
class QueryModel(
67
val originalClassName: ClassName,
78
val typeParameterCount: Int,
89
val className: ClassName,
9-
val type: Type
10+
val type: QueryModelType,
11+
val originatingFile: KSFile
1012
) {
1113
var superclass: QueryModel? = null
1214

1315
val properties = mutableListOf<QProperty>()
14-
15-
enum class Type {
16-
ENTITY,
17-
EMBEDDABLE,
18-
SUPERCLASS
19-
}
2016
}

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

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,79 @@ import com.google.devtools.ksp.symbol.KSPropertyDeclaration
77
import com.squareup.kotlinpoet.ClassName
88
import com.squareup.kotlinpoet.asClassName
99
import com.squareup.kotlinpoet.ksp.toClassName
10+
import jakarta.persistence.Transient
1011

11-
object QueryModelExtractor {
12+
class QueryModelExtractor(
13+
private val settings: KspSettings
14+
) {
1215
private val transientClassName = Transient::class.asClassName()
16+
private val processed = mutableMapOf<String, ModelDeclaration>()
1317

14-
fun process(
15-
settings: KspSettings,
16-
declarations: List<ModelDeclaration>
17-
): List<QueryModel> {
18-
val declarationToModelMap = declarations.associateWith { modelDeclaration ->
19-
val declaration = modelDeclaration.classDeclaration
20-
QueryModel(
21-
originalClassName = declaration.toClassName(),
22-
typeParameterCount = declaration.typeParameters.size,
23-
className = ClassName(
24-
"${declaration.packageName.asString()}${settings.packageSuffix}",
25-
"${settings.prefix}${declaration.simpleName.asString()}${settings.suffix}"
26-
),
27-
type = modelDeclaration.type
18+
fun add(classDeclaration: KSClassDeclaration, type: QueryModelType): QueryModel {
19+
return lookupOrInsert(classDeclaration) { addNew(classDeclaration, type) }
20+
.model
21+
}
22+
23+
fun add(classDeclaration: KSClassDeclaration): QueryModel {
24+
return lookupOrInsert(classDeclaration) {
25+
addNew(
26+
classDeclaration,
27+
QueryModelType.autodetect(classDeclaration)
28+
?: error("Unable to resolve type of entity for ${classDeclaration.qualifiedName!!.asString()}")
2829
)
30+
}.model
31+
}
32+
33+
fun process(): List<QueryModel> {
34+
processProperties()
35+
return processed.values.map { it.model }
36+
}
37+
38+
private fun lookupOrInsert(classDeclaration: KSClassDeclaration, create: () -> QueryModel): ModelDeclaration {
39+
val qualifiedName = classDeclaration.qualifiedName!!.asString()
40+
val processedType = processed[qualifiedName]
41+
if (processedType == null) {
42+
val newQueryModel = create()
43+
val declaration = ModelDeclaration(classDeclaration, newQueryModel)
44+
processed[qualifiedName] = declaration
45+
return declaration
46+
} else {
47+
return processedType
2948
}
30-
val models = declarationToModelMap.values.toList()
31-
declarationToModelMap.forEach { entry ->
32-
val classDeclaration = entry.key.classDeclaration
33-
val superclass = classDeclaration.superclassOrNull()
34-
if (superclass != null) {
35-
val match = models.singleOrNull { it.originalClassName == superclass }
36-
if (match == null) {
37-
error("${classDeclaration.qualifiedName!!.asString()} has superclass ${superclass}, but this is not a processed entity")
38-
} else {
39-
entry.value.superclass = match
40-
}
41-
}
49+
}
50+
51+
private fun addNew(
52+
classDeclaration: KSClassDeclaration,
53+
type: QueryModelType
54+
): QueryModel {
55+
val queryModel = toQueryModel(classDeclaration, type)
56+
val superclass = classDeclaration.superclassOrNull()
57+
if (superclass != null) {
58+
queryModel.superclass = add(superclass)
4259
}
43-
declarationToModelMap.forEach { entry ->
44-
val model = entry.value
45-
val classDeclaration = entry.key.classDeclaration
46-
val properties = classDeclaration.getDeclaredProperties()
47-
.filter { !it.isTransient() }
48-
.filter { !it.isGetterTransient() }
49-
.filter { it.hasBackingField }
50-
.map { property ->
51-
val propName = property.simpleName.asString()
52-
val extractor = TypeExtractor(property, models)
53-
val type = extractor.extract(property.type.resolve())
54-
QProperty(propName, type)
55-
}
56-
model.properties.addAll(properties)
60+
return queryModel
61+
}
62+
63+
private fun processProperties() {
64+
processed.values.map { modelDeclaration ->
65+
val properties = extractProperties(modelDeclaration.declaration)
66+
modelDeclaration.model.properties.addAll(properties)
5767
}
58-
return models
68+
}
69+
70+
private fun extractProperties(declaration: KSClassDeclaration): List<QProperty> {
71+
return declaration
72+
.getDeclaredProperties()
73+
.filter { !it.isTransient() }
74+
.filter { !it.isGetterTransient() }
75+
.filter { it.hasBackingField }
76+
.map { property ->
77+
val propName = property.simpleName.asString()
78+
val extractor = TypeExtractor(settings, property)
79+
val type = extractor.extract(property.type.resolve())
80+
QProperty(propName, type)
81+
}
82+
.toList()
5983
}
6084

6185
private fun KSPropertyDeclaration.isTransient(): Boolean {
@@ -68,22 +92,41 @@ object QueryModelExtractor {
6892
} ?: false
6993
}
7094

71-
private fun KSClassDeclaration.superclassOrNull(): ClassName? {
95+
private fun KSClassDeclaration.superclassOrNull(): KSClassDeclaration? {
7296
for (superType in superTypes) {
7397
val resolvedType = superType.resolve()
7498
val declaration = resolvedType.declaration
7599
if (declaration is KSClassDeclaration) {
76100
val superClassName = declaration.toClassName()
77101
if (declaration.classKind == ClassKind.CLASS && superClassName != Any::class.asClassName()) {
78-
return superClassName
102+
return declaration
79103
}
80104
}
81105
}
82106
return null
83107
}
84108

85-
class ModelDeclaration(
86-
val classDeclaration: KSClassDeclaration,
87-
val type: QueryModel.Type
109+
private fun toQueryModel(classDeclaration: KSClassDeclaration, type: QueryModelType): QueryModel {
110+
return QueryModel(
111+
originalClassName = classDeclaration.toClassName(),
112+
typeParameterCount = classDeclaration.typeParameters.size,
113+
className = queryClassName(classDeclaration, settings),
114+
type = type,
115+
originatingFile = classDeclaration.containingFile!!
116+
)
117+
}
118+
119+
companion object {
120+
fun queryClassName(classDeclaration: KSClassDeclaration, settings: KspSettings): ClassName {
121+
return ClassName(
122+
"${classDeclaration.packageName.asString()}${settings.packageSuffix}",
123+
"${settings.prefix}${classDeclaration.simpleName.asString()}${settings.suffix}"
124+
)
125+
}
126+
}
127+
128+
private class ModelDeclaration(
129+
val declaration: KSClassDeclaration,
130+
val model: QueryModel
88131
)
89132
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package com.querydsl.ksp.codegen
33
import com.querydsl.core.types.Path
44
import com.querydsl.core.types.PathMetadata
55
import com.querydsl.core.types.dsl.*
6+
import com.querydsl.ksp.codegen.Naming.toCamelCase
67
import com.squareup.kotlinpoet.*
78
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
8-
import com.querydsl.ksp.codegen.Naming.toCamelCase
9-
import java.util.*
109

1110
object QueryModelRenderer {
1211
fun render(model: QueryModel): TypeSpec {
@@ -32,9 +31,8 @@ object QueryModelRenderer {
3231
}
3332
superclass(
3433
when (model.type) {
35-
QueryModel.Type.ENTITY,
36-
QueryModel.Type.SUPERCLASS -> EntityPathBase::class.asClassName().parameterizedBy(constraint)
37-
QueryModel.Type.EMBEDDABLE -> BeanPath::class.asClassName().parameterizedBy(constraint)
34+
QueryModelType.ENTITY, QueryModelType.SUPERCLASS -> EntityPathBase::class.asClassName().parameterizedBy(constraint)
35+
QueryModelType.EMBEDDABLE -> BeanPath::class.asClassName().parameterizedBy(constraint)
3836
}
3937
)
4038
return this
@@ -133,11 +131,11 @@ object QueryModelRenderer {
133131

134132
private fun renderObjectReference(name: String, type: QPropertyType.ObjectReference): PropertySpec {
135133
return PropertySpec
136-
.builder(name, type.target.className)
134+
.builder(name, type.queryClassName)
137135
.delegate(
138136
CodeBlock.builder()
139137
.beginControlFlow("lazy")
140-
.addStatement("${type.target.className}(forProperty(\"${name}\"))")
138+
.addStatement("${type.queryClassName}(forProperty(\"${name}\"))")
141139
.endControlFlow()
142140
.build()
143141
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.querydsl.ksp.codegen
2+
3+
import com.google.devtools.ksp.symbol.KSAnnotation
4+
import com.google.devtools.ksp.symbol.KSClassDeclaration
5+
import com.squareup.kotlinpoet.ksp.toTypeName
6+
import jakarta.persistence.Embeddable
7+
import jakarta.persistence.Entity
8+
import jakarta.persistence.MappedSuperclass
9+
10+
enum class QueryModelType(
11+
val associatedAnnotation: String
12+
) {
13+
ENTITY(Entity::class.qualifiedName!!),
14+
EMBEDDABLE(Embeddable::class.qualifiedName!!),
15+
SUPERCLASS(MappedSuperclass::class.qualifiedName!!);
16+
17+
companion object {
18+
fun autodetect(classDeclaration: KSClassDeclaration): QueryModelType? {
19+
for (annotation in classDeclaration.annotations) {
20+
for (type in QueryModelType.entries) {
21+
if (annotation.isEqualTo(type.associatedAnnotation)) {
22+
return type
23+
}
24+
}
25+
}
26+
return null
27+
}
28+
29+
private fun KSAnnotation.isEqualTo(annotationQualifiedName: String): Boolean {
30+
return annotationType.toTypeName().toString() == annotationQualifiedName
31+
}
32+
}
33+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ sealed interface SimpleType {
9595
override fun render(name: String): PropertySpec {
9696
return PropertySpec
9797
.builder(name, pathTypeName)
98-
.initializer("createNumber(\"$name\", ${innerType}::class.java)")
98+
.initializer("createNumber(\"$name\", ${innerType}::class.javaObjectType)")
9999
.build()
100100
}
101101
}

0 commit comments

Comments
 (0)