diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java index 6b5063c0fd773..8fb1132a60d67 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/DotNames.java @@ -1,12 +1,28 @@ package io.quarkus.spring.data.deployment; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.sql.Blob; +import java.sql.NClob; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZonedDateTime; import java.util.Arrays; +import java.util.Calendar; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.TimeZone; import java.util.stream.Stream; import javax.persistence.Id; @@ -87,6 +103,8 @@ public final class DotNames { public static final DotName PRIMITIVE_FLOAT = DotName.createSimple(float.class.getName()); public static final DotName BOOLEAN = DotName.createSimple(Boolean.class.getName()); public static final DotName PRIMITIVE_BOOLEAN = DotName.createSimple(boolean.class.getName()); + public static final DotName BIG_INTEGER = DotName.createSimple(BigInteger.class.getName()); + public static final DotName BIG_DECIMAL = DotName.createSimple(BigDecimal.class.getName()); public static final DotName STRING = DotName.createSimple(String.class.getName()); public static final DotName ITERATOR = DotName.createSimple(Iterator.class.getName()); public static final DotName COLLECTION = DotName.createSimple(Collection.class.getName()); @@ -95,6 +113,53 @@ public final class DotNames { public static final DotName STREAM = DotName.createSimple(Stream.class.getName()); public static final DotName OPTIONAL = DotName.createSimple(Optional.class.getName()); public static final DotName OBJECT = DotName.createSimple(Object.class.getName()); + public static final DotName LOCALE = DotName.createSimple(Locale.class.getName()); + public static final DotName TIMEZONE = DotName.createSimple(TimeZone.class.getName()); + public static final DotName URL = DotName.createSimple(java.net.URL.class.getName()); + public static final DotName CLASS = DotName.createSimple(Class.class.getName()); + public static final DotName UUID = DotName.createSimple(java.util.UUID.class.getName()); + public static final DotName BLOB = DotName.createSimple(java.sql.Blob.class.getName()); + public static final DotName CLOB = DotName.createSimple(java.sql.Clob.class.getName()); + public static final DotName NCLOB = DotName.createSimple(java.sql.NClob.class.getName()); + + // temporal types + public static final DotName UTIL_DATE = DotName.createSimple(java.util.Date.class.getName()); + public static final DotName CALENDAR = DotName.createSimple(Calendar.class.getName()); + // java.sql + public static final DotName SQL_DATE = DotName.createSimple(java.sql.Date.class.getName()); + public static final DotName SQL_TIME = DotName.createSimple(java.sql.Time.class.getName()); + public static final DotName SQL_TIMESTAMP = DotName.createSimple(java.sql.Timestamp.class.getName()); + // java.time + public static final DotName LOCAL_DATE = DotName.createSimple(LocalDate.class.getName()); + public static final DotName LOCAL_TIME = DotName.createSimple(LocalTime.class.getName()); + public static final DotName LOCAL_DATETIME = DotName.createSimple(LocalDateTime.class.getName()); + public static final DotName OFFSET_TIME = DotName.createSimple(OffsetTime.class.getName()); + public static final DotName OFFSET_DATETIME = DotName.createSimple(OffsetDateTime.class.getName()); + public static final DotName DURATION = DotName.createSimple(Duration.class.getName()); + public static final DotName INSTANT = DotName.createSimple(Instant.class.getName()); + public static final DotName ZONED_DATETIME = DotName.createSimple(ZonedDateTime.class.getName()); + + // https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#basic + // Should be in sync with org.hibernate.type.BasicTypeRegistry + public static final Set HIBERNATE_PROVIDED_BASIC_TYPES = new HashSet<>(Arrays.asList( + STRING, CLASS, + BOOLEAN, PRIMITIVE_BOOLEAN, + INTEGER, PRIMITIVE_INTEGER, + LONG, PRIMITIVE_LONG, + SHORT, PRIMITIVE_SHORT, + BYTE, PRIMITIVE_BYTE, + CHARACTER, PRIMITIVE_CHAR, + DOUBLE, PRIMITIVE_DOUBLE, + FLOAT, PRIMITIVE_FLOAT, + BIG_INTEGER, BIG_DECIMAL, + UTIL_DATE, CALENDAR, + SQL_DATE, SQL_TIME, SQL_TIMESTAMP, + LOCAL_DATE, LOCAL_TIME, LOCAL_DATETIME, + OFFSET_TIME, OFFSET_DATETIME, + DURATION, INSTANT, + ZONED_DATETIME, TIMEZONE, + LOCALE, URL, UUID, + BLOB, CLOB, NCLOB)); private DotNames() { } diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java index 75315d6eab8ba..11edc7df2f9ba 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/MethodNameParser.java @@ -57,17 +57,6 @@ public class MethodNameParser { private static final Set BOOLEAN_OPERATIONS = new HashSet<>(Arrays.asList("True", "False")); - private static final Set SIMPLE_FIELD_TYPES = new HashSet<>(Arrays.asList( - DotNames.STRING, - DotNames.BOOLEAN, DotNames.PRIMITIVE_BOOLEAN, - DotNames.INTEGER, DotNames.PRIMITIVE_INTEGER, - DotNames.LONG, DotNames.PRIMITIVE_LONG, - DotNames.SHORT, DotNames.PRIMITIVE_SHORT, - DotNames.BYTE, DotNames.PRIMITIVE_BYTE, - DotNames.CHARACTER, DotNames.PRIMITIVE_CHAR, - DotNames.DOUBLE, DotNames.PRIMITIVE_DOUBLE, - DotNames.FLOAT, DotNames.PRIMITIVE_FLOAT)); - private final ClassInfo entityClass; private final IndexView indexView; private final List mappedSuperClassInfos; @@ -380,7 +369,7 @@ private FieldInfo resolveNestedField(String methodName, String fieldPathExpressi fieldPathBuilder.append('.'); } fieldPathBuilder.append(fieldInfo.name()); - if (!isSupportedHibernateType(fieldInfo.type().name())) { + if (!isHibernateProvidedBasicType(fieldInfo.type().name())) { parentClassInfo = indexView.getClassByName(fieldInfo.type().name()); if (parentClassInfo == null) { throw new IllegalStateException( @@ -542,8 +531,8 @@ private List getMappedSuperClassInfos(IndexView indexView, ClassInfo return mappedSuperClassInfoElements; } - private boolean isSupportedHibernateType(DotName dotName) { - return SIMPLE_FIELD_TYPES.contains(dotName); + private boolean isHibernateProvidedBasicType(DotName dotName) { + return DotNames.HIBERNATE_PROVIDED_BASIC_TYPES.contains(dotName); } private static class MutableReference { diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java index a3144d1aa574a..1ee8f2d6d13db 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/AbstractMethodsAdder.java @@ -228,7 +228,7 @@ protected void generateFindQueryResultHandling(MethodCreator methodCreator, Resu } methodCreator.returnValue(sliceResult); - } else if (isIntLongOrBoolean(returnType)) { + } else if (isHibernateSupportedReturnType(returnType)) { ResultHandle singleResult = methodCreator.invokeInterfaceMethod( MethodDescriptor.ofMethod(PanacheQuery.class, "singleResult", Object.class), panacheQuery); @@ -291,9 +291,7 @@ protected void handleClearAutomatically(AnnotationInstance modifyingAnnotation, } } - protected boolean isIntLongOrBoolean(DotName dotName) { - return DotNames.BOOLEAN.equals(dotName) || DotNames.PRIMITIVE_BOOLEAN.equals(dotName) - || DotNames.INTEGER.equals(dotName) || DotNames.PRIMITIVE_INTEGER.equals(dotName) - || DotNames.LONG.equals(dotName) || DotNames.PRIMITIVE_LONG.equals(dotName); + protected boolean isHibernateSupportedReturnType(DotName dotName) { + return dotName.equals(DotNames.OBJECT) || DotNames.HIBERNATE_PROVIDED_BASIC_TYPES.contains(dotName); } } diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java index 5c7c7cd0824e0..dc73dd03a05c0 100644 --- a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/CustomQueryMethodsAdder.java @@ -264,7 +264,7 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr DotName customResultTypeName = resultType.name(); if (customResultTypeName.equals(entityClassInfo.name()) - || isSupportedJavaLangType(customResultTypeName)) { + || isHibernateSupportedReturnType(customResultTypeName)) { // no special handling needed customResultTypeName = null; } else { @@ -385,7 +385,7 @@ private ResultHandle generateSort(Integer sortParameterIndex, MethodCreator meth // Unless it is some kind of collection containing multiple types, // return the type used in the query result. private Type verifyQueryResultType(Type t) { - if (isSupportedJavaLangType(t.name())) { + if (isHibernateSupportedReturnType(t.name())) { return t; } if (t.kind() == Kind.ARRAY) { @@ -513,10 +513,6 @@ private void generateCustomResultTypes(DotName interfaceName, DotName implName, } } - private boolean isSupportedJavaLangType(DotName dotName) { - return isIntLongOrBoolean(dotName) || dotName.equals(DotNames.OBJECT) || dotName.equals(DotNames.STRING); - } - private ResultHandle castReturnValue(MethodCreator methodCreator, ResultHandle resultHandle, String type) { switch (type) { case "I": diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeData.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeData.java new file mode 100644 index 0000000000000..7cb157b46a4f8 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeData.java @@ -0,0 +1,97 @@ +package io.quarkus.spring.data.deployment; + +import java.math.BigDecimal; +import java.net.URL; +import java.time.Duration; +import java.util.Locale; +import java.util.TimeZone; +import java.util.UUID; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class BasicTypeData { + + @Id + @GeneratedValue + private Integer id; + + private Double doubleValue; + private BigDecimal bigDecimalValue; + private Locale locale; + private TimeZone timeZone; + private java.net.URL url; + private Class clazz; + private java.util.UUID uuid; + private Duration duration; + + public Integer getId() { + return id; + } + + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + + public TimeZone getTimeZone() { + return timeZone; + } + + public void setTimeZone(TimeZone timeZone) { + this.timeZone = timeZone; + } + + public URL getUrl() { + return url; + } + + public void setUrl(URL url) { + this.url = url; + } + + public Class getClazz() { + return clazz; + } + + public void setClazz(Class clazz) { + this.clazz = clazz; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public Duration getDuration() { + return duration; + } + + public void setDuration(Duration duration) { + this.duration = duration; + } +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepository.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepository.java new file mode 100644 index 0000000000000..6446259fb5145 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepository.java @@ -0,0 +1,25 @@ +package io.quarkus.spring.data.deployment; + +import java.net.URL; +import java.time.Duration; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface BasicTypeDataRepository extends JpaRepository { + + @Query("select doubleValue from BasicTypeData where url = ?1") + Double doubleByURL(URL url); + + @Query("select duration from BasicTypeData where uuid = ?1") + Duration durationByUUID(UUID uuid); + + @Query("select timeZone from BasicTypeData where locale = ?1") + Set timeZonesByLocale(Locale locale); +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepositoryTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepositoryTest.java new file mode 100644 index 0000000000000..dcf461f8fbee1 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BasicTypeDataRepositoryTest.java @@ -0,0 +1,90 @@ +package io.quarkus.spring.data.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.Locale; +import java.util.Set; +import java.util.TimeZone; +import java.util.UUID; + +import javax.inject.Inject; +import javax.transaction.Transactional; + +import org.assertj.core.data.Percentage; +import org.hibernate.Hibernate; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BasicTypeDataRepositoryTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addClasses(BasicTypeData.class, BasicTypeDataRepository.class)) + .withConfigurationResource("application.properties"); + + private static final UUID uuid = UUID.randomUUID(); + private static final String QUARKUS_URL = "https://quarkus.io/guides/spring-data-jpa"; + private static final String DURATION = "PT828H19M54.656S"; + private static final String TIME_ZONE = "CST"; + + @Inject + BasicTypeDataRepository repo; + + @Test + @Order(1) + @Transactional + public void testInsert() throws Exception { + BasicTypeData item = populateData(new BasicTypeData()); + repo.save(item); + } + + @Test + @Order(2) + @Transactional + public void testDoubleByURL() throws Exception { + Double price = repo.doubleByURL(new URL(QUARKUS_URL)); + assertThat(price).isCloseTo(Math.PI, Percentage.withPercentage(1)); + } + + @Test + @Order(3) + @Transactional + public void testDurationByUUID() { + Duration duration = repo.durationByUUID(uuid); + assertThat(duration).isEqualTo(Duration.parse(DURATION)); + } + + @Test + @Order(4) + @Transactional + public void testTimeZonesByLocale() { + final Set timeZones = repo.timeZonesByLocale(Locale.TRADITIONAL_CHINESE); + assertThat(timeZones).isNotEmpty().contains(TimeZone.getTimeZone("CST")); + } + + private BasicTypeData populateData(BasicTypeData basicTypeData) throws MalformedURLException { + basicTypeData.setDoubleValue(Math.PI); + basicTypeData.setBigDecimalValue(BigDecimal.valueOf(Math.PI * 2.0)); + basicTypeData.setLocale(Locale.TRADITIONAL_CHINESE); + basicTypeData.setTimeZone(TimeZone.getTimeZone(TIME_ZONE)); + basicTypeData.setUrl(new URL(QUARKUS_URL)); + basicTypeData.setClazz(Hibernate.class); + basicTypeData.setUuid(uuid); + basicTypeData.setDuration(Duration.parse(DURATION)); + + return basicTypeData; + } +}