From 33a75f4e0bb38bddc767c1db8deb1483afa6e43c Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Tue, 27 Aug 2024 18:40:18 +0100 Subject: [PATCH 01/15] feat: create query type Signed-off-by: Otavio Santana --- .../query/data/UpdateProvider.java | 2 +- .../semistructured/QueryParser.java | 19 ++--- .../semistructured/QueryType.java | 81 +++++++++++++++++++ .../repository/DynamicReturnConverter.java | 2 +- 4 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/UpdateProvider.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/UpdateProvider.java index f666e322a..93d491b72 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/UpdateProvider.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/UpdateProvider.java @@ -32,7 +32,7 @@ */ public final class UpdateProvider extends AbstractWhere implements Function { - private List items = new ArrayList<>(); + private final List items = new ArrayList<>(); @Override public UpdateQuery apply(String query) { diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryParser.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryParser.java index fb0212f79..6114ddff4 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryParser.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryParser.java @@ -38,10 +38,10 @@ public final class QueryParser { */ public Stream query(String query, String entity, DatabaseManager manager, CommunicationObserverParser observer) { validation(query, manager, observer); - String command = extractQueryCommand(query); + var command = QueryType.parse(query); return switch (command) { - case "DELETE" -> delete.query(query, manager, observer); - case "UPDATE" -> update.query(query, manager, observer); + case DELETE -> delete.query(query, manager, observer); + case UPDATE -> update.query(query, manager, observer); default -> select.query(query, entity, manager, observer); }; } @@ -60,21 +60,14 @@ public Stream query(String query, String entity, DatabaseMa */ public CommunicationPreparedStatement prepare(String query, String entity, DatabaseManager manager, CommunicationObserverParser observer) { validation(query, manager, observer); - String command = extractQueryCommand(query); + var command = QueryType.parse(query); return switch (command) { - case "DELETE" -> delete.prepare(query, manager, observer); - case "UPDATE" -> update.prepare(query, manager, observer); + case DELETE -> delete.prepare(query, manager, observer); + case UPDATE -> update.prepare(query, manager, observer); default -> select.prepare(query, entity, manager, observer); }; } - private String extractQueryCommand(String query){ - if(query.length() < 6){ - return ""; - } - return query.substring(0, 6).toUpperCase(); - } - private void validation(String query, DatabaseManager manager, CommunicationObserverParser observer) { Objects.requireNonNull(query, "query is required"); Objects.requireNonNull(manager, "manager is required"); diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java new file mode 100644 index 000000000..b1d7f2d06 --- /dev/null +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 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. + * + */ +package org.eclipse.jnosql.communication.semistructured; + +/** + * Enum representing the different types of queries supported in Jakarta Data. + * + *

The {@code QueryType} enum categorizes queries into three main types: {@code SELECT}, + * {@code DELETE}, and {@code UPDATE}. These types correspond to the standard operations + * typically executed against a database. This enum is used to interpret and classify + * queries within the Jakarta Data API, particularly in implementations like Eclipse JNoSQL.

+ * + *
    + *
  • {@link #SELECT} - Represents a query that retrieves data from the database.
  • + *
  • {@link #DELETE} - Represents a query that removes data from the database.
  • + *
  • {@link #UPDATE} - Represents a query that modifies existing data in the database.
  • + *
+ * + *

The {@link #parse(String)} method is provided to determine the type of a given query + * string by extracting and evaluating its command keyword. The method returns a corresponding + * {@code QueryType} based on the first six characters of the query, assuming that the query + * begins with a standard SQL-like command.

+ * + *

Note that if the query string does not contain a recognizable command (e.g., if it is + * shorter than six characters or does not match any known command), the method defaults to + * {@code SELECT}.

+ * + *

This enum is particularly relevant for NoSQL implementations like Eclipse JNoSQL, where + * the query language might differ from traditional SQL, yet still, adhere to the concepts + * of selection, deletion, and updating of data.

+ */ +public enum QueryType { + + /** + * Represents a query that retrieves data from the database. + * This is the default query type when no specific command is recognized. + */ + SELECT, + + /** + * Represents a query that removes data from the database. + * Typically used to delete one or more records based on certain conditions. + */ + DELETE, + + /** + * Represents a query that modifies existing data in the database. + * Typically used to update one or more records based on certain conditions. + */ + UPDATE; + + /** + * Parses the given query string to determine the type of query. + * + * @param query the query string to parse + * @return the {@code QueryType} corresponding to the query command + */ + public static QueryType parse(String query) { + String command = QueryType.extractQueryCommand(query); + return switch (command) { + case "DELETE" -> DELETE; + case "UPDATE" -> UPDATE; + default -> SELECT; + }; + } + + private static String extractQueryCommand(String query){ + if(query.length() < 6){ + return ""; + } + return query.substring(0, 6).toUpperCase(); + } +} diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/DynamicReturnConverter.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/DynamicReturnConverter.java index 162463eb4..d2f014cb7 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/DynamicReturnConverter.java +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/DynamicReturnConverter.java @@ -66,7 +66,7 @@ public Object convert(DynamicReturn dynamic) { * * @return the result from the query annotation */ - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"unchecked"}) public Object convert(DynamicQueryMethodReturn dynamicQueryMethod) { Method method = dynamicQueryMethod.method(); Object[] args = dynamicQueryMethod.args(); From c5dadc6a0553060250ad0c73bfa4c5a462e2ff60 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Tue, 27 Aug 2024 18:45:04 +0100 Subject: [PATCH 02/15] test: create cover scenarios to QueryType Signed-off-by: Otavio Santana --- .../semistructured/QueryTypeTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java diff --git a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java new file mode 100644 index 000000000..88e2728df --- /dev/null +++ b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 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. + * + */ +package org.eclipse.jnosql.communication.semistructured; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class QueryTypeTest { + + + @Test + void shouldParseSelectQuery() { + String query = "SELECT * FROM table"; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.SELECT); + } + + @Test + void shouldParseDeleteQuery() { + String query = "DELETE FROM table WHERE id = 1"; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.DELETE); + } + + @Test + void shouldParseUpdateQuery() { + String query = "UPDATE table SET name = 'newName' WHERE id = 1"; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.UPDATE); + } + + @Test + void shouldDefaultToSelectForUnknownQuery() { + String query = "INSERT INTO table (id, name) VALUES (1, 'name')"; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.SELECT); + } + + @Test + void shouldDefaultToSelectForShortQuery() { + String query = "DELE"; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.SELECT); + } + + @Test + void shouldDefaultToSelectForEmptyQuery() { + String query = ""; + QueryType result = QueryType.parse(query); + assertThat(result).isEqualTo(QueryType.SELECT); + } + + @Test + void shouldThrowNullPointerExceptionForNullQuery() { + String query = null; + assertThatThrownBy(() -> QueryType.parse(query)) + .isInstanceOf(NullPointerException.class); + } +} \ No newline at end of file From 358801f6fbe87a8d0fbbe55f3d4f2515f11c1818 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Tue, 27 Aug 2024 18:45:18 +0100 Subject: [PATCH 03/15] feat: include validation to query type when is null Signed-off-by: Otavio Santana --- .../eclipse/jnosql/communication/semistructured/QueryType.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java index b1d7f2d06..818537464 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -10,6 +10,8 @@ */ package org.eclipse.jnosql.communication.semistructured; +import java.util.Objects; + /** * Enum representing the different types of queries supported in Jakarta Data. * @@ -64,6 +66,7 @@ public enum QueryType { * @return the {@code QueryType} corresponding to the query command */ public static QueryType parse(String query) { + Objects.requireNonNull(query, "Query string cannot be null"); String command = QueryType.extractQueryCommand(query); return switch (command) { case "DELETE" -> DELETE; From 3098dd538fd0db60aea73243d65fdc1fa198701a Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Wed, 28 Aug 2024 11:59:51 +0100 Subject: [PATCH 04/15] feat: include isNotSelectquery at the query function Signed-off-by: Otavio Santana --- .../jnosql/communication/semistructured/QueryType.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java index 818537464..05c62f0f1 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -75,6 +75,10 @@ public static QueryType parse(String query) { }; } + public boolean isNotSelect() { + return this != SELECT; + } + private static String extractQueryCommand(String query){ if(query.length() < 6){ return ""; From 87d5691bd17b6982487b12160cbe8c22d3739d38 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Wed, 28 Aug 2024 12:00:09 +0100 Subject: [PATCH 05/15] test: include test for queryType Signed-off-by: Otavio Santana --- .../communication/semistructured/QueryTypeTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java index 88e2728df..4668e3001 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java @@ -10,6 +10,7 @@ */ package org.eclipse.jnosql.communication.semistructured; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -67,4 +68,11 @@ void shouldThrowNullPointerExceptionForNullQuery() { assertThatThrownBy(() -> QueryType.parse(query)) .isInstanceOf(NullPointerException.class); } + + @Test + void shouldReturnIsNotSelect() { + Assertions.assertThat(QueryType.SELECT.isNotSelect()).isFalse(); + Assertions.assertThat(QueryType.DELETE.isNotSelect()).isTrue(); + Assertions.assertThat(QueryType.UPDATE.isNotSelect()).isTrue(); + } } \ No newline at end of file From 18e14e3e6db4b124ba5cce2f8609c2757cef3217 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Wed, 28 Aug 2024 12:00:23 +0100 Subject: [PATCH 06/15] feat: include validation for query Signed-off-by: Otavio Santana --- .../query/CustomRepositoryHandler.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 727ba495f..94fb2e3f4 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 @@ -19,6 +19,7 @@ import jakarta.data.page.Page; import jakarta.data.repository.Query; import jakarta.enterprise.inject.spi.CDI; +import org.eclipse.jnosql.communication.semistructured.QueryType; import org.eclipse.jnosql.mapping.core.Converters; import org.eclipse.jnosql.mapping.core.query.AbstractRepository; import org.eclipse.jnosql.mapping.core.query.AnnotationOperation; @@ -127,6 +128,8 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr var repositoryMetadata = repositoryMetadata(method); if (repositoryMetadata.metadata().isEmpty()) { var query = method.getAnnotation(Query.class); + var queryType = QueryType.parse(query.value()); + LOGGER.fine("Executing the query " + query.value() + " with the type " + queryType); Map parameters = RepositoryReflectionUtils.INSTANCE.getParams(method, params); var prepare = template.prepare(query.value()); parameters.forEach(prepare::bind); @@ -134,9 +137,14 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr return prepare.count(); } Stream entities = prepare.result(); - if (method.getReturnType().equals(long.class) || method.getReturnType().equals(Long.class)) { + if(isLong(method)) { + if(queryType.isNotSelect()) { + throw new UnsupportedOperationException("Because of eventual persistence consistency, " + + "it is not possible to execute a query that is not a SELECT query and return a long value, query: " + query.value()); + } return entities.count(); } + return Void.class; } return unwrapInvocationTargetException(() -> repository(method).invoke(instance, method, params)); @@ -270,5 +278,9 @@ private Class getGenericTypeFromParameter(Parameter parameter) { throw new IllegalArgumentException("Cannot determine generic type from parameter"); } + private static boolean isLong(Method method) { + return method.getReturnType().equals(long.class) || method.getReturnType().equals(Long.class); + } + } From 66ec9e410e2d25d1e159c4605b57d16cfb6d571a Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Wed, 28 Aug 2024 13:19:37 +0100 Subject: [PATCH 07/15] test: create scenario to return invalid when it is long Signed-off-by: Otavio Santana --- .../query/CustomRepositoryHandlerTest.java | 20 +++++++++++++++++++ .../mapping/semistructured/query/People.java | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandlerTest.java b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandlerTest.java index f277666f7..27cfae466 100644 --- a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandlerTest.java +++ b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CustomRepositoryHandlerTest.java @@ -436,4 +436,24 @@ void shouldExecuteExistBy() { }); } + + @Test + void shouldReturnNotSupportedWhenQueryIsNotSelectAsDelete() { + var preparedStatement = Mockito.mock(org.eclipse.jnosql.mapping.semistructured.PreparedStatement.class); + Mockito.when(template.prepare(Mockito.anyString())).thenReturn(preparedStatement); + Mockito.when(template.query(Mockito.anyString())) + .thenReturn(Stream.of(Person.builder().age(26).name("Ada").build())); + Assertions.assertThatThrownBy(() ->people.deleteByNameReturnInt()) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + void shouldReturnNotSupportedWhenQueryIsNotSelectAsUpdate() { + var preparedStatement = Mockito.mock(org.eclipse.jnosql.mapping.semistructured.PreparedStatement.class); + Mockito.when(template.prepare(Mockito.anyString())).thenReturn(preparedStatement); + Mockito.when(template.query(Mockito.anyString())) + .thenReturn(Stream.of(Person.builder().age(26).name("Ada").build())); + Assertions.assertThatThrownBy(() ->people.updateReturnInt()) + .isInstanceOf(UnsupportedOperationException.class); + } } \ No newline at end of file diff --git a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/People.java b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/People.java index 4a01f1a3a..8ce126ebb 100644 --- a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/People.java +++ b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/People.java @@ -97,4 +97,10 @@ public interface People { default String defaultMethod() { return "default"; } + + @Query("delete from Person where name = :name") + long deleteByNameReturnInt(); + + @Query("update Person where name = :name") + long updateReturnInt(); } From b8c941ece828a628809862f63c290aeb40ddb43c Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 15:16:28 +0100 Subject: [PATCH 08/15] docs: enhance documentation to query type Signed-off-by: Otavio Santana --- .../jnosql/communication/semistructured/QueryType.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java index 05c62f0f1..c550c17b2 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -75,6 +75,16 @@ public static QueryType parse(String query) { }; } + /** + * Checks if the current {@code QueryType} is not a {@code SELECT} operation. + * This method is useful for determining whether the query is intended to modify data + * (i.e., either a {@code DELETE} or {@code UPDATE} operation) rather than retrieve it. + * It can be employed in scenarios where different logic is applied based on whether + * a query modifies data. For example, {@code if (queryType.isNotSelect())} can be used + * to trigger actions specific to non-SELECT queries. This method returns {@code true} + * if the current {@code QueryType} is either {@code DELETE} or {@code UPDATE}, + * and {@code false} if it is {@code SELECT}. + */ public boolean isNotSelect() { return this != SELECT; } From 189dc91cb8f1834047a26e54f019d8f99e8ef0e4 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 17:59:10 +0100 Subject: [PATCH 09/15] feat: include logs at the custom repository handler Signed-off-by: Otavio Santana --- .../semistructured/query/CustomRepositoryHandler.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 94fb2e3f4..9971d2905 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 @@ -129,8 +129,10 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr if (repositoryMetadata.metadata().isEmpty()) { var query = method.getAnnotation(Query.class); var queryType = QueryType.parse(query.value()); - LOGGER.fine("Executing the query " + query.value() + " with the type " + queryType); + var returnType = method.getReturnType(); + LOGGER.fine("Executing the query " + query.value() + " with the type " + queryType + " and the return type " + returnType); Map parameters = RepositoryReflectionUtils.INSTANCE.getParams(method, params); + LOGGER.fine("Parameters: " + parameters); var prepare = template.prepare(query.value()); parameters.forEach(prepare::bind); if (prepare.isCount()) { @@ -138,10 +140,6 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr } Stream entities = prepare.result(); if(isLong(method)) { - if(queryType.isNotSelect()) { - throw new UnsupportedOperationException("Because of eventual persistence consistency, " + - "it is not possible to execute a query that is not a SELECT query and return a long value, query: " + query.value()); - } return entities.count(); } From a11850e0f103f39591532370cb596af18ffade24 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 18:45:12 +0100 Subject: [PATCH 10/15] feat: create check validat return Signed-off-by: Otavio Santana --- .../semistructured/QueryType.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java index c550c17b2..5105ca3c5 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -89,6 +89,28 @@ public boolean isNotSelect() { return this != SELECT; } + /** + * Validates the return type of method based on the type of query being executed. + *

+ * This method checks whether the specified query is a {@code DELETE} or {@code UPDATE} operation + * and ensures that the return type is {@code Void}. If the query is not a {@code SELECT} operation + * and the return type is not {@code Void}, an {@code UnsupportedOperationException} is thrown. + *

+ * This validation is necessary because {@code DELETE} and {@code UPDATE} operations typically + * do not return a result set, and as such, they should have a {@code Void} return type. + * + * @param returnType the return type of the method executing the query + * @param query the query being executed + * @throws UnsupportedOperationException if the query is a {@code DELETE} or {@code UPDATE} operation + * and the return type is not {@code Void} + */ + public void checkValidReturn(Class returnType, String query) { + if (isNotSelect() && returnType != Void.class) { + throw new UnsupportedOperationException("The return type must be Void when the query is not a SELECT operation, due to the nature" + + " of DELETE and UPDATE operations. The query: " + query); + } + } + private static String extractQueryCommand(String query){ if(query.length() < 6){ return ""; From a407428d8f14f75bf7e649cdd82dd1e5fcee9de3 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 18:46:13 +0100 Subject: [PATCH 11/15] test: create query test Signed-off-by: Otavio Santana --- .../communication/semistructured/QueryTypeTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java index 4668e3001..9319335c5 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/QueryTypeTest.java @@ -75,4 +75,15 @@ void shouldReturnIsNotSelect() { Assertions.assertThat(QueryType.DELETE.isNotSelect()).isTrue(); Assertions.assertThat(QueryType.UPDATE.isNotSelect()).isTrue(); } + + @Test + void shouldCheckValidReturn() { + QueryType.SELECT.checkValidReturn(String.class, "SELECT * FROM table"); + QueryType.DELETE.checkValidReturn(Void.class, "DELETE FROM table WHERE id = 1"); + QueryType.UPDATE.checkValidReturn(Void.class, "UPDATE table SET name = 'newName' WHERE id = 1"); + assertThatThrownBy(() -> QueryType.DELETE.checkValidReturn(String.class, "DELETE FROM table WHERE id = 1")) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> QueryType.UPDATE.checkValidReturn(String.class, "UPDATE table SET name = 'newName' WHERE id = 1")) + .isInstanceOf(UnsupportedOperationException.class); + } } \ No newline at end of file From cd36a3b4abb7cd861012d0cbd03d5aca60a02ed8 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 18:50:00 +0100 Subject: [PATCH 12/15] feat: create return type to validation Signed-off-by: Otavio Santana --- .../jnosql/communication/semistructured/QueryType.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java index 5105ca3c5..69506eb13 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/QueryType.java @@ -105,12 +105,16 @@ public boolean isNotSelect() { * and the return type is not {@code Void} */ public void checkValidReturn(Class returnType, String query) { - if (isNotSelect() && returnType != Void.class) { + if (isNotSelect() && !isVoid(returnType)) { throw new UnsupportedOperationException("The return type must be Void when the query is not a SELECT operation, due to the nature" + " of DELETE and UPDATE operations. The query: " + query); } } + private boolean isVoid(Class returnType) { + return returnType == Void.class || returnType == Void.TYPE; + } + private static String extractQueryCommand(String query){ if(query.length() < 6){ return ""; From f83616751f9354ed12122a6aa702aa33465000f5 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 18:51:03 +0100 Subject: [PATCH 13/15] feat: include validation Signed-off-by: Otavio Santana --- .../mapping/semistructured/query/CustomRepositoryHandler.java | 1 + 1 file changed, 1 insertion(+) 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 9971d2905..5b54ab260 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 @@ -131,6 +131,7 @@ public Object invoke(Object instance, Method method, Object[] params) throws Thr var queryType = QueryType.parse(query.value()); var returnType = method.getReturnType(); LOGGER.fine("Executing the query " + query.value() + " with the type " + queryType + " and the return type " + returnType); + queryType.checkValidReturn(returnType, query.value()); Map parameters = RepositoryReflectionUtils.INSTANCE.getParams(method, params); LOGGER.fine("Parameters: " + parameters); var prepare = template.prepare(query.value()); From 081b77807ad6079df2edccc4edf16e680e60db78 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 19:15:21 +0100 Subject: [PATCH 14/15] feat: include validation to delete and update Signed-off-by: Otavio Santana --- .../query/AbstractSemiStructuredRepositoryProxy.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/AbstractSemiStructuredRepositoryProxy.java b/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/AbstractSemiStructuredRepositoryProxy.java index d03c8ded8..a07155931 100644 --- a/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/AbstractSemiStructuredRepositoryProxy.java +++ b/jnosql-mapping/jnosql-mapping-semistructured/src/main/java/org/eclipse/jnosql/mapping/semistructured/query/AbstractSemiStructuredRepositoryProxy.java @@ -19,6 +19,7 @@ import jakarta.data.repository.OrderBy; import jakarta.data.repository.Query; import org.eclipse.jnosql.communication.semistructured.DeleteQuery; +import org.eclipse.jnosql.communication.semistructured.QueryType; import org.eclipse.jnosql.mapping.core.repository.DynamicQueryMethodReturn; import org.eclipse.jnosql.mapping.core.repository.DynamicReturn; import org.eclipse.jnosql.mapping.core.repository.RepositoryReflectionUtils; @@ -28,6 +29,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.logging.Logger; import java.util.stream.Stream; /** @@ -38,11 +40,20 @@ */ public abstract class AbstractSemiStructuredRepositoryProxy extends BaseSemiStructuredRepository { + private static final Logger LOGGER = Logger.getLogger(AbstractSemiStructuredRepositoryProxy.class.getName()); + @Override protected Object executeQuery(Object instance, Method method, Object[] params) { + LOGGER.finest("Executing query on method: " + method); Class type = entityMetadata().type(); var entity = entityMetadata().name(); var pageRequest = DynamicReturn.findPageRequest(params); + var queryValue = method.getAnnotation(Query.class).value(); + var queryType = QueryType.parse(queryValue); + var returnType = method.getReturnType(); + LOGGER.finest("Query: " + queryValue + " with type: " + queryType + " and return type: " + returnType); + queryType.checkValidReturn(returnType, queryValue); + var methodReturn = DynamicQueryMethodReturn.builder() .args(params) .method(method) From db1e0dffb59f7cb0a81c8de60ea2caf8c892a656 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Thu, 29 Aug 2024 20:46:20 +0100 Subject: [PATCH 15/15] feat: include filters to special parameter Signed-off-by: Otavio Santana --- .../core/repository/SpecialParameters.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/SpecialParameters.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/SpecialParameters.java index 25f393f7f..fa270a092 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/SpecialParameters.java +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/SpecialParameters.java @@ -141,10 +141,12 @@ static SpecialParameters of(Object[] parameters, Function sortPa Arrays.stream(sortArray).map(s -> mapper(s, sortParser)).forEach(sorts::add); } else if (parameter instanceof PageRequest request) { pageRequest = request; - } else if (parameter instanceof Iterable iterable) { - for (Object value : iterable) { - if (value instanceof Sort sortValue) { - sorts.add(mapper(sortValue, sortParser)); + }else { + if (parameter instanceof Iterable iterable) { + for (Object value : iterable) { + if (value instanceof Sort sortValue) { + sorts.add(mapper(sortValue, sortParser)); + } } } } @@ -162,7 +164,8 @@ public static boolean isSpecialParameter(Object parameter) { return parameter instanceof Sort || parameter instanceof Limit || parameter instanceof Order - || parameter instanceof PageRequest; + || parameter instanceof PageRequest + || parameter instanceof Sort[]; } /** @@ -185,7 +188,8 @@ public static boolean isSpecialParameter(Class parameter) { return Sort.class.isAssignableFrom(parameter) || Limit.class.isAssignableFrom(parameter) || Order.class.isAssignableFrom(parameter) - || PageRequest.class.isAssignableFrom(parameter); + || PageRequest.class.isAssignableFrom(parameter) + || parameter.isArray() && Sort.class.isAssignableFrom(parameter.getComponentType()); } /**