From 51c3d549a11a8fcc8c5437a28e0137648ae0180d Mon Sep 17 00:00:00 2001 From: qumn Date: Thu, 13 Jun 2024 16:44:57 +0800 Subject: [PATCH 1/3] feat(ksp): Table annotation support specify supper class --- .../kotlin/org/ktorm/ksp/annotation/Table.kt | 11 +++++++ .../compiler/generator/TableClassGenerator.kt | 8 +---- .../ksp/compiler/parser/MetadataParser.kt | 7 ++++- .../org/ktorm/ksp/compiler/BaseKspTest.kt | 10 ++++--- .../generator/TableClassGeneratorTest.kt | 29 ++++++++++++++++++- .../parser/DatabaseNamingStrategyTest.kt | 4 +-- .../kotlin/org/ktorm/ksp/spi/TableMetadata.kt | 8 ++++- 7 files changed, 61 insertions(+), 16 deletions(-) diff --git a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt index 45741042..42286587 100644 --- a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt +++ b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt @@ -16,6 +16,9 @@ package org.ktorm.ksp.annotation +import org.ktorm.schema.BaseTable +import kotlin.reflect.KClass + /** * Specify the table for an entity class. */ @@ -81,4 +84,12 @@ public annotation class Table( * Specify properties that should be ignored for generating column definitions. */ val ignoreProperties: Array = [], + + /** + * The super class of the generated table class. + * + * If not specified, the super class will be determined based on the class kind of the entity: + * `org.ktorm.schema.BaseTable` for interfaces, and `org.ktorm.schema.Table` for classes. + */ + val superClass: KClass> = Nothing::class ) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt index 98609c1e..692d077e 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt @@ -28,9 +28,7 @@ import org.ktorm.ksp.compiler.util._type import org.ktorm.ksp.compiler.util.getKotlinType import org.ktorm.ksp.compiler.util.getRegisteringCodeBlock import org.ktorm.ksp.spi.TableMetadata -import org.ktorm.schema.BaseTable import org.ktorm.schema.Column -import org.ktorm.schema.Table @OptIn(KotlinPoetKspPreview::class) internal object TableClassGenerator { @@ -49,11 +47,7 @@ internal object TableClassGenerator { } private fun TypeSpec.Builder.configureSuperClass(table: TableMetadata): TypeSpec.Builder { - if (table.entityClass.classKind == ClassKind.INTERFACE) { - superclass(Table::class.asClassName().parameterizedBy(table.entityClass.toClassName())) - } else { - superclass(BaseTable::class.asClassName().parameterizedBy(table.entityClass.toClassName())) - } + superclass(table.superClass.asClassName().parameterizedBy(table.entityClass.toClassName())) addSuperclassConstructorParameter("%S", table.name) addSuperclassConstructorParameter("alias") diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 55b94489..97dfaa02 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -28,6 +28,7 @@ import org.ktorm.ksp.spi.CodingNamingStrategy import org.ktorm.ksp.spi.ColumnMetadata import org.ktorm.ksp.spi.DatabaseNamingStrategy import org.ktorm.ksp.spi.TableMetadata +import org.ktorm.schema.BaseTable import org.ktorm.schema.TypeReference import java.lang.reflect.InvocationTargetException import java.util.* @@ -93,6 +94,9 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn _logger.info("[ktorm-ksp-compiler] parse table metadata from entity: $className") val table = cls.getAnnotationsByType(Table::class).first() + val superClass = table.superClass.takeIf { it != Nothing::class } + ?: if (cls.classKind == INTERFACE) org.ktorm.schema.Table::class else BaseTable::class + val tableMetadata = TableMetadata( entityClass = cls, name = table.name.ifEmpty { _databaseNamingStrategy.getTableName(cls) }, @@ -102,7 +106,8 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn tableClassName = table.className.ifEmpty { _codingNamingStrategy.getTableClassName(cls) }, entitySequenceName = table.entitySequenceName.ifEmpty { _codingNamingStrategy.getEntitySequenceName(cls) }, ignoreProperties = table.ignoreProperties.toSet(), - columns = ArrayList() + columns = ArrayList(), + superClass = superClass ) val columns = tableMetadata.columns as MutableList diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt index 5b882e88..4d6d15b6 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/BaseKspTest.kt @@ -40,14 +40,14 @@ abstract class BaseKspTest { } protected fun kspFailing(message: String, @Language("kotlin") code: String, vararg options: Pair) { - val result = compile(code, mapOf(*options)) + val result = compile(code, emptyList(), mapOf(*options)) assert(result.exitCode == KotlinCompilation.ExitCode.COMPILATION_ERROR) assert(result.messages.contains("e: Error occurred in KSP, check log for detail")) assert(result.messages.contains(message)) } - protected fun runKotlin(@Language("kotlin") code: String, vararg options: Pair) { - val result = compile(code, mapOf(*options)) + protected fun runKotlin(@Language("kotlin") code: String, additionalImports: List = emptyList(), vararg options: Pair) { + val result = compile(code, additionalImports, mapOf(*options)) assert(result.exitCode == KotlinCompilation.ExitCode.OK) try { @@ -59,7 +59,7 @@ abstract class BaseKspTest { } } - private fun compile(@Language("kotlin") code: String, options: Map): KotlinCompilation.Result { + private fun compile(@Language("kotlin") code: String, additionalImports: List, options: Map): KotlinCompilation.Result { @Language("kotlin") val header = """ import java.math.* @@ -72,6 +72,8 @@ abstract class BaseKspTest { import org.ktorm.dsl.* import org.ktorm.entity.* import org.ktorm.ksp.annotation.* + + ${additionalImports.joinToString("\n") { "import $it" }} lateinit var database: Database diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/TableClassGeneratorTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/TableClassGeneratorTest.kt index 0d21efa8..20a7a325 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/TableClassGeneratorTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/generator/TableClassGeneratorTest.kt @@ -1,7 +1,9 @@ package org.ktorm.ksp.compiler.generator import org.junit.Test +import org.ktorm.entity.Entity import org.ktorm.ksp.compiler.BaseKspTest +import kotlin.reflect.KClass class TableClassGeneratorTest : BaseKspTest() { @@ -101,7 +103,7 @@ class TableClassGeneratorTest : BaseKspTest() { assert(user.username == "jack") assert(user.phone == "12345") } - """.trimIndent(), "ktorm.allowReflection" to "true") + """.trimIndent(), emptyList(), "ktorm.allowReflection" to "true") @Test fun `ignore properties`() = runKotlin(""" @@ -199,4 +201,29 @@ class TableClassGeneratorTest : BaseKspTest() { assert(Users.columns.map { it.name }.toSet() == setOf("id", "class", "operator")) } """.trimIndent()) + + @Test + fun `super class`() = runKotlin(""" + @Table(superClass = CstmTable::class) + interface User: Entity { + @PrimaryKey + var id: Int + var `class`: String + var operator: String + } + + fun run() { + assert(CstmTable::class.isSubclassOf(CstmTable::class)) + } + """.trimIndent(), listOf("org.ktorm.ksp.compiler.generator.CstmTable", "kotlin.reflect.full.*")) } + +abstract class CstmTable>( + tableName: String, + alias: String? = null, + catalog: String? = null, + schema: String? = null, + entityClass: KClass? = null +) : org.ktorm.schema.Table( + tableName, alias, catalog, schema, entityClass +) diff --git a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/parser/DatabaseNamingStrategyTest.kt b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/parser/DatabaseNamingStrategyTest.kt index 76d1b4a0..948e442a 100644 --- a/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/parser/DatabaseNamingStrategyTest.kt +++ b/ktorm-ksp-compiler/src/test/kotlin/org/ktorm/ksp/compiler/parser/DatabaseNamingStrategyTest.kt @@ -101,7 +101,7 @@ class DatabaseNamingStrategyTest : BaseKspTest() { assert(UserProfiles.tableName == "USER_PROFILE") assert(UserProfiles.columns.map { it.name }.toSet() == setOf("ID", "PUBLIC_EMAIL", "PROFILE_PICTURE", "COMPANY_ID")) } - """.trimIndent(), "ktorm.dbNamingStrategy" to "upper-snake-case") + """.trimIndent(), emptyList(), "ktorm.dbNamingStrategy" to "upper-snake-case") @Test fun testUpperCamelCaseNamingByClassName() = runKotlin(""" @@ -128,5 +128,5 @@ class DatabaseNamingStrategyTest : BaseKspTest() { assert(UserProfiles.tableName == "USER_PROFILE") assert(UserProfiles.columns.map { it.name }.toSet() == setOf("ID", "PUBLIC_EMAIL", "PROFILE_PICTURE", "COMPANY_ID")) } - """.trimIndent(), "ktorm.dbNamingStrategy" to "org.ktorm.ksp.compiler.util.UpperSnakeCaseDatabaseNamingStrategy") + """.trimIndent(), emptyList(), "ktorm.dbNamingStrategy" to "org.ktorm.ksp.compiler.util.UpperSnakeCaseDatabaseNamingStrategy") } diff --git a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt index 25ed0b15..89fd6bfa 100644 --- a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt +++ b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt @@ -17,6 +17,7 @@ package org.ktorm.ksp.spi import com.google.devtools.ksp.symbol.KSClassDeclaration +import kotlin.reflect.KClass /** * Table definition metadata. @@ -66,5 +67,10 @@ public data class TableMetadata( /** * Columns in the table. */ - val columns: List + val columns: List, + + /** + * The super class of the table class in the generated code. + */ + val superClass: KClass<*> ) From 3322a32ed16052691da8491883dea55b0bc9171f Mon Sep 17 00:00:00 2001 From: qumn Date: Fri, 14 Jun 2024 14:01:02 +0800 Subject: [PATCH 2/3] update doc --- .../src/main/kotlin/org/ktorm/ksp/annotation/Table.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt index 42286587..b60359c3 100644 --- a/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt +++ b/ktorm-ksp-annotations/src/main/kotlin/org/ktorm/ksp/annotation/Table.kt @@ -89,7 +89,7 @@ public annotation class Table( * The super class of the generated table class. * * If not specified, the super class will be determined based on the class kind of the entity: - * `org.ktorm.schema.BaseTable` for interfaces, and `org.ktorm.schema.Table` for classes. + * `org.ktorm.schema.Table` for interfaces, and `org.ktorm.schema.BaseTable` for classes. */ val superClass: KClass> = Nothing::class ) From 513f06def044285e0773ebb607b4541fc58f82d0 Mon Sep 17 00:00:00 2001 From: qumn Date: Fri, 14 Jun 2024 14:00:50 +0800 Subject: [PATCH 3/3] workaround for the issue #1038 of ksp temp --- .../compiler/generator/TableClassGenerator.kt | 2 +- .../ksp/compiler/parser/MetadataParser.kt | 39 +++++++++++++++++-- .../kotlin/org/ktorm/ksp/spi/TableMetadata.kt | 4 +- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt index 692d077e..d10060d5 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/TableClassGenerator.kt @@ -47,7 +47,7 @@ internal object TableClassGenerator { } private fun TypeSpec.Builder.configureSuperClass(table: TableMetadata): TypeSpec.Builder { - superclass(table.superClass.asClassName().parameterizedBy(table.entityClass.toClassName())) + superclass(table.superClassName.parameterizedBy(table.entityClass.toClassName())) addSuperclassConstructorParameter("%S", table.name) addSuperclassConstructorParameter("alias") diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 97dfaa02..6668d6b7 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -19,8 +19,14 @@ package org.ktorm.ksp.compiler.parser import com.google.devtools.ksp.* import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.symbol.* import com.google.devtools.ksp.symbol.ClassKind.* +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSPropertyDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.toClassName import org.ktorm.entity.Entity import org.ktorm.ksp.annotation.* import org.ktorm.ksp.compiler.util.* @@ -32,6 +38,7 @@ import org.ktorm.schema.BaseTable import org.ktorm.schema.TypeReference import java.lang.reflect.InvocationTargetException import java.util.* +import kotlin.reflect.KClass import kotlin.reflect.jvm.jvmName @OptIn(KspExperimental::class) @@ -94,8 +101,19 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn _logger.info("[ktorm-ksp-compiler] parse table metadata from entity: $className") val table = cls.getAnnotationsByType(Table::class).first() - val superClass = table.superClass.takeIf { it != Nothing::class } - ?: if (cls.classKind == INTERFACE) org.ktorm.schema.Table::class else BaseTable::class + + // due to the BUG of KSP, we cannot use `table.superClass` directly, so we use `parseAnnotationClassParameter` to get the value + // https://github.com/google/ksp/issues/1038 + // TODO: remove the workaround after the bug is fixed + val annotationSuperClass = parseAnnotationClassParameter(table) + val superClass = if (annotationSuperClass != Nothing::class.asClassName()) { + annotationSuperClass + } else if(cls.classKind == INTERFACE) { + org.ktorm.schema.Table::class.asClassName() + }else { + BaseTable::class.asClassName() + } + val tableMetadata = TableMetadata( entityClass = cls, @@ -107,7 +125,7 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn entitySequenceName = table.entitySequenceName.ifEmpty { _codingNamingStrategy.getEntitySequenceName(cls) }, ignoreProperties = table.ignoreProperties.toSet(), columns = ArrayList(), - superClass = superClass + superClassName = superClass ) val columns = tableMetadata.columns as MutableList @@ -301,4 +319,17 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn stack.pop() } + + @OptIn(KspExperimental::class, KotlinPoetKspPreview::class) + private fun parseAnnotationClassParameter(table: Table): ClassName { + return try { + ClassName(table.superClass.java.`package`.name, table.superClass.simpleName.orEmpty()) + } catch (e: KSTypeNotPresentException) { + if (e.ksType.declaration is KSClassDeclaration){ + return (e.ksType.declaration as KSClassDeclaration).toClassName() + }else { + throw e + } + } + } } diff --git a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt index 89fd6bfa..8e60e0c8 100644 --- a/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt +++ b/ktorm-ksp-spi/src/main/kotlin/org/ktorm/ksp/spi/TableMetadata.kt @@ -17,7 +17,7 @@ package org.ktorm.ksp.spi import com.google.devtools.ksp.symbol.KSClassDeclaration -import kotlin.reflect.KClass +import com.squareup.kotlinpoet.ClassName /** * Table definition metadata. @@ -72,5 +72,5 @@ public data class TableMetadata( /** * The super class of the table class in the generated code. */ - val superClass: KClass<*> + val superClassName: ClassName )