From 0b0f8b7d4b2d89851dbd6335d40166be1ca70dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Tue, 14 Apr 2026 10:11:46 +0200 Subject: [PATCH] #356 [SPARQL 1.1] - Parser and AST : ENCODE FOR URI --- .../query/impl/parser/SparqlAstBuilder.java | 2 + .../ast/constraint/EncodeForUriAst.java | 14 +++ .../parser/SparqlParserEncodeForUriTest.java | 112 ++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/EncodeForUriAst.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserEncodeForUriTest.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java index b0e055543..8444b0ffc 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilder.java @@ -1154,6 +1154,8 @@ public TermAst termFromBuiltInCall(fr.inria.corese.core.next.impl.parser.antlr.S List args = ctx.expression().stream().map(this::termFromExpression).toList(); if (ctx.STR() != null) { return this.createConstraint(ASTConstants.FUNCTION_CALL.STR, args); + } else if (ctx.ENCODE_FOR_URI() != null) { + return new EncodeForUriAst(args); } else if (ctx.LANG() != null) { return this.createConstraint(ASTConstants.FUNCTION_CALL.LANG, args); } else if (ctx.LANGMATCHES() != null) { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/EncodeForUriAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/EncodeForUriAst.java new file mode 100644 index 000000000..22aa881f1 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/constraint/EncodeForUriAst.java @@ -0,0 +1,14 @@ +package fr.inria.corese.core.next.query.impl.sparql.ast.constraint; + +import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst; + +import java.util.List; + +/** + * Function {@code ENCODE_FOR_URI(string)} in SPARQL 1.1. + */ +public class EncodeForUriAst extends AbstractUnaryConstraintAst implements SimpleLiteralExpressionAst { + public EncodeForUriAst(List args) { + super(args); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserEncodeForUriTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserEncodeForUriTest.java new file mode 100644 index 000000000..93dbb2bae --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserEncodeForUriTest.java @@ -0,0 +1,112 @@ +package fr.inria.corese.core.next.query.impl.parser; + +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.EncodeForUriAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.EqualsAst; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +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; + +@DisplayName("SPARQL 1.1 - Parser and AST : ENCODE FOR URI") +class SparqlParserEncodeForUriTest extends AbstractSparqlParserFeatureTest { + + @Test + @DisplayName("BIND(ENCODE_FOR_URI(?label) AS ?encoded)") + void shouldParseEncodeForUriInBind() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?s ?p ?label . + BIND(ENCODE_FOR_URI(?label) AS ?encoded) + } + """); + + assertNotNull(ast); + BindAst bind = assertInstanceOf(BindAst.class, ast.whereClause().patterns().getLast()); + EncodeForUriAst encode = assertInstanceOf(EncodeForUriAst.class, bind.expression()); + assertEquals("label", assertInstanceOf(VarAst.class, encode.getArgument()).name()); + assertEquals("encoded", bind.variable().name()); + } + + @Test + @DisplayName("BIND(ENCODE_FOR_URI(\"Los Angeles\") AS ?encoded)") + void shouldParseEncodeForUriWithLiteralArgument() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?s ?p ?label . + BIND(ENCODE_FOR_URI("Los Angeles") AS ?encoded) + } + """); + + assertNotNull(ast); + BindAst bind = assertInstanceOf(BindAst.class, ast.whereClause().patterns().getLast()); + EncodeForUriAst encode = assertInstanceOf(EncodeForUriAst.class, bind.expression()); + LiteralAst literal = assertInstanceOf(LiteralAst.class, encode.getArgument()); + assertEquals("\"Los Angeles\"", literal.lexical()); + assertEquals("encoded", bind.variable().name()); + } + + @Test + @DisplayName("FILTER(ENCODE_FOR_URI(?label) = \"abc\")") + void shouldParseEncodeForUriInFilterComparison() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT * WHERE { + ?s ?p ?label . + FILTER(ENCODE_FOR_URI(?label) = "abc") + } + """); + + assertNotNull(ast); + FilterAst filter = assertInstanceOf(FilterAst.class, ast.whereClause().patterns().getLast()); + EqualsAst equals = assertInstanceOf(EqualsAst.class, filter.operator()); + EncodeForUriAst encode = assertInstanceOf(EncodeForUriAst.class, equals.getLeftArgument()); + assertEquals("label", assertInstanceOf(VarAst.class, encode.getArgument()).name()); + } + + @Test + @DisplayName("ORDER BY ENCODE_FOR_URI(?label)") + void shouldParseOrderByEncodeForUri() { + SparqlParser parser = newParserDefault(); + + QueryAst ast = parser.parse(""" + SELECT ?label WHERE { + ?s ?p ?label . + } ORDER BY ENCODE_FOR_URI(?label) + """); + + assertNotNull(ast); + SelectQueryAst select = assertInstanceOf(SelectQueryAst.class, ast); + assertFalse(select.solutionModifier().orderBy().isEmpty()); + assertInstanceOf(EncodeForUriAst.class, select.solutionModifier().orderBy().getFirst().expression()); + } + + @Test + @DisplayName("ORDER BY ENCODE_FOR_URI(?missing) — should reject variable not visible in WHERE") + void shouldRejectOrderByEncodeForUriWhenVariableNotVisibleInWhere() { + SparqlParser parser = newParserDefault(); + + QueryValidationException exception = assertThrows(QueryValidationException.class, () -> parser.parse(""" + SELECT ?label WHERE { + ?s ?p ?label . + } ORDER BY ENCODE_FOR_URI(?missing) + """)); + + assertEquals("Variable ?missing used in ORDER BY is not visible in WHERE clause", exception.getMessage()); + } +}