From dfe9ab45644f39e496ba1a83d39823ae14b209e1 Mon Sep 17 00:00:00 2001 From: Muhammad Hewedy Date: Sat, 22 Jun 2019 02:09:13 +0300 Subject: [PATCH] DATAJPA-1564 - Support for linebreaks in SQL and JPQL statements. Annotated queries now my contain linebreaks. This seems especially relevant for JVM languages with multiline String literals. Original pull request: #386. --- .../data/jpa/repository/query/QueryUtils.java | 17 +++++++--- .../repository/query/QueryUtilsUnitTests.java | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index ac842dd0c..b0d36310c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -78,6 +78,7 @@ import org.springframework.util.StringUtils; * @author Reda.Housni-Alaoui * @author Florian Lüdiger * @author Grégoire Druant + * @author Mohammad Hewedy */ public abstract class QueryUtils { @@ -95,7 +96,8 @@ public abstract class QueryUtils { private static final String COUNT_REPLACEMENT_TEMPLATE = "select count(%s) $5$6$7"; private static final String SIMPLE_COUNT_VALUE = "$2"; - private static final String COMPLEX_COUNT_VALUE = "$3$6"; + private static final String COMPLEX_COUNT_VALUE = "$3 $6"; + private static final String COMPLEX_COUNT_LAST_VALUE = "$6"; private static final String ORDER_BY_PART = "(?iu)\\s+order\\s+by\\s+.*"; private static final Pattern ALIAS_MATCH; @@ -119,6 +121,7 @@ public abstract class QueryUtils { private static final int QUERY_JOIN_ALIAS_GROUP_INDEX = 3; private static final int VARIABLE_NAME_GROUP_INDEX = 4; + private static final int COMPLEX_COUNT_FIRST_INDEX = 3; private static final Pattern PUNCTATION_PATTERN = Pattern.compile(".*((?![\\._])[\\p{Punct}|\\s])"); private static final Pattern FUNCTION_PATTERN; @@ -136,12 +139,12 @@ public abstract class QueryUtils { builder.append(IDENTIFIER_GROUP); // Entity name, can be qualified (any builder.append("(?:\\sas)*"); // exclude possible "as" keyword builder.append("(?:\\s)+"); // at least one space separating - builder.append("(?!(?:where|group by|order by))(\\w+)"); // the actual alias + builder.append("(?!(?:where|group\\s*by|order\\s*by))(\\w+)"); // the actual alias ALIAS_MATCH = compile(builder.toString(), CASE_INSENSITIVE); builder = new StringBuilder(); - builder.append("(select\\s+((distinct )?(.+?)?)\\s+)?(from\\s+"); + builder.append("(select\\s+((distinct)?((?s).+?)?)\\s+)?(from\\s+"); builder.append(IDENTIFIER); builder.append("(?:\\s+as)?\\s+)"); builder.append(IDENTIFIER_GROUP); @@ -480,10 +483,14 @@ public abstract class QueryUtils { if (countProjection == null) { String variable = matcher.matches() ? matcher.group(VARIABLE_NAME_GROUP_INDEX) : null; - boolean useVariable = variable != null && StringUtils.hasText(variable) && !variable.startsWith("new") + boolean useVariable = StringUtils.hasText(variable) && !variable.startsWith(" new") && !variable.startsWith("count(") && !variable.contains(","); - String replacement = useVariable ? SIMPLE_COUNT_VALUE : COMPLEX_COUNT_VALUE; + String complexCountValue = matcher.matches() && + StringUtils.hasText(matcher.group(COMPLEX_COUNT_FIRST_INDEX)) ? + COMPLEX_COUNT_VALUE : COMPLEX_COUNT_LAST_VALUE; + + String replacement = useVariable ? SIMPLE_COUNT_VALUE : complexCountValue; countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, replacement)); } else { countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, countProjection)); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java index cd43c8d9a..190994334 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsUnitTests.java @@ -40,6 +40,7 @@ import org.springframework.data.jpa.domain.JpaSort; * @author Jens Schauder * @author Florian Lüdiger * @author Grégoire Druant + * @author Mohammad Hewedy */ public class QueryUtilsUnitTests { @@ -424,6 +425,18 @@ public class QueryUtilsUnitTests { " where user.age = 18\n ")); } + @Test + public void createCountQuerySupportsLineBreaksInSelectClause() { + + assertThat(createCountQueryFor("select user.age,\n" + // + " user.name\n" + // + " from User user\n" + // + " where user.age = 18\n" + // + " order\nby\nuser.name\n "), // + is("select count(user) from User user\n" + // + " where user.age = 18\n ")); + } + @Test // DATAJPA-1061 public void appliesSortCorrectlyForFieldAliases() { @@ -457,6 +470,26 @@ public class QueryUtilsUnitTests { assertThat(fullQuery, endsWith("order by m.price asc")); } + @Test + public void createCountQuerySupportsLineBreakRightAfterDistinct() { + + assertThat(createCountQueryFor("select\ndistinct\nuser.age,\n" + // + "user.name\n" + // + "from\nUser\nuser"), // + is(createCountQueryFor("select\ndistinct user.age,\n" + // + "user.name\n" + // + "from\nUser\nuser"))); + } + + @Test + public void detectsAliasWithGroupAndOrderByWithLineBreaks() { + + assertThat(detectAlias("select * from User group\nby name")).isNull(); + assertThat(detectAlias("select * from User order\nby name")).isNull(); + assertThat(detectAlias("select * from User u group\nby name")).isEqualTo("u"); + assertThat(detectAlias("select * from User u order\nby name")).isEqualTo("u"); + } + private static void assertCountQuery(String originalQuery, String countQuery) { assertThat(createCountQueryFor(originalQuery), is(countQuery)); }