From 8a5d96e154bf8599b202fd62d63be14950431f48 Mon Sep 17 00:00:00 2001 From: Poorva Gokhale Date: Sun, 31 Oct 2021 09:22:53 +0530 Subject: [PATCH] Poorva | Refactor codegenerator. (#207) Extract Command and Query classes --- cli/src/main/kotlin/norm/api/NormApi.kt | 4 +- .../test/kotlin/norm/test/utils/TestUtils.kt | 2 +- .../test/resources/gen/array-any.expected.txt | 12 +- .../gen/array-columns-mapping.expected.txt | 11 +- .../resources/gen/array-contains.expected.txt | 10 +- .../gen/empty-params-class.expected.txt | 13 +- .../left-joined-nullable-check.expected.txt | 20 +- .../gen/query-class-generator.expected.txt | 12 +- .../uuid-column-type-generator.expected.txt | 16 +- .../main/kotlin/norm/codegen/CodeGenerator.kt | 215 +++--------------- .../src/main/kotlin/norm/model/ColumnModel.kt | 11 +- codegen/src/main/kotlin/norm/model/Command.kt | 41 ++++ .../main/kotlin/norm/model/ParamBuilder.kt | 83 +++++++ .../main/kotlin/norm/model/ParamDetails.kt | 3 + .../src/main/kotlin/norm/model/ParamModel.kt | 11 +- codegen/src/main/kotlin/norm/model/Query.kt | 109 +++++++++ .../main/kotlin/norm/model/QueryDetails.kt | 3 + codegen/src/main/kotlin/norm/model/Sql.kt | 20 ++ 18 files changed, 359 insertions(+), 237 deletions(-) create mode 100644 codegen/src/main/kotlin/norm/model/Command.kt create mode 100644 codegen/src/main/kotlin/norm/model/ParamBuilder.kt create mode 100644 codegen/src/main/kotlin/norm/model/ParamDetails.kt create mode 100644 codegen/src/main/kotlin/norm/model/Query.kt create mode 100644 codegen/src/main/kotlin/norm/model/QueryDetails.kt create mode 100644 codegen/src/main/kotlin/norm/model/Sql.kt diff --git a/cli/src/main/kotlin/norm/api/NormApi.kt b/cli/src/main/kotlin/norm/api/NormApi.kt index f6133fe..23b4ac1 100644 --- a/cli/src/main/kotlin/norm/api/NormApi.kt +++ b/cli/src/main/kotlin/norm/api/NormApi.kt @@ -13,13 +13,11 @@ class NormApi( connection: Connection ) { private val sqlAnalyzer = SqlAnalyzer(connection) - private val codeGenerator = CodeGenerator() - /** * Generates code of classes against given SQL Query */ fun generate(query: String, packageName: String, baseName: String): String { val sqlModel = sqlAnalyzer.sqlModel(query) - return codeGenerator.generate(sqlModel, packageName, baseName) + return CodeGenerator.generate(listOf(sqlModel), packageName, baseName) } } diff --git a/cli/src/test/kotlin/norm/test/utils/TestUtils.kt b/cli/src/test/kotlin/norm/test/utils/TestUtils.kt index d0dbab9..e06067d 100644 --- a/cli/src/test/kotlin/norm/test/utils/TestUtils.kt +++ b/cli/src/test/kotlin/norm/test/utils/TestUtils.kt @@ -11,7 +11,7 @@ fun toArgs(str: String): Array = str.split(" ").toTypedArray() fun codegen(conn: Connection, query: String, pkg: String, base: String): String { val sqlModel = SqlAnalyzer(conn).sqlModel(query) - return CodeGenerator().generate(sqlModel, pkg, base).trim() + return CodeGenerator.generate(listOf(sqlModel), pkg, base).trim() } fun String.readAsResource(): String = object {}.javaClass.getResource(this).readText().trim() diff --git a/cli/src/test/resources/gen/array-any.expected.txt b/cli/src/test/resources/gen/array-any.expected.txt index f3ae6d3..61877dd 100644 --- a/cli/src/test/resources/gen/array-any.expected.txt +++ b/cli/src/test/resources/gen/array-any.expected.txt @@ -20,12 +20,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val firstName: String?, - public val lastName: String? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -40,3 +34,9 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: Int, + public val firstName: String?, + public val lastName: String? +) diff --git a/cli/src/test/resources/gen/array-columns-mapping.expected.txt b/cli/src/test/resources/gen/array-columns-mapping.expected.txt index 3a94d4f..876837d 100644 --- a/cli/src/test/resources/gen/array-columns-mapping.expected.txt +++ b/cli/src/test/resources/gen/array-columns-mapping.expected.txt @@ -18,12 +18,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val colors: Array?, - public val details: PGobject? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -39,3 +33,8 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } +public data class FooResult( + public val id: Int, + public val colors: Array?, + public val details: PGobject? +) diff --git a/cli/src/test/resources/gen/array-contains.expected.txt b/cli/src/test/resources/gen/array-contains.expected.txt index 2375edc..794ff25 100644 --- a/cli/src/test/resources/gen/array-contains.expected.txt +++ b/cli/src/test/resources/gen/array-contains.expected.txt @@ -20,11 +20,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val colors: Array? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -38,3 +33,8 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: Int, + public val colors: Array? +) diff --git a/cli/src/test/resources/gen/empty-params-class.expected.txt b/cli/src/test/resources/gen/empty-params-class.expected.txt index 46dbd85..f98e9ff 100644 --- a/cli/src/test/resources/gen/empty-params-class.expected.txt +++ b/cli/src/test/resources/gen/empty-params-class.expected.txt @@ -16,12 +16,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val firstName: String?, - public val lastName: String? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -36,3 +30,10 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: Int, + public val firstName: String?, + public val lastName: String? +) + diff --git a/cli/src/test/resources/gen/left-joined-nullable-check.expected.txt b/cli/src/test/resources/gen/left-joined-nullable-check.expected.txt index 643ce86..607b8fb 100644 --- a/cli/src/test/resources/gen/left-joined-nullable-check.expected.txt +++ b/cli/src/test/resources/gen/left-joined-nullable-check.expected.txt @@ -17,16 +17,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val firstName: String?, - public val lastName: String?, - public val departmentId: Int?, - public val name: String?, - public val combinationsId: Int?, - public val colors: Array? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -50,3 +40,13 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: Int, + public val firstName: String?, + public val lastName: String?, + public val departmentId: Int?, + public val name: String?, + public val combinationsId: Int?, + public val colors: Array? +) diff --git a/cli/src/test/resources/gen/query-class-generator.expected.txt b/cli/src/test/resources/gen/query-class-generator.expected.txt index 58f423f..21c2b69 100644 --- a/cli/src/test/resources/gen/query-class-generator.expected.txt +++ b/cli/src/test/resources/gen/query-class-generator.expected.txt @@ -21,12 +21,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: Int, - public val firstName: String?, - public val lastName: String? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as kotlin.Int, @@ -41,3 +35,9 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: Int, + public val firstName: String?, + public val lastName: String? +) diff --git a/cli/src/test/resources/gen/uuid-column-type-generator.expected.txt b/cli/src/test/resources/gen/uuid-column-type-generator.expected.txt index 004281a..bbbe8cd 100644 --- a/cli/src/test/resources/gen/uuid-column-type-generator.expected.txt +++ b/cli/src/test/resources/gen/uuid-column-type-generator.expected.txt @@ -17,14 +17,6 @@ public class FooParamSetter : ParamSetter { } } -public data class FooResult( - public val id: UUID, - public val columnName: String?, - public val oldValue: String?, - public val newValue: String?, - public val capturedAt: OffsetDateTime? -) - public class FooRowMapper : RowMapper { public override fun map(rs: ResultSet): FooResult = FooResult( id = rs.getObject("id") as java.util.UUID, @@ -41,3 +33,11 @@ public class FooQuery : Query { public override val paramSetter: ParamSetter = FooParamSetter() } + +public data class FooResult( + public val id: UUID, + public val columnName: String?, + public val oldValue: String?, + public val newValue: String?, + public val capturedAt: OffsetDateTime? +) diff --git a/codegen/src/main/kotlin/norm/codegen/CodeGenerator.kt b/codegen/src/main/kotlin/norm/codegen/CodeGenerator.kt index 798b2b1..b901175 100644 --- a/codegen/src/main/kotlin/norm/codegen/CodeGenerator.kt +++ b/codegen/src/main/kotlin/norm/codegen/CodeGenerator.kt @@ -1,206 +1,53 @@ package norm.codegen import com.squareup.kotlinpoet.* -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import norm.model.ColumnModel -import norm.model.ParamModel -import norm.model.SqlModel -import norm.typemapper.DbToKtTypeMapperFactory +import com.squareup.kotlinpoet.FileSpec.Builder +import norm.model.* -class CodeGenerator(private val typeMapper: DbToKtTypeMapperFactory = DbToKtTypeMapperFactory) { - - fun generate(sqlModel: SqlModel, packageName: String, baseName: String): String { - - val (params, cols, preparableStmt) = sqlModel - val paramsClassName = "${baseName}Params" - val paramSetterClassName = "${baseName}ParamSetter" +object CodeGenerator { + fun generate(sqlModels: List, packageName: String, baseName: String): String { val fileBuilder = FileSpec.builder(packageName, baseName) + var resultClassName: String? = null + sqlModels.map { sqlModel -> + val paramDetails = ParamBuilder.build(baseName, fileBuilder, packageName, sqlModel.params) + Sql.make(sqlModel.cols).generate(baseName, fileBuilder, packageName, sqlModel, paramDetails) + if (sqlModel.cols.isNotEmpty() && resultClassName.isNullOrEmpty()) { + resultClassName = "${baseName}Result" + buildResult(fileBuilder, packageName, resultClassName!!, sqlModel.cols) + } + } + return fileBuilder.build().toString() + } + private fun buildResult( + fileBuilder: Builder, + packageName: String, + resultClassName: String, + cols: List + ) { fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, paramsClassName)) - .also { if (params.isNotEmpty()) it.addModifiers(KModifier.DATA) } + TypeSpec.classBuilder(ClassName(packageName, resultClassName)) + .addModifiers(KModifier.DATA) .primaryConstructor( FunSpec.constructorBuilder() .addParameters( - params.distinctBy { it.name }.map { - ParameterSpec.builder(it.name, getTypeName(it)).build() + cols.map { + ParameterSpec.builder( + it.fieldName, + it.getTypeName() + ).build() } ).build() ) .addProperties( - params.distinctBy { it.name }.map { - PropertySpec.builder(it.name, getTypeName(it)) - .initializer(it.name) + cols.map { + PropertySpec.builder(it.fieldName, it.getTypeName()) + .initializer(it.fieldName) .build() } ) .build() ) - - fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, paramSetterClassName)) - .addSuperinterface( - ClassName("norm", "ParamSetter") - .parameterizedBy(ClassName(packageName, paramsClassName)) - ) - .addFunction( - FunSpec.builder("map") - .addModifiers(KModifier.OVERRIDE) - .addParameter("ps", ClassName("java.sql", "PreparedStatement")) - .addParameter("params", ClassName(packageName, paramsClassName)) - .also { addStatementsForParams(it, params) } - .build() - ) - .build() - ) - - if (cols.isEmpty()) { // command - val commandClassName = "${baseName}Command" - - fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, commandClassName)) - .addSuperinterface( - ClassName("norm", "Command") - .parameterizedBy(ClassName(packageName, paramsClassName)) - ) - .addProperty( - PropertySpec.builder("sql", String::class) - .addModifiers(KModifier.OVERRIDE) - .initializer("%S", preparableStmt) - .build() - ) - .addProperty( - PropertySpec.builder( - "paramSetter", - ClassName("norm", "ParamSetter") - .parameterizedBy(ClassName(packageName, paramsClassName)) - ) - .addModifiers(KModifier.OVERRIDE) - .initializer("%T()", ClassName(packageName, paramSetterClassName)) - .build() - ) - .build() - ) - } else { // query - val resultClassName = "${baseName}Result" - val queryClassName = "${baseName}Query" - val rowMapperClassName = "${baseName}RowMapper" - - fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, resultClassName)) - .addModifiers(KModifier.DATA) - .primaryConstructor( - FunSpec.constructorBuilder() - .addParameters( - cols.map { - ParameterSpec.builder( - it.fieldName, - getTypeName(it) - ).build() - } - ).build() - ) - .addProperties( - cols.map { - PropertySpec.builder(it.fieldName, getTypeName(it)) - .initializer(it.fieldName) - .build() - } - ) - .build() - ) - - val constructArgs = "\n" + cols.joinToString(",\n ") { - if (it.colType.startsWith("_")) - "${it.fieldName} = rs.getArray(\"${it.colName}\")${if (it.isNullable) "?" else ""}.array as ${ - getTypeName( - it - ) - }" - else - "${it.fieldName} = rs.getObject(\"${it.colName}\") as ${getTypeName(it)}" - } - - fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, rowMapperClassName)) - .addSuperinterface( - ClassName("norm", "RowMapper") - .parameterizedBy(ClassName(packageName, resultClassName)) - ) - .addFunction( - FunSpec.builder("map") - .addModifiers(KModifier.OVERRIDE) - .addParameter("rs", ClassName("java.sql", "ResultSet")) - .addStatement("return %T($constructArgs)", ClassName(packageName, resultClassName)) - .returns(ClassName(packageName, resultClassName)) - .build() - ) - .build() - ) - - fileBuilder.addType( - TypeSpec.classBuilder(ClassName(packageName, queryClassName)) - .addSuperinterface( - ClassName("norm", "Query") - .parameterizedBy( - ClassName(packageName, paramsClassName), - ClassName(packageName, resultClassName) - ) - ) - .addProperty( - PropertySpec.builder("sql", String::class) - .addModifiers(KModifier.OVERRIDE) - .initializer("%S", preparableStmt) - .build() - ).addProperty( - PropertySpec.builder( - "mapper", - ClassName("norm", "RowMapper") - .parameterizedBy(ClassName(packageName, resultClassName)) - ) - .addModifiers(KModifier.OVERRIDE) - .initializer("%T()", ClassName(packageName, rowMapperClassName)) - .build() - ) - .addProperty( - PropertySpec.builder( - "paramSetter", - ClassName("norm", "ParamSetter") - .parameterizedBy(ClassName(packageName, paramsClassName)) - ) - .addModifiers(KModifier.OVERRIDE) - .initializer("%T()", ClassName(packageName, paramSetterClassName)) - .build() - ) - .build() - ) - } - - return fileBuilder.build().toString() } - - private fun getTypeName(it: ColumnModel) = - if (it.colType.startsWith("_")) ARRAY.parameterizedBy(typeMapper.getType(it.colType, false)) - .copy(nullable = it.isNullable) - else typeMapper.getType(it.colType, it.isNullable) - - private fun getTypeName(it: ParamModel) = - if (it.dbType.startsWith("_")) ARRAY.parameterizedBy(typeMapper.getType(it.dbType, false)) - .copy(nullable = it.isNullable) - else typeMapper.getType(it.dbType, it.isNullable) - - private fun addStatementsForParams(fb: FunSpec.Builder, params: List) = - params.forEachIndexed { i, pm -> - when { - pm.dbType.startsWith("_") -> fb.addStatement( - "ps.setArray(${i + 1}, ps.connection.createArrayOf(\"${ - pm.dbType.removePrefix( - "_" - ) - }\", params.${pm.name}))" - ) - else -> fb.addStatement("ps.setObject(${i + 1}, params.${pm.name})") - } - } } diff --git a/codegen/src/main/kotlin/norm/model/ColumnModel.kt b/codegen/src/main/kotlin/norm/model/ColumnModel.kt index f4370a3..c60b3a0 100644 --- a/codegen/src/main/kotlin/norm/model/ColumnModel.kt +++ b/codegen/src/main/kotlin/norm/model/ColumnModel.kt @@ -1,8 +1,17 @@ package norm.model +import com.squareup.kotlinpoet.ARRAY +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import norm.typemapper.DbToKtTypeMapperFactory + data class ColumnModel( val fieldName: String, val colType: String, val colName: String, val isNullable: Boolean -) +) { + fun getTypeName() = + if (colType.startsWith("_")) ARRAY.parameterizedBy(DbToKtTypeMapperFactory.getType(colType, false)) + .copy(nullable = this.isNullable) + else DbToKtTypeMapperFactory.getType(colType, isNullable) +} diff --git a/codegen/src/main/kotlin/norm/model/Command.kt b/codegen/src/main/kotlin/norm/model/Command.kt new file mode 100644 index 0000000..4deeeaf --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/Command.kt @@ -0,0 +1,41 @@ +package norm.model + +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy + +object Command : Sql { + override fun generate( + baseName: String, + fileBuilder: FileSpec.Builder, + packageName: String, + sqlModel: SqlModel, + paramDetails: ParamDetails + ) { + val commandClassName = "${baseName}Command" + + fileBuilder.addType( + TypeSpec.classBuilder(ClassName(packageName, commandClassName)) + .addSuperinterface( + ClassName("norm", "Command") + .parameterizedBy(ClassName(packageName, paramDetails.paramClassName)) + ) + .addProperty( + PropertySpec.builder("sql", String::class) + .addModifiers(KModifier.OVERRIDE) + .initializer("%S", sqlModel.preparableStatement) + .build() + ) + .addProperty( + PropertySpec.builder( + "paramSetter", + ClassName("norm", "ParamSetter") + .parameterizedBy(ClassName(packageName, paramDetails.paramClassName)) + ) + .addModifiers(KModifier.OVERRIDE) + .initializer("%T()", ClassName(packageName, paramDetails.paramSetterClassName)) + .build() + ) + .build() + ) + } +} diff --git a/codegen/src/main/kotlin/norm/model/ParamBuilder.kt b/codegen/src/main/kotlin/norm/model/ParamBuilder.kt new file mode 100644 index 0000000..54c0c84 --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/ParamBuilder.kt @@ -0,0 +1,83 @@ +package norm.model + +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy + +object ParamBuilder { + fun build(baseName: String, fileBuilder: FileSpec.Builder, packageName: String, params: List): ParamDetails { + val paramsClassName = "${baseName}Params" + val paramSetterClassName = "${baseName}ParamSetter" + + buildParam(fileBuilder, packageName, paramsClassName, params) + buildParamSetter(fileBuilder, packageName, paramSetterClassName, paramsClassName, params) + + return ParamDetails(paramsClassName, paramSetterClassName) + } + + private fun addStatementsForParams(fb: FunSpec.Builder, params: List) = + params.forEachIndexed { i, pm -> + when { + pm.dbType.startsWith("_") -> fb.addStatement( + "ps.setArray(${i + 1}, ps.connection.createArrayOf(\"${ + pm.dbType.removePrefix( + "_" + ) + }\", params.${pm.name}))" + ) + else -> fb.addStatement("ps.setObject(${i + 1}, params.${pm.name})") + } + } + + private fun buildParamSetter( + fileBuilder: FileSpec.Builder, + packageName: String, + paramSetterClassName: String, + paramsClassName: String, + params: List + ) { + fileBuilder.addType( + TypeSpec.classBuilder(ClassName(packageName, paramSetterClassName)) + .addSuperinterface( + ClassName("norm", "ParamSetter") + .parameterizedBy(ClassName(packageName, paramsClassName)) + ) + .addFunction( + FunSpec.builder("map") + .addModifiers(KModifier.OVERRIDE) + .addParameter("ps", ClassName("java.sql", "PreparedStatement")) + .addParameter("params", ClassName(packageName, paramsClassName)) + .also { addStatementsForParams(it, params) } + .build() + ) + .build() + ) + } + + private fun buildParam( + fileBuilder: FileSpec.Builder, + packageName: String, + paramsClassName: String, + params: List + ) { + fileBuilder.addType( + TypeSpec.classBuilder(ClassName(packageName, paramsClassName)) + .also { if (params.isNotEmpty()) it.addModifiers(KModifier.DATA) } + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameters( + params.distinctBy { it.name }.map { + ParameterSpec.builder(it.name, it.getTypeName()).build() + } + ).build() + ) + .addProperties( + params.distinctBy { it.name }.map { + PropertySpec.builder(it.name, it.getTypeName()) + .initializer(it.name) + .build() + } + ) + .build() + ) + } +} diff --git a/codegen/src/main/kotlin/norm/model/ParamDetails.kt b/codegen/src/main/kotlin/norm/model/ParamDetails.kt new file mode 100644 index 0000000..3990400 --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/ParamDetails.kt @@ -0,0 +1,3 @@ +package norm.model + +data class ParamDetails(val paramClassName: String, val paramSetterClassName: String) diff --git a/codegen/src/main/kotlin/norm/model/ParamModel.kt b/codegen/src/main/kotlin/norm/model/ParamModel.kt index 1f6d453..e8910a9 100644 --- a/codegen/src/main/kotlin/norm/model/ParamModel.kt +++ b/codegen/src/main/kotlin/norm/model/ParamModel.kt @@ -1,7 +1,16 @@ package norm.model +import com.squareup.kotlinpoet.ARRAY +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import norm.typemapper.DbToKtTypeMapperFactory + data class ParamModel( val name: String, val dbType: String, val isNullable: Boolean -) +) { + fun getTypeName() = + if (dbType.startsWith("_")) ARRAY.parameterizedBy(DbToKtTypeMapperFactory.getType(dbType, false)) + .copy(nullable = this.isNullable) + else DbToKtTypeMapperFactory.getType(dbType, isNullable) +} diff --git a/codegen/src/main/kotlin/norm/model/Query.kt b/codegen/src/main/kotlin/norm/model/Query.kt new file mode 100644 index 0000000..1ac238e --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/Query.kt @@ -0,0 +1,109 @@ +package norm.model + +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import norm.typemapper.DbToKtTypeMapperFactory + +object Query : Sql { + override fun generate( + baseName: String, + fileBuilder: FileSpec.Builder, + packageName: String, + sqlModel: SqlModel, + paramDetails: ParamDetails + ) { + val queryClassName = "${baseName}Query" + val rowMapperClassName = "${baseName}RowMapper" + val resultClassName = "${baseName}Result" + + val constructArgs = "\n" + sqlModel.cols.joinToString(",\n ") { + if (it.colType.startsWith("_")) + "${it.fieldName} = rs.getArray(\"${it.colName}\")${if (it.isNullable) "?" else ""}.array as ${ + getTypeName( + it + ) + }" + else + "${it.fieldName} = rs.getObject(\"${it.colName}\") as ${getTypeName(it)}" + } + + val queryDetails = QueryDetails(queryClassName, resultClassName, rowMapperClassName) + buildRowMapper(fileBuilder, packageName, queryDetails, constructArgs) + buildQuery(fileBuilder, packageName, sqlModel.preparableStatement, paramDetails, queryDetails) + } + + private fun getTypeName(it: ColumnModel) = + if (it.colType.startsWith("_")) ARRAY.parameterizedBy(DbToKtTypeMapperFactory.getType(it.colType, false)) + .copy(nullable = it.isNullable) + else DbToKtTypeMapperFactory.getType(it.colType, it.isNullable) + + private fun buildRowMapper( + fileBuilder: FileSpec.Builder, + packageName: String, + queryDetails: QueryDetails, + constructArgs: String + ) { + fileBuilder.addType( + TypeSpec.classBuilder(ClassName(packageName, queryDetails.rowMapperClassName)) + .addSuperinterface( + ClassName("norm", "RowMapper") + .parameterizedBy(ClassName(packageName, queryDetails.resultClassName)) + ) + .addFunction( + FunSpec.builder("map") + .addModifiers(KModifier.OVERRIDE) + .addParameter("rs", ClassName("java.sql", "ResultSet")) + .addStatement("return %T($constructArgs)", ClassName(packageName, queryDetails.resultClassName)) + .returns(ClassName(packageName, queryDetails.resultClassName)) + .build() + ) + .build() + ) + } + + private fun buildQuery( + fileBuilder: FileSpec.Builder, + packageName: String, + preparableStmt: String, + paramDetails: ParamDetails, + queryDetails: QueryDetails + ) { + fileBuilder.addType( + TypeSpec.classBuilder(ClassName(packageName, queryDetails.queryClassName)) + .addSuperinterface( + ClassName("norm", "Query") + .parameterizedBy( + ClassName(packageName, paramDetails.paramClassName), + ClassName(packageName, queryDetails.resultClassName) + ) + ) + .addProperty( + PropertySpec.builder("sql", String::class) + .addModifiers(KModifier.OVERRIDE) + .initializer("%S", preparableStmt) + .build() + ).addProperty( + PropertySpec.builder( + "mapper", + ClassName("norm", "RowMapper") + .parameterizedBy(ClassName(packageName, queryDetails.resultClassName)) + ) + .addModifiers(KModifier.OVERRIDE) + .initializer("%T()", ClassName(packageName, queryDetails.rowMapperClassName)) + .build() + ) + .addProperty( + PropertySpec.builder( + "paramSetter", + ClassName("norm", "ParamSetter") + .parameterizedBy(ClassName(packageName, paramDetails.paramClassName)) + ) + .addModifiers(KModifier.OVERRIDE) + .initializer("%T()", ClassName(packageName, paramDetails.paramSetterClassName)) + .build() + ) + .build() + ) + } + +} diff --git a/codegen/src/main/kotlin/norm/model/QueryDetails.kt b/codegen/src/main/kotlin/norm/model/QueryDetails.kt new file mode 100644 index 0000000..c19b9d0 --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/QueryDetails.kt @@ -0,0 +1,3 @@ +package norm.model + +data class QueryDetails(val queryClassName: String, val resultClassName: String, val rowMapperClassName: String) diff --git a/codegen/src/main/kotlin/norm/model/Sql.kt b/codegen/src/main/kotlin/norm/model/Sql.kt new file mode 100644 index 0000000..a842edc --- /dev/null +++ b/codegen/src/main/kotlin/norm/model/Sql.kt @@ -0,0 +1,20 @@ +package norm.model + +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.FileSpec.Builder +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import norm.typemapper.DbToKtTypeMapperFactory + +interface Sql { + fun generate( + baseName: String, + fileBuilder: Builder, + packageName: String, + sqlModel: SqlModel, + paramDetails: ParamDetails + ) + + companion object { + fun make(col: List): Sql = if(col.isEmpty()) Command else Query + } +}