diff --git a/.github/workflows/tck-runner.yml b/.github/workflows/tck-runner.yml deleted file mode 100644 index 919145dc7..000000000 --- a/.github/workflows/tck-runner.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Run Jakarta Data TCK - -on: - schedule: - - cron: '0 0 * * 1,3,5' #Monday, Wednesday, Friday at Midnight - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - java-version: [17, 21] - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: ${{ matrix.java-version }} - cache: maven - - name: Test with maven - run: mvn test --file tck-runner/pom.xml diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 4a8bd38c5..e5cadc587 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,7 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version - Enables custom Repository - Include the `First` keyword in the method by query in the Repository +- Include the `Null`, `NotNull` and `countAll` keywords in the method by query in the Repository == [1.1.1] - 2023-05-25 diff --git a/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 b/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 index 23e7c9c55..5b06cc33e 100644 --- a/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 +++ b/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 @@ -2,9 +2,9 @@ grammar Method; select: selectStart where? order? EOF; deleteBy: 'deleteBy' where? EOF; -selectStart: 'find' limit 'By' | 'findBy'| 'countBy'| 'existsBy'; +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; +condition: eq | gt | gte | lt | lte | between | in | like | truth | untruth | nullable; order: 'OrderBy' orderName (orderName)*; orderName: variable | variable asc | variable desc; limit : 'First' max?; @@ -22,6 +22,7 @@ lte: variable not? 'LessThanEqual'; between: variable not? 'Between'; in: variable not? 'In'; like: variable not? 'Like'; +nullable: variable not? 'Null'; not: 'Not'; variable: ANY_NAME; max: INT; diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/DefaultQueryValue.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/DefaultQueryValue.java index 82e751bad..8753c5f42 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/DefaultQueryValue.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/DefaultQueryValue.java @@ -31,7 +31,7 @@ public String toString() { } public static DefaultQueryValue of(String text) { - if(text.startsWith(":")) { + if(text != null && text.startsWith(":")) { return new DefaultQueryValue(text.substring(1)); } return new DefaultQueryValue(text); 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 6feca50f7..2045ec3bd 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 @@ -23,6 +23,7 @@ import org.eclipse.jnosql.communication.query.ParamQueryValue; import org.eclipse.jnosql.communication.query.QueryCondition; import org.eclipse.jnosql.communication.query.QueryErrorListener; +import org.eclipse.jnosql.communication.query.StringQueryValue; import org.eclipse.jnosql.communication.query.Where; import org.eclipse.jnosql.query.grammar.method.MethodBaseListener; import org.eclipse.jnosql.query.grammar.method.MethodLexer; @@ -37,6 +38,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import static java.util.stream.Collectors.joining; import static org.eclipse.jnosql.communication.Condition.AND; import static org.eclipse.jnosql.communication.Condition.BETWEEN; import static org.eclipse.jnosql.communication.Condition.EQUALS; @@ -48,7 +50,6 @@ import static org.eclipse.jnosql.communication.Condition.LIKE; import static org.eclipse.jnosql.communication.Condition.NOT; import static org.eclipse.jnosql.communication.Condition.OR; -import static java.util.stream.Collectors.joining; abstract class AbstractMethodQueryProvider extends MethodBaseListener { @@ -59,6 +60,8 @@ abstract class AbstractMethodQueryProvider extends MethodBaseListener { protected boolean and = true; + protected boolean shouldCount = false; + protected void runQuery(String query) { CharStream stream = CharStreams.fromString(query); @@ -81,6 +84,11 @@ protected void runQuery(String query) { abstract Function getParserTree(); + @Override + public void exitSelectStart(MethodParser.SelectStartContext ctx) { + this.shouldCount = ctx.getText().startsWith("count"); + } + @Override public void exitEq(MethodParser.EqContext ctx) { Condition operator = EQUALS; @@ -158,6 +166,13 @@ public void exitBetween(MethodParser.BetweenContext ctx) { checkCondition(new MethodCondition(variable, operator, value), hasNot); } + @Override + public void exitNullable(MethodParser.NullableContext ctx) { + boolean hasNot = Objects.nonNull(ctx.not()); + String variable = getVariable(ctx.variable()); + checkCondition(new MethodCondition(variable, EQUALS, StringQueryValue.of(null)), hasNot); + } + @Override public void exitAnd(MethodParser.AndContext ctx) { this.and = true; 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 869bc6540..e02901111 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 @@ -21,9 +21,9 @@ public final class MethodQuery implements Supplier { private final String value; - private static final Pattern PATTERN = Pattern.compile("findBy|deleteBy|countBy|existsBy|" + private static final Pattern PATTERN = Pattern.compile("findBy|deleteBy|countAll|countBy|existsBy|" + "OrderBy|First(?=\\d+By)|(?<=First\\d{1,})By|" - + "And|Or(?!der)|Not|Equals|GreaterThanEqual|True|False|" + + + "And|Or(?!der)|Null|Not|Equals|GreaterThanEqual|True|False|" + "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/main/java/org/eclipse/jnosql/communication/query/method/MethodSelectQuery.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodSelectQuery.java index e1b54df22..e55bdf4f6 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodSelectQuery.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/MethodSelectQuery.java @@ -31,11 +31,14 @@ final class MethodSelectQuery implements SelectQuery { private final long limit; - MethodSelectQuery(String entity, List> sorts, Where where, long limit) { + private final boolean count; + + MethodSelectQuery(String entity, List> sorts, Where where, long limit, boolean count) { this.entity = entity; this.sorts = sorts; this.where = where; this.limit = limit; + this.count = count; } @@ -70,7 +73,7 @@ public List> orderBy() { @Override public boolean isCount() { - return false; + return count; } public boolean equals(Object o) { diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProvider.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProvider.java index 8d1507b06..9dbc53401 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProvider.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/method/SelectMethodQueryProvider.java @@ -34,7 +34,7 @@ public SelectQuery apply(String query, String entity) { Objects.requireNonNull(query, " query is required"); Objects.requireNonNull(entity, " entity is required"); runQuery(MethodQuery.of(query).get()); - return new MethodSelectQuery(entity, sorts, where, limit); + return new MethodSelectQuery(entity, sorts, where, limit, shouldCount); } @Override 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 1e7403f68..e29a1768d 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 @@ -11,15 +11,16 @@ */ package org.eclipse.jnosql.communication.query.method; -import org.eclipse.jnosql.communication.Condition; -import jakarta.data.Sort; import jakarta.data.Direction; +import jakarta.data.Sort; +import org.eclipse.jnosql.communication.Condition; import org.eclipse.jnosql.communication.query.BooleanQueryValue; import org.eclipse.jnosql.communication.query.ConditionQueryValue; import org.eclipse.jnosql.communication.query.ParamQueryValue; import org.eclipse.jnosql.communication.query.QueryCondition; import org.eclipse.jnosql.communication.query.QueryValue; import org.eclipse.jnosql.communication.query.SelectQuery; +import org.eclipse.jnosql.communication.query.StringQueryValue; import org.eclipse.jnosql.communication.query.Where; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; @@ -32,6 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; class SelectMethodQueryProviderTest { @@ -40,7 +42,7 @@ class SelectMethodQueryProviderTest { @ParameterizedTest(name = "Should parser the query {0}") - @ValueSource(strings = {"findBy", "countBy", "existsBy"}) + @ValueSource(strings = {"findBy", "existsBy"}) void shouldReturnParserQuery(String query) { String entity = "entity"; SelectQuery selectQuery = queryProvider.apply(query, entity); @@ -48,12 +50,30 @@ void shouldReturnParserQuery(String query) { assertEquals(entity, selectQuery.entity()); assertTrue(selectQuery.fields().isEmpty()); assertTrue(selectQuery.orderBy().isEmpty()); + assertFalse(selectQuery.isCount()); + assertEquals(0, selectQuery.limit()); + assertEquals(0, selectQuery.skip()); + Optional where = selectQuery.where(); + assertFalse(where.isPresent()); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"countBy", "countAll"}) + void shouldReturnParsedCountableQuery(String query) { + String entity = "entity"; + SelectQuery selectQuery = queryProvider.apply(query, entity); + assertNotNull(selectQuery); + assertEquals(entity, selectQuery.entity()); + assertTrue(selectQuery.fields().isEmpty()); + assertTrue(selectQuery.orderBy().isEmpty()); + assertTrue(selectQuery.isCount()); assertEquals(0, selectQuery.limit()); assertEquals(0, selectQuery.skip()); Optional where = selectQuery.where(); assertFalse(where.isPresent()); } + @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"findFirst10By"}) void shouldFindFirstLimit(String query) { @@ -470,6 +490,53 @@ void shouldReturnParserQuery35(String query) { checkNotCondition(query, Condition.EQUALS, "name"); } + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"findByNameNotNull", "countByNameNotNull", "existsByNameNotNull"}) + void shouldReturnParserQuery36(String query) { + String entity = "entity"; + SelectQuery selectQuery = queryProvider.apply(query, entity); + assertNotNull(selectQuery); + assertEquals(entity, selectQuery.entity()); + assertTrue(selectQuery.fields().isEmpty()); + assertTrue(selectQuery.orderBy().isEmpty()); + assertEquals(0, selectQuery.limit()); + assertEquals(0, selectQuery.skip()); + Optional where = selectQuery.where(); + assertTrue(where.isPresent()); + QueryCondition condition = where.get().condition(); + QueryValue value = condition.value(); + assertEquals(Condition.NOT, condition.condition()); + + + assertEquals("_NOT", condition.name()); + assertTrue(value instanceof ConditionQueryValue); + QueryCondition condition1 = ConditionQueryValue.class.cast(value).get().get(0); + + assertEquals("name", condition1.name()); + assertEquals(Condition.EQUALS, condition1.condition()); + var param = condition1.value(); + assertNull(StringQueryValue.class.cast(param).get()); + + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"findByNameNull", "countByNameNull", "existsByNameNull"}) + void shouldReturnParserQuery37(String query) { + String entity = "entity"; + SelectQuery selectQuery = queryProvider.apply(query, entity); + assertNotNull(selectQuery); + assertEquals(entity, selectQuery.entity()); + assertTrue(selectQuery.fields().isEmpty()); + assertTrue(selectQuery.orderBy().isEmpty()); + assertEquals(0, selectQuery.limit()); + assertEquals(0, selectQuery.skip()); + Optional where = selectQuery.where(); + assertTrue(where.isPresent()); + QueryCondition condition = where.get().condition(); + assertEquals("name", condition.name()); + assertEquals(Condition.EQUALS, condition.condition()); + assertNull(condition.value().get()); + } private void checkOrderBy(String query, Direction direction, Direction direction2) { String entity = "entity"; diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/AbstractRepositoryProxy.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/AbstractRepositoryProxy.java index f9068cb3e..0102daf77 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/AbstractRepositoryProxy.java +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/AbstractRepositoryProxy.java @@ -151,7 +151,7 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr case FIND_BY -> { return unwrapInvocationTargetException(() -> executeFindByQuery(instance, method, params)); } - case COUNT_BY -> { + case COUNT_ALL, COUNT_BY -> { return unwrapInvocationTargetException(() -> executeCountByQuery(instance, method, params)); } case EXISTS_BY -> { diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/RepositoryType.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/RepositoryType.java index 535ec5246..204c90723 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/RepositoryType.java +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/query/RepositoryType.java @@ -56,6 +56,9 @@ public enum RepositoryType { */ FIND_ALL("findAll"), /** + * Count projection returning a numeric result. It starts and ends with "countAll" keyword + */ + COUNT_ALL("countAll"),/** * Count projection returning a numeric result. It starts with "countBy" keyword */ COUNT_BY("countBy"), @@ -115,7 +118,7 @@ public enum RepositoryType { .or(Predicate.isEqual(BasicRepository.class)) .or(Predicate.isEqual(NoSQLRepository.class)); - private static final Set KEY_WORLD_METHODS = EnumSet.of(FIND_BY, DELETE_BY, COUNT_BY, EXISTS_BY); + private static final Set KEY_WORLD_METHODS = EnumSet.of(FIND_BY, DELETE_BY, COUNT_ALL, COUNT_BY, EXISTS_BY); private static final Set OPERATION_ANNOTATIONS = EnumSet.of(INSERT, SAVE, DELETE, UPDATE, QUERY, PARAMETER_BASED); private final String keyword; diff --git a/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/query/RepositoryTypeTest.java b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/query/RepositoryTypeTest.java index 7fc6c8b61..57d6828a0 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/query/RepositoryTypeTest.java +++ b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/query/RepositoryTypeTest.java @@ -143,6 +143,11 @@ void shouldReturnCountBy() throws NoSuchMethodException { Assertions.assertEquals(RepositoryType.COUNT_BY, RepositoryType.of(getMethod(DevRepository.class, "countByName"), CrudRepository.class)); } + @Test + void shouldReturnCountAll() throws NoSuchMethodException { + Assertions.assertEquals(RepositoryType.COUNT_ALL, RepositoryType.of(getMethod(DevRepository.class, "countAll"), CrudRepository.class)); + } + @Test void shouldReturnExistsBy() throws NoSuchMethodException { Assertions.assertEquals(RepositoryType.EXISTS_BY, RepositoryType.of(getMethod(DevRepository.class, "existsByName"), CrudRepository.class)); @@ -229,6 +234,8 @@ interface DevRepository extends CrudRepository, Calculate { Long countByName(String name); + Long countAll(); + Long existsByName(String name); void nope(); diff --git a/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandler.java b/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandler.java index 1850c9625..d73e13582 100644 --- a/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandler.java +++ b/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandler.java @@ -137,7 +137,7 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr return unwrapInvocationTargetException(() -> repository(method).invoke(instance, method, params)); } - case DELETE_BY, COUNT_BY, EXISTS_BY -> + case DELETE_BY, COUNT_ALL, COUNT_BY, EXISTS_BY -> throw new UnsupportedOperationException("The custom repository does not support the method " + method); default -> { return Void.class;