Skip to content

Commit d092eb8

Browse files
Fix order by rendering for queries containing UNION
Make sure to append space after order by clause and fix alias detection for wrapped sub select. Also make sure to ignore alias used in subselect so they do not conflict with root ones.
1 parent 2d2fbf6 commit d092eb8

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public List<JpaQueryParsingToken> visitQueryExpression(HqlParser.QueryExpression
6969

7070
for (int i = 1; i < ctx.orderedQuery().size(); i++) {
7171

72+
SPACE(tokens);
7273
tokens.addAll(visit(ctx.setOperator(i - 1)));
7374
tokens.addAll(visit(ctx.orderedQuery(i)));
7475
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryTransformer.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public List<JpaQueryParsingToken> visitJoinPath(HqlParser.JoinPathContext ctx) {
288288

289289
List<JpaQueryParsingToken> tokens = super.visitJoinPath(ctx);
290290

291-
if (ctx.variable() != null) {
291+
if (ctx.variable() != null && !isSubquery(ctx)) {
292292
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
293293
}
294294

@@ -300,7 +300,7 @@ public List<JpaQueryParsingToken> visitJoinSubquery(HqlParser.JoinSubqueryContex
300300

301301
List<JpaQueryParsingToken> tokens = super.visitJoinSubquery(ctx);
302302

303-
if (ctx.variable() != null) {
303+
if (ctx.variable() != null && !isSubquery(ctx)) {
304304
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
305305
}
306306

@@ -324,7 +324,7 @@ public List<JpaQueryParsingToken> visitVariable(HqlParser.VariableContext ctx) {
324324

325325
List<JpaQueryParsingToken> tokens = super.visitVariable(ctx);
326326

327-
if (ctx.identifier() != null) {
327+
if (ctx.identifier() != null && !isSubquery(ctx)) {
328328
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
329329
}
330330

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java

+38
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,22 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
2022
import java.util.stream.Stream;
2123

2224
import org.assertj.core.api.SoftAssertions;
2325
import org.junit.jupiter.api.Test;
2426
import org.junit.jupiter.params.ParameterizedTest;
2527
import org.junit.jupiter.params.provider.Arguments;
2628
import org.junit.jupiter.params.provider.MethodSource;
29+
import org.junit.jupiter.params.provider.ValueSource;
2730
import org.springframework.dao.InvalidDataAccessApiUsageException;
2831
import org.springframework.data.domain.PageRequest;
2932
import org.springframework.data.domain.Sort;
3033
import org.springframework.data.jpa.domain.JpaSort;
3134
import org.springframework.lang.Nullable;
35+
import org.springframework.util.StringUtils;
3236

3337
/**
3438
* Verify that HQL queries are properly transformed through the {@link JpaQueryEnhancer} and the {@link HqlQueryParser}.
@@ -1043,6 +1047,40 @@ void createsCountQueryUsingAliasCorrectly() {
10431047
assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n","select count(distinct a, count(b)) from Employee AS __ GROUP BY n");
10441048
}
10451049

1050+
@Test // GH-3427
1051+
void sortShouldBeAppendedWithSpacingInCaseOfSetOperator() {
1052+
1053+
String source = "SELECT tb FROM Test tb WHERE (tb.type='A') UNION SELECT tb FROM Test tb WHERE (tb.type='B')";
1054+
String target = createQueryFor(source, Sort.by("Type").ascending());
1055+
1056+
assertThat(target).contains(" UNION SELECT ").doesNotContainPattern(Pattern.compile(".*\\SUNION"));
1057+
}
1058+
1059+
@ParameterizedTest // GH-3427
1060+
@ValueSource(strings = {"", "res"})
1061+
void sortShouldBeAppendedToSubSelectWithSetOperatorInSubselect(String alias) {
1062+
1063+
String prefix = StringUtils.hasText(alias) ? (alias + ".") : "";
1064+
String source = "SELECT %sname FROM (SELECT c.name as name FROM Category c UNION SELECT t.name as name FROM Tag t)".formatted(prefix);
1065+
if(StringUtils.hasText(alias)) {
1066+
source = source + " %s".formatted(alias);
1067+
}
1068+
1069+
String target = createQueryFor(source, Sort.by("name").ascending());
1070+
1071+
assertThat(target).contains(" UNION SELECT ").doesNotContainPattern(Pattern.compile(".*\\SUNION"));
1072+
assertThat(target).endsWith("order by %sname asc".formatted(prefix)).satisfies(it -> {
1073+
Pattern pattern = Pattern.compile("order by %sname".formatted(prefix));
1074+
Matcher matcher = pattern.matcher(target);
1075+
int count = 0;
1076+
while(matcher.find()) {
1077+
count++;
1078+
}
1079+
assertThat(count).describedAs("Found order by clause more than once in: \n%s", it).isOne();
1080+
});
1081+
1082+
}
1083+
10461084
private void assertCountQuery(String originalQuery, String countQuery) {
10471085
assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery);
10481086
}

0 commit comments

Comments
 (0)