diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy index 0a352af516..905b2bf479 100644 --- a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/oraclexe/OracleXERepositorySpec.groovy @@ -17,6 +17,7 @@ package io.micronaut.data.jdbc.oraclexe import groovy.transform.Memoized import io.micronaut.data.tck.entities.Book +import io.micronaut.data.tck.entities.Face import io.micronaut.data.tck.repositories.* import io.micronaut.data.tck.tests.AbstractRepositorySpec import spock.lang.PendingFeature @@ -271,4 +272,17 @@ class OracleXERepositorySpec extends AbstractRepositorySpec implements OracleTes bookRepository.findById(book.id).get().title == "Xyz" } + void "test native query with colon"() { + given: + def face = faceRepository.save(new Face("New")) + def oracleFaceRepository = (OracleXEFaceRepository) faceRepository + when: + def faces = oracleFaceRepository.findAllWithOptionalFilters(null, "2024-01-01") + then: + faces + faces[0].name == face.name + cleanup: + faceRepository.delete(face) + } + } diff --git a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEFaceRepository.java b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEFaceRepository.java index c3da619088..e3cf2ee3f0 100644 --- a/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEFaceRepository.java +++ b/data-jdbc/src/test/java/io/micronaut/data/jdbc/oraclexe/OracleXEFaceRepository.java @@ -15,10 +15,25 @@ */ package io.micronaut.data.jdbc.oraclexe; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.annotation.Query; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.tck.entities.Face; import io.micronaut.data.tck.repositories.FaceRepository; +import java.util.List; + @JdbcRepository(dialect = Dialect.ORACLE) public interface OracleXEFaceRepository extends FaceRepository { + + @Query( + """ + SELECT * FROM face f WHERE + (f.date_created >= COALESCE(TO_TIMESTAMP(:dateCreatedParam, 'YYYY-MM-DD"T"HH24\\:MI\\:SS"Z"'), f.date_created) OR :dateCreatedParam IS NULL) AND + (f.name = :name OR :name IS NULL) + """) + List findAllWithOptionalFilters( + @Nullable String name, + @Nullable String dateCreatedParam); } 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 08777ad390..19399944ab 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 @@ -68,6 +68,9 @@ public class RawQueryMethodMatcher implements MethodMatcher { private static final Pattern RETURNING_PATTERN = Pattern.compile(".*\\breturning\\b.*"); private static final Pattern VARIABLE_PATTERN = Pattern.compile("([^:\\\\]*)((?> parameterExpressions = matchContext.getMethodElement() .getAnnotationMetadata() @@ -262,9 +266,9 @@ private QueryResult getQueryResult(MethodMatchContext matchContext, int index = 1; int lastOffset = 0; while (matcher.find()) { - String prefix = queryString.substring(lastOffset, matcher.start(3) - 1); + String prefix = newQueryString.substring(lastOffset, matcher.start(3) - 1); if (!prefix.isEmpty()) { - queryParts.add(prefix); + queryParts.add(prefix.replace(COLON_TEMP_REPLACEMENT, COLON)); } lastOffset = matcher.end(3); String name = matcher.group(3); @@ -285,13 +289,12 @@ private QueryResult getQueryResult(MethodMatchContext matchContext, parameterBindings.add(queryParameterBinding); } - queryString = queryString.replace("\\:", ":"); if (queryParts.isEmpty()) { - queryParts.add(queryString); + queryParts.add(newQueryString.replace(COLON_TEMP_REPLACEMENT, COLON)); } else if (lastOffset > 0) { - queryParts.add(queryString.substring(lastOffset)); + queryParts.add(newQueryString.substring(lastOffset).replace(COLON_TEMP_REPLACEMENT, COLON)); } - String finalQueryString = queryString; + String finalQueryString = newQueryString.replace(COLON_TEMP_REPLACEMENT, COLON); return new QueryResult() { @Override public String getQuery() { 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 2a92bf95d3..c2e0d1626e 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 @@ -50,6 +50,7 @@ import static io.micronaut.data.processor.visitors.TestUtils.getParameterBinding import static io.micronaut.data.processor.visitors.TestUtils.getParameterExpressions import static io.micronaut.data.processor.visitors.TestUtils.getParameterPropertyPaths import static io.micronaut.data.processor.visitors.TestUtils.getQuery +import static io.micronaut.data.processor.visitors.TestUtils.getQueryParts import static io.micronaut.data.processor.visitors.TestUtils.getRawQuery import static io.micronaut.data.processor.visitors.TestUtils.getResultDataType import static io.micronaut.data.processor.visitors.TestUtils.isExpandableQuery @@ -640,6 +641,36 @@ interface FacesRepository extends CrudRepository { } + void "test native query with colon used in query"() { + given: + def repository = buildRepository('test.FacesRepository', """ +import io.micronaut.core.annotation.Nullable; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.GenericRepository; +import io.micronaut.data.tck.entities.Face; +import java.util.UUID; + +@JdbcRepository(dialect = Dialect.ORACLE) +interface FacesRepository extends GenericRepository { + + @Query("SELECT * FROM face f WHERE" + + " (f.date_created >= COALESCE(TO_TIMESTAMP(:dateCreated, 'YYYY-MM-DD\\"T\\"HH24\\\\:MI\\\\:SS\\"Z\\"'), f.date_created) OR :dateCreated IS NULL) AND" + + " (f.name = :name OR :name IS NULL)") + List findAllWithOptionalFilters( + @Nullable String name, + @Nullable String dateCreated); +} +""") + def method = repository.getRequiredMethod("findAllWithOptionalFilters", String, String) + def rawQuery = getRawQuery(method) + def query = getQuery(method) + + expect: + rawQuery == 'SELECT * FROM face f WHERE (f.date_created >= COALESCE(TO_TIMESTAMP(?, \'YYYY-MM-DD"T"HH24:MI:SS"Z"\'), f.date_created) OR ? IS NULL) AND (f.name = ? OR ? IS NULL)' + query == 'SELECT * FROM face f WHERE (f.date_created >= COALESCE(TO_TIMESTAMP(:dateCreated, \'YYYY-MM-DD"T"HH24\\:MI\\:SS"Z"\'), f.date_created) OR :dateCreated IS NULL) AND (f.name = :name OR :name IS NULL)' + } + void "test In in properties"() { given: def repository = buildRepository('test.PurchaseRepository', """