Skip to content

Commit e8406af

Browse files
authored
Add hibernate support for printing Specification/TypedQuery to string (for debug purposes) (#2213)
Signed-off-by: Avgustin Marinov <[email protected]>
1 parent d93a73e commit e8406af

File tree

4 files changed

+146
-36
lines changed
  • hawkbit-repository
    • hawkbit-repository-jpa-eclipselink/src/main/java/org/eclipse/hawkbit/repository/jpa
    • hawkbit-repository-jpa-hibernate/src/main/java/org/eclipse/hawkbit/repository/jpa
    • hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql
    • hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test

4 files changed

+146
-36
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.eclipse.hawkbit.repository.jpa;
11+
12+
import jakarta.persistence.TypedQuery;
13+
14+
import lombok.NoArgsConstructor;
15+
import lombok.extern.slf4j.Slf4j;
16+
import org.eclipse.persistence.config.PersistenceUnitProperties;
17+
import org.eclipse.persistence.jpa.JpaQuery;
18+
19+
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
20+
@Slf4j
21+
public class Utils {
22+
23+
public static String toSql(final TypedQuery<?> typedQuery) {
24+
typedQuery.setParameter(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "DEFAULT");
25+
// executes the query - otherwise the SQL string is not generated
26+
typedQuery.getResultList();
27+
return typedQuery.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString();
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*/
10+
package org.eclipse.hawkbit.repository.jpa;
11+
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.lang.reflect.Method;
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
import jakarta.persistence.TypedQuery;
18+
19+
import lombok.NoArgsConstructor;
20+
import lombok.extern.slf4j.Slf4j;
21+
import org.hibernate.engine.spi.SessionFactoryImplementor;
22+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
23+
import org.hibernate.metamodel.mapping.MappingModelExpressible;
24+
import org.hibernate.query.spi.QueryEngine;
25+
import org.hibernate.query.spi.QueryParameterImplementor;
26+
import org.hibernate.query.sqm.internal.QuerySqmImpl;
27+
import org.hibernate.query.sqm.internal.SqmUtil;
28+
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
29+
import org.hibernate.query.sqm.sql.SqmTranslation;
30+
import org.hibernate.query.sqm.sql.SqmTranslator;
31+
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
32+
import org.hibernate.query.sqm.tree.SqmDmlStatement;
33+
import org.hibernate.query.sqm.tree.expression.SqmParameter;
34+
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
35+
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
36+
import org.hibernate.sql.ast.tree.MutationStatement;
37+
import org.hibernate.sql.ast.tree.Statement;
38+
import org.hibernate.sql.ast.tree.select.SelectStatement;
39+
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
40+
import org.hibernate.sql.exec.spi.JdbcParametersList;
41+
42+
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
43+
@Slf4j
44+
public class Utils {
45+
46+
private static final Method getSqmTranslatorFactory;
47+
48+
static {
49+
Method method = null;
50+
try {
51+
method = QueryEngine.class.getMethod("getSqmTranslatorFactory");
52+
} catch (final NoSuchMethodException e) {
53+
log.warn("Can't resolve getSqmTranslatorFactory method (Utils.toString won't work)", e);
54+
}
55+
getSqmTranslatorFactory = method;
56+
}
57+
58+
public static String toSql(final TypedQuery<?> typedQuery) {
59+
if (getSqmTranslatorFactory == null) {
60+
throw new UnsupportedOperationException("SqmTranslatorFactory resolver is not available");
61+
}
62+
63+
final QuerySqmImpl<?> hqlQuery = typedQuery.unwrap(QuerySqmImpl.class);
64+
final SessionFactoryImplementor factory = hqlQuery.getSessionFactory();
65+
final SharedSessionContractImplementor session = hqlQuery.getSession();
66+
final SessionFactoryImplementor sessionFactory = session.getFactory();
67+
68+
final SqmTranslatorFactory sqmTranslatorFactory;
69+
try {
70+
sqmTranslatorFactory = (SqmTranslatorFactory) getSqmTranslatorFactory.invoke(factory.getQueryEngine());
71+
} catch (final IllegalAccessException | InvocationTargetException e) {
72+
throw new UnsupportedOperationException("Can't create SqmTranslatorFactory", e);
73+
}
74+
75+
final SqmTranslator<? extends Statement> sqmSelectTranslator =
76+
hqlQuery.getSqmStatement() instanceof SqmSelectStatement<?> selectStatement
77+
? sqmTranslatorFactory.createSelectTranslator(selectStatement,
78+
hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(),
79+
hqlQuery.getLoadQueryInfluencers(), sessionFactory, false)
80+
: sqmTranslatorFactory.createMutationTranslator((SqmDmlStatement<?>) hqlQuery.getSqmStatement(),
81+
hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(),
82+
hqlQuery.getLoadQueryInfluencers(), sessionFactory);
83+
84+
final SqmTranslation<? extends Statement> sqmTranslation = sqmSelectTranslator.translate();
85+
final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
86+
final Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
87+
hqlQuery.getDomainParameterXref(), sqmTranslation::getJdbcParamsBySqmParam);
88+
89+
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(hqlQuery.getQueryParameterBindings(),
90+
hqlQuery.getDomainParameterXref(), jdbcParamsXref, factory.getRuntimeMetamodels().getMappingMetamodel(),
91+
sqmSelectTranslator.getFromClauseAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() {
92+
93+
@Override
94+
@SuppressWarnings("unchecked")
95+
public <T> MappingModelExpressible<T> getResolvedMappingModelType(final SqmParameter<T> parameter) {
96+
return (MappingModelExpressible<T>) sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter);
97+
}
98+
}, hqlQuery.getSession());
99+
return (sqmTranslation.getSqlAst() instanceof SelectStatement selectStatement
100+
? sqlAstTranslatorFactory.buildSelectTranslator(factory, selectStatement)
101+
.translate(jdbcParameterBindings, hqlQuery.getQueryOptions())
102+
: sqlAstTranslatorFactory.buildMutationTranslator(factory, (MutationStatement) sqmTranslation.getSqlAst())
103+
.translate(jdbcParameterBindings, hqlQuery.getQueryOptions()))
104+
.getSqlString();
105+
}
106+
}

hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQL.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
*/
1010
package org.eclipse.hawkbit.repository.jpa.rsql;
1111

12-
import java.lang.reflect.InvocationTargetException;
13-
import java.lang.reflect.Method;
1412
import java.util.List;
1513

1614
import jakarta.persistence.EntityManager;
@@ -25,7 +23,7 @@
2523
import cz.jirutka.rsql.parser.ast.RSQLOperators;
2624
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
2725
import org.eclipse.hawkbit.repository.RsqlQueryField;
28-
import org.eclipse.hawkbit.repository.jpa.Jpa;
26+
import org.eclipse.hawkbit.repository.jpa.Utils;
2927
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
3028
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
3129
import org.springframework.orm.jpa.vendor.Database;
@@ -44,25 +42,7 @@ public <T, A extends Enum<A> & RsqlQueryField> String toSQL(
4442
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
4543
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor);
4644
final TypedQuery<?> typedQuery = entityManager.createQuery(query);
47-
// executes the query - otherwise the SQL string is not generated
48-
if (Jpa.JPA_VENDOR.equals(Jpa.JpaVendor.ECLIPSELINK)) {
49-
typedQuery.setParameter("eclipselink.tenant-id", "DEFAULT");
50-
typedQuery.getResultList();
51-
try {
52-
final Class<?> jpaQueryClass = Class.forName("org.eclipse.persistence.jpa.JpaQuery");
53-
final Method getDatabaseQueryMethod = jpaQueryClass.getMethod("getDatabaseQuery");
54-
final Method getSQLString = getDatabaseQueryMethod.getReturnType().getMethod("getSQLString");
55-
return (String)getSQLString.invoke(getDatabaseQueryMethod.invoke(typedQuery.unwrap(jpaQueryClass)));
56-
} catch (final RuntimeException e) {
57-
throw e;
58-
} catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
59-
throw new UnsupportedOperationException("EclipseLink is not supported", e);
60-
} catch (final InvocationTargetException e) {
61-
throw e.getCause() instanceof RuntimeException ? (RuntimeException)e.getCause() : new RuntimeException(e.getCause());
62-
}
63-
} else { // hibernate
64-
throw new UnsupportedOperationException("Hibernate is not supported");
65-
}
45+
return Utils.toSql(typedQuery);
6646
}
6747

6848
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(

hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/TestConfiguration.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.eclipse.hawkbit.im.authentication.SpRole;
2828
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
2929
import org.eclipse.hawkbit.repository.RolloutStatusCache;
30-
import org.eclipse.hawkbit.repository.SystemManagement;
3130
import org.eclipse.hawkbit.repository.event.ApplicationEventFilter;
3231
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
3332
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
@@ -75,9 +74,9 @@
7574
* Spring context configuration required for Dev.Environment.
7675
*/
7776
@Configuration
78-
@EnableConfigurationProperties({ DdiSecurityProperties.class,
79-
ArtifactUrlHandlerProperties.class, ArtifactFilesystemProperties.class, HawkbitSecurityProperties.class,
80-
ControllerPollProperties.class, TenantConfigurationProperties.class })
77+
@EnableConfigurationProperties({
78+
DdiSecurityProperties.class, ArtifactUrlHandlerProperties.class, ArtifactFilesystemProperties.class,
79+
HawkbitSecurityProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class })
8180
@Profile("test")
8281
@EnableAutoConfiguration
8382
@PropertySource("classpath:/hawkbit-test-defaults.properties")
@@ -99,9 +98,7 @@ public ScheduledExecutorService scheduledExecutorService() {
9998
return new DelegatingSecurityContextScheduledExecutorService(
10099
Executors.newScheduledThreadPool(1, runnable -> {
101100
final Thread thread = Executors.defaultThreadFactory().newThread(runnable);
102-
thread.setName(
103-
String.format(
104-
Locale.ROOT, "central-scheduled-executor-pool-%d", count.getAndIncrement()));
101+
thread.setName(String.format(Locale.ROOT, "central-scheduled-executor-pool-%d", count.getAndIncrement()));
105102
return thread;
106103
}));
107104
}
@@ -135,9 +132,8 @@ ArtifactRepository artifactRepository(final ArtifactFilesystemProperties artifac
135132
}
136133

137134
/**
138-
* @return the {@link org.eclipse.hawkbit.repository.test.util.SystemManagementHolder} singleton bean which holds the
139-
* current {@link SystemManagement} service and make it accessible in
140-
* beans which cannot access the service directly, e.g. JPA entities.
135+
* @return the {@link org.eclipse.hawkbit.repository.test.util.SystemManagementHolder} singleton bean which holds the current
136+
* {@link SystemManagement} service and make it accessible in beans which cannot access the service directly, e.g. JPA entities.
141137
*/
142138
@Bean
143139
SystemManagementHolder systemManagementHolder() {
@@ -150,8 +146,7 @@ TestdataFactory testdataFactory() {
150146
}
151147

152148
@Bean
153-
PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler(
154-
final ArtifactUrlHandlerProperties urlHandlerProperties) {
149+
PropertyBasedArtifactUrlHandler testPropertyBasedArtifactUrlHandler(final ArtifactUrlHandlerProperties urlHandlerProperties) {
155150
return new PropertyBasedArtifactUrlHandler(urlHandlerProperties, "");
156151
}
157152

@@ -185,8 +180,8 @@ TenantAwareCacheManager cacheManager(final TenantAware tenantAware) {
185180

186181
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
187182
SimpleApplicationEventMulticaster applicationEventMulticaster(final ApplicationEventFilter applicationEventFilter) {
188-
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new FilterEnabledApplicationEventPublisher(
189-
applicationEventFilter);
183+
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
184+
new FilterEnabledApplicationEventPublisher(applicationEventFilter);
190185
simpleApplicationEventMulticaster.setTaskExecutor(asyncExecutor());
191186
return simpleApplicationEventMulticaster;
192187
}

0 commit comments

Comments
 (0)