Skip to content

Commit

Permalink
Merge pull request #531 from eclipse/is-null-support
Browse files Browse the repository at this point in the history
Include support to Eclipse JNoSQL
  • Loading branch information
otaviojava authored Jul 27, 2024
2 parents 5c77202 + 986fdb3 commit fd469d0
Show file tree
Hide file tree
Showing 14 changed files with 533 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
37 changes: 21 additions & 16 deletions antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 : '=';
Expand All @@ -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]+;

Expand Down
Original file line number Diff line number Diff line change
@@ -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<NullQueryValue> {

INSTANCE;

@Override
public NullQueryValue get() {
return INSTANCE;
}

@Override
public ValueType type() {
return ValueType.NULL;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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;
}
Expand All @@ -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());
}

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

Expand Down Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

}
Loading

0 comments on commit fd469d0

Please sign in to comment.