diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 28a43aea5..f544151c0 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -13,6 +13,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 +- Include condition to is NUll and is Not Null in the query === Fixed diff --git a/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 b/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 index 72da441c6..f7a38b15c 100644 --- a/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 +++ b/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 @@ -11,7 +11,7 @@ from_clause : FROM entity_name; where_clause : WHERE conditional_expression; set_clause : SET update_item (',' update_item)*; -update_item : state_field_path_expression '=' (scalar_expression | 'NULL'); +update_item : state_field_path_expression '=' (scalar_expression | NULL); select_clause : SELECT select_list; select_list @@ -65,27 +65,27 @@ primary_expression ; function_expression - : 'ABS' '(' scalar_expression ')' - | 'LENGTH' '(' scalar_expression ')' - | 'LOWER' '(' scalar_expression ')' - | 'UPPER' '(' scalar_expression ')' - | 'LEFT' '(' scalar_expression ',' scalar_expression ')' - | 'RIGHT' '(' scalar_expression ',' scalar_expression ')' + : ABS '(' scalar_expression ')' + | LENGTH '(' scalar_expression ')' + | LOWER '(' scalar_expression ')' + | UPPER '(' scalar_expression ')' + | LEFT '(' scalar_expression ',' scalar_expression ')' + | RIGHT '(' scalar_expression ',' scalar_expression ')' ; special_expression - : 'LOCAL' 'DATE' - | 'LOCAL' 'DATETIME' - | 'LOCAL' 'TIME' - | 'TRUE' - | 'FALSE' + : LOCAL DATE + | LOCAL DATETIME + | LOCAL TIME + | TRUE + | FALSE ; -state_field_path_expression : IDENTIFIER ('.' IDENTIFIER)*; +state_field_path_expression : IDENTIFIER ('.' IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER; entity_name : IDENTIFIER; // no ambiguity -enum_literal : IDENTIFIER ('.' IDENTIFIER)*; // ambiguity with state_field_path_expression resolvable semantically +enum_literal : IDENTIFIER ('.' IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER; // ambiguity with state_field_path_expression resolvable semantically input_parameter : ':' IDENTIFIER | '?' INTEGER; @@ -123,6 +123,10 @@ LOCAL_TIME : [lL][oO][cC][aA][lL] [tT][iI][mM][eE]; BETWEEN : [bB][eE][tT][wW][eE][eE][nN]; LIKE : [lL][iI][kK][eE]; THIS : [tT][hH][iI][sS]; +LOCAL : [lL][oO][cC][aA][lL]; +DATE : [dD][aA][tT][eE]; +DATETIME : [dD][aA][tT][eE][tT][iI][mM][eE]; +TIME : [tT][iI][mM][eE]; // Operators EQ : '='; @@ -146,9 +150,10 @@ COLON : ':'; QUESTION : '?'; // Identifier and literals +FULLY_QUALIFIED_IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]*('.'[a-zA-Z_][a-zA-Z0-9_]*)+; IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]*; -STRING : '"' ( ~["\\] | '\\' . )* '"' // double quoted strings - | '\'' ( ~['\\] | '\\' . )* '\''; // single quoted strings +STRING : '"' ( ~["\\] | '\\' . )* '"' // double quoted strings + | '\'' ( ~['\\] | '\\' . )* '\''; // single quoted strings INTEGER : '-'?[0-9]+; DOUBLE : '-'?[0-9]+'.'[0-9]* | '-'?'.'[0-9]+; diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/NullQueryValue.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/NullQueryValue.java new file mode 100644 index 000000000..7da080c24 --- /dev/null +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/NullQueryValue.java @@ -0,0 +1,30 @@ +/* + * 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. + * Contributors: + * Otavio Santana + */ +package org.eclipse.jnosql.communication.query; + +/** + * A query value that represents a null value. + */ +public enum NullQueryValue implements QueryValue { + + INSTANCE; + + @Override + public NullQueryValue get() { + return INSTANCE; + } + + @Override + public ValueType type() { + return ValueType.NULL; + } +} diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/ValueType.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/ValueType.java index 5304e44ee..beaf8abd9 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/ValueType.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/ValueType.java @@ -71,5 +71,11 @@ public enum ValueType { * type that can have only the values true or false. Used in NoSQL queries to * handle logical branching and conditional checks. */ - BOOLEAN + BOOLEAN, + /** + * Represents a null value. Null is a special value in NoSQL databases that + * indicates the absence of a value. Used in queries to handle missing or + * unknown data, or to represent the absence of a value in a specific context. + */ + NULL } diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/AbstractWhere.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/AbstractWhere.java index 78ad5c801..cd61d5138 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/AbstractWhere.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/AbstractWhere.java @@ -13,6 +13,7 @@ import org.eclipse.jnosql.communication.Condition; import org.eclipse.jnosql.communication.query.ConditionQueryValue; +import org.eclipse.jnosql.communication.query.NullQueryValue; import org.eclipse.jnosql.communication.query.QueryCondition; import org.eclipse.jnosql.communication.query.QueryValue; import org.eclipse.jnosql.communication.query.StringQueryValue; @@ -59,13 +60,18 @@ public void exitComparison_expression(JDQLParser.Comparison_expressionContext ct super.exitComparison_expression(ctx); boolean hasNot = false; boolean andCondition = true; - if(ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent - && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent){ + if (ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent + && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent) { hasNot = Objects.nonNull(grandParent.NOT()); andCondition = Objects.isNull(grandParent.OR()); } var contexts = ctx.scalar_expression(); var contextCondition = getCondition(ctx); + + if (contextCondition.equals(NOT)) { + contextCondition = EQUALS; + hasNot = !hasNot; + } var name = contexts.get(0).getText(); var value = contexts.get(1); var literal = PrimaryFunction.INSTANCE.apply(value.primary_expression()); @@ -76,23 +82,41 @@ public void exitComparison_expression(JDQLParser.Comparison_expressionContext ct and = andCondition; } + @Override + public void exitNull_comparison_expression(JDQLParser.Null_comparison_expressionContext ctx) { + super.exitNull_comparison_expression(ctx); + boolean hasNot = Objects.nonNull(ctx.NOT()); + boolean andCondition = true; + if (ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent + && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent) { + andCondition = Objects.isNull(grandParent.OR()); + } + var stateFieldPathExpressionContext = ctx.state_field_path_expression(); + var name = stateFieldPathExpressionContext.getText(); + if (this.condition != null && this.condition.value() instanceof ConditionQueryValue) { + and = andCondition; + } + checkCondition(new DefaultQueryCondition(name, EQUALS, NullQueryValue.INSTANCE), hasNot); + and = andCondition; + } + @Override public void exitLike_expression(JDQLParser.Like_expressionContext ctx) { super.exitLike_expression(ctx); boolean hasNot = Objects.nonNull(ctx.NOT()); boolean andCondition = true; - if(ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent - && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent){ + if (ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent + && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent) { andCondition = Objects.isNull(grandParent.OR()); } var contexts = ctx.scalar_expression(); var name = contexts.getText(); var contextCondition = Condition.LIKE; - var likeValueIndex = ctx.getChildCount() -1 ; + var likeValueIndex = ctx.getChildCount() - 1; var likeValue = contexts.getParent().getChild(likeValueIndex).getText(); - var literal = StringQueryValue.of(likeValue.substring(1, likeValue.length() -1)); + var literal = StringQueryValue.of(likeValue.substring(1, likeValue.length() - 1)); if (this.condition != null && this.condition.value() instanceof ConditionQueryValue) { and = andCondition; } @@ -106,8 +130,8 @@ public void exitBetween_expression(JDQLParser.Between_expressionContext ctx) { boolean hasNot = Objects.nonNull(ctx.NOT()); boolean andCondition = true; - if(ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent - && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent){ + if (ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent + && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent) { andCondition = Objects.isNull(grandParent.OR()); } @@ -130,8 +154,8 @@ public void exitIn_expression(JDQLParser.In_expressionContext ctx) { boolean hasNot = Objects.nonNull(ctx.NOT()); boolean andCondition = true; - if(ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent - && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent){ + if (ctx.getParent() instanceof JDQLParser.Conditional_expressionContext ctxParent + && ctxParent.getParent() instanceof JDQLParser.Conditional_expressionContext grandParent) { andCondition = Objects.isNull(grandParent.OR()); } @@ -166,6 +190,8 @@ private Condition getCondition(JDQLParser.Comparison_expressionContext ctx) { return Condition.GREATER_THAN; } else if (ctx.GTEQ() != null) { return Condition.GREATER_EQUALS_THAN; + } else if (ctx.NEQ() != null) { + return NOT; } throw new UnsupportedOperationException("The operation does not support: " + ctx.getText()); } diff --git a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/PrimaryFunction.java b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/PrimaryFunction.java index 9c77fb403..bc8c4cf59 100644 --- a/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/PrimaryFunction.java +++ b/jnosql-communication/jnosql-communication-query/src/main/java/org/eclipse/jnosql/communication/query/data/PrimaryFunction.java @@ -19,6 +19,7 @@ import org.eclipse.jnosql.communication.query.StringQueryValue; import org.eclipse.jnosql.query.grammar.data.JDQLParser; +import java.util.Locale; import java.util.function.Function; import java.util.logging.Logger; @@ -44,7 +45,7 @@ public QueryValue apply(JDQLParser.Primary_expressionContext context) { return DefaultQueryValue.of(context.input_parameter().getText()); } else if (context.special_expression() != null) { var specialExpression = context.special_expression().getText(); - return switch (specialExpression) { + return switch (specialExpression.toUpperCase(Locale.US)) { case "TRUE" -> BooleanQueryValue.TRUE; case "FALSE" -> BooleanQueryValue.FALSE; default -> diff --git a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/DeleteJakartaDataQueryProviderTest.java b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/DeleteJakartaDataQueryProviderTest.java index 790fca159..d402ba8eb 100644 --- a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/DeleteJakartaDataQueryProviderTest.java +++ b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/DeleteJakartaDataQueryProviderTest.java @@ -14,8 +14,11 @@ import org.assertj.core.api.SoftAssertions; 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.EnumQueryValue; +import org.eclipse.jnosql.communication.query.NullQueryValue; import org.eclipse.jnosql.communication.query.NumberQueryValue; +import org.eclipse.jnosql.communication.query.SelectQuery; import org.eclipse.jnosql.communication.query.StringQueryValue; import org.eclipse.jnosql.communication.query.data.DefaultQueryValue; import org.eclipse.jnosql.communication.query.data.DeleteProvider; @@ -268,4 +271,75 @@ void shouldEqUsingEnumLiteral(String query){ }); } + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "DELETE FROM entity WHERE hexadecimal IS NULL") + void shouldQueryIsNull(String query) { + var deleteQuery = deleteProvider.apply(query); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(deleteQuery.fields()).isEmpty(); + soft.assertThat(deleteQuery.entity()).isEqualTo("entity"); + var where = deleteQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(condition.name()).isEqualTo("hexadecimal"); + soft.assertThat(condition.value()).isEqualTo(NullQueryValue.INSTANCE); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "DELETE FROM entity WHERE hexadecimal IS NOT NULL") + void shouldQueryIsNotNull(String query) { + + var deleteQuery = deleteProvider.apply(query); + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(deleteQuery.fields()).isEmpty(); + soft.assertThat(deleteQuery.entity()).isEqualTo("entity"); + soft.assertThat(deleteQuery.where()).isNotEmpty(); + var where = deleteQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.NOT); + var notCondition = (ConditionQueryValue) condition.value(); + var queryCondition = notCondition.get().get(0); + soft.assertThat(queryCondition.name()).isEqualTo("hexadecimal"); + soft.assertThat(queryCondition.value()).isEqualTo(NullQueryValue.INSTANCE); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "DELETE FROM entity WHERE NOT age <> 10") + void shouldUseNotNotEquals(String query) { + var deleteQuery = deleteProvider.apply(query); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(deleteQuery.fields()).isEmpty(); + soft.assertThat(deleteQuery.entity()).isEqualTo("entity"); + soft.assertThat(deleteQuery.where()).isNotEmpty(); + var where = deleteQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(condition.name()).isEqualTo("age"); + soft.assertThat(condition.value()).isEqualTo(NumberQueryValue.of(10)); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "DELETE FROM entity WHERE age <> 10") + void shouldUseNotEquals(String query) { + var deleteQuery = deleteProvider.apply(query); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(deleteQuery.fields()).isEmpty(); + soft.assertThat(deleteQuery.entity()).isEqualTo("entity"); + soft.assertThat(deleteQuery.where()).isNotEmpty(); + var where = deleteQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.NOT); + var notCondition = (ConditionQueryValue) condition.value(); + var queryCondition = notCondition.get().get(0); + soft.assertThat(queryCondition.name()).isEqualTo("age"); + soft.assertThat(queryCondition.value()).isEqualTo(NumberQueryValue.of(10)); + }); + } + } diff --git a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/SelectJakartaDataQueryProviderTest.java b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/SelectJakartaDataQueryProviderTest.java index 09cff6fbf..53aa3837b 100644 --- a/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/SelectJakartaDataQueryProviderTest.java +++ b/jnosql-communication/jnosql-communication-query/src/test/java/org/eclipse/jnosql/communication/query/data/SelectJakartaDataQueryProviderTest.java @@ -15,7 +15,9 @@ import org.assertj.core.api.SoftAssertions; 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.EnumQueryValue; +import org.eclipse.jnosql.communication.query.NullQueryValue; import org.eclipse.jnosql.communication.query.NumberQueryValue; import org.eclipse.jnosql.communication.query.SelectQuery; import org.eclipse.jnosql.communication.query.StringQueryValue; @@ -78,7 +80,7 @@ void shouldKeepEntityFromParameter(String query) { @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"", " "}) void shouldReturnErrorWhenEntityIsMissing(String query) { - Assertions.assertThrows(IllegalArgumentException.class, () ->{ + Assertions.assertThrows(IllegalArgumentException.class, () -> { selectProvider.apply(query, null); }); } @@ -86,7 +88,7 @@ void shouldReturnErrorWhenEntityIsMissing(String query) { @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"FROM entity ORDER BY name ASC", "ORDER BY name ASC"}) - void shouldQueryOrder(String query){ + void shouldQueryOrder(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -99,7 +101,7 @@ void shouldQueryOrder(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"FROM entity ORDER BY name DESC", "ORDER BY name DESC"}) - void shouldQueryOrderDesc(String query){ + void shouldQueryOrderDesc(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -112,7 +114,7 @@ void shouldQueryOrderDesc(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"FROM entity ORDER BY name ASC, age DESC", "ORDER BY name ASC, age DESC"}) - void shouldQueryOrders(String query){ + void shouldQueryOrders(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -125,7 +127,7 @@ void shouldQueryOrders(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"SELECT name, age FROM entity", "SELECT name, age"}) - void shouldSelectFields(String query){ + void shouldSelectFields(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -139,7 +141,7 @@ void shouldSelectFields(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE age = 10", "FROM entity WHERE age = 10"}) - void shouldEq(String query){ + void shouldEq(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -157,7 +159,7 @@ void shouldEq(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE salary = 10.15", "FROM entity WHERE salary = 10.15"}) - void shouldEqDouble(String query){ + void shouldEqDouble(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -175,7 +177,7 @@ void shouldEqDouble(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE name = \"Otavio\"", "FROM entity WHERE name = \"Otavio\""}) - void shouldEqString(String query){ + void shouldEqString(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -193,7 +195,7 @@ void shouldEqString(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE name = 'Otavio'", "FROM entity WHERE name = 'Otavio'"}) - void shouldEqStringSingleQuote(String query){ + void shouldEqStringSingleQuote(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -211,7 +213,7 @@ void shouldEqStringSingleQuote(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE name = :name", "FROM entity WHERE name = :name"}) - void shouldEQQueryWithCondition(String query){ + void shouldEQQueryWithCondition(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -229,7 +231,7 @@ void shouldEQQueryWithCondition(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE name = ?1", "FROM entity WHERE name = ?1"}) - void shouldEQQueryWithConditionPosition(String query){ + void shouldEQQueryWithConditionPosition(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -247,7 +249,7 @@ void shouldEQQueryWithConditionPosition(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE active = TRUE", "FROM entity WHERE active = TRUE"}) - void shouldUseSpecialExpressionTrue(String query){ + void shouldUseSpecialExpressionTrue(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -265,7 +267,7 @@ void shouldUseSpecialExpressionTrue(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE active = FALSE", "FROM entity WHERE active = FALSE"}) - void shouldUseSpecialExpressionFalse(String query){ + void shouldUseSpecialExpressionFalse(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -283,7 +285,7 @@ void shouldUseSpecialExpressionFalse(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE age < 10", "FROM entity WHERE age < 10"}) - void shouldUseSpecialExpressionLesser(String query){ + void shouldUseSpecialExpressionLesser(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -301,7 +303,7 @@ void shouldUseSpecialExpressionLesser(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE age > 10", "FROM entity WHERE age > 10"}) - void shouldUseSpecialExpressionGreater(String query){ + void shouldUseSpecialExpressionGreater(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -319,7 +321,7 @@ void shouldUseSpecialExpressionGreater(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE age <= 10", "FROM entity WHERE age <= 10"}) - void shouldUseSpecialExpressionLesserThanEquals(String query){ + void shouldUseSpecialExpressionLesserThanEquals(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -337,7 +339,7 @@ void shouldUseSpecialExpressionLesserThanEquals(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE age >= 10", "FROM entity WHERE age >= 10"}) - void shouldUseSpecialExpressionGreaterThanEquals(String query){ + void shouldUseSpecialExpressionGreaterThanEquals(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -356,7 +358,7 @@ void shouldUseSpecialExpressionGreaterThanEquals(String query){ @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = {"WHERE days = java.time.DayOfWeek.MONDAY", "FROM entity WHERE days = java.time.DayOfWeek.MONDAY"}) - void shouldEqUsingEnumLiteral(String query){ + void shouldEqUsingEnumLiteral(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -374,10 +376,9 @@ void shouldEqUsingEnumLiteral(String query){ } - @ParameterizedTest(name = "Should parser the query {0}") @ValueSource(strings = "SELECT COUNT (THIS) WHERE age = 10") - void shouldAggregate(String query){ + void shouldAggregate(String query) { SelectQuery selectQuery = selectProvider.apply(query, "entity"); SoftAssertions.assertSoftly(soft -> { @@ -393,4 +394,110 @@ void shouldAggregate(String query){ soft.assertThat(selectQuery.isCount()).isTrue(); }); } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "SELECT hexadecimal WHERE hexadecimal IS NULL") + void shouldQueryIsNull(String query) { + SelectQuery selectQuery = selectProvider.apply(query, "entity"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(selectQuery.fields()).hasSize(1).contains("hexadecimal"); + soft.assertThat(selectQuery.entity()).isEqualTo("entity"); + soft.assertThat(selectQuery.orderBy()).isEmpty(); + soft.assertThat(selectQuery.where()).isNotEmpty(); + var where = selectQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(condition.name()).isEqualTo("hexadecimal"); + soft.assertThat(condition.value()).isEqualTo(NullQueryValue.INSTANCE); + soft.assertThat(selectQuery.isCount()).isFalse(); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "SELECT hexadecimal WHERE hexadecimal IS NOT NULL") + void shouldQueryIsNotNull(String query) { + var selectQuery = selectProvider.apply(query, "entity"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(selectQuery.fields()).hasSize(1).contains("hexadecimal"); + soft.assertThat(selectQuery.entity()).isEqualTo("entity"); + soft.assertThat(selectQuery.orderBy()).isEmpty(); + soft.assertThat(selectQuery.where()).isNotEmpty(); + var where = selectQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.NOT); + var notCondition = (ConditionQueryValue) condition.value(); + var queryCondition = notCondition.get().get(0); + soft.assertThat(queryCondition.name()).isEqualTo("hexadecimal"); + soft.assertThat(queryCondition.value()).isEqualTo(NullQueryValue.INSTANCE); + soft.assertThat(selectQuery.isCount()).isFalse(); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "WHERE isOdd = false AND numType = java.time.DayOfWeek.MONDAY") + void shouldQueryConditionEnum(String query) { + var selectQuery = selectProvider.apply(query, "entity"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(selectQuery.fields()).isEmpty(); + soft.assertThat(selectQuery.entity()).isEqualTo("entity"); + soft.assertThat(selectQuery.orderBy()).isEmpty(); + soft.assertThat(selectQuery.where()).isNotEmpty(); + var where = selectQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.AND); + var andCondition = (ConditionQueryValue) condition.value(); + var first = andCondition.get().get(0); + var second = andCondition.get().get(1); + soft.assertThat(first.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(first.name()).isEqualTo("isOdd"); + soft.assertThat(first.value()).isEqualTo(BooleanQueryValue.FALSE); + soft.assertThat(second.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(second.name()).isEqualTo("numType"); + soft.assertThat(second.value().get()).isEqualTo(DayOfWeek.MONDAY); + soft.assertThat(selectQuery.isCount()).isFalse(); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "WHERE NOT age <> 10") + void shouldUseNotNotEquals(String query) { + SelectQuery selectQuery = selectProvider.apply(query, "entity"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(selectQuery.fields()).isEmpty(); + soft.assertThat(selectQuery.entity()).isEqualTo("entity"); + soft.assertThat(selectQuery.orderBy()).isEmpty(); + soft.assertThat(selectQuery.where()).isNotEmpty(); + var where = selectQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.EQUALS); + soft.assertThat(condition.name()).isEqualTo("age"); + soft.assertThat(condition.value()).isEqualTo(NumberQueryValue.of(10)); + soft.assertThat(selectQuery.isCount()).isFalse(); + }); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = "WHERE age <> 10") + void shouldUseNotEquals(String query) { + SelectQuery selectQuery = selectProvider.apply(query, "entity"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(selectQuery.fields()).isEmpty(); + soft.assertThat(selectQuery.entity()).isEqualTo("entity"); + soft.assertThat(selectQuery.orderBy()).isEmpty(); + soft.assertThat(selectQuery.where()).isNotEmpty(); + var where = selectQuery.where().orElseThrow(); + var condition = where.condition(); + soft.assertThat(condition.condition()).isEqualTo(Condition.NOT); + var notCondition = (ConditionQueryValue) condition.value(); + var queryCondition = notCondition.get().get(0); + soft.assertThat(queryCondition.name()).isEqualTo("age"); + soft.assertThat(queryCondition.value()).isEqualTo(NumberQueryValue.of(10)); + soft.assertThat(selectQuery.isCount()).isFalse(); + }); + } } diff --git a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/Values.java b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/Values.java index 7c479f984..27579ee94 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/Values.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/main/java/org/eclipse/jnosql/communication/semistructured/Values.java @@ -45,6 +45,9 @@ static Object get(QueryValue value, Params parameters) { case ENUM -> { return ((EnumQueryValue) value).get().name(); } + case NULL -> { + return null; + } default -> throw new QueryException("There is not support to the value: " + type); } } diff --git a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/SelectQueryParserTest.java b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/SelectQueryParserTest.java index 3a5e2b4cf..8a0dff58b 100644 --- a/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/SelectQueryParserTest.java +++ b/jnosql-communication/jnosql-communication-semistructured/src/test/java/org/eclipse/jnosql/communication/semistructured/SelectQueryParserTest.java @@ -17,6 +17,7 @@ import jakarta.data.Sort; import jakarta.data.Direction; import org.eclipse.jnosql.communication.TypeReference; +import org.eclipse.jnosql.communication.Value; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -461,6 +462,43 @@ void shouldReturnErrorWhenResultInsteadOfCount(String query) { assertThrows(UnsupportedOperationException.class, prepare::singleResult); } + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"FROM entity WHERE name is null"}) + void shouldRunIsNull(String query) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SelectQuery.class); + parser.query(query, null, manager, observer); + Mockito.verify(manager).select(captor.capture()); + var selectQuery = captor.getValue(); + + checkBaseQuery(selectQuery); + assertTrue(selectQuery.condition().isPresent()); + var condition = selectQuery.condition().get(); + + assertEquals(Condition.EQUALS, condition.condition()); + assertEquals(Element.of("name", null), condition.element()); + } + + @ParameterizedTest(name = "Should parser the query {0}") + @ValueSource(strings = {"FROM entity WHERE name is not null"}) + void shouldRunIsNotNull(String query) { + ArgumentCaptor captor = ArgumentCaptor.forClass(SelectQuery.class); + parser.query(query, null, manager, observer); + Mockito.verify(manager).select(captor.capture()); + var selectQuery = captor.getValue(); + + checkBaseQuery(selectQuery); + assertTrue(selectQuery.condition().isPresent()); + var condition = selectQuery.condition().get(); + var criteriaCondition = condition.element().get(CriteriaCondition.class); + assertEquals(Condition.NOT, condition.condition()); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(criteriaCondition.condition()).isEqualTo(Condition.EQUALS); + softly.assertThat(criteriaCondition.element()).isEqualTo(Element.of("name", null)); + }); + } + + private void checkBaseQuery(SelectQuery selectQuery) { assertTrue(selectQuery.columns().isEmpty()); diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturn.java b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturn.java new file mode 100644 index 000000000..aaeafc49f --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-core/src/main/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturn.java @@ -0,0 +1,54 @@ +/* + * 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. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.core.repository.returns; + +import org.eclipse.jnosql.mapping.core.repository.DynamicReturn; + +import java.util.List; + +public class ArrayRepositoryReturn extends AbstractRepositoryReturn { + + public ArrayRepositoryReturn() { + super(null); + } + + @Override + public boolean isCompatible(Class entity, Class returnType) { + return returnType.isArray(); + } + + @Override + + public Object convert(DynamicReturn dynamicReturn) { + List entities = dynamicReturn.result().toList(); + return toArray(entities); + } + + @Override + public Object convertPageRequest(DynamicReturn dynamicReturn) { + List entities = dynamicReturn.streamPagination().toList(); + return toArray(entities); + } + + @SuppressWarnings("unchecked") + private T[] toArray(List entities) { + if (entities.isEmpty()) { + return (T[]) java.lang.reflect.Array.newInstance(Object.class, 0); + } + T[] array = (T[]) java.lang.reflect.Array.newInstance( + entities.get(0).getClass(), entities.size()); + return entities.toArray(array); + } +} diff --git a/jnosql-mapping/jnosql-mapping-core/src/main/resources/META-INF/services/org.eclipse.jnosql.mapping.core.repository.RepositoryReturn b/jnosql-mapping/jnosql-mapping-core/src/main/resources/META-INF/services/org.eclipse.jnosql.mapping.core.repository.RepositoryReturn index ed49eb58c..e4f24dd4c 100644 --- a/jnosql-mapping/jnosql-mapping-core/src/main/resources/META-INF/services/org.eclipse.jnosql.mapping.core.repository.RepositoryReturn +++ b/jnosql-mapping/jnosql-mapping-core/src/main/resources/META-INF/services/org.eclipse.jnosql.mapping.core.repository.RepositoryReturn @@ -5,4 +5,5 @@ org.eclipse.jnosql.mapping.core.repository.returns.PageRepositoryReturn org.eclipse.jnosql.mapping.core.repository.returns.QueueRepositoryReturn org.eclipse.jnosql.mapping.core.repository.returns.SetRepositoryReturn org.eclipse.jnosql.mapping.core.repository.returns.SortedSetRepositoryReturn -org.eclipse.jnosql.mapping.core.repository.returns.StreamRepositoryReturn \ No newline at end of file +org.eclipse.jnosql.mapping.core.repository.returns.StreamRepositoryReturn +org.eclipse.jnosql.mapping.core.repository.returns.ArrayRepositoryReturn \ No newline at end of file diff --git a/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturnTest.java b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturnTest.java new file mode 100644 index 000000000..c3bddd3b0 --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-core/src/test/java/org/eclipse/jnosql/mapping/core/repository/returns/ArrayRepositoryReturnTest.java @@ -0,0 +1,132 @@ +/* + * 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. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.core.repository.returns; + +import jakarta.data.page.Page; +import jakarta.data.page.PageRequest; +import org.assertj.core.api.SoftAssertions; +import org.eclipse.jnosql.mapping.core.repository.DynamicReturn; +import org.eclipse.jnosql.mapping.core.repository.RepositoryReturn; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class ArrayRepositoryReturnTest { + + private final RepositoryReturn repositoryReturn = new ArrayRepositoryReturn(); + + @Mock + private Page page; + + @Test + void shouldReturnIsCompatible() { + Assertions.assertTrue(repositoryReturn.isCompatible(Person.class, Person[].class)); + Assertions.assertFalse(repositoryReturn.isCompatible(Person.class, Iterable.class)); + Assertions.assertFalse(repositoryReturn.isCompatible(Person.class, Collection.class)); + assertFalse(repositoryReturn.isCompatible(Object.class, Person.class)); + assertFalse(repositoryReturn.isCompatible(Person.class, Object.class)); + } + + @SuppressWarnings("unchecked") + @Test + void shouldReturnArray() { + Person ada = new Person("Ada"); + DynamicReturn dynamic = DynamicReturn.builder() + .withSingleResult(Optional::empty) + .withClassSource(Person.class) + .withResult(() -> Stream.of(ada)) + .withMethodSource(Person.class.getDeclaredMethods()[0]) + .build(); + Person[] person = (Person[]) repositoryReturn.convert(dynamic); + SoftAssertions.assertSoftly(s -> { + s.assertThat(person).isNotNull(); + s.assertThat(person).hasSize(1); + s.assertThat(person[0]).isEqualTo(ada); + }); + } + + @SuppressWarnings("unchecked") + @Test + void shouldReturnListPage() { + Person ada = new Person("Ada"); + DynamicReturn dynamic = DynamicReturn.builder() + .withClassSource(Person.class) + .withSingleResult(Optional::empty) + .withResult(Collections::emptyList) + .withSingleResultPagination(p -> Optional.empty()) + .withStreamPagination(p -> Stream.of(ada)) + .withMethodSource(Person.class.getDeclaredMethods()[0]) + .withPagination(PageRequest.ofPage(2).size(2)) + .withPage(p -> page) + .build(); + Person[] person = (Person[]) repositoryReturn.convertPageRequest(dynamic); + SoftAssertions.assertSoftly(s -> { + s.assertThat(person).isNotNull(); + s.assertThat(person).hasSize(1); + s.assertThat(person[0]).isEqualTo(ada); + }); + } + + + private static class Person implements Comparable { + + private String name; + + public Person(String name) { + this.name = name; + } + + public Person() { + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return Objects.equals(name, person.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + '}'; + } + + @Override + public int compareTo(Person o) { + return name.compareTo(o.name); + } + } +} \ No newline at end of file diff --git a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CrudRepositoryProxyTest.java b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CrudRepositoryProxyTest.java index 7f9ad452e..5bf6906ea 100644 --- a/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CrudRepositoryProxyTest.java +++ b/jnosql-mapping/jnosql-mapping-semistructured/src/test/java/org/eclipse/jnosql/mapping/semistructured/query/CrudRepositoryProxyTest.java @@ -258,8 +258,11 @@ void shouldFindByFirstAgeInstance() { when(template.select(any(SelectQuery.class))) .thenReturn(Stream.of(Person.builder().build())); - personRepository.findFirst10ByAge(10); - + Person[] first10ByAge = personRepository.findFirst10ByAge(10); + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(first10ByAge).isNotNull(); + soft.assertThat(first10ByAge).hasSize(1); + }); ArgumentCaptor captor = ArgumentCaptor.forClass(SelectQuery.class); verify(template).select(captor.capture()); SelectQuery query = captor.getValue(); @@ -791,7 +794,7 @@ void shouldConvertMapAddressRepositoryOrder() { interface PersonRepository extends NoSQLRepository { - List findFirst10ByAge(int age); + Person[] findFirst10ByAge(int age); List findBySalary_Currency(String currency);