Polishing.

Use structured NullsPrecedence rendering in JPQL renderer.

Throw UnsupportedOperationException for the time being until Nulls Precedence is supported through Criteria API.

See #3529
This commit is contained in:
Mark Paluch
2024-08-21 14:35:36 +02:00
parent 410152af3a
commit 52e6aef4a7
3 changed files with 35 additions and 8 deletions

View File

@@ -796,7 +796,7 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<QueryTokenStream> {
builder.append(QueryTokens.expression(ctx.DESC()));
}
if(ctx.nullsPrecedence() != null) {
if (ctx.nullsPrecedence() != null) {
builder.append(visit(ctx.nullsPrecedence()));
}
@@ -805,8 +805,18 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<QueryTokenStream> {
@Override
public QueryTokenStream visitNullsPrecedence(NullsPrecedenceContext ctx) {
// return QueryTokenStream.concat(ctx.children, it-> QueryRendererBuilder.from(QueryTokens.token(it.getText())), TOKEN_SPACE);
return QueryTokenStream.justAs(ctx.children, it-> QueryTokens.token(it.getText()));
QueryRendererBuilder builder = QueryRenderer.builder();
builder.append(TOKEN_NULLS);
if (ctx.FIRST() != null) {
builder.append(TOKEN_FIRST);
} else if (ctx.LAST() != null) {
builder.append(TOKEN_LAST);
}
return builder;
}
@Override

View File

@@ -753,6 +753,10 @@ public abstract class QueryUtils {
PropertyPath property = PropertyPath.from(order.getProperty(), from.getJavaType());
Expression<?> expression = toExpressionRecursively(from, property);
if (order.getNullHandling() != Sort.NullHandling.NATIVE) {
throw new UnsupportedOperationException("Applying Null Precedence using Criteria Queries is not yet supported.");
}
if (order.isIgnoreCase() && String.class.equals(expression.getJavaType())) {
Expression<String> upper = cb.lower((Expression<String>) expression);
return order.isAscending() ? cb.asc(upper) : cb.desc(upper);

View File

@@ -15,11 +15,9 @@
*/
package org.springframework.data.jpa.repository.query;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
@@ -48,6 +46,7 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.sample.Category;
@@ -315,6 +314,20 @@ class QueryUtilsIntegrationTests {
assertThat(orders).hasSize(1);
}
@Test // GH-3529
void nullPrecedenceThroughCriteriaApiNotYetSupported() {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, User> join = root.join("manager", JoinType.LEFT);
Sort sort = Sort.by(Sort.Order.desc("manager").nullsFirst());
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> QueryUtils.toOrders(sort, join, builder));
}
/**
* This test documents an ambiguity in the JPA spec (or it's implementation in Hibernate vs EclipseLink) that we have
* to work around in the test {@link #doesNotCreateJoinForOptionalAssociationWithoutFurtherNavigation()}. See also: