From eff38035949c83d2a281331817d47205d9e2d748 Mon Sep 17 00:00:00 2001 From: Diego Krupitza Date: Thu, 14 Jul 2022 19:55:07 +0200 Subject: [PATCH] Made `JSqlParserQueryEnhancer` aware of `INSERT` statements. The `detectParsedType()` inside `JSqlParserQueryEnhancer` is now aware of `INSERT` statements, which means `INSERT` statements can now be used in native queries. Closes #2593 --- .../query/JSqlParserQueryEnhancer.java | 17 ++++-- .../jpa/repository/UserRepositoryTests.java | 56 ++++++++++++++++--- .../query/QueryEnhancerUnitTests.java | 42 +++++++++++++- .../jpa/repository/sample/UserRepository.java | 19 ++++++- 4 files changed, 119 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index ab9214323..6c6f54d12 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -15,8 +15,9 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*; -import static org.springframework.data.jpa.repository.query.QueryUtils.*; +import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlCount; +import static org.springframework.data.jpa.repository.query.JSqlParserUtils.getJSqlLower; +import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; @@ -26,6 +27,7 @@ import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; @@ -82,7 +84,9 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { try { Statement statement = CCJSqlParserUtil.parse(this.query.getQueryString()); - if (statement instanceof Update) { + if (statement instanceof Insert) { + return ParsedType.INSERT; + } else if (statement instanceof Update) { return ParsedType.UPDATE; } else if (statement instanceof Delete) { return ParsedType.DELETE; @@ -141,7 +145,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { /** * Returns the {@link SetOperationList} as a string query with {@link Sort}s applied in the right order. - * + * * @param setOperationListStatement * @param sort * @return @@ -306,7 +310,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { Select selectStatement = parseSelectStatement(query); /* - For all the other types ({@link ValuesStatement} and {@link SetOperationList}) it does not make sense to provide + For all the other types ({@link ValuesStatement} and {@link SetOperationList}) it does not make sense to provide alias since: * ValuesStatement has no alias * SetOperation can have multiple alias for each operation item @@ -478,10 +482,11 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { *
  • {@code ParsedType.DELETE}: means the top level statement is {@link Delete}
  • *
  • {@code ParsedType.UPDATE}: means the top level statement is {@link Update}
  • *
  • {@code ParsedType.SELECT}: means the top level statement is {@link Select}
  • + *
  • {@code ParsedType.INSERT}: means the top level statement is {@link Insert}
  • * */ enum ParsedType { - DELETE, UPDATE, SELECT; + DELETE, UPDATE, SELECT, INSERT; } } diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 4d2e4691f..440778bc7 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -15,14 +15,23 @@ */ package org.springframework.data.jpa.repository; -import static java.util.Arrays.*; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.domain.Example.*; -import static org.springframework.data.domain.ExampleMatcher.*; -import static org.springframework.data.domain.Sort.Direction.*; -import static org.springframework.data.jpa.domain.Specification.*; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.data.domain.Example.of; +import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; +import static org.springframework.data.domain.ExampleMatcher.StringMatcher; +import static org.springframework.data.domain.ExampleMatcher.matching; +import static org.springframework.data.domain.Sort.Direction.ASC; +import static org.springframework.data.domain.Sort.Direction.DESC; import static org.springframework.data.jpa.domain.Specification.not; -import static org.springframework.data.jpa.domain.sample.UserSpecifications.*; +import static org.springframework.data.jpa.domain.Specification.where; +import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasAgeLess; +import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasFirstname; +import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasFirstnameLike; +import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasLastname; +import static org.springframework.data.jpa.domain.sample.UserSpecifications.userHasLastnameLikeWithSort; import lombok.Data; @@ -2768,6 +2777,39 @@ public class UserRepositoryTests { assertThat(foundData).containsExactly("joachim", "dave", "kevin"); } + @Test // GH-2593 + void insertStatementModifyingQueryWorks() { + + flushTestUsers(); + + repository.insertNewUserWithNativeQuery(); + + List all = repository.findAll(); + assertThat(all) // + .isNotNull() // + .isNotEmpty() // + .hasSize(5) // + .map(User::getLastname) // + .contains("Gierke", "Arrasz", "Matthews", "raymond", "K"); + } + + @Test // GH-2593 + void insertStatementModifyingQueryWithParamsWorks() { + + flushTestUsers(); + + String testLastName = "TestLastName"; + repository.insertNewUserWithParamNativeQuery(testLastName); + + List all = repository.findAll(); + assertThat(all) // + .isNotNull() // + .isNotEmpty() // + .hasSize(5) // + .map(User::getLastname) // + .contains("Gierke", "Arrasz", "Matthews", "raymond", testLastName); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index c87946863..5424d84b9 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -15,7 +15,9 @@ */ package org.springframework.data.jpa.repository.query; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Arrays; import java.util.Collections; @@ -889,6 +891,44 @@ class QueryEnhancerUnitTests { assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); } + @ParameterizedTest // GH-2593 + @MethodSource("insertStatementIsProcessedSameAsDefaultSource") + void insertStatementIsProcessedSameAsDefault(String insertQuery) { + + StringQuery stringQuery = new StringQuery(insertQuery, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + Sort sorting = Sort.by("day").descending(); + + // queryutils results + String queryUtilsDetectAlias = QueryUtils.detectAlias(insertQuery); + String queryUtilsProjection = QueryUtils.getProjection(insertQuery); + String queryUtilsCountQuery = QueryUtils.createCountQueryFor(insertQuery); + Set queryUtilsOuterJoinAlias = QueryUtils.getOuterJoinAliases(insertQuery); + + // direct access + assertThat(stringQuery.getAlias()).isEqualToIgnoringCase(queryUtilsDetectAlias); + assertThat(stringQuery.getProjection()).isEqualToIgnoringCase(queryUtilsProjection); + assertThat(stringQuery.hasConstructorExpression()).isFalse(); + + // access over enhancer + assertThat(queryEnhancer.createCountQueryFor()).isEqualToIgnoringCase(queryUtilsCountQuery); + assertThat(queryEnhancer.applySorting(sorting)).isEqualTo(insertQuery); // cant check with queryutils result since + // query utils appens order by which is not + // supported by sql standard. + assertThat(queryEnhancer.getJoinAliases()).isEqualTo(queryUtilsOuterJoinAlias); + assertThat(queryEnhancer.detectAlias()).isEqualToIgnoringCase(queryUtilsDetectAlias); + assertThat(queryEnhancer.getProjection()).isEqualToIgnoringCase(queryUtilsProjection); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + + public static Stream insertStatementIsProcessedSameAsDefaultSource() { + return Stream.of( // + Arguments.of("INSERT INTO FOO(A) VALUES('A')"), // + Arguments.of("INSERT INTO randomsecondTable(A,B,C,D) VALUES('A','B','C','D')") // + ); + } + public static Stream detectsJoinAliasesCorrectlySource() { return Stream.of( // diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index fb3d0e416..3e514d18c 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -23,7 +23,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.persistence.EntityManager; import javax.persistence.QueryHint; import org.springframework.data.domain.Page; @@ -55,6 +54,7 @@ import org.springframework.transaction.annotation.Transactional; * @author JyotirmoyVS * @author Greg Turnquist * @author Simon Paradies + * @author Diego Krupitza */ public interface UserRepository extends JpaRepository, JpaSpecificationExecutor, UserRepositoryCustom { @@ -676,6 +676,23 @@ public interface UserRepository nativeQuery = true) List complexWithNativeStatement(); + // GH-2607 + List findByAttributesContains(String attribute); + + // GH-2593 + @Modifying + @Query( + value = "INSERT INTO SD_User(id,active,age,firstname,lastname,emailAddress,DTYPE) VALUES (9999,true,23,'Diego','K','dk@email.com','User')", + nativeQuery = true) + void insertNewUserWithNativeQuery(); + + // GH-2593 + @Modifying + @Query( + value = "INSERT INTO SD_User(id,active,age,firstname,lastname,emailAddress,DTYPE) VALUES (9999,true,23,'Diego',:lastname,'dk@email.com','User')", + nativeQuery = true) + void insertNewUserWithParamNativeQuery(@Param("lastname") String lastname); + interface RolesAndFirstname { String getFirstname();