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
This commit is contained in:
committed by
Greg L. Turnquist
parent
d64cfd15ce
commit
eff3803594
@@ -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 {
|
||||
* <li>{@code ParsedType.DELETE}: means the top level statement is {@link Delete}</li>
|
||||
* <li>{@code ParsedType.UPDATE}: means the top level statement is {@link Update}</li>
|
||||
* <li>{@code ParsedType.SELECT}: means the top level statement is {@link Select}</li>
|
||||
* <li>{@code ParsedType.INSERT}: means the top level statement is {@link Insert}</li>
|
||||
* </ul>
|
||||
*/
|
||||
enum ParsedType {
|
||||
DELETE, UPDATE, SELECT;
|
||||
DELETE, UPDATE, SELECT, INSERT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<User> 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<User> all = repository.findAll();
|
||||
assertThat(all) //
|
||||
.isNotNull() //
|
||||
.isNotEmpty() //
|
||||
.hasSize(5) //
|
||||
.map(User::getLastname) //
|
||||
.contains("Gierke", "Arrasz", "Matthews", "raymond", testLastName);
|
||||
}
|
||||
|
||||
private Page<User> executeSpecWithSort(Sort sort) {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
@@ -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<String> 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<Arguments> 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<Arguments> detectsJoinAliasesCorrectlySource() {
|
||||
|
||||
return Stream.of( //
|
||||
|
||||
@@ -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<User, Integer>, JpaSpecificationExecutor<User>, UserRepositoryCustom {
|
||||
@@ -676,6 +676,23 @@ public interface UserRepository
|
||||
nativeQuery = true)
|
||||
List<String> complexWithNativeStatement();
|
||||
|
||||
// GH-2607
|
||||
List<User> 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();
|
||||
|
||||
Reference in New Issue
Block a user