From d066434a13c76b87c1525587858e3454f1b13b49 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 25 Nov 2024 15:59:15 +0100 Subject: [PATCH 1/6] Fix SELECT FOR UPDATE method matching in raw queries --- .../java/io/micronaut/data/hibernate/BookRepository.java | 7 +++++-- .../processor/visitors/finders/RawQueryMethodMatcher.java | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java index 7c9cc476126..12c75cd6e0b 100644 --- a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java +++ b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java @@ -23,9 +23,9 @@ import io.micronaut.data.annotation.Repository; import io.micronaut.data.annotation.Where; import io.micronaut.data.jpa.annotation.EntityGraph; -import io.micronaut.data.jpa.repository.JpaSpecificationExecutor; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; +import io.micronaut.data.repository.jpa.JpaSpecificationExecutor; import io.micronaut.data.tck.entities.Author; import io.micronaut.data.tck.entities.Book; import io.micronaut.data.tck.repositories.AuthorRepository; @@ -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. */ @@ -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 listNativeBooksNullableSearch(@Nullable String arg0); @Query(value = "select * from book where (CASE WHEN exists ( select (:arg0) ) THEN title IN (:arg0) ELSE true END)", nativeQuery = true) diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java index 4450b924b85..6a40a7db2ba 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java @@ -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(".*\\bfor.*\\bupdate\\b.*"); 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.*"); @@ -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; From 8b711dd5550f850727fffca3ff506f7beb9cb2c7 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 25 Nov 2024 16:34:17 +0100 Subject: [PATCH 2/6] Use correct JpaSpecificationExecutor and add processor test for RawQueryMethodMatcher --- .../data/hibernate/BookRepository.java | 2 +- .../data/processor/sql/BuildQuerySpec.groovy | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java index 12c75cd6e0b..088b163a072 100644 --- a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java +++ b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/BookRepository.java @@ -23,9 +23,9 @@ import io.micronaut.data.annotation.Repository; import io.micronaut.data.annotation.Where; import io.micronaut.data.jpa.annotation.EntityGraph; +import io.micronaut.data.jpa.repository.JpaSpecificationExecutor; import io.micronaut.data.model.Page; import io.micronaut.data.model.Pageable; -import io.micronaut.data.repository.jpa.JpaSpecificationExecutor; import io.micronaut.data.tck.entities.Author; import io.micronaut.data.tck.entities.Book; import io.micronaut.data.tck.repositories.AuthorRepository; diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy index b0da47088e1..b561b6fd4c6 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy @@ -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 { @@ -2098,14 +2098,24 @@ interface PersonRepository extends CrudRepository { \""") $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 From 4145dadd6a24ad41c3d19fb2bb48ddfb0cc3f030 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 25 Nov 2024 17:05:08 +0100 Subject: [PATCH 3/6] Try to improve regex --- .../data/processor/visitors/finders/RawQueryMethodMatcher.java | 2 +- .../io/micronaut/data/processor/sql/BuildQuerySpec.groovy | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java index 6a40a7db2ba..eb2b37cb0c3 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java @@ -62,7 +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(".*\\bfor.*\\bupdate\\b.*"); + private static final Pattern FOR_UPDATE_PATTERN = Pattern.compile(".*\\bfor\\s+update\\b.*"); 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.*"); diff --git a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy index b561b6fd4c6..2a92bf95d30 100644 --- a/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy +++ b/data-processor/src/test/groovy/io/micronaut/data/processor/sql/BuildQuerySpec.groovy @@ -2099,7 +2099,8 @@ interface PersonRepository extends CrudRepository { $type customSelect(Long id); @Query(\""" - SELECT * FROM person WHERE id = :id FOR UPDATE + SELECT * FROM person WHERE id = :id FOR + UPDATE \""") $type selectForUpdate(Long id); From a8a586f6569db44995720ba57cffad84b4e77f20 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Mon, 25 Nov 2024 17:14:08 +0100 Subject: [PATCH 4/6] Simple regex for FOR UPDATE --- .../data/processor/visitors/finders/RawQueryMethodMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java index eb2b37cb0c3..65bae0cab8e 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java @@ -62,7 +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(".*\\bfor\\s+update\\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.*"); From 867c91c28c765f6e17b9b218b4ef96660c5cf231 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 26 Nov 2024 09:22:12 +0100 Subject: [PATCH 5/6] Try to make Sonar happy with the regex used to match FOR UPDATE --- .../data/processor/visitors/finders/RawQueryMethodMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java index 65bae0cab8e..f54ce7bda17 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java @@ -62,7 +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 FOR_UPDATE_PATTERN = Pattern.compile("\\bfor\\s+update\\b"); 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.*"); From 110ae8ad6d0a1f2ac11f8542bd23d7b795f325f6 Mon Sep 17 00:00:00 2001 From: radovanradic Date: Tue, 26 Nov 2024 09:31:01 +0100 Subject: [PATCH 6/6] Simplified FOR UPDATE matching regex --- .../data/processor/visitors/finders/RawQueryMethodMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java index f54ce7bda17..08777ad3901 100644 --- a/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java +++ b/data-processor/src/main/java/io/micronaut/data/processor/visitors/finders/RawQueryMethodMatcher.java @@ -62,7 +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("\\bfor\\s+update\\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.*");