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
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,9 @@ public TermAst termFromBuiltInCall(fr.inria.corese.core.next.impl.parser.antlr.S
} else if (ctx.CONCAT() != null) {
List<TermAst> args = ctx.expression().stream().map(this::termFromExpression).toList();
return new FunctionCallAst(new IriAst("CONCAT"), args);
} else if (ctx.COALESCE() != null) {
List<TermAst> args = ctx.expression().stream().map(this::termFromExpression).toList();
return new CoalesceAst(args);
} else if (ctx.expression() != null) {
List<TermAst> args = ctx.expression().stream().map(this::termFromExpression).toList();
if (ctx.STR() != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package fr.inria.corese.core.next.query.impl.parser.semantic.support;

import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import fr.inria.corese.core.next.query.impl.sparql.ast.*;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.BinaryConstraintAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.FunctionCallAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.IfAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.TrinaryRegexAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.UnaryConstraintAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.*;

import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -157,6 +149,12 @@ case IfAst(TermAst condition, TermAst thenExpr, TermAst elseExpr) -> {
collectReferencedVariables(elseExpr, referencedVariables);
}

case CoalesceAst(List<TermAst> arguments) -> {
for (TermAst argument : arguments) {
collectReferencedVariables(argument, referencedVariables);
}
}

case ConstraintAst ignored -> {
// Other constraint shapes are ignored until they are supported here.
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.inria.corese.core.next.query.impl.sparql.ast.constraint;

import fr.inria.corese.core.next.query.impl.sparql.ast.ConstraintAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst;
import java.util.List;

/**
* Function {@code COALESCE(expr1, expr2, ...)} in SPARQL 1.1
* Returns the first argument that evaluates without error.
*/
public record CoalesceAst(List<TermAst> arguments) implements ConstraintAst {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package fr.inria.corese.core.next.query.impl.parser;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import fr.inria.corese.core.next.query.api.exception.QueryValidationException;
import fr.inria.corese.core.next.query.impl.sparql.ast.BindAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.FilterAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.LiteralAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.SelectQueryAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.VarAst;
import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.CoalesceAst;

@DisplayName("SPARQL 1.1 - Parser and AST : COALESCE")
class SparqlParserCoalesceTest extends AbstractSparqlParserFeatureTest {

@Test
@DisplayName("COALESCE(?a, ?b, ?c)")
void shouldParseCoalesceWithThreeArgs() {
SparqlParser parser = newParserDefault();

QueryAst ast = parser.parse("""
SELECT * WHERE {
?s ?p ?o .
FILTER(COALESCE(?s, ?p, ?o))
}
""");

assertNotNull(ast);
FilterAst filter = (FilterAst) ast.whereClause().patterns().getLast();
assertInstanceOf(CoalesceAst.class, filter.operator());

CoalesceAst coalesce = (CoalesceAst) filter.operator();
assertEquals(3, coalesce.arguments().size());
assertInstanceOf(VarAst.class, coalesce.arguments().get(0));
assertInstanceOf(VarAst.class, coalesce.arguments().get(1));
assertInstanceOf(VarAst.class, coalesce.arguments().get(2));
assertEquals("s", ((VarAst) coalesce.arguments().get(0)).name());
assertEquals("p", ((VarAst) coalesce.arguments().get(1)).name());
assertEquals("o", ((VarAst) coalesce.arguments().get(2)).name());
}

@Test
@DisplayName("BIND(COALESCE(?a, \"default\") AS ?result)")
void shouldParseCoalesceInBind() {
SparqlParser parser = newParserDefault();

QueryAst ast = parser.parse("""
SELECT * WHERE {
?s ?p ?o .
BIND(COALESCE(?s, "default") AS ?result)
}
""");

assertNotNull(ast);
BindAst bind = (BindAst) ast.whereClause().patterns().getLast();
assertInstanceOf(CoalesceAst.class, bind.expression());

CoalesceAst coalesce = (CoalesceAst) bind.expression();
assertEquals(2, coalesce.arguments().size());
assertInstanceOf(VarAst.class, coalesce.arguments().getFirst());
assertInstanceOf(LiteralAst.class, coalesce.arguments().getLast());
assertEquals("s", ((VarAst) coalesce.arguments().getFirst()).name());
}

@Test
@DisplayName("COALESCE(?a) — single argument")
void shouldParseCoalesceWithOneArg() {
SparqlParser parser = newParserDefault();

QueryAst ast = parser.parse("""
SELECT * WHERE {
?s ?p ?o .
FILTER(COALESCE(?s))
}
""");

assertNotNull(ast);
FilterAst filter = (FilterAst) ast.whereClause().patterns().getLast();
assertInstanceOf(CoalesceAst.class, filter.operator());

CoalesceAst coalesce = (CoalesceAst) filter.operator();
assertEquals(1, coalesce.arguments().size());
}

@Test
@DisplayName("ORDER BY COALESCE(?s, ?o) — exercises VariableScopeAnalyzer on COALESCE via semantic validation")
void shouldParseOrderByCoalesceAndValidateVariableScope() {
SparqlParser parser = newParserDefault();

QueryAst ast = parser.parse("""
SELECT ?s WHERE {
?s ?p ?o .
} ORDER BY COALESCE(?s, ?o)
""");

assertNotNull(ast);
SelectQueryAst select = assertInstanceOf(SelectQueryAst.class, ast);
assertFalse(select.solutionModifier().orderBy().isEmpty());
var order = select.solutionModifier().orderBy().getFirst();
assertInstanceOf(CoalesceAst.class, order.expression());
CoalesceAst coalesce = (CoalesceAst) order.expression();
assertEquals(2, coalesce.arguments().size());
}

@Test
@DisplayName("ORDER BY COALESCE(?s, ?z) — should reject variable not visible in WHERE")
void shouldRejectOrderByCoalesceWhenVariableNotVisibleInWhere() {
SparqlParser parser = newParserDefault();

QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse("""
SELECT ?s WHERE {
?s ?p ?o .
} ORDER BY COALESCE(?s, ?z)
"""));

assertEquals("Variable ?z used in ORDER BY is not visible in WHERE clause", exception.getMessage());
}
}
Loading