diff --git a/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 b/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 index 5b06cc33e..835cbaff6 100644 --- a/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 +++ b/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 @@ -4,7 +4,7 @@ deleteBy: 'deleteBy' where? EOF; selectStart: 'find' limit 'By' | 'findBy' | 'countAll' | 'countBy' | 'existsBy'; where: condition (and condition| or condition)* ; -condition: eq | gt | gte | lt | lte | between | in | like | truth | untruth | nullable; +condition: eq | gt | gte | lt | lte | between | in | like | truth | untruth | nullable | contains | endsWith | startsWith; order: 'OrderBy' orderName (orderName)*; orderName: variable | variable asc | variable desc; limit : 'First' max?; @@ -22,6 +22,9 @@ lte: variable not? 'LessThanEqual'; between: variable not? 'Between'; in: variable not? 'In'; like: variable not? 'Like'; +contains: variable not? 'Contains'; +endsWith: variable not? 'EndsWith'; +startsWith: variable not? 'StartsWith'; nullable: variable not? 'Null'; not: 'Not'; variable: ANY_NAME; diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/AbstractMethodQueryProvider.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/AbstractMethodQueryProvider.java index 2045ec3bd..1f6144960 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/AbstractMethodQueryProvider.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/AbstractMethodQueryProvider.java @@ -183,6 +183,21 @@ public void exitOr(MethodParser.OrContext ctx) { this.and = false; } + @Override + public void exitContains(MethodParser.ContainsContext ctx) { + throw new UnsupportedOperationException("Contains is not supported in Eclipse JNoSQL method query"); + } + + @Override + public void exitEndsWith(MethodParser.EndsWithContext ctx) { + throw new UnsupportedOperationException("EndsWith is not supported in Eclipse JNoSQL method query"); + } + + @Override + public void exitStartsWith(MethodParser.StartsWithContext ctx) { + throw new UnsupportedOperationException("StartsWith is not supported in Eclipse JNoSQL method query"); + } + private void appendCondition(boolean hasNot, String variable, Condition operator) { ParamQueryValue queryValue = new MethodParamQueryValue(variable); checkCondition(new MethodCondition(variable, operator, queryValue), hasNot); diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodQuery.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodQuery.java index e02901111..86c928831 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodQuery.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodQuery.java @@ -23,7 +23,7 @@ public final class MethodQuery implements Supplier { private final String value; private static final Pattern PATTERN = Pattern.compile("findBy|deleteBy|countAll|countBy|existsBy|" + "OrderBy|First(?=\\d+By)|(?<=First\\d{1,})By|" - + "And|Or(?!der)|Null|Not|Equals|GreaterThanEqual|True|False|" + + + "And|Or(?!der)|Null|Not|Equals|GreaterThanEqual|True|False|Contains|EndsWith|StartsWith|" + "LessThanEqual|GreaterThan|LessThan|Between|In|Like|Asc|Desc"); private static final Map CACHE = Collections.synchronizedMap(new WeakHashMap<>()); private MethodQuery(String value) { diff --git a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProviderTest.java b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProviderTest.java index e29a1768d..fbb0c5e06 100644 --- a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProviderTest.java +++ b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProviderTest.java @@ -538,6 +538,20 @@ void shouldReturnParserQuery37(String query) { assertNull(condition.value().get()); } + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"findByNameContains", "findByNameEndsWith", "findByNameStartsWith"}) + void shouldReturnUnsupportedOperationExceptionQuery(String query) { + String entity = "entity"; + Assertions.assertThrows(UnsupportedOperationException.class, () -> queryProvider.apply(query, entity)); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"findByNameNotContains", "findByNameNotEndsWith", "findByNameNotStartsWith"}) + void shouldReturnUnsupportedOperationExceptionQueryWithNegation(String query) { + String entity = "entity"; + Assertions.assertThrows(UnsupportedOperationException.class, () -> queryProvider.apply(query, entity)); + } + private void checkOrderBy(String query, Direction direction, Direction direction2) { String entity = "entity"; SelectQuery selectQuery = queryProvider.apply(query, entity); diff --git a/jnosql-mapping/jnosql-mapping-core/pom.xml b/jnosql-mapping/jnosql-mapping-core/pom.xml index 55635e109..8de989c20 100644 --- a/jnosql-mapping/jnosql-mapping-core/pom.xml +++ b/jnosql-mapping/jnosql-mapping-core/pom.xml @@ -1,17 +1,17 @@ +~ Copyright (c) 2022 Contributors to the Eclipse Foundation +~ All rights reserved. This program and the accompanying materials +~ are made available under the terms of the Eclipse Public License v1.0 +~ and Apache License v2.0 which accompanies this distribution. +~ The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html +~ and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. +~ +~ You may elect to redistribute this code under either of these licenses. +~ +~ Contributors: +~ +~ Otavio Santana +--> @@ -42,4 +42,30 @@ ${project.version} + + + + + maven-compiler-plugin + + + parameters-testCompile + test-compile + + testCompile + + + + src${file.separator}test${file.separator}/java-parameters + + + + -parameters + + + + + + + diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtils.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtils.java index 06d3a5ad5..c67fb0a82 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtils.java +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtils.java @@ -47,13 +47,14 @@ public Map getParams(Method method, Object[] args) { int queryIndex = 1; for (int index = 0; index < parameters.length; index++) { Parameter parameter = parameters[index]; - boolean isNotSpecialParameter = SpecialParameters.isNotSpecialParameter(parameter); + boolean isNotSpecialParameter = SpecialParameters.isNotSpecialParameter(parameter.getType()); Param param = parameter.getAnnotation(Param.class); if (Objects.nonNull(param)) { params.put(param.value(), args[index]); - } else if (parameter.isNamePresent() && isNotSpecialParameter) { - params.put(parameter.getName(), args[index]); } else if (isNotSpecialParameter) { + if (parameter.isNamePresent()) { + params.put(parameter.getName(), args[index]); + } params.put("?" + queryIndex++, args[index]); } } @@ -73,10 +74,11 @@ public Map getBy(Method method, Object[] args) { Parameter[] parameters = method.getParameters(); for (int index = 0; index < parameters.length; index++) { Parameter parameter = parameters[index]; + boolean isNotSpecialParameter = SpecialParameters.isNotSpecialParameter(parameter.getType()); By by = parameter.getAnnotation(By.class); if (Objects.nonNull(by)) { params.put(by.value(), args[index]); - } else if(parameter.isNamePresent()) { + } else if(parameter.isNamePresent() && isNotSpecialParameter) { params.put(parameter.getName(), args[index]); } } diff --git a/jnosql-mapping/jnosql-mapping-core/src/test/java-parameters/org/eclipse/jnosql/mapping/core/repository/PersonRepositoryCompiledWithParameters.java b/jnosql-mapping/jnosql-mapping-core/src/test/java-parameters/org/eclipse/jnosql/mapping/core/repository/PersonRepositoryCompiledWithParameters.java new file mode 100644 index 000000000..671aab4a1 --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-core/src/test/java-parameters/org/eclipse/jnosql/mapping/core/repository/PersonRepositoryCompiledWithParameters.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Ondro Mihalyi + */ +package org.eclipse.jnosql.mapping.core.repository; + +import jakarta.data.Sort; +import jakarta.data.repository.BasicRepository; +import jakarta.data.repository.By; +import jakarta.data.repository.Param; +import jakarta.data.repository.Query; +import java.util.List; +import org.eclipse.jnosql.mapping.core.entities.Person; + +public interface PersonRepositoryCompiledWithParameters extends BasicRepository { + + @Query("FROM Person WHERE name = :name") + List query(@Param("name") @By("name") String name, Sort sort); + + @Query("FROM Person WHERE age = ?1") + List findAge(int age); + + @Query("FROM Person WHERE age = ?1 AND name = ?2") + List findAgeAndName(int age, String name); + +} diff --git a/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtilsTest.java b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtilsTest.java index 302d12b56..15f028aa6 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtilsTest.java +++ b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/RepositoryReflectionUtilsTest.java @@ -18,7 +18,6 @@ import jakarta.data.repository.BasicRepository; import jakarta.data.repository.Param; import jakarta.data.repository.Query; -import org.assertj.core.api.Assertions; import org.eclipse.jnosql.mapping.core.entities.Person; import org.junit.jupiter.api.Test; @@ -31,13 +30,26 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; +import jakarta.data.Sort; + class RepositoryReflectionUtilsTest { + final Class PERSON_REPOSITORY_COMPILED_WITH_PARAMETERS_CLASS; + + { + try { + PERSON_REPOSITORY_COMPILED_WITH_PARAMETERS_CLASS = Class.forName(this.getClass().getPackageName() + ".PersonRepositoryCompiledWithParameters"); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + @Test - void shouldGetParams(){ + void shouldGetParamsWithoutSpecialParams() { Method method = Arrays.stream(PersonRepository.class.getDeclaredMethods()).filter(m -> m.getName().equals("query")) .findFirst().orElseThrow(); - Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{"Ada"}); + final Sort SPECIAL_PARAM = Sort.asc(""); + Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{"Ada", SPECIAL_PARAM}); assertThat(params) .hasSize(1) .containsEntry("name", "Ada"); @@ -45,7 +57,7 @@ void shouldGetParams(){ } @Test - void shouldQuery(){ + void shouldQuery() { Method method = Arrays.stream(PersonRepository.class.getDeclaredMethods()).filter(m -> m.getName().equals("query")) .findFirst().orElseThrow(); String query = RepositoryReflectionUtils.INSTANCE.getQuery(method); @@ -53,27 +65,44 @@ void shouldQuery(){ } @Test - void shouldBy(){ + void shouldByWithoutSpecialParams() { Method method = Arrays.stream(PersonRepository.class.getDeclaredMethods()).filter(m -> m.getName().equals("query")) .findFirst().orElseThrow(); - Map params = RepositoryReflectionUtils.INSTANCE.getBy(method, new Object[]{"Ada"}); + final Sort SPECIAL_PARAM = Sort.asc(""); + Map params = RepositoryReflectionUtils.INSTANCE.getBy(method, new Object[]{"Ada", SPECIAL_PARAM}); assertThat(params) .hasSize(1) .containsEntry("name", "Ada"); } @Test - void shouldFindByAge(){ + // for code compiled without -parameters + void shouldFindByAgeWithoutParams() { Method method = Stream.of(PersonRepository.class.getDeclaredMethods()).filter(m -> m.getName().equals("findAge")) .findFirst().orElseThrow(); Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{10}); + assertThat(method.getParameters()[0].isNamePresent()).isFalse(); assertThat(params) .hasSize(1) .containsEntry("?1", 10); } @Test - void shouldFindByAgeAndName(){ + // for code compiled with -parameters + void shouldFindByAgeWithParams() throws ClassNotFoundException { + Method method = Stream.of(PERSON_REPOSITORY_COMPILED_WITH_PARAMETERS_CLASS.getDeclaredMethods()).filter(m -> m.getName().equals("findAge")) + .findFirst().orElseThrow(); + Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{10}); + assertThat(method.getParameters()[0].isNamePresent()).isTrue(); + assertThat(params) + .hasSize(2) + .containsEntry("?1", 10) + .containsEntry("age", 10); + } + + @Test + // for code compiled without -parameters + void shouldFindByAgeAndNameWithoutParams() { Method method = Stream.of(PersonRepository.class.getDeclaredMethods()).filter(m -> m.getName().equals("findAgeAndName")) .findFirst().orElseThrow(); Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{10, "Ada"}); @@ -83,10 +112,24 @@ void shouldFindByAgeAndName(){ .containsEntry("?2", "Ada"); } + @Test + // for code compiled with -parameters + void shouldFindByAgeAndNameWithParams() { + Method method = Stream.of(PERSON_REPOSITORY_COMPILED_WITH_PARAMETERS_CLASS.getDeclaredMethods()).filter(m -> m.getName().equals("findAgeAndName")) + .findFirst().orElseThrow(); + Map params = RepositoryReflectionUtils.INSTANCE.getParams(method, new Object[]{10, "Ada"}); + assertThat(params) + .hasSize(4) + .containsEntry("?1", 10) + .containsEntry("?2", "Ada") + .containsEntry("age", 10) + .containsEntry("name", "Ada"); + } + interface PersonRepository extends BasicRepository { @Query("FROM Person WHERE name = :name") - List query(@Param("name") @By("name") String name); + List query(@Param("name") @By("name") String name, Sort sort); @Query("FROM Person WHERE age = ?1") List findAge(int age); @@ -94,4 +137,4 @@ interface PersonRepository extends BasicRepository { @Query("FROM Person WHERE age = ?1 AND name = ?2") List findAgeAndName(int age, String name); } -} \ No newline at end of file +}