Skip to content

Commit

Permalink
Fix SELECT FOR UPDATE method matching in raw queries (#3249)
Browse files Browse the repository at this point in the history
* Fix SELECT FOR UPDATE method matching in raw queries

* Use correct JpaSpecificationExecutor and add processor test for RawQueryMethodMatcher

* Try to improve regex

* Simple regex for FOR UPDATE

* Try to make Sonar happy with the regex used to match FOR UPDATE

* Simplified FOR UPDATE matching regex
  • Loading branch information
radovanradic authored Nov 26, 2024
1 parent 1352324 commit 86a54bb
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public BookRepository(AuthorRepository authorRepository) {
super(authorRepository);
}

@Override
public abstract Book save(Book book);

/**
* @deprecated Order by 'author.name' case without a join. Hibernate will do the cross join if the association property is accessed by the property path without join.
*/
Expand Down Expand Up @@ -71,7 +74,7 @@ public BookRepository(AuthorRepository authorRepository) {
@Query(value = "select count(*) from book b where b.title like :title and b.total_pages > :pages", nativeQuery = true)
abstract int countNativeByTitleWithPagesGreaterThan(String title, int pages);

@Query(value = "select * from book where (CASE WHEN CAST(:arg0 AS VARCHAR) is not null THEN title = :arg0 ELSE true END)", nativeQuery = true)
@Query(value = "select * from book where (CASE WHEN CAST(:arg0 AS VARCHAR) is not null THEN title = :arg0 ELSE true END) FOR UPDATE", nativeQuery = true)
public abstract List<Book> listNativeBooksNullableSearch(@Nullable String arg0);

@Query(value = "select * from book where (CASE WHEN exists ( select (:arg0) ) THEN title IN (:arg0) ELSE true END)", nativeQuery = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
public class RawQueryMethodMatcher implements MethodMatcher {

private static final Pattern UPDATE_PATTERN = Pattern.compile(".*\\bupdate\\b.*");
private static final Pattern FOR_UPDATE_PATTERN = Pattern.compile("for\\s+update");
private static final Pattern DELETE_PATTERN = Pattern.compile(".*\\bdelete\\b.*");
private static final Pattern INSERT_PATTERN = Pattern.compile(".*\\binsert\\b.*");
private static final Pattern RETURNING_PATTERN = Pattern.compile(".*\\breturning\\b.*");
Expand Down Expand Up @@ -185,7 +186,9 @@ private DataMethod.OperationType findOperationType(String methodName, String que
if (DeleteMethodMatcher.METHOD_PATTERN.matcher(methodName.toLowerCase(Locale.ENGLISH)).matches()) {
return DataMethod.OperationType.DELETE;
}
return DataMethod.OperationType.UPDATE;
if (!FOR_UPDATE_PATTERN.matcher(query).find()) {
return DataMethod.OperationType.UPDATE;
}
} else if (INSERT_PATTERN.matcher(query).find()) {
if (RETURNING_PATTERN.matcher(query).find()) {
return DataMethod.OperationType.INSERT_RETURNING;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2088,7 +2088,7 @@ import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.tck.entities.Person;
@JdbcRepository(dialect= Dialect.MYSQL)
@JdbcRepository(dialect = Dialect.MYSQL)
@io.micronaut.context.annotation.Executable
interface PersonRepository extends CrudRepository<Person, Long> {
Expand All @@ -2098,14 +2098,25 @@ interface PersonRepository extends CrudRepository<Person, Long> {
\""")
$type customSelect(Long id);
@Query(\"""
SELECT * FROM person WHERE id = :id FOR
UPDATE
\""")
$type selectForUpdate(Long id);
}
""")
def method = repository.findPossibleMethods("customSelect").findFirst().get()
def selectQuery = getQuery(method)
def customSelectMethod = repository.findPossibleMethods("customSelect").findFirst().get()
def customSelectQuery = getQuery(customSelectMethod)
def selectForUpdateMethod = repository.findPossibleMethods("selectForUpdate").findFirst().get()
def selectForUpdateQuery = getQuery(selectForUpdateMethod)

expect:
selectQuery.replace('\n', ' ') == "WITH ids AS (SELECT id FROM person) SELECT * FROM person "
method.classValue(DataMethod, "interceptor").get() == interceptor
customSelectQuery.replace('\n', ' ') == "WITH ids AS (SELECT id FROM person) SELECT * FROM person "
customSelectMethod.classValue(DataMethod, "interceptor").get() == interceptor

selectForUpdateQuery.replace('\n', ' ') == "SELECT * FROM person WHERE id = :id FOR UPDATE "
selectForUpdateMethod.classValue(DataMethod, "interceptor").get() == interceptor

where:
type | interceptor
Expand Down

0 comments on commit 86a54bb

Please sign in to comment.