Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support CTE in raw query method matching #3236

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,11 @@
*/
public class RawQueryMethodMatcher implements MethodMatcher {

private static final String SELECT = "select";
private static final String DELETE = "delete";
private static final String UPDATE = "update";
private static final String INSERT = "insert";

private static final Pattern UPDATE_PATTERN = Pattern.compile(".*\\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.*");

private static final Pattern VARIABLE_PATTERN = Pattern.compile("([^:\\\\]*)((?<![:]):([a-zA-Z0-9]+))([^:]*)");

@Override
Expand Down Expand Up @@ -173,22 +172,21 @@ private boolean isValidReturnType(ClassElement returnType, DataMethod.OperationT

private DataMethod.OperationType findOperationType(String methodName, String query, boolean readOnly) {
query = query.trim().toLowerCase(Locale.ENGLISH);
if (query.startsWith(SELECT)) {
return DataMethod.OperationType.QUERY;
} else if (query.startsWith(DELETE)) {

if (DELETE_PATTERN.matcher(query).find()) {
if (RETURNING_PATTERN.matcher(query).find()) {
return DataMethod.OperationType.DELETE_RETURNING;
}
return DataMethod.OperationType.DELETE;
} else if (query.startsWith(UPDATE)) {
} else if (UPDATE_PATTERN.matcher(query).find()) {
if (RETURNING_PATTERN.matcher(query).find()) {
return DataMethod.OperationType.UPDATE_RETURNING;
}
if (DeleteMethodMatcher.METHOD_PATTERN.matcher(methodName.toLowerCase(Locale.ENGLISH)).matches()) {
return DataMethod.OperationType.DELETE;
}
return DataMethod.OperationType.UPDATE;
} else if (query.startsWith(INSERT)) {
} 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 @@ -15,6 +15,7 @@
*/
package io.micronaut.data.processor.sql

import io.micronaut.data.intercept.DeleteAllInterceptor
import io.micronaut.data.intercept.DeleteReturningManyInterceptor
import io.micronaut.data.intercept.DeleteReturningOneInterceptor
import io.micronaut.data.intercept.annotation.DataMethod
Expand Down Expand Up @@ -557,4 +558,33 @@ interface PersonRepository extends CrudRepository<Person, Long> {
'java.util.List<Long>' | DeleteReturningManyInterceptor
'Long' | DeleteReturningOneInterceptor
}

@Unroll
void "test build delete with CTE"() {
given:
def repository = buildRepository('test.PersonRepository', """
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)
@io.micronaut.context.annotation.Executable
interface PersonRepository extends CrudRepository<Person, Long> {

@Query(\"""
WITH ids AS (SELECT id FROM person)
DELETE FROM person
WHERE id = :id
\""")
void customDelete(Long id);

}
""")
def method = repository.findPossibleMethods("customDelete").findFirst().get()
def deleteQuery = getQuery(method)

expect:
deleteQuery.replace('\n', ' ') == "WITH ids AS (SELECT id FROM person) DELETE FROM person WHERE id = :id "
method.classValue(DataMethod, "interceptor").get() == DeleteAllInterceptor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micronaut.data.processor.sql

import io.micronaut.data.intercept.SaveEntityInterceptor
import io.micronaut.data.intercept.annotation.DataMethod
import io.micronaut.data.model.DataType
import io.micronaut.data.model.entities.Person
Expand Down Expand Up @@ -589,4 +590,33 @@ interface AccountRepository extends CrudRepository<Account, Long> {
getResultDataType(saveMethod) == DataType.ENTITY
getOperationType(saveMethod) == DataMethod.OperationType.INSERT
}

@Unroll
void "test build insert with CTE"() {
given:
def repository = buildRepository('test.PersonRepository', """
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)
@io.micronaut.context.annotation.Executable
interface PersonRepository extends CrudRepository<Person, Long> {

@Query(\"""
WITH ids AS (SELECT id FROM person)
INSERT INTO person(name, age, enabled)
VALUES (:name, :age, TRUE)
\""")
void customInsert(Person person);

}
""")
def method = repository.findPossibleMethods("customInsert").findFirst().get()
def insertQuery = getQuery(method)

expect:
insertQuery.replace('\n', ' ') == "WITH ids AS (SELECT id FROM person) INSERT INTO person(name, age, enabled) VALUES (:name, :age, TRUE) "
method.classValue(DataMethod, "interceptor").get() == SaveEntityInterceptor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package io.micronaut.data.processor.sql

import io.micronaut.core.annotation.AnnotationMetadata
import io.micronaut.data.annotation.Join
import io.micronaut.data.intercept.FindAllInterceptor
import io.micronaut.data.intercept.FindOneInterceptor
import io.micronaut.data.intercept.annotation.DataMethod
import io.micronaut.data.model.CursoredPageable
import io.micronaut.data.model.DataType
Expand Down Expand Up @@ -2077,4 +2079,37 @@ interface OtherRepository extends GenericRepository<BookEntity, Long> {
then:
noExceptionThrown()
}

@Unroll
void "test build select with CTE for type #type"() {
given:
def repository = buildRepository('test.PersonRepository', """
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)
@io.micronaut.context.annotation.Executable
interface PersonRepository extends CrudRepository<Person, Long> {

@Query(\"""
WITH ids AS (SELECT id FROM person)
SELECT * FROM person
\""")
$type customSelect(Long id);

}
""")
def method = repository.findPossibleMethods("customSelect").findFirst().get()
def selectQuery = getQuery(method)

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

where:
type | interceptor
'java.util.List<Person>' | FindAllInterceptor
'Person' | FindOneInterceptor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,35 @@ interface PersonRepository extends CrudRepository<Person, Long> {
'Long' | UpdateReturningOneInterceptor
}

@Unroll
void "test build update with CTE"() {
given:
def repository = buildRepository('test.PersonRepository', """
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)
@io.micronaut.context.annotation.Executable
interface PersonRepository extends CrudRepository<Person, Long> {

@Query(\"""
WITH ids AS (SELECT id FROM person)
UPDATE person SET name = 'test'
WHERE id = :id
\""")
void customUpdate(Long id);

}
""")
def method = repository.findPossibleMethods("customUpdate").findFirst().get()
def updateQuery = getQuery(method)

expect:
updateQuery.replace('\n', ' ') == "WITH ids AS (SELECT id FROM person) UPDATE person SET name = 'test' WHERE id = :id "
method.classValue(DataMethod, "interceptor").get() == UpdateInterceptor
}

@Unroll
void "test build update with datasource set"() {
given:
Expand Down
Loading