Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version

=== Added

- Add support for scalar function expressions (UPPER, LOWER, LEFT, RIGHT, LENGTH, ABS) in JDQL string queries
- Include support to Restriction interface
- Include support to record projector
- Include Is annotation support to the Repository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ primary_expression
;

function_expression
: ('abs(' | 'ABS(') scalar_expression ')'
| ('length(' | 'LENGTH(') scalar_expression ')'
| ('lower(' | 'LOWER(') scalar_expression ')'
| ('upper(' | 'UPPER(') scalar_expression ')'
| ('left(' | 'LEFT(') scalar_expression ',' scalar_expression ')'
| ('right(' | 'RIGHT(') scalar_expression ',' scalar_expression ')'
: (ABS | LENGTH | LOWER | UPPER | LEFT | RIGHT) '(' scalar_expression (',' scalar_expression)* ')'
;

special_expression
Expand All @@ -84,9 +79,11 @@ special_expression
| FALSE
;

state_field_path_expression : IDENTIFIER (DOT IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER | FUNCTION_ID;
state_field_path_expression : identifier (DOT identifier)* | FULLY_QUALIFIED_IDENTIFIER | FUNCTION_ID;

entity_name : IDENTIFIER; // no ambiguity
identifier : IDENTIFIER | ABS | LENGTH | LOWER | UPPER | LEFT | RIGHT;

entity_name : identifier; // no ambiguity

enum_literal : IDENTIFIER (DOT IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER; // ambiguity with state_field_path_expression resolvable semantically

Expand Down Expand Up @@ -120,6 +117,12 @@ 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];
ABS : [aA][bB][sS];
LENGTH : [lL][eE][nN][gG][tT][hH];
LOWER : [lL][oO][wW][eE][rR];
UPPER : [uU][pP][pP][eE][rR];
LEFT : [lL][eE][fF][tT];
RIGHT : [rR][iI][gG][hH][tT];
LOCAL : [lL][oO][cC][aA][lL];
DATE : [dD][aA][tT][eE];
DATETIME : [dD][aA][tT][eE][tT][iI][mM][eE];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* You may elect to redistribute this code under either of these licenses.
* Contributors:
* Otavio Santana
* Matheus Oliveira
*/
package org.eclipse.jnosql.communication.query.data;

Expand Down Expand Up @@ -185,11 +186,6 @@ public void exitIn_expression(JDQLParser.In_expressionContext ctx) {
and = andCondition;
}

@Override
public void exitFunction_expression(JDQLParser.Function_expressionContext ctx) {
throw new UnsupportedOperationException("The function is not supported in the query: " + ctx.getText());
}

private Condition getCondition(JDQLParser.Comparison_expressionContext ctx) {
var context = ctx.comparison_operator();
if (context.EQ() != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2026 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:
* Matheus Oliveira
*/
package org.eclipse.jnosql.communication.query.data;

import org.eclipse.jnosql.communication.query.Function;

import java.util.Arrays;
import java.util.Objects;

/**
* The default implementation of {@link Function}.
*
* @param name the function name (e.g., {@code "UPPER"})
* @param params the function parameters
*/
record DefaultFunction(String name, Object... params) implements Function {

public DefaultFunction {
Objects.requireNonNull(name, "name is required");
Objects.requireNonNull(params, "params is required");
}

@Override
public String toString() {
return name + "(" + Arrays.toString(params) + ")";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2026 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:
* Matheus Oliveira
*/
package org.eclipse.jnosql.communication.query.data;

import org.eclipse.jnosql.communication.query.Function;
import org.eclipse.jnosql.communication.query.QueryValue;
import org.eclipse.jnosql.communication.query.ValueType;

import java.util.Objects;

record FunctionQueryValue(Function function) implements QueryValue<Function> {

FunctionQueryValue {
Objects.requireNonNull(function, "function is required");
}

@Override
public Function get() {
return function;
}

@Override
public ValueType type() {
return ValueType.FUNCTION;
}

@Override
public String toString() {
return function.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2026 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:
* Matheus Oliveira
*/
package org.eclipse.jnosql.communication.query.data;

import org.eclipse.jnosql.query.grammar.data.JDQLParser;

import java.util.function.Predicate;

/**
* Maps each JDQL function keyword token to its canonical function name string.
*
* <p>Use {@link #from(JDQLParser.Function_expressionContext)} to resolve the
* function name from a grammar context without if-else chains.</p>
*/
enum FunctionType {

ABS(ctx -> ctx.ABS() != null, 1),
LENGTH(ctx -> ctx.LENGTH() != null, 1),
LOWER(ctx -> ctx.LOWER() != null, 1),
UPPER(ctx -> ctx.UPPER() != null, 1),
LEFT(ctx -> ctx.LEFT() != null, 2),
RIGHT(ctx -> ctx.RIGHT() != null, 2);

private final Predicate<JDQLParser.Function_expressionContext> matcher;
private final int arity;

FunctionType(Predicate<JDQLParser.Function_expressionContext> matcher, int arity) {
this.matcher = matcher;
this.arity = arity;
}

/**
* Resolves the canonical function name from a grammar context,
* validating that the argument count matches the expected arity.
*
* @param ctx the function expression context; must not be {@code null}
* @return the uppercase function name (e.g., {@code "UPPER"})
* @throws UnsupportedOperationException if no known function matches
* @throws IllegalArgumentException if the argument count does not match
*/
static String from(JDQLParser.Function_expressionContext ctx) {
for (FunctionType type : values()) {
if (type.matcher.test(ctx)) {
int actual = ctx.scalar_expression().size();
if (actual != type.arity) {
throw new IllegalArgumentException(
"Function " + type.name() + " expects " + type.arity
+ " argument(s) but got " + actual);
}
return type.name();
}
}
throw new UnsupportedOperationException("The function is not supported yet: " + ctx.getText());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* You may elect to redistribute this code under either of these licenses.
* Contributors:
* Otavio Santana
* Matheus Oliveira
*/
package org.eclipse.jnosql.communication.query.data;

Expand Down Expand Up @@ -53,10 +54,17 @@ public QueryValue<?> apply(JDQLParser.Primary_expressionContext context) {
default ->
throw new UnsupportedOperationException("The special expression is not supported yet: " + specialExpression);
};
} else if(context.enum_literal() != null) {
} else if (context.function_expression() != null) {
var functionExpression = context.function_expression();
var functionName = FunctionType.from(functionExpression);
var params = functionExpression.scalar_expression().stream()
.map(this::processScalar)
.toArray();
return new FunctionQueryValue(new DefaultFunction(functionName, params));
} else if (context.enum_literal() != null) {
Enum<?> value = EnumConverter.INSTANCE.apply(context.enum_literal().getText());
return EnumQueryValue.of(value);
} else if(context.state_field_path_expression() != null) {
} else if (context.state_field_path_expression() != null) {
var stateContext = context.state_field_path_expression();
var stateContextText = stateContext.getText();
try {
Expand All @@ -67,6 +75,16 @@ public QueryValue<?> apply(JDQLParser.Primary_expressionContext context) {
return QueryPath.of(stateContextText);
}
}
throw new UnsupportedOperationException("The primary expression is not supported yet: " + context.getText());
throw new UnsupportedOperationException("The primary expression is not supported yet: " + context.getText());
}
}

private Object processScalar(JDQLParser.Scalar_expressionContext context) {
if (context.primary_expression() != null) {
return apply(context.primary_expression());
}
if (context.LPAREN() != null && context.scalar_expression().size() == 1) {
return processScalar(context.scalar_expression(0));
}
throw new UnsupportedOperationException("Arithmetic expressions in function arguments are not supported yet: " + context.getText());
}
}
Loading
Loading