diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilder.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilder.java index 808664e35..e5c6641a1 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilder.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilder.java @@ -1,9 +1,9 @@ package fr.inria.corese.core.next.query.impl.sparql.bridge; -import fr.inria.corese.core.next.query.impl.sparql.ast.ConstraintAst; -import fr.inria.corese.core.next.query.impl.sparql.ast.FilterAst; -import fr.inria.corese.core.next.query.impl.sparql.ast.TermAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.*; import fr.inria.corese.core.next.query.kgram.api.core.Filter; +import fr.inria.corese.core.next.query.kgram.core.Exp; +import fr.inria.corese.core.next.query.kgram.core.Query; import java.util.Objects; @@ -15,6 +15,40 @@ public final class CoreseAstQueryBuilder { public CoreseAstQueryBuilder() { } + private final WhereCompiler whereCompiler = new WhereCompiler(); + + /** + * Builds a KGRAM {@link Query} from a SPARQL {@code ASK} query AST. + */ + public Query toQuery(AskQueryAst ask) { + Objects.requireNonNull(ask, "ask"); + rejectUnsupportedClauses(ask); + + Exp body = whereCompiler.compile(ask.whereClause()); + Query query = Query.create(body); + query.setAsk(true); + + return query; + } + + private static void rejectUnsupportedClauses(AskQueryAst ask) { + DatasetClauseAst dataset = ask.datasetClause(); + if (!dataset.graphs().isEmpty() || !dataset.namedGraphs().isEmpty()) { + throw new UnsupportedOperationException( + "FROM / FROM NAMED is not supported yet for ASK (dataset handling is a follow-up)"); + } + if (!ask.valuesClause().mappings().isEmpty()) { + throw new UnsupportedOperationException( + "Inline VALUES is not supported yet for ASK (values handling is a follow-up)"); + } + SolutionModifierAst mod = ask.solutionModifier(); + if (mod.hasOrderBy() || mod.hasGroupBy() || mod.hasHaving() + || mod.hasLimit() || mod.hasOffset() || mod.distinct() || mod.reduced()) { + throw new UnsupportedOperationException( + "Solution modifiers (ORDER BY, GROUP BY, HAVING, LIMIT, OFFSET, DISTINCT, REDUCED) are not supported for ASK"); + } + } + /** * Converts a SPARQL {@code FILTER} clause by converting {@link FilterAst#operator()} the same way as * {@link #toNextFilter(TermAst)}. @@ -44,4 +78,4 @@ public Filter toNextFilter(ConstraintAst filterExpression) { return SparqlAstToExpression.toNextFilter(filterExpression); } -} +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/SparqlAstToExpression.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/SparqlAstToExpression.java index 91ed53006..b2c7aaf80 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/SparqlAstToExpression.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/bridge/SparqlAstToExpression.java @@ -6,12 +6,9 @@ import fr.inria.corese.core.next.query.kgram.api.core.Filter; import fr.inria.corese.core.next.util.StringUtils; import fr.inria.corese.core.sparql.datatype.RDF; -import fr.inria.corese.core.sparql.triple.parser.Constant; -import fr.inria.corese.core.sparql.triple.parser.Expression; -import fr.inria.corese.core.sparql.triple.parser.Processor; -import fr.inria.corese.core.sparql.triple.parser.Term; -import fr.inria.corese.core.sparql.triple.parser.Variable; +import fr.inria.corese.core.sparql.triple.parser.*; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -60,10 +57,23 @@ public static Filter toNextFilter(FilterAst filterClause) { */ public static Filter toNextFilter(TermAst filterExpression) { Expression exprTree = convert(filterExpression); + initializeExpList(exprTree); AstBackedExpr expr = new AstBackedExpr(exprTree, Optional.of(filterExpression)); return expr.getFilter(); } + /** + * Initializes the {@code Expr} list ({@code lExp}) of every {@link Term} in the tree. + */ + private static void initializeExpList(Expression expression) { + if (expression instanceof Term term) { + for (Expression arg : term.getArgs()) { + initializeExpList(arg); + } + term.setExpList(new ArrayList<>(term.getArgs())); + } + } + private static Constant literalToConstant(LiteralAst l) { if (l.lang() != null && !l.lang().isEmpty()) { return Constant.create(unquoteLexical(l.lexical()), RDF.rdflangString, l.lang()); diff --git a/src/main/java/fr/inria/corese/core/next/query/kgram/core/Query.java b/src/main/java/fr/inria/corese/core/next/query/kgram/core/Query.java index f6d20b3cb..a10860ba8 100644 --- a/src/main/java/fr/inria/corese/core/next/query/kgram/core/Query.java +++ b/src/main/java/fr/inria/corese/core/next/query/kgram/core/Query.java @@ -139,6 +139,7 @@ public static DQPFactory getFactory() { private boolean isInsert = false; boolean isDelete = false; boolean isUpdate = false; + boolean isAsk = false; boolean isCheckLoop = false; boolean isListGroup = false; boolean isListPath = true; @@ -685,6 +686,14 @@ public void setUpdate(boolean b) { isUpdate = b; } + public boolean isAsk() { + return isAsk; + } + + public void setAsk(boolean b) { + isAsk = b; + } + public boolean isTest() { return isTest; } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilderAskTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilderAskTest.java new file mode 100644 index 000000000..1585b1040 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/sparql/bridge/CoreseAstQueryBuilderAskTest.java @@ -0,0 +1,114 @@ +package fr.inria.corese.core.next.query.impl.sparql.bridge; + +import fr.inria.corese.core.next.query.impl.sparql.ast.*; +import fr.inria.corese.core.next.query.impl.sparql.ast.constraint.GreaterThanAst; +import fr.inria.corese.core.next.query.kgram.core.Query; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class CoreseAstQueryBuilderAskTest { + + private final CoreseAstQueryBuilder builder = new CoreseAstQueryBuilder(); + + private static GroupGraphPatternAst whereOneTriple() { + return new GroupGraphPatternAst(List.of( + new BgpAst(List.of( + new TriplePatternAst(new VarAst("s"), new VarAst("p"), new VarAst("o")))))); + } + + @Test + @DisplayName("Query body is the compiled WHERE and the query is flagged as ASK") + void buildsBodyAndAskFlag() { + AskQueryAst ask = new AskQueryAst(DatasetClauseAst.none(), whereOneTriple()); + + Query query = builder.toQuery(ask); + + assertNotNull(query.getBody(), "body set from WHERE"); + assertTrue(query.getBody().isAnd(), "group compiles to AND"); + assertTrue(query.getBody().get(0).isBGP(), "containing the BGP"); + assertTrue(query.isAsk(), "query flagged as ASK"); + } + + @Test + @DisplayName("A FILTER in the WHERE no longer throws an NPE (lExp fix)") + void buildsAskWithFilter() { + FilterAst filter = new FilterAst(new GreaterThanAst(List.of( + new VarAst("o"), new LiteralAst("5", null, null)))); + GroupGraphPatternAst where = new GroupGraphPatternAst(List.of( + new BgpAst(List.of( + new TriplePatternAst(new VarAst("s"), new VarAst("p"), new VarAst("o")))), + filter)); + AskQueryAst ask = new AskQueryAst(DatasetClauseAst.none(), where); + + Query query = builder.toQuery(ask); + + assertTrue(query.isAsk()); + assertTrue(query.getBody().get(1).isFilter(), "FILTER compiled into the body"); + } + + @Test + @DisplayName("FROM is not supported yet → UnsupportedOperationException") + void rejectsFromClause() { + DatasetClauseAst dataset = new DatasetClauseAst( + Set.of(new IriAst("http://example.org/g")), Set.of()); + AskQueryAst ask = new AskQueryAst(dataset, whereOneTriple()); + + assertThrows(UnsupportedOperationException.class, () -> builder.toQuery(ask)); + } + + @Test + @DisplayName("FROM NAMED is not supported yet → UnsupportedOperationException") + void rejectsFromNamedClause() { + DatasetClauseAst dataset = new DatasetClauseAst( + Set.of(), Set.of(new IriAst("http://example.org/g"))); + AskQueryAst ask = new AskQueryAst(dataset, whereOneTriple()); + + assertThrows(UnsupportedOperationException.class, () -> builder.toQuery(ask)); + } + + @Test + @DisplayName("Inline VALUES is not supported yet → UnsupportedOperationException") + void rejectsValuesClause() { + ValuesAst values = new ValuesAst(List.of( + new ValueMappingAst(Map.of(new VarAst("s"), new IriAst("http://example.org/x"))))); + AskQueryAst ask = new AskQueryAst( + DatasetClauseAst.none(), whereOneTriple(), null, null, values); + + assertThrows(UnsupportedOperationException.class, () -> builder.toQuery(ask)); + } + + @Test + @DisplayName("toQuery(null) throws NullPointerException") + void rejectsNullAsk() { + assertThrows(NullPointerException.class, () -> builder.toQuery(null)); + } + + @Test + @DisplayName("ASK {} with empty WHERE compiles to an empty AND body") + void buildsAskWithEmptyWhere() { + AskQueryAst ask = new AskQueryAst(DatasetClauseAst.none(), + new GroupGraphPatternAst(List.of())); + + Query query = builder.toQuery(ask); + + assertNotNull(query.getBody()); + assertTrue(query.getBody().isAnd()); + assertEquals(0, query.getBody().size(), "empty AND — no children"); + assertTrue(query.isAsk()); + } + + @Test + @DisplayName("Solution modifiers (LIMIT) are not supported yet → UnsupportedOperationException") + void rejectsSolutionModifier() { + SolutionModifierAst mod = SolutionModifierAst.withoutGroupBy(false, false, List.of(), 10L, null); + AskQueryAst ask = new AskQueryAst(DatasetClauseAst.none(), whereOneTriple(), mod, null, null); + + assertThrows(UnsupportedOperationException.class, () -> builder.toQuery(ask)); + } +} \ No newline at end of file