Skip to content

Commit

Permalink
ORM/HR Panache: support named query projection
Browse files Browse the repository at this point in the history
  • Loading branch information
FroMage committed Jun 8, 2023
1 parent d29a3c3 commit 82e2ae4
Show file tree
Hide file tree
Showing 16 changed files with 93 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package io.quarkus.hibernate.orm.panache.common.deployment;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
Expand Down Expand Up @@ -38,7 +36,7 @@ void lookupNamedQueries(CombinedIndexBuildItem index,
JpaModelBuildItem jpaModel) {
for (String modelClass : jpaModel.getAllModelClassNames()) {
// lookup for `@NamedQuery` on the hierarchy and produce NamedQueryEntityClassBuildStep
Set<String> typeNamedQueries = new HashSet<>();
Map<String, String> typeNamedQueries = new HashMap<>();
lookupNamedQueries(index, DotName.createSimple(modelClass), typeNamedQueries);
namedQueries.produce(new PanacheNamedQueryEntityClassBuildStep(modelClass, typeNamedQueries));
}
Expand All @@ -48,15 +46,15 @@ void lookupNamedQueries(CombinedIndexBuildItem index,
@Record(ExecutionTime.STATIC_INIT)
void buildNamedQueryMap(List<PanacheNamedQueryEntityClassBuildStep> namedQueryEntityClasses,
PanacheHibernateRecorder panacheHibernateRecorder) {
Map<String, Set<String>> namedQueryMap = new HashMap<>();
Map<String, Map<String, String>> namedQueryMap = new HashMap<>();
for (PanacheNamedQueryEntityClassBuildStep entityNamedQueries : namedQueryEntityClasses) {
namedQueryMap.put(entityNamedQueries.getClassName(), entityNamedQueries.getNamedQueries());
}

panacheHibernateRecorder.setNamedQueryMap(namedQueryMap);
}

private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<String> namedQueries) {
private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Map<String, String> namedQueries) {
ClassInfo classInfo = index.getComputingIndex().getClassByName(name);
if (classInfo == null) {
return;
Expand All @@ -65,7 +63,7 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<
List<AnnotationInstance> namedQueryInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERY);
if (namedQueryInstances != null) {
for (AnnotationInstance namedQueryInstance : namedQueryInstances) {
namedQueries.add(namedQueryInstance.value("name").asString());
namedQueries.put(namedQueryInstance.value("name").asString(), namedQueryInstance.value("query").asString());
}
}

Expand All @@ -75,7 +73,7 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<
AnnotationValue value = namedQueriesInstance.value();
AnnotationInstance[] nestedInstances = value.asNestedArray();
for (AnnotationInstance nested : nestedInstances) {
namedQueries.add(nested.value("name").asString());
namedQueries.put(nested.value("name").asString(), nested.value("query").asString());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package io.quarkus.hibernate.orm.panache.common.deployment;

import java.util.Set;
import java.util.Map;

import io.quarkus.builder.item.MultiBuildItem;

final class PanacheNamedQueryEntityClassBuildStep extends MultiBuildItem {
private String className;
private Set<String> namedQueries;
private Map<String, String> namedQueries;

public PanacheNamedQueryEntityClassBuildStep(String className, Set<String> namedQueries) {
public PanacheNamedQueryEntityClassBuildStep(String className, Map<String, String> namedQueries) {
this.className = className;
this.namedQueries = namedQueries;
}
Expand All @@ -17,7 +17,7 @@ public String getClassName() {
return this.className;
}

public Set<String> getNamedQueries() {
public Map<String, String> getNamedQueries() {
return namedQueries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,16 @@ private CommonPanacheQueryImpl(CommonPanacheQueryImpl<?> previousQuery, String n
// Builder

public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
String selectQuery = query;
if (PanacheJpaUtil.isNamedQuery(query)) {
throw new PanacheQueryException("Unable to perform a projection on a named query");
org.hibernate.query.Query q = (org.hibernate.query.Query) em.createNamedQuery(query.substring(1));
selectQuery = q.getQueryString();
}

String lowerCasedTrimmedQuery = query.trim().replace('\n', ' ').replace('\r', ' ').toLowerCase();
if (lowerCasedTrimmedQuery.startsWith("select new ")) {
throw new PanacheQueryException("Unable to perform a projection on a 'select new' query: " + query);
String lowerCasedTrimmedQuery = selectQuery.trim().replace('\n', ' ').replace('\r', ' ').toLowerCase();
if (lowerCasedTrimmedQuery.startsWith("select new ")
|| lowerCasedTrimmedQuery.startsWith("select distinct new ")) {
throw new PanacheQueryException("Unable to perform a projection on a 'select [distinct]? new' query: " + query);
}

// If the query starts with a select clause, we generate an HQL query
Expand All @@ -101,7 +104,7 @@ public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
// New query: SELECT new org.acme.ProjectionClass(e.field1, e.field2) from EntityClass e
if (lowerCasedTrimmedQuery.startsWith("select ")) {
int endSelect = lowerCasedTrimmedQuery.indexOf(" from ");
String trimmedQuery = query.trim().replace('\n', ' ').replace('\r', ' ');
String trimmedQuery = selectQuery.trim().replace('\n', ' ').replace('\r', ' ');
// 7 is the length of "select "
String selectClause = trimmedQuery.substring(7, endSelect).trim();
String from = trimmedQuery.substring(endSelect);
Expand Down Expand Up @@ -150,7 +153,7 @@ public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
}
select.append(") ");

return new CommonPanacheQueryImpl<>(this, select.toString() + query, "select count(*) " + query);
return new CommonPanacheQueryImpl<>(this, select.toString() + selectQuery, "select count(*) " + selectQuery);
}

public void filter(String filterName, Map<String, Object> parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import org.hibernate.query.SemanticException;

Expand All @@ -11,13 +10,13 @@
public final class NamedQueryUtil {

// will be replaced at augmentation phase
private static volatile Map<String, Set<String>> namedQueryMap = Collections.emptyMap();
private static volatile Map<String, Map<String, String>> namedQueryMap = Collections.emptyMap();

private NamedQueryUtil() {
// prevent initialization
}

public static void setNamedQueryMap(Map<String, Set<String>> newNamedQueryMap) {
public static void setNamedQueryMap(Map<String, Map<String, String>> newNamedQueryMap) {
namedQueryMap = newNamedQueryMap;
}

Expand All @@ -29,13 +28,13 @@ public static void checkNamedQuery(Class<?> entityClass, String namedQuery) {
}

public static boolean isNamedQuery(Class<?> entityClass, String namedQuery) {
Set<String> namedQueries = namedQueryMap.get(entityClass.getName());
return namedQueries != null && namedQueries.contains(namedQuery);
Map<String, String> namedQueries = namedQueryMap.get(entityClass.getName());
return namedQueries != null && namedQueries.containsKey(namedQuery);
}

private static boolean isNamedQuery(String namedQuery) {
for (Set<String> namedQueries : namedQueryMap.values()) {
if (namedQueries.contains(namedQuery)) {
for (Map<String, String> namedQueries : namedQueryMap.values()) {
if (namedQueries.containsKey(namedQuery)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package io.quarkus.hibernate.orm.panache.common.runtime;

import java.util.Map;
import java.util.Set;

import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class PanacheHibernateRecorder {
public void setNamedQueryMap(Map<String, Set<String>> namedQueryMap) {
public void setNamedQueryMap(Map<String, Map<String, String>> namedQueryMap) {
NamedQueryUtil.setNamedQueryMap(namedQueryMap);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ public interface PanacheQuery<Entity> {
* class'
* single constructor, using its parameter names (or their {@link ProjectedFieldName} annotations), in the same order as the
* constructor.</li>
* <li>If this is a named query, we throw a {@link PanacheQueryException}</li>
* <li>If this is already a project query of the form <code>select distinct? new…</code>, we throw a
* {@link PanacheQueryException}</li>
*
* @param type the projected class type
* @return a new query with the same state as the previous one (params, page, range, lockMode, hints, ...) but a projected
* result of the type
* <code>type</code>
* @throws PanacheQueryException if this represents a named query or an already-projected query
* @throws PanacheQueryException if this represents an already-projected query
*/
public <T> PanacheQuery<T> project(Class<T> type);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ void lookupNamedQueries(CombinedIndexBuildItem index,
JpaModelBuildItem jpaModel) {
for (String modelClass : jpaModel.getAllModelClassNames()) {
// lookup for `@NamedQuery` on the hierarchy and produce NamedQueryEntityClassBuildStep
Set<String> typeNamedQueries = new HashSet<>();
Map<String, String> typeNamedQueries = new HashMap<>();
lookupNamedQueries(index, DotName.createSimple(modelClass), typeNamedQueries);
namedQueries.produce(new PanacheNamedQueryEntityClassBuildStep(modelClass, typeNamedQueries));
}
Expand All @@ -201,7 +201,7 @@ void lookupNamedQueries(CombinedIndexBuildItem index,
@Record(ExecutionTime.STATIC_INIT)
void buildNamedQueryMap(List<PanacheNamedQueryEntityClassBuildStep> namedQueryEntityClasses,
PanacheHibernateRecorder panacheHibernateRecorder) {
Map<String, Set<String>> namedQueryMap = new HashMap<>();
Map<String, Map<String, String>> namedQueryMap = new HashMap<>();
for (PanacheNamedQueryEntityClassBuildStep entityNamedQueries : namedQueryEntityClasses) {
namedQueryMap.put(entityNamedQueries.getClassName(), entityNamedQueries.getNamedQueries());
}
Expand All @@ -215,7 +215,7 @@ public void shutdown(ShutdownContextBuildItem shutdownContextBuildItem, PanacheH
panacheHibernateRecorder.clear(shutdownContextBuildItem);
}

private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<String> namedQueries) {
private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Map<String, String> namedQueries) {
ClassInfo classInfo = index.getComputingIndex().getClassByName(name);
if (classInfo == null) {
return;
Expand All @@ -224,7 +224,8 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<
List<AnnotationInstance> namedQueryInstances = classInfo.annotationsMap().get(DOTNAME_NAMED_QUERY);
if (namedQueryInstances != null) {
for (AnnotationInstance namedQueryInstance : namedQueryInstances) {
namedQueries.add(namedQueryInstance.value("name").asString());
namedQueries.put(namedQueryInstance.value("name").asString(),
namedQueryInstance.value("query").asString());
}
}

Expand All @@ -234,7 +235,7 @@ private void lookupNamedQueries(CombinedIndexBuildItem index, DotName name, Set<
AnnotationValue value = namedQueriesInstance.value();
AnnotationInstance[] nestedInstances = value.asNestedArray();
for (AnnotationInstance nested : nestedInstances) {
namedQueries.add(nested.value("name").asString());
namedQueries.put(nested.value("name").asString(), nested.value("query").asString());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package io.quarkus.hibernate.reactive.panache.common.deployment;

import java.util.Set;
import java.util.Map;

import io.quarkus.builder.item.MultiBuildItem;

final class PanacheNamedQueryEntityClassBuildStep extends MultiBuildItem {
private String className;
private Set<String> namedQueries;
private Map<String, String> namedQueries;

public PanacheNamedQueryEntityClassBuildStep(String className, Set<String> namedQueries) {
public PanacheNamedQueryEntityClassBuildStep(String className, Map<String, String> namedQueries) {
this.className = className;
this.namedQueries = namedQueries;
}
Expand All @@ -17,7 +17,7 @@ public String getClassName() {
return this.className;
}

public Set<String> getNamedQueries() {
public Map<String, String> getNamedQueries() {
return namedQueries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ private CommonPanacheQueryImpl(CommonPanacheQueryImpl<?> previousQuery, String n
// Builder

public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
String selectQuery = query;
if (PanacheJpaUtil.isNamedQuery(query)) {
throw new PanacheQueryException("Unable to perform a projection on a named query");
selectQuery = NamedQueryUtil.getNamedQuery(query.substring(1));
}

String lowerCasedTrimmedQuery = query.trim().toLowerCase();
String lowerCasedTrimmedQuery = selectQuery.trim().toLowerCase();
if (lowerCasedTrimmedQuery.startsWith("select new ")) {
throw new PanacheQueryException("Unable to perform a projection on a 'select new' query: " + query);
}
Expand All @@ -88,7 +89,7 @@ public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
// New query: SELECT new org.acme.ProjectionClass(e.field1, e.field2) from EntityClass e
if (lowerCasedTrimmedQuery.startsWith("select ")) {
int endSelect = lowerCasedTrimmedQuery.indexOf(" from ");
String trimmedQuery = query.trim();
String trimmedQuery = selectQuery.trim();
// 7 is the length of "select "
String selectClause = trimmedQuery.substring(7, endSelect).trim();
String from = trimmedQuery.substring(endSelect);
Expand Down Expand Up @@ -136,7 +137,7 @@ public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
}
select.append(") ");

return new CommonPanacheQueryImpl<>(this, select.toString() + query, "select count(*) " + query);
return new CommonPanacheQueryImpl<>(this, select.toString() + selectQuery, "select count(*) " + selectQuery);
}

public void filter(String filterName, Map<String, Object> parameters) {
Expand Down Expand Up @@ -235,14 +236,17 @@ public void withHint(String hintName, Object value) {

@SuppressWarnings("unchecked")
public Uni<Long> count() {
String selectQuery;
if (PanacheJpaUtil.isNamedQuery(query)) {
throw new PanacheQueryException("Unable to perform a count operation on a named query");
selectQuery = NamedQueryUtil.getNamedQuery(query.substring(1));
} else {
selectQuery = query;
}

if (count == null) {
// FIXME: question about caching the result here
count = em.flatMap(session -> {
Mutiny.Query<Long> countQuery = session.createQuery(countQuery());
Mutiny.Query<Long> countQuery = session.createQuery(countQuery(selectQuery));
if (paramsArrayOrMap instanceof Map)
AbstractJpaOperations.bindParameters(countQuery, (Map<String, Object>) paramsArrayOrMap);
else
Expand All @@ -253,11 +257,11 @@ public Uni<Long> count() {
return count;
}

private String countQuery() {
private String countQuery(String selectQuery) {
if (countQuery != null) {
return countQuery;
}
return PanacheJpaUtil.getCountQuery(query);
return PanacheJpaUtil.getCountQuery(selectQuery);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.ParsingException;
Expand All @@ -12,13 +11,13 @@
public final class NamedQueryUtil {

// will be replaced at augmentation phase
private static volatile Map<String, Set<String>> namedQueryMap = Collections.emptyMap();
private static volatile Map<String, Map<String, String>> namedQueryMap = Collections.emptyMap();

private NamedQueryUtil() {
// prevent initialization
}

public static void setNamedQueryMap(Map<String, Set<String>> newNamedQueryMap) {
public static void setNamedQueryMap(Map<String, Map<String, String>> newNamedQueryMap) {
namedQueryMap = newNamedQueryMap;
}

Expand All @@ -30,19 +29,29 @@ public static void checkNamedQuery(Class<?> entityClass, String namedQuery) {
}

public static boolean isNamedQuery(Class<?> entityClass, String namedQuery) {
Set<String> namedQueries = namedQueryMap.get(entityClass.getName());
return namedQueries != null && namedQueries.contains(namedQuery);
Map<String, String> namedQueries = namedQueryMap.get(entityClass.getName());
return namedQueries != null && namedQueries.containsKey(namedQuery);
}

private static boolean isNamedQuery(String namedQuery) {
for (Set<String> namedQueries : namedQueryMap.values()) {
if (namedQueries.contains(namedQuery)) {
for (Map<String, String> namedQueries : namedQueryMap.values()) {
if (namedQueries.containsKey(namedQuery)) {
return true;
}
}
return false;
}

static String getNamedQuery(String namedQuery) {
for (Map<String, String> namedQueries : namedQueryMap.values()) {
String query = namedQueries.get(namedQuery);
if (query != null) {
return query;
}
}
return null;
}

public static RuntimeException checkForNamedQueryMistake(IllegalArgumentException x, String originalQuery) {
if (originalQuery != null
&& (x.getCause() instanceof SemanticException || x.getCause() instanceof ParsingException)
Expand Down
Loading

0 comments on commit 82e2ae4

Please sign in to comment.