Skip to content

Commit 90e9d98

Browse files
authored
Fix @ColumTransformer on joined entity (#3341)
1 parent 2b1aceb commit 90e9d98

File tree

3 files changed

+147
-10
lines changed

3 files changed

+147
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.micronaut.data.jdbc.postgres
2+
3+
import io.micronaut.context.ApplicationContext
4+
import io.micronaut.data.annotation.GeneratedValue
5+
import io.micronaut.data.annotation.Id
6+
import io.micronaut.data.annotation.Join
7+
import io.micronaut.data.annotation.MappedEntity
8+
import io.micronaut.data.annotation.MappedProperty
9+
import io.micronaut.data.annotation.Relation
10+
import io.micronaut.data.annotation.sql.ColumnTransformer
11+
import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource
12+
import io.micronaut.data.jdbc.TestResourcesDatabaseTestPropertyProvider
13+
import io.micronaut.data.jdbc.annotation.JdbcRepository
14+
import io.micronaut.data.model.query.builder.sql.Dialect
15+
import io.micronaut.data.repository.GenericRepository
16+
import io.micronaut.inject.qualifiers.Qualifiers
17+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
18+
import jakarta.inject.Inject
19+
import spock.lang.AutoCleanup
20+
import spock.lang.Shared
21+
import spock.lang.Specification
22+
23+
import javax.sql.DataSource
24+
import java.sql.Connection
25+
import java.time.Duration
26+
27+
import static io.micronaut.data.annotation.Relation.Kind.MANY_TO_ONE
28+
29+
/**
30+
* Test for https://github.com/micronaut-projects/micronaut-data/issues/3338 .
31+
*/
32+
class PostgresColumnTransformerSpec extends Specification implements PostgresTestPropertyProvider {
33+
34+
@Shared
35+
@AutoCleanup
36+
ApplicationContext applicationContext = ApplicationContext.run(properties)
37+
38+
void setup() {
39+
def dataSource = DelegatingDataSource.unwrapDataSource(applicationContext.getBean(DataSource.class, Qualifiers.byName("default")))
40+
def connection = dataSource.connection
41+
connection.prepareStatement("CREATE CAST (varchar AS interval) WITH INOUT AS IMPLICIT;").execute()
42+
}
43+
44+
void cleanup() {
45+
def dataSource = DelegatingDataSource.unwrapDataSource(applicationContext.getBean(DataSource.class, Qualifiers.byName("default")))
46+
def connection = dataSource.connection
47+
connection.prepareStatement("DROP CAST IF EXISTS (varchar AS interval);").execute()
48+
}
49+
50+
void 'load entity with transformer column'() {
51+
when:
52+
def riderRepository = applicationContext.getBean(RiderRepository)
53+
def savedRider = riderRepository.save(new Rider(name: 'Peter', finishTime: Duration.ofHours(3).plusMinutes(45)))
54+
def optRider = riderRepository.findById(savedRider.id)
55+
then:
56+
optRider.present
57+
optRider.get().finishTime == savedRider.finishTime
58+
cleanup:
59+
riderRepository.deleteAll()
60+
}
61+
62+
void 'load joined entity with transformer column'() {
63+
when:
64+
def riderRepository = applicationContext.getBean(RiderRepository)
65+
def reportRepository = applicationContext.getBean(ReportRepository)
66+
def savedRider = riderRepository.save(new Rider(name: 'Peter', finishTime: Duration.ofHours(3).plusMinutes(45)))
67+
def savedReport = reportRepository.save(new Report(rider: savedRider, message: 'Hello, World'))
68+
def optReport = reportRepository.findById(savedReport.getId())
69+
then:
70+
optReport.present
71+
savedRider.finishTime == optReport.get().rider.finishTime
72+
cleanup:
73+
reportRepository.deleteAll()
74+
riderRepository.deleteAll()
75+
}
76+
77+
@Override
78+
List<String> packages() {
79+
List.of("io.micronaut.data.jdbc.postgres")
80+
}
81+
}
82+
83+
@MappedEntity
84+
class Rider {
85+
@Id
86+
@GeneratedValue
87+
Integer id
88+
89+
String name
90+
91+
@ColumnTransformer(read = "to_char(@.finish_time, 'PTHH24HMIM')")
92+
Duration finishTime
93+
}
94+
95+
@MappedEntity
96+
class Report {
97+
98+
@Id
99+
@GeneratedValue
100+
Integer id
101+
102+
@Relation(value = MANY_TO_ONE)
103+
@MappedProperty(value = "rider")
104+
Rider rider
105+
106+
private String message
107+
}
108+
109+
@JdbcRepository(dialect = Dialect.POSTGRES)
110+
@Join(value = "rider")
111+
interface ReportRepository extends GenericRepository<Report, Integer> {
112+
113+
Optional<Report> findById(Integer id)
114+
115+
Report save(Report entity)
116+
117+
void deleteAll()
118+
}
119+
120+
@JdbcRepository(dialect = Dialect.POSTGRES)
121+
interface RiderRepository extends GenericRepository<Rider, Integer> {
122+
123+
Optional<Rider> findById(Integer id)
124+
125+
Rider save(Rider entity)
126+
127+
void deleteAll()
128+
}

data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -849,19 +849,23 @@ protected void selectAllColumnsFromJoinPaths(QueryState queryState,
849849
// in the case of a foreign key association the ID is not in the table,
850850
// so we need to retrieve it
851851
traversePersistentProperties(associatedEntity, includeIdentity, true, (propertyAssociations, prop) -> {
852+
String transformed = getDataTransformerReadValue(joinAlias, prop).orElse(null);
853+
String columnAlias = getColumnAlias(prop);
852854
String columnName;
853855
if (computePropertyPaths()) {
854856
columnName = getMappedName(namingStrategy, propertyAssociations, prop);
855857
} else {
856858
columnName = asPath(propertyAssociations, prop);
857859
}
858-
String columnAlias = getColumnAlias(prop);
859-
860-
queryBuffer
860+
if (transformed != null) {
861+
queryBuffer.append(transformed).append(AS_CLAUSE);
862+
} else {
863+
queryBuffer
861864
.append(joinAlias)
862865
.append(DOT)
863866
.append(queryState.shouldEscape() ? quote(columnName) : columnName)
864867
.append(AS_CLAUSE);
868+
}
865869
if (StringUtils.isNotEmpty(columnAlias)) {
866870
queryBuffer.append(columnAlias);
867871
} else {

data-model/src/main/java/io/micronaut/data/model/query/builder/sql/SqlQueryBuilder2.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -1670,19 +1670,24 @@ protected void selectAllColumnsFromJoinPaths(Collection<JoinPath> allPaths,
16701670
// in the case of a foreign key association the ID is not in the table,
16711671
// so we need to retrieve it
16721672
PersistentEntityUtils.traversePersistentProperties(associatedEntity, includeIdentity, true, (propertyAssociations, prop) -> {
1673+
1674+
String transformed = getDataTransformerReadValue(joinAlias, prop).orElse(null);
1675+
String columnAlias = getColumnAlias(prop);
16731676
String columnName;
16741677
if (computePropertyPaths()) {
16751678
columnName = getMappedName(namingStrategy, propertyAssociations, prop);
16761679
} else {
16771680
columnName = asPath(propertyAssociations, prop);
16781681
}
1679-
String columnAlias = getColumnAlias(prop);
1680-
1681-
query
1682-
.append(joinAlias)
1683-
.append(DOT)
1684-
.append(queryState.shouldEscape() ? quote(columnName) : columnName)
1685-
.append(AS_CLAUSE);
1682+
if (transformed != null) {
1683+
query.append(transformed).append(AS_CLAUSE);
1684+
} else {
1685+
query
1686+
.append(joinAlias)
1687+
.append(DOT)
1688+
.append(queryState.shouldEscape() ? quote(columnName) : columnName)
1689+
.append(AS_CLAUSE);
1690+
}
16861691
if (StringUtils.isNotEmpty(columnAlias)) {
16871692
query.append(columnAlias);
16881693
} else {

0 commit comments

Comments
 (0)