Skip to content

Commit

Permalink
ORM/HR with Panache: if a query fails and it matches a named query we…
Browse files Browse the repository at this point in the history
… generate a nicer exception
  • Loading branch information
FroMage authored and sberyozkin committed Jun 21, 2023
1 parent b1f560a commit cea417d
Show file tree
Hide file tree
Showing 19 changed files with 511 additions and 208 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ public void close() {
};

private Object paramsArrayOrMap;
/**
* this is the HQL query expanded from the Panache-Query
*/
private String query;
/**
* this is the original Panache-Query, if any (can be null)
*/
private String originalQuery;
protected String countQuery;
private String orderBy;
private EntityManager em;
Expand All @@ -53,9 +60,11 @@ public void close() {

private Map<String, Map<String, Object>> filters;

public CommonPanacheQueryImpl(EntityManager em, String query, String orderBy, Object paramsArrayOrMap) {
public CommonPanacheQueryImpl(EntityManager em, String query, String originalQuery, String orderBy,
Object paramsArrayOrMap) {
this.em = em;
this.query = query;
this.originalQuery = originalQuery;
this.orderBy = orderBy;
this.paramsArrayOrMap = paramsArrayOrMap;
}
Expand Down Expand Up @@ -350,7 +359,11 @@ private Query createBaseQuery() {
String namedQuery = query.substring(1);
jpaQuery = em.createNamedQuery(namedQuery);
} else {
jpaQuery = em.createQuery(orderBy != null ? query + orderBy : query);
try {
jpaQuery = em.createQuery(orderBy != null ? query + orderBy : query);
} catch (IllegalArgumentException x) {
throw NamedQueryUtil.checkForNamedQueryMistake(x, originalQuery);
}
}

if (paramsArrayOrMap instanceof Map) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.Map;
import java.util.Set;

import org.hibernate.query.SemanticException;

import io.quarkus.panache.common.exception.PanacheQueryException;

public final class NamedQueryUtil {
Expand All @@ -20,10 +22,35 @@ public static void setNamedQueryMap(Map<String, Set<String>> newNamedQueryMap) {
}

public static void checkNamedQuery(Class<?> entityClass, String namedQuery) {
Set<String> namedQueries = namedQueryMap.get(entityClass.getName());
if (namedQueries == null || !namedQueries.contains(namedQuery)) {
if (!isNamedQuery(entityClass, namedQuery)) {
throw new PanacheQueryException("The named query '" + namedQuery +
"' must be defined on your JPA entity or one of its super classes");
}
}

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

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

public static RuntimeException checkForNamedQueryMistake(IllegalArgumentException x, String originalQuery) {
if (originalQuery != null
&& x.getCause() instanceof SemanticException
&& isNamedQuery(originalQuery)) {
return new PanacheQueryException("Invalid query '" + originalQuery
+ "' but it matches a known @NamedQuery, perhaps you should prefix it with a '#' to use it as a named query: '#"
+ originalQuery + "'", x);
} else {
return x;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import jakarta.persistence.EntityManager
class KotlinJpaOperations : AbstractJpaOperations<PanacheQueryImpl<*>>() {
override fun createPanacheQuery(
em: EntityManager,
query: String,
hqlQuery: String,
originalQuery: String?,
orderBy: String?,
paramsArrayOrMap: Any?
) = PanacheQueryImpl<Any>(em, query, orderBy, paramsArrayOrMap)
) = PanacheQueryImpl<Any>(em, hqlQuery, originalQuery, orderBy, paramsArrayOrMap)

override fun list(query: PanacheQueryImpl<*>) = query.list()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ class PanacheQueryImpl<Entity : Any> : PanacheQuery<Entity> {

internal constructor(
em: EntityManager?,
query: String?,
hqlQuery: String?,
originalQuery: String?,
orderBy: String?,
paramsArrayOrMap: Any?
) {
delegate = CommonPanacheQueryImpl(em, query, orderBy, paramsArrayOrMap)
delegate = CommonPanacheQueryImpl(em, hqlQuery, originalQuery, orderBy, paramsArrayOrMap)
}

private constructor(delegate: CommonPanacheQueryImpl<Entity>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CustomCountPanacheQuery<Entity> extends PanacheQueryImpl<Entity> {

public CustomCountPanacheQuery(EntityManager em, Query jpaQuery, String customCountQuery,
Object paramsArrayOrMap) {
super(new CommonPanacheQueryImpl<>(em, castQuery(jpaQuery).getQueryString(), null, paramsArrayOrMap) {
super(new CommonPanacheQueryImpl<>(em, castQuery(jpaQuery).getQueryString(), null, null, paramsArrayOrMap) {
{
this.countQuery = customCountQuery;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class JpaOperations extends AbstractJpaOperations<PanacheQueryImpl<?>> {
public static final JpaOperations INSTANCE = new JpaOperations();

@Override
protected PanacheQueryImpl<?> createPanacheQuery(EntityManager em, String query, String orderBy,
protected PanacheQueryImpl<?> createPanacheQuery(EntityManager em, String query, String originalQuery, String orderBy,
Object paramsArrayOrMap) {
return new PanacheQueryImpl<>(em, query, orderBy, paramsArrayOrMap);
return new PanacheQueryImpl<>(em, query, originalQuery, orderBy, paramsArrayOrMap);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {

private CommonPanacheQueryImpl<Entity> delegate;

PanacheQueryImpl(EntityManager em, String query, String orderBy, Object paramsArrayOrMap) {
this.delegate = new CommonPanacheQueryImpl<>(em, query, orderBy, paramsArrayOrMap);
PanacheQueryImpl(EntityManager em, String query, String originalQuery, String orderBy, Object paramsArrayOrMap) {
this.delegate = new CommonPanacheQueryImpl<>(em, query, originalQuery, orderBy, paramsArrayOrMap);
}

protected PanacheQueryImpl(CommonPanacheQueryImpl<Entity> delegate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public abstract class AbstractJpaOperations<PanacheQueryType> {
static final long TIMEOUT_MS = 5000;
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

protected abstract PanacheQueryType createPanacheQuery(Uni<Mutiny.Session> session, String query, String orderBy,
protected abstract PanacheQueryType createPanacheQuery(Uni<Mutiny.Session> session, String query, String originalQuery,
String orderBy,
Object paramsArrayOrMap);

protected abstract Uni<List<?>> list(PanacheQueryType query);
Expand Down Expand Up @@ -102,44 +103,44 @@ public Uni<?> findById(Class<?> entityClass, Object id, LockModeType lockModeTyp
.chain(session -> session.find(entityClass, id, LockModeConverter.convertToLockMode(lockModeType)));
}

public PanacheQueryType find(Class<?> entityClass, String query, Object... params) {
return find(entityClass, query, null, params);
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Object... params) {
return find(entityClass, panacheQuery, null, params);
}

public PanacheQueryType find(Class<?> entityClass, String query, Sort sort, Object... params) {
String findQuery = PanacheJpaUtil.createFindQuery(entityClass, query, paramCount(params));
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Sort sort, Object... params) {
Uni<Mutiny.Session> session = getSession();
if (PanacheJpaUtil.isNamedQuery(query)) {
String namedQuery = query.substring(1);
if (PanacheJpaUtil.isNamedQuery(panacheQuery)) {
String namedQuery = panacheQuery.substring(1);
if (sort != null) {
throw new IllegalArgumentException(
"Sort cannot be used with named query, add an \"order by\" clause to the named query \"" + namedQuery
+ "\" instead");
}
NamedQueryUtil.checkNamedQuery(entityClass, namedQuery);
return createPanacheQuery(session, query, PanacheJpaUtil.toOrderBy(sort), params);
return createPanacheQuery(session, panacheQuery, panacheQuery, PanacheJpaUtil.toOrderBy(sort), params);
}
return createPanacheQuery(session, findQuery, PanacheJpaUtil.toOrderBy(sort), params);
String hqlQuery = PanacheJpaUtil.createFindQuery(entityClass, panacheQuery, paramCount(params));
return createPanacheQuery(session, hqlQuery, panacheQuery, PanacheJpaUtil.toOrderBy(sort), params);
}

public PanacheQueryType find(Class<?> entityClass, String query, Map<String, Object> params) {
return find(entityClass, query, null, params);
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {
return find(entityClass, panacheQuery, null, params);
}

public PanacheQueryType find(Class<?> entityClass, String query, Sort sort, Map<String, Object> params) {
String findQuery = PanacheJpaUtil.createFindQuery(entityClass, query, paramCount(params));
public PanacheQueryType find(Class<?> entityClass, String panacheQuery, Sort sort, Map<String, Object> params) {
Uni<Mutiny.Session> session = getSession();
if (PanacheJpaUtil.isNamedQuery(query)) {
String namedQuery = query.substring(1);
if (PanacheJpaUtil.isNamedQuery(panacheQuery)) {
String namedQuery = panacheQuery.substring(1);
if (sort != null) {
throw new IllegalArgumentException(
"Sort cannot be used with named query, add an \"order by\" clause to the named query \"" + namedQuery
+ "\" instead");
}
NamedQueryUtil.checkNamedQuery(entityClass, namedQuery);
return createPanacheQuery(session, query, PanacheJpaUtil.toOrderBy(sort), params);
return createPanacheQuery(session, panacheQuery, panacheQuery, PanacheJpaUtil.toOrderBy(sort), params);
}
return createPanacheQuery(session, findQuery, PanacheJpaUtil.toOrderBy(sort), params);
String hqlQuery = PanacheJpaUtil.createFindQuery(entityClass, panacheQuery, paramCount(params));
return createPanacheQuery(session, hqlQuery, panacheQuery, PanacheJpaUtil.toOrderBy(sort), params);
}

public PanacheQueryType find(Class<?> entityClass, String query, Parameters params) {
Expand Down Expand Up @@ -177,13 +178,13 @@ public Uni<List<?>> list(Class<?> entityClass, String query, Sort sort, Paramete
public PanacheQueryType findAll(Class<?> entityClass) {
String query = "FROM " + PanacheJpaUtil.getEntityName(entityClass);
Uni<Mutiny.Session> session = getSession();
return createPanacheQuery(session, query, null, null);
return createPanacheQuery(session, query, null, null, null);
}

public PanacheQueryType findAll(Class<?> entityClass, Sort sort) {
String query = "FROM " + PanacheJpaUtil.getEntityName(entityClass);
Uni<Mutiny.Session> session = getSession();
return createPanacheQuery(session, query, PanacheJpaUtil.toOrderBy(sort), null);
return createPanacheQuery(session, query, null, PanacheJpaUtil.toOrderBy(sort), null);
}

public Uni<List<?>> listAll(Class<?> entityClass) {
Expand All @@ -202,33 +203,37 @@ public Uni<Long> count(Class<?> entityClass) {
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public Uni<Long> count(Class<?> entityClass, String query, Object... params) {
public Uni<Long> count(Class<?> entityClass, String panacheQuery, Object... params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName, Long.class), params).getSingleResult();
});

return (Uni) getSession().chain(session -> bindParameters(
session.createQuery(PanacheJpaUtil.createCountQuery(entityClass, query, paramCount(params))),
params).getSingleResult());
session.createQuery(PanacheJpaUtil.createCountQuery(entityClass, panacheQuery, paramCount(params))),
params).getSingleResult())
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public Uni<Long> count(Class<?> entityClass, String query, Map<String, Object> params) {
public Uni<Long> count(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName, Long.class), params).getSingleResult();
});

return (Uni) getSession().chain(session -> bindParameters(
session.createQuery(PanacheJpaUtil.createCountQuery(entityClass, query, paramCount(params))),
params).getSingleResult());
session.createQuery(PanacheJpaUtil.createCountQuery(entityClass, panacheQuery, paramCount(params))),
params).getSingleResult())
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

public Uni<Long> count(Class<?> entityClass, String query, Parameters params) {
Expand Down Expand Up @@ -269,32 +274,36 @@ public Uni<Boolean> deleteById(Class<?> entityClass, Object id) {
});
}

public Uni<Long> delete(Class<?> entityClass, String query, Object... params) {
public Uni<Long> delete(Class<?> entityClass, String panacheQuery, Object... params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate().map(Integer::longValue);
});

return getSession().chain(session -> bindParameters(
session.createQuery(PanacheJpaUtil.createDeleteQuery(entityClass, query, paramCount(params))), params)
.executeUpdate().map(Integer::longValue));
session.createQuery(PanacheJpaUtil.createDeleteQuery(entityClass, panacheQuery, paramCount(params))), params)
.executeUpdate().map(Integer::longValue))
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

public Uni<Long> delete(Class<?> entityClass, String query, Map<String, Object> params) {
public Uni<Long> delete(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate().map(Integer::longValue);
});

return getSession().chain(session -> bindParameters(
session.createQuery(PanacheJpaUtil.createDeleteQuery(entityClass, query, paramCount(params))), params)
.executeUpdate().map(Integer::longValue));
session.createQuery(PanacheJpaUtil.createDeleteQuery(entityClass, panacheQuery, paramCount(params))), params)
.executeUpdate().map(Integer::longValue))
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

public Uni<Long> delete(Class<?> entityClass, String query, Parameters params) {
Expand All @@ -306,30 +315,34 @@ public IllegalStateException implementationInjectionMissing() {
"This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?");
}

public Uni<Integer> executeUpdate(Class<?> entityClass, String query, Object... params) {
public Uni<Integer> executeUpdate(Class<?> entityClass, String panacheQuery, Object... params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate();
});

String updateQuery = PanacheJpaUtil.createUpdateQuery(entityClass, query, paramCount(params));
return executeUpdate(updateQuery, params);
String updateQuery = PanacheJpaUtil.createUpdateQuery(entityClass, panacheQuery, paramCount(params));
return executeUpdate(updateQuery, params)
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

public Uni<Integer> executeUpdate(Class<?> entityClass, String query, Map<String, Object> params) {
public Uni<Integer> executeUpdate(Class<?> entityClass, String panacheQuery, Map<String, Object> params) {

if (PanacheJpaUtil.isNamedQuery(query))
if (PanacheJpaUtil.isNamedQuery(panacheQuery))
return (Uni) getSession().chain(session -> {
String namedQueryName = query.substring(1);
String namedQueryName = panacheQuery.substring(1);
NamedQueryUtil.checkNamedQuery(entityClass, namedQueryName);
return bindParameters(session.createNamedQuery(namedQueryName), params).executeUpdate();
});

String updateQuery = PanacheJpaUtil.createUpdateQuery(entityClass, query, paramCount(params));
return executeUpdate(updateQuery, params);
String updateQuery = PanacheJpaUtil.createUpdateQuery(entityClass, panacheQuery, paramCount(params));
return executeUpdate(updateQuery, params)
.onFailure(IllegalArgumentException.class)
.transform(x -> NamedQueryUtil.checkForNamedQueryMistake((IllegalArgumentException) x, panacheQuery));
}

public Uni<Integer> update(Class<?> entityClass, String query, Map<String, Object> params) {
Expand Down
Loading

0 comments on commit cea417d

Please sign in to comment.