From 214761792d5e132c28562e39e3414f2439f6b6fc Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 11 May 2023 11:36:46 +0200 Subject: [PATCH] Register PGobject subtypes for reflection --- .../deployment/PostgreSQLJDBCReflections.java | 21 +++++++++++++++ .../JPAFunctionalityTestEndpoint.java | 2 ++ .../io/quarkus/it/jpa/postgresql/Person.java | 26 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java index e0e455bb140a8..6b351c420710f 100644 --- a/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java +++ b/extensions/jdbc/jdbc-postgresql/deployment/src/main/java/io/quarkus/jdbc/postgresql/deployment/PostgreSQLJDBCReflections.java @@ -20,6 +20,27 @@ void build(BuildProducer reflectiveClass) { final String driverName = "org.postgresql.Driver"; reflectiveClass.produce(ReflectiveClassBuildItem.builder(driverName).build()); + // We want to register these postgresql "object" types since they are used by a driver to build result set elements + // and reflection is used to create their instances. While ORM might only use a `PGInterval` if a @JdbcType(PostgreSQLIntervalSecondJdbcType.class) + // is applied to a Duration property, we still register other types as users might create their own JdbcTypes that + // would rely on some subtype of a PGobject: + final String[] pgObjectClasses = new String[] { + "org.postgresql.util.PGobject", + "org.postgresql.util.PGInterval", + "org.postgresql.util.PGmoney", + "org.postgresql.geometric.PGbox", + "org.postgresql.geometric.PGcircle", + "org.postgresql.geometric.PGline", + "org.postgresql.geometric.PGlseg", + "org.postgresql.geometric.PGpath", + "org.postgresql.geometric.PGpoint", + "org.postgresql.geometric.PGpolygon", + // One more subtype of the PGobject, it doesn't look like that this one will be instantiated through reflection, + // so let's not include it: + // "org.postgresql.jdbc.PgResultSet.NullObject" + }; + reflectiveClass.produce(ReflectiveClassBuildItem.builder(pgObjectClasses).build()); + // Needed when quarkus.datasource.jdbc.transactions=xa for the setting of the username and password reflectiveClass.produce(ReflectiveClassBuildItem.builder("org.postgresql.ds.common.BaseDataSource").constructors(false) .methods().build()); diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index 6b2af30915ad4..0bc9b7d02b6e7 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.time.Duration; import java.util.List; import java.util.UUID; @@ -135,6 +136,7 @@ private static void persistNewPerson(EntityManager entityManager, String name) { person.setName(name); person.setStatus(Status.LIVING); person.setAddress(new SequencedAddress("Street " + randomName())); + person.setLatestLunchBreakDuration(Duration.ofMinutes(30)); entityManager.persist(person); } diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java index 0393fd951f115..f8c82d346ee3f 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java @@ -1,6 +1,9 @@ package io.quarkus.it.jpa.postgresql; +import java.time.Duration; + import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -10,6 +13,9 @@ import jakarta.persistence.NamedQuery; import jakarta.persistence.Table; +import org.hibernate.annotations.JdbcType; +import org.hibernate.dialect.PostgreSQLIntervalSecondJdbcType; + @Entity @Table(schema = "myschema") @NamedQuery(name = "get_person_by_name", query = "select p from Person p where name = :name") @@ -19,6 +25,7 @@ public class Person { private String name; private SequencedAddress address; private Status status; + private Duration latestLunchBreakDuration = Duration.ZERO; public Person() { } @@ -64,8 +71,27 @@ public void setStatus(Status status) { this.status = status; } + /** + * Need to explicitly set the scale (and the precision so that the scale will actually be read from the annotation). + * Postgresql would only allow maximum scale of 6 for a `interval second`. + * + * @see org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType + */ + @Column(precision = 5, scale = 5) + //NOTE: while https://hibernate.atlassian.net/browse/HHH-16591 is open we cannot replace the currently used @JdbcType annotation + // with a @JdbcTypeCode( INTERVAL_SECOND ) + @JdbcType(PostgreSQLIntervalSecondJdbcType.class) + public Duration getLatestLunchBreakDuration() { + return latestLunchBreakDuration; + } + + public void setLatestLunchBreakDuration(Duration duration) { + this.latestLunchBreakDuration = duration; + } + public void describeFully(StringBuilder sb) { sb.append("Person with id=").append(id).append(", name='").append(name).append("', status='").append(status) + .append("', latestLunchBreakDuration='").append(latestLunchBreakDuration) .append("', address { "); getAddress().describeFully(sb); sb.append(" }");