diff --git a/pom.xml b/pom.xml index 18170b5c61..d4d12c1fad 100644 --- a/pom.xml +++ b/pom.xml @@ -101,11 +101,11 @@ 5.3 - 5.12.0 + 5.12.1 3.33.0 3.2.1 - 3.4.3 - 6.2.3 + 3.4.4 + 6.2.5 12.1.0.0 @@ -115,7 +115,7 @@ 42.7.5 23.7.0.25.01 9.2.0 - 12.9.0.jre8-preview + 12.10.0.jre8 9.3.9.0002 3.49.1.0 13.10.00.35 @@ -125,7 +125,7 @@ 1.0.0.RELEASE - 6.6.10.Final + 6.6.12.Final 8.0.2.Final 4.0.5 3.1.0 @@ -133,22 +133,22 @@ 3.27.3 3.9.9 - 33.4.0-jre + 33.4.6-jre 0.6.8 0.2.4 - 1.17.2 + 1.17.5 2.0.17 - 3.5.2 + 3.5.3 1.24 2.2.3 1.3.2 1.37 - 2.1.10 - 2.1.10-1.0.31 + 2.1.20 + 2.1.20-1.0.32 2.1.0 2.0.0 2.11.12 - 9.7.1 + 9.8 ${project.version} @@ -222,7 +222,7 @@ org.geolatte geolatte-geom - 1.9.1 + 1.10 org.jetbrains.kotlin @@ -487,7 +487,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 prepare-agent @@ -1009,18 +1009,18 @@ org.openrewrite.maven rewrite-maven-plugin - 6.3.0 + 6.4.0 org.openrewrite.recipe rewrite-testing-frameworks - 3.4.0 + 3.5.1 org.openrewrite.recipe rewrite-migrate-java - 3.4.0 + 3.5.0 diff --git a/querydsl-examples/querydsl-example-jpa-quarkus/pom.xml b/querydsl-examples/querydsl-example-jpa-quarkus/pom.xml index 4145ec23d5..e519e210c5 100644 --- a/querydsl-examples/querydsl-example-jpa-quarkus/pom.xml +++ b/querydsl-examples/querydsl-example-jpa-quarkus/pom.xml @@ -12,7 +12,7 @@ Querydsl example - JPA Quarkus - 3.19.2 + 3.21.1 diff --git a/querydsl-examples/querydsl-example-ksp-codegen/src/main/kotlin/com/querydsl/example/ksp/PersonDTO.kt b/querydsl-examples/querydsl-example-ksp-codegen/src/main/kotlin/com/querydsl/example/ksp/PersonDTO.kt new file mode 100644 index 0000000000..e1399c5b50 --- /dev/null +++ b/querydsl-examples/querydsl-example-ksp-codegen/src/main/kotlin/com/querydsl/example/ksp/PersonDTO.kt @@ -0,0 +1,11 @@ +package com.querydsl.example.ksp + +import com.querydsl.core.annotations.QueryProjection + +data class PersonClassConstructorDTO @QueryProjection constructor( + val id: Int, + val name: String, +) + +@QueryProjection +data class PersonClassDTO (val id: Int, val name: String) diff --git a/querydsl-examples/querydsl-example-ksp-codegen/src/test/kotlin/Tests.kt b/querydsl-examples/querydsl-example-ksp-codegen/src/test/kotlin/Tests.kt index a847f8b89e..04b835cbf7 100644 --- a/querydsl-examples/querydsl-example-ksp-codegen/src/test/kotlin/Tests.kt +++ b/querydsl-examples/querydsl-example-ksp-codegen/src/test/kotlin/Tests.kt @@ -3,6 +3,8 @@ import com.querydsl.example.ksp.CatType import com.querydsl.example.ksp.Person import com.querydsl.example.ksp.QCat import com.querydsl.example.ksp.QPerson +import com.querydsl.example.ksp.QPersonClassDTO +import com.querydsl.example.ksp.QPersonClassConstructorDTO import com.querydsl.jpa.impl.JPAQueryFactory import jakarta.persistence.EntityManagerFactory import org.hibernate.cfg.AvailableSettings @@ -111,6 +113,55 @@ class Tests { } } + @Test + fun `select dto`() { + val emf = initialize() + + run { + val em = emf.createEntityManager() + em.transaction.begin() + em.persist(Person(424, "John Smith")) + em.transaction.commit() + em.close() + } + + run { + val em = emf.createEntityManager() + val queryFactory = JPAQueryFactory(em) + val q = QPerson.person + val personDTO = queryFactory + .select(QPersonClassConstructorDTO(q.id, q.name)) + .from(q) + .where(q.name.eq("John Smith")) + .fetchOne() + if (personDTO == null) { + fail("No personDTO was returned") + } else { + assertThat(personDTO.id).isEqualTo(424) + assertThat(personDTO.name).isEqualTo("John Smith") + } + em.close() + } + + run { + val em = emf.createEntityManager() + val queryFactory = JPAQueryFactory(em) + val q = QPerson.person + val personDTO = queryFactory + .select(QPersonClassDTO(q.id, q.name)) + .from(q) + .where(q.name.eq("John Smith")) + .fetchOne() + if (personDTO == null) { + fail("No personDTO was returned") + } else { + assertThat(personDTO.id).isEqualTo(424) + assertThat(personDTO.name).isEqualTo("John Smith") + } + em.close() + } + } + private fun initialize(): EntityManagerFactory { val configuration = Configuration() .setProperty(AvailableSettings.JAKARTA_JDBC_DRIVER, org.h2.Driver::class.qualifiedName!!) diff --git a/querydsl-examples/querydsl-example-sql-spring/pom.xml b/querydsl-examples/querydsl-example-sql-spring/pom.xml index 971a10c7be..5b6996d9da 100644 --- a/querydsl-examples/querydsl-example-sql-spring/pom.xml +++ b/querydsl-examples/querydsl-example-sql-spring/pom.xml @@ -32,7 +32,7 @@ org.projectlombok lombok - 1.18.36 + 1.18.38 provided diff --git a/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLSerializer.java b/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLSerializer.java index d780470bdd..10779004c4 100644 --- a/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLSerializer.java +++ b/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLSerializer.java @@ -65,7 +65,7 @@ public class JPQLSerializer extends SerializerBase { Ops.ADD, Ops.SUB, Ops.MULT, Ops.DIV, Ops.LT, Ops.LOE, Ops.GT, Ops.GOE, Ops.BETWEEN)); private static final Set CASE_OPS = - Collections.unmodifiableSet(EnumSet.of(Ops.CASE_ELSE)); + Collections.unmodifiableSet(EnumSet.of(Ops.CASE_WHEN, Ops.CASE_ELSE)); private static final String COMMA = ", "; @@ -383,6 +383,10 @@ private void serializeSources(boolean forCountRow, List joins) { @Override public void visitConstant(Object constant) { + if (inCaseOperation && constant instanceof Enum) { + visitLiteral(constant); + return; + } if (inCaseOperation && templates.isCaseWithLiterals()) { if (constant instanceof Collection) { append("("); diff --git a/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java b/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java index 8f0d3e73b9..acee3973b5 100644 --- a/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java +++ b/querydsl-libraries/querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLTemplates.java @@ -213,7 +213,7 @@ public String asLiteral(Object constant) { } else if (constant instanceof String) { return "'" + escapeLiteral(constant.toString()) + "'"; } else if (constant instanceof Enum) { - return constant.getClass().getName() + "." + ((Enum) constant).name(); + return "'" + ((Enum) constant).name() + "'"; } else { return "'" + constant.toString() + "'"; } diff --git a/querydsl-libraries/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java b/querydsl-libraries/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java index 3040899b07..06eeb5c91f 100644 --- a/querydsl-libraries/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java +++ b/querydsl-libraries/querydsl-jpa/src/test/java/com/querydsl/jpa/JPQLSerializerTest.java @@ -19,19 +19,13 @@ import com.querydsl.core.DefaultQueryMetadata; import com.querydsl.core.JoinType; import com.querydsl.core.QueryMetadata; -import com.querydsl.core.domain.QAnimal; import com.querydsl.core.domain.QCat; import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.Expression; import com.querydsl.core.types.Path; import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.EntityPathBase; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberPath; -import com.querydsl.jpa.domain.JobFunction; -import com.querydsl.jpa.domain.Location; -import com.querydsl.jpa.domain.QDomesticCat; -import com.querydsl.jpa.domain.QEmployee; +import com.querydsl.core.types.dsl.*; +import com.querydsl.jpa.domain.*; import java.util.Arrays; import org.junit.Test; @@ -67,7 +61,7 @@ public void case1_hibernate() { Expressions.cases().when(cat.toes.eq(2)).then(2).when(cat.toes.eq(3)).then(3).otherwise(4); serializer.handle(expr); assertThat(serializer.toString()) - .isEqualTo("case when (cat.toes = ?1) then ?2 when (cat.toes = ?3) then ?4 else 4 end"); + .isEqualTo("case when (cat.toes = ?1) then 2 when (cat.toes = ?2) then 3 else 4 end"); } @Test @@ -337,13 +331,6 @@ public void visitLiteral_string() { assertThat(serializer).hasToString("'abc''''def'"); } - @Test - public void visitLiteral_enum() { - var serializer = new JPQLSerializer(HQLTemplates.DEFAULT); - serializer.visitLiteral(JobFunction.MANAGER); - assertThat(serializer).hasToString("com.querydsl.jpa.domain.JobFunction.MANAGER"); - } - @Test public void substring_indexOf() { var cat = QCat.cat; @@ -351,4 +338,31 @@ public void substring_indexOf() { cat.name.substring(cat.name.indexOf("")).accept(serializer, null); assertThat(serializer).hasToString("substring(cat.name,locate(?1,cat.name)-1 + ?2)"); } + + @Test + public void case_enumConversion() { + var serializer = new JPQLSerializer(JPQLTemplates.DEFAULT); + + Expression expr = + new CaseBuilder() + .when(Expressions.TRUE) + .then(JobFunction.MANAGER) + .otherwise(JobFunction.CONSULTANT); + + serializer.handle(expr); + + assertThat(serializer.toString()) + .isEqualTo("case when true then 'MANAGER' else 'CONSULTANT' end"); + } + + @Test + public void inClause_enumCollection() { + QAnimal animal = QAnimal.animal; + Expression predicate = animal.color.in(Arrays.asList(Color.BLACK, Color.TABBY)); + JPQLSerializer serializer = new JPQLSerializer(JPQLTemplates.DEFAULT); + serializer.handle(predicate); + assertThat(serializer.toString()).isEqualTo("animal.color in ?1"); + Object constant = serializer.getConstants().get(0); + assertThat(constant.toString()).isEqualTo("[BLACK, TABBY]"); + } } diff --git a/querydsl-libraries/querydsl-scala/pom.xml b/querydsl-libraries/querydsl-scala/pom.xml index c5e1a1c8ea..d2e96a9d6a 100644 --- a/querydsl-libraries/querydsl-scala/pom.xml +++ b/querydsl-libraries/querydsl-scala/pom.xml @@ -153,7 +153,7 @@ net.alchim31.maven scala-maven-plugin - 4.9.2 + 4.9.5 ${character.encoding} ${scala.version} diff --git a/querydsl-tooling/querydsl-apt/pom.xml b/querydsl-tooling/querydsl-apt/pom.xml index 77617d4308..1ecf47e180 100644 --- a/querydsl-tooling/querydsl-apt/pom.xml +++ b/querydsl-tooling/querydsl-apt/pom.xml @@ -100,7 +100,7 @@ org.joda joda-money - 2.0.1 + 2.0.2 test diff --git a/querydsl-tooling/querydsl-codegen-utils/src/main/java/com/querydsl/codegen/utils/model/SimpleType.java b/querydsl-tooling/querydsl-codegen-utils/src/main/java/com/querydsl/codegen/utils/model/SimpleType.java index be74b84277..832a3c1d82 100644 --- a/querydsl-tooling/querydsl-codegen-utils/src/main/java/com/querydsl/codegen/utils/model/SimpleType.java +++ b/querydsl-tooling/querydsl-codegen-utils/src/main/java/com/querydsl/codegen/utils/model/SimpleType.java @@ -103,7 +103,7 @@ public SimpleType( this.fullName = fullName; this.packageName = packageName; this.simpleName = simpleName; - if (packageName.length() > 0) { + if (packageName.length() > 0 && fullName.length() > packageName.length()) { this.localName = fullName.substring(packageName.length() + 1); } else { this.localName = fullName; diff --git a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryDslProcessor.kt b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryDslProcessor.kt index 545d7469f2..5d78c965f0 100644 --- a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryDslProcessor.kt +++ b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryDslProcessor.kt @@ -1,5 +1,6 @@ package com.querydsl.ksp.codegen +import com.google.devtools.ksp.isConstructor import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor @@ -17,9 +18,32 @@ class QueryDslProcessor( if (settings.enable) { QueryModelType.entries.forEach { type -> resolver.getSymbolsWithAnnotation(type.associatedAnnotation) - .map { it as KSClassDeclaration } - .filter { isIncluded(it) } - .forEach { declaration -> typeProcessor.add(declaration, type) } + .map { declaration -> + when { + type == QueryModelType.QUERY_PROJECTION -> { + val errorMessage = "${type.associatedAnnotation} annotation" + + " must be declared on a constructor function or class" + when (declaration) { + is KSFunctionDeclaration -> { + if (!declaration.isConstructor()) error(errorMessage) + declaration.parent as? KSClassDeclaration + ?: error(errorMessage) + } + + is KSClassDeclaration -> declaration + else -> error(errorMessage) + } + } + + else -> declaration as KSClassDeclaration + } + } + .filter { + isIncluded(it) + } + .forEach { declaration -> + typeProcessor.add(declaration, type) + } } } return emptyList() diff --git a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelRenderer.kt b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelRenderer.kt index 29b9498c52..231ebc06af 100644 --- a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelRenderer.kt +++ b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelRenderer.kt @@ -1,7 +1,9 @@ package com.querydsl.ksp.codegen +import com.querydsl.core.types.ConstructorExpression import com.querydsl.core.types.Path import com.querydsl.core.types.PathMetadata +import com.querydsl.core.types.Expression import com.querydsl.core.types.dsl.* import com.querydsl.ksp.codegen.Naming.toCamelCase import com.squareup.kotlinpoet.* @@ -9,17 +11,25 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy object QueryModelRenderer { fun render(model: QueryModel): TypeSpec { - return TypeSpec.classBuilder(model.className) - .setEntitySuperclass(model) - .addSuperProperty(model) - .addProperties(model) - .constructorForPath(model) - .constructorForMetadata(model) - .constructorForVariable(model) - .constructorForTypeMetadata(model) - .addInitializerCompanionObject(model) - .addInheritedProperties(model) - .build() + return when (model.type) { + QueryModelType.QUERY_PROJECTION -> TypeSpec.classBuilder(model.className) + .setPrimaryConstructor(model) + .setEntitySuperclass(model) + .addSuperConstructorParameter(model) + .build() + + else -> TypeSpec.classBuilder(model.className) + .setEntitySuperclass(model) + .addSuperProperty(model) + .addProperties(model) + .constructorForPath(model) + .constructorForMetadata(model) + .constructorForVariable(model) + .constructorForTypeMetadata(model) + .addInitializerCompanionObject(model) + .addInheritedProperties(model) + .build() + } } private fun TypeSpec.Builder.setEntitySuperclass(model: QueryModel): TypeSpec.Builder { @@ -33,6 +43,7 @@ object QueryModelRenderer { when (model.type) { QueryModelType.ENTITY, QueryModelType.SUPERCLASS -> EntityPathBase::class.asClassName().parameterizedBy(constraint) QueryModelType.EMBEDDABLE -> BeanPath::class.asClassName().parameterizedBy(constraint) + QueryModelType.QUERY_PROJECTION -> ConstructorExpression::class.asClassName().parameterizedBy(constraint) } ) return this @@ -221,4 +232,28 @@ object QueryModelRenderer { addType(companionObject) return this } + + private fun TypeSpec.Builder.setPrimaryConstructor(model: QueryModel): TypeSpec.Builder { + val constructorSpec = FunSpec.constructorBuilder().apply { + model.properties.forEach { + addParameter( + it.name, + Expression::class.asClassName().parameterizedBy(it.type.originalTypeName) + ) + } + }.build() + primaryConstructor(constructorSpec) + return this + } + + private fun TypeSpec.Builder.addSuperConstructorParameter(model: QueryModel): TypeSpec.Builder { + val paramTypes = model.properties.joinToString(", ", prefix = "arrayOf(", postfix = ")") { + "${it.type.originalClassName}::class.java" + } + val paramNames = model.properties.joinToString(", ") { it.name } + addSuperclassConstructorParameter( + "${model.originalClassName}::class.java, $paramTypes, $paramNames" + ) + return this + } } diff --git a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelType.kt b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelType.kt index 00809331aa..7fe0313374 100644 --- a/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelType.kt +++ b/querydsl-tooling/querydsl-ksp-codegen/src/main/kotlin/com/querydsl/ksp/codegen/QueryModelType.kt @@ -2,6 +2,7 @@ package com.querydsl.ksp.codegen import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.querydsl.core.annotations.QueryProjection import com.squareup.kotlinpoet.ksp.toTypeName import jakarta.persistence.Embeddable import jakarta.persistence.Entity @@ -12,7 +13,8 @@ enum class QueryModelType( ) { ENTITY(Entity::class.qualifiedName!!), EMBEDDABLE(Embeddable::class.qualifiedName!!), - SUPERCLASS(MappedSuperclass::class.qualifiedName!!); + SUPERCLASS(MappedSuperclass::class.qualifiedName!!), + QUERY_PROJECTION(QueryProjection::class.qualifiedName!!); companion object { fun autodetect(classDeclaration: KSClassDeclaration): QueryModelType? { diff --git a/querydsl-tooling/querydsl-ksp-codegen/src/test/kotlin/RenderTest.kt b/querydsl-tooling/querydsl-ksp-codegen/src/test/kotlin/RenderTest.kt index 6a69c8ff17..98be096b6e 100644 --- a/querydsl-tooling/querydsl-ksp-codegen/src/test/kotlin/RenderTest.kt +++ b/querydsl-tooling/querydsl-ksp-codegen/src/test/kotlin/RenderTest.kt @@ -176,6 +176,31 @@ class RenderTest { val features: com.querydsl.core.types.dsl.SetPath = createSet("features", kotlin.String::class.java, com.querydsl.core.types.dsl.StringPath::class.java, null) """.trimIndent()) } + + @Test + fun queryProjection() { + val model = QueryModel( + originalClassName = ClassName("", "CatDTO"), + typeParameterCount = 0, + className = ClassName("", "QCatDTO"), + type = QueryModelType.QUERY_PROJECTION, + mockk() + ) + val properties = listOf( + QProperty("id", QPropertyType.Simple(SimpleType.QNumber(Int::class.asClassName()))), + QProperty("name", QPropertyType.Simple(SimpleType.QString)), + ) + model.properties.addAll(properties) + val typeSpec = QueryModelRenderer.render(model) + val code = typeSpec.toString() + code.assertCompiles() + code.assertContainAll(""" + public class QPersonDTO( + id: com.querydsl.core.types.Expression, + name: com.querydsl.core.types.Expression, + ) : com.querydsl.core.types.ConstructorExpression(CatDTO::class.java, arrayOf(kotlin.Int::class.java, kotlin.String::class.java), id, name) + """.trimIndent()) + } } private fun String.assertLines(expected: String) { @@ -234,3 +259,5 @@ class QAnimal( } class Cat + +class CatDTO