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 4b5d64fc5..48cdd1873 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.merge.Merge; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.OrderByElement; import net.sf.jsqlparser.statement.select.PlainSelect; @@ -57,6 +58,7 @@ import org.springframework.util.StringUtils; * * @author Diego Krupitza * @author Greg Turnquist + * @author Geoffrey Deremetz * @since 2.7.0 */ public class JSqlParserQueryEnhancer implements QueryEnhancer { @@ -91,6 +93,8 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { return ParsedType.DELETE; } else if (statement instanceof Select) { return ParsedType.SELECT; + } else if (statement instanceof Merge) { + return ParsedType.MERGE; } else { return ParsedType.SELECT; } @@ -479,10 +483,11 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { *
  • {@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}
  • + *
  • {@code ParsedType.MERGE}: means the top level statement is {@link Merge}
  • * */ enum ParsedType { - DELETE, UPDATE, SELECT, INSERT; + DELETE, UPDATE, SELECT, INSERT, MERGE; } } 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 4f88fe896..955dc32da 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 @@ -83,6 +83,7 @@ import org.springframework.transaction.annotation.Transactional; * @author Diego Krupitza * @author Daniel Shuy * @author Simon Paradies + * @author Geoffrey Deremetz */ @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:application-context.xml") @@ -3002,6 +3003,22 @@ public class UserRepositoryTests { .contains("Gierke", "Arrasz", "Matthews", "raymond", testLastName); } + @Test // GH-2641 + void mergeWithNativeStatement() { + + flushTestUsers(); + + Optional byIdUser = repository.findById(firstUser.getId()); + assertThat(byIdUser).isPresent().map(User::getAge).get().isEqualTo(28); + + // when + repository.mergeNativeStatement(); + + // then + Optional afterUpdate = repository.findById(firstUser.getId()); + assertThat(afterUpdate).isPresent().map(User::getAge).get().isEqualTo(30); + } + 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 bc9f20010..8aa76ffc8 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 @@ -36,6 +36,7 @@ import org.springframework.data.jpa.domain.JpaSort; * Unit tests for {@link QueryEnhancer}. * * @author Diego Krupitza + * @author Geoffrey Deremetz */ class QueryEnhancerUnitTests { @@ -920,6 +921,18 @@ class QueryEnhancerUnitTests { assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); } + @Test // GH-2641 + void mergeStatementWorksWithJSqlParser() { + String query = "merge into a using (select id, value from b) query on (a.id = query.id) when matched then update set a.value = value"; + StringQuery stringQuery = new StringQuery(query, true); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery); + + assertThat(queryEnhancer.getJoinAliases()).isEmpty(); + assertThat(queryEnhancer.detectAlias()).isNull(); + assertThat(queryEnhancer.getProjection()).isEmpty(); + assertThat(queryEnhancer.hasConstructorExpression()).isFalse(); + } + public static Stream insertStatementIsProcessedSameAsDefaultSource() { return Stream.of( // Arguments.of("INSERT INTO FOO(A) VALUES('A')"), // 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 b7bd9b5b9..df67e8beb 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 @@ -43,6 +43,7 @@ import org.springframework.transaction.annotation.Transactional; * @author Greg Turnquist * @author Simon Paradies * @author Diego Krupitza + * @author Geoffrey Deremetz */ public interface UserRepository extends JpaRepository, JpaSpecificationExecutor, UserRepositoryCustom { @@ -681,6 +682,16 @@ public interface UserRepository nativeQuery = true) void insertNewUserWithParamNativeQuery(@Param("lastname") String lastname); + // GH-2641 + @Modifying(clearAutomatically = true) + @Query(value = "merge into sd_user " + + "using (select id from sd_user where age < 30) request " + + "on (sd_user.id = request.id) " + + "when matched then " + + " update set sd_user.age = 30", + nativeQuery = true) + int mergeNativeStatement(); + interface RolesAndFirstname { String getFirstname();