Skip to content

Commit

Permalink
Merge pull request #546 from eclipse/count-exception-when-delete-update
Browse files Browse the repository at this point in the history
Execute an exception when update and delete returns long at CustomRepository
  • Loading branch information
otaviojava authored Aug 30, 2024
2 parents 441f270 + db1e0df commit 7f65ed1
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
*/
public final class UpdateProvider extends AbstractWhere implements Function<String, UpdateQuery> {

private List<UpdateItem> items = new ArrayList<>();
private final List<UpdateItem> items = new ArrayList<>();

@Override
public UpdateQuery apply(String query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public final class QueryParser {
*/
public Stream<CommunicationEntity> 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);
};
}
Expand All @@ -60,21 +60,14 @@ public Stream<CommunicationEntity> 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");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 java.util.Objects;

/**
* Enum representing the different types of queries supported in Jakarta Data.
*
* <p>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.</p>
*
* <ul>
* <li>{@link #SELECT} - Represents a query that retrieves data from the database.</li>
* <li>{@link #DELETE} - Represents a query that removes data from the database.</li>
* <li>{@link #UPDATE} - Represents a query that modifies existing data in the database.</li>
* </ul>
*
* <p>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.</p>
*
* <p>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}.</p>
*
* <p>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.</p>
*/
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) {
Objects.requireNonNull(query, "Query string cannot be null");
String command = QueryType.extractQueryCommand(query);
return switch (command) {
case "DELETE" -> DELETE;
case "UPDATE" -> UPDATE;
default -> SELECT;
};
}

/**
* 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;
}

/**
* Validates the return type of method based on the type of query being executed.
* <p>
* 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.
* <p>
* 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() && !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 "";
}
return query.substring(0, 6).toUpperCase();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.assertj.core.api.Assertions;
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);
}

@Test
void shouldReturnIsNotSelect() {
Assertions.assertThat(QueryType.SELECT.isNotSelect()).isFalse();
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ static SpecialParameters of(Object[] parameters, Function<String, String> 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));
}
}
}
}
Expand All @@ -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<?>[];
}

/**
Expand All @@ -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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -38,11 +40,20 @@
*/
public abstract class AbstractSemiStructuredRepositoryProxy<T, K> extends BaseSemiStructuredRepository<T, K> {

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -127,16 +128,22 @@ 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());
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<String, Object> parameters = RepositoryReflectionUtils.INSTANCE.getParams(method, params);
LOGGER.fine("Parameters: " + parameters);
var prepare = template.prepare(query.value());
parameters.forEach(prepare::bind);
if (prepare.isCount()) {
return prepare.count();
}
Stream<?> entities = prepare.result();
if (method.getReturnType().equals(long.class) || method.getReturnType().equals(Long.class)) {
if(isLong(method)) {
return entities.count();
}

return Void.class;
}
return unwrapInvocationTargetException(() -> repository(method).invoke(instance, method, params));
Expand Down Expand Up @@ -270,5 +277,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);
}


}
Loading

0 comments on commit 7f65ed1

Please sign in to comment.