From 22663503f10b592cbd87d2cd8400aca1a5ddac95 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 | 8 ++- .../jpa/repository/UserRepositoryTests.java | 58 ++++++++++++------- .../query/QueryEnhancerUnitTests.java | 38 ++++++++++++ .../jpa/repository/sample/UserRepository.java | 34 ++++++----- 4 files changed, 99 insertions(+), 39 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 1c6c42ae8..4b5d64fc5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -26,6 +26,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 +83,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; @@ -475,10 +478,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/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 283e45161..4f88fe896 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -15,13 +15,14 @@ */ package org.springframework.data.jpa.repository; -import static java.util.Arrays.*; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.domain.Example.*; +import static org.springframework.data.domain.Example.of; 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 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.Specification.where; import static org.springframework.data.jpa.domain.sample.UserSpecifications.*; import jakarta.persistence.EntityManager; @@ -33,14 +34,7 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import lombok.Data; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; @@ -54,14 +48,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.domain.Example; -import org.springframework.data.domain.ExampleMatcher; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.Specification; @@ -2969,7 +2956,7 @@ public class UserRepositoryTests { } @Test // GH-2607 - void containsWithCollection(){ + void containsWithCollection() { firstUser.getAttributes().add("cool"); firstUser.getAttributes().add("hip"); @@ -2986,6 +2973,35 @@ public class UserRepositoryTests { assertThat(result).containsOnly(firstUser, secondUser); } + @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/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index c87946863..bc9f20010 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -889,6 +889,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/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 3236ddfa3..b7bd9b5b9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -18,27 +18,14 @@ package org.springframework.data.jpa.repository.sample; import jakarta.persistence.EntityManager; import jakarta.persistence.QueryHint; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Stream; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.SpecialUser; import org.springframework.data.jpa.domain.sample.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.jpa.repository.*; import org.springframework.data.jpa.repository.query.Procedure; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -55,6 +42,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 { @@ -679,6 +667,20 @@ public interface UserRepository // 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();