From 6879092fd33224d881eadb3de9f8a4b349ed7b2f Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Wed, 18 Mar 2026 15:40:00 +0100 Subject: [PATCH 1/4] Base classes --- .../query/impl/parser/SparqlAstBuilder.java | 22 ++++++++++++++----- .../query/impl/sparql/ast/AskQueryAst.java | 2 +- .../impl/sparql/ast/DatasetClauseAst.java | 20 +++++++++++++++++ .../next/query/impl/sparql/ast/QueryAst.java | 1 + .../query/impl/sparql/ast/SelectQueryAst.java | 11 ++++++---- .../query/api/sparql/ast/SparqlAstTest.java | 10 ++++----- 6 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.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 8bdb5b1b0..0b09862d5 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 @@ -65,8 +65,11 @@ public final class SparqlAstBuilder { private ProjectionAst projection = ProjectionAsts.selectAll(); /** - * SELECT DISTINCT / REDUCED. Set by SelectQueryFeature when parsing SELECT (DISTINCT | REDUCED)? ... + * Dataset clause (FROM/FROM NAMED) */ + private DatasetClauseAst datasets = DatasetClauseAst.none(); + + /** SELECT DISTINCT / REDUCED. Set by SelectQueryFeature when parsing SELECT (DISTINCT | REDUCED)? ... */ private boolean distinct; private boolean reduced; @@ -160,6 +163,14 @@ public void setReduced(boolean reduced) { this.reduced = reduced; } + public void addFromGraph(IriAst graph) { + this.datasets.graphs().add(graph); + } + + public void addFromNamedGraph(IriAst graph) { + this.datasets.namedGraphs().add(graph); + } + /** * Sets the LIMIT for pagination * @@ -281,12 +292,11 @@ public QueryAst getResult() { throw new IllegalStateException("No WHERE clause: did you call exitGroup() for the top-level GroupGraphPattern?"); } return switch (this.queryType) { - case ASK -> new AskQueryAst(whereClause); + case DESCRIBE -> new DescribeQueryAst(describeResources, datasets, whereClause); case CONSTRUCT -> null; - case DESCRIBE -> new DescribeQueryAst(describeResources, whereClause); - case SELECT -> new SelectQueryAst(projection, whereClause, buildSolutionModifier()); - case UNDEFINED -> - throw new QueryEvaluationException("Could not determine the type of query during parsing"); + case ASK -> new AskQueryAst(datasets, whereClause); + case SELECT -> new SelectQueryAst(projection, datasets, whereClause, buildSolutionModifier()); + case UNDEFINED -> throw new QueryEvaluationException("Could not determine the type of query during parsing"); }; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java index 652cd4532..243246219 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java @@ -15,7 +15,7 @@ * } * } */ -public record AskQueryAst(GroupGraphPatternAst whereClause) implements QueryAst { +public record AskQueryAst(DatasetClauseAst datasetClause, GroupGraphPatternAst whereClause) implements QueryAst { public AskQueryAst { if (whereClause == null) { whereClause = new GroupGraphPatternAst(List.of()); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java new file mode 100644 index 000000000..150a118a0 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java @@ -0,0 +1,20 @@ +package fr.inria.corese.core.next.query.impl.sparql.ast; + +import java.util.HashSet; +import java.util.Set; + +public record DatasetClauseAst(Set graphs, Set namedGraphs) { + + public DatasetClauseAst { + if (graphs == null) { + graphs = new HashSet<>(); + } + if (namedGraphs == null) { + namedGraphs = new HashSet<>(); + } + } + + public static DatasetClauseAst none() { + return new DatasetClauseAst(new HashSet<>(), new HashSet<>()); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/QueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/QueryAst.java index 673f54093..03e29ba1e 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/QueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/QueryAst.java @@ -5,5 +5,6 @@ * Holds the WHERE clause as a group graph pattern; query-specific projection/template can be added later. */ public sealed interface QueryAst permits AskQueryAst, ConstructQueryAst, DescribeQueryAst, SelectQueryAst { + DatasetClauseAst datasetClause(); GroupGraphPatternAst whereClause(); } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/SelectQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/SelectQueryAst.java index fa811fd74..6f8fc3cb7 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/SelectQueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/SelectQueryAst.java @@ -6,22 +6,25 @@ * Abstract Syntax Tree (AST) representation of a SPARQL {@code SELECT} query. * Holds the projection (SELECT * or SELECT ?v1 ?v2 ...) and the WHERE clause. */ -public record SelectQueryAst(ProjectionAst projection, GroupGraphPatternAst whereClause, SolutionModifierAst solutionModifier) implements QueryAst { +public record SelectQueryAst(ProjectionAst projection, DatasetClauseAst datasetClause, GroupGraphPatternAst whereClause, SolutionModifierAst solutionModifier) implements QueryAst { /** Constructor with default projection SELECT *. */ public SelectQueryAst(GroupGraphPatternAst whereClause) { - this(ProjectionAsts.selectAll(), whereClause); + this(ProjectionAsts.selectAll(), DatasetClauseAst.none(), whereClause); } /** Constructor with default solution modifier (no DISTINCT/REDUCED/ORDER BY/LIMIT/OFFSET). */ - public SelectQueryAst(ProjectionAst projection, GroupGraphPatternAst whereClause) { - this(projection, whereClause, null); + public SelectQueryAst(ProjectionAst projection, DatasetClauseAst datasetClause, GroupGraphPatternAst whereClause) { + this(projection, datasetClause, whereClause, null); } public SelectQueryAst { if (projection == null) { projection = ProjectionAsts.selectAll(); } + if(datasetClause == null) { + datasetClause = DatasetClauseAst.none(); + } if (whereClause == null) { whereClause = new GroupGraphPatternAst(List.of()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/api/sparql/ast/SparqlAstTest.java b/src/test/java/fr/inria/corese/core/next/query/api/sparql/ast/SparqlAstTest.java index 53c403fb0..4eb2342c1 100644 --- a/src/test/java/fr/inria/corese/core/next/query/api/sparql/ast/SparqlAstTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/api/sparql/ast/SparqlAstTest.java @@ -426,7 +426,7 @@ void whereClauseAccessor() { GroupGraphPatternAst where = new GroupGraphPatternAst(List.of( new BgpAst(List.of(new TriplePatternAst( new VarAst("s"), new VarAst("p"), new VarAst("o")))))); - QueryAst q = new AskQueryAst(where); + QueryAst q = new AskQueryAst(DatasetClauseAst.none(), where); assertSame(where, q.whereClause()); } @@ -434,8 +434,8 @@ void whereClauseAccessor() { @DisplayName("AskQueryAst record equality when same whereClause") void askQueryAstEquality() { GroupGraphPatternAst where = new GroupGraphPatternAst(List.of()); - AskQueryAst a = new AskQueryAst(where); - AskQueryAst b = new AskQueryAst(where); + AskQueryAst a = new AskQueryAst(DatasetClauseAst.none(), where); + AskQueryAst b = new AskQueryAst(DatasetClauseAst.none(), where); assertEquals(a, b); assertEquals(a.hashCode(), b.hashCode()); } @@ -443,7 +443,7 @@ void askQueryAstEquality() { @Test @DisplayName("AskQueryAst with null whereClause uses empty group") void nullWhereClauseDefaultsToEmpty() { - AskQueryAst q = new AskQueryAst(null); + AskQueryAst q = new AskQueryAst(DatasetClauseAst.none(), null); assertNotNull(q.whereClause()); assertTrue(q.whereClause().patterns().isEmpty()); } @@ -451,7 +451,7 @@ void nullWhereClauseDefaultsToEmpty() { @Test @DisplayName("AskQueryAst implements QueryAst") void implementsQueryAst() { - assertInstanceOf(QueryAst.class, new AskQueryAst(new GroupGraphPatternAst(List.of()))); + assertInstanceOf(QueryAst.class, new AskQueryAst(DatasetClauseAst.none(), new GroupGraphPatternAst(List.of()))); } } From c32d0ba6055eeb81a90571bc223a0d8ccfefe5e8 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 19 Mar 2026 15:59:34 +0100 Subject: [PATCH 2/4] UTs --- .../query/impl/parser/SparqlAstBuilder.java | 2 +- .../impl/sparql/ast/ConstructQueryAst.java | 5 +- .../impl/sparql/ast/DescribeQueryAst.java | 5 +- .../parser/SparqlAstBuilderDescribeTest.java | 4 +- .../parser/SparqlParserSelectQueryTest.java | 122 ++++++++++++++++++ 5 files changed, 133 insertions(+), 5 deletions(-) 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 0b09862d5..df077aa58 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 @@ -292,7 +292,7 @@ public QueryAst getResult() { throw new IllegalStateException("No WHERE clause: did you call exitGroup() for the top-level GroupGraphPattern?"); } return switch (this.queryType) { - case DESCRIBE -> new DescribeQueryAst(describeResources, datasets, whereClause); + case DESCRIBE -> new DescribeQueryAst(datasets, describeResources, whereClause); case CONSTRUCT -> null; case ASK -> new AskQueryAst(datasets, whereClause); case SELECT -> new SelectQueryAst(projection, datasets, whereClause, buildSolutionModifier()); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/ConstructQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/ConstructQueryAst.java index 20045a0e6..4f95a336f 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/ConstructQueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/ConstructQueryAst.java @@ -30,7 +30,7 @@ * triples are added to the output graph. *

*/ -public record ConstructQueryAst(GroupGraphPatternAst constructTemplate, GroupGraphPatternAst whereClause) implements QueryAst { +public record ConstructQueryAst(DatasetClauseAst datasetClause, GroupGraphPatternAst constructTemplate, GroupGraphPatternAst whereClause) implements QueryAst { public ConstructQueryAst { if (constructTemplate == null) { constructTemplate = new GroupGraphPatternAst(List.of()); @@ -38,5 +38,8 @@ public record ConstructQueryAst(GroupGraphPatternAst constructTemplate, GroupGra if (whereClause == null) { whereClause = new GroupGraphPatternAst(List.of()); } + if (datasetClause == null) { + datasetClause = DatasetClauseAst.none(); + } } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java index c2cdefbc7..e70fa98e3 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DescribeQueryAst.java @@ -21,12 +21,15 @@ * } * } */ -public record DescribeQueryAst(List described, GroupGraphPatternAst whereClause) implements QueryAst { +public record DescribeQueryAst(DatasetClauseAst datasetClause, List described, GroupGraphPatternAst whereClause) implements QueryAst { public DescribeQueryAst { described = described != null ? List.copyOf(described) : List.of(); if (whereClause == null) { whereClause = new GroupGraphPatternAst(List.of()); } + if(datasetClause == null) { + datasetClause = DatasetClauseAst.none(); + } } /** diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilderDescribeTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilderDescribeTest.java index d5508821b..ca05ef10c 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilderDescribeTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlAstBuilderDescribeTest.java @@ -203,7 +203,7 @@ class NullSafety { @Test @DisplayName("null described list defaults to empty list") void nullDescribedDefaultsToEmpty() { - DescribeQueryAst ast = new DescribeQueryAst(null, null); + DescribeQueryAst ast = new DescribeQueryAst(null,null, null); assertNotNull(ast.described()); assertTrue(ast.described().isEmpty()); } @@ -211,7 +211,7 @@ void nullDescribedDefaultsToEmpty() { @Test @DisplayName("null whereClause defaults to empty GroupGraphPatternAst") void nullWhereClauseDefaultsToEmptyGroup() { - DescribeQueryAst ast = new DescribeQueryAst(null, null); + DescribeQueryAst ast = new DescribeQueryAst(null,null, null); assertNotNull(ast.whereClause()); assertTrue(ast.whereClause().patterns().isEmpty()); } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java index 722a82837..c24e7519e 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java @@ -712,4 +712,126 @@ public void shouldIgnoreCommentInTheMiddleOfQueryWithIRI() { assertNotNull(selectQueryAst.solutionModifier()); assertEquals(10, selectQueryAst.solutionModifier().limit()); } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + SELECT DISTINCT ?c ?o + FROM + WHERE { + ?s a ?c ; + ?o . + } LIMIT 10 + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(1, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + SELECT DISTINCT ?c ?o + FROM + FROM + FROM + WHERE { + ?s a ?c ; + ?o . + } LIMIT 10 + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(3, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[2]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + SELECT DISTINCT ?c ?o + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } LIMIT 10 + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + SELECT DISTINCT ?c ?o + FROM NAMED + FROM NAMED + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } LIMIT 10 + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(3, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[2]); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleMixedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + SELECT DISTINCT ?c ?o + FROM + FROM NAMED + FROM + WHERE { + ?s a ?c ; + ?o . + } LIMIT 10 + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(3, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + } } \ No newline at end of file From 042a16e9096cd009254d9590a57a1a323fcda90f Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Thu, 19 Mar 2026 16:54:25 +0100 Subject: [PATCH 3/4] DON'T FORGET TO ADD YOUR FEATURE TO THE LISTENER --- .../query/impl/parser/SparqlListener.java | 10 ++ .../next/query/impl/parser/SparqlParser.java | 3 +- .../parser/listener/DatasetClauseFeature.java | 21 +++ .../impl/parser/SparqlParserAskQueryTest.java | 122 +++++++++++++++++ .../impl/parser/SparqlParserDescribeTest.java | 124 +++++++++++++++++- .../parser/SparqlParserSelectQueryTest.java | 6 +- 6 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DatasetClauseFeature.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListener.java index 7ed43d5e8..efc8dfdd0 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListener.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListener.java @@ -130,4 +130,14 @@ public void enterDescribeQuery(SparqlParser.DescribeQueryContext ctx) { public void exitDescribeQuery(SparqlParser.DescribeQueryContext ctx) { for (var d : delegates) d.exitDescribeQuery(ctx); } + + @Override + public void exitDefaultGraphClause(SparqlParser.DefaultGraphClauseContext ctx) { + for (var d : delegates) d.exitDefaultGraphClause(ctx); + } + + @Override + public void exitNamedGraphClause(SparqlParser.NamedGraphClauseContext ctx) { + for (var d : delegates) d.exitNamedGraphClause(ctx); + } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlParser.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlParser.java index 49816b3c2..dadbff16f 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlParser.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlParser.java @@ -106,7 +106,8 @@ public QueryAst parse(Reader reader, String baseIRI) { new SolutionModifierFeature(builder), new FilterFeature(builder), new UnionFeature(builder), - new DescribeQueryFeature(builder) + new DescribeQueryFeature(builder), + new DatasetClauseFeature(builder) )); walker.walk(listener, tree); diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DatasetClauseFeature.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DatasetClauseFeature.java new file mode 100644 index 000000000..e5b892213 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DatasetClauseFeature.java @@ -0,0 +1,21 @@ +package fr.inria.corese.core.next.query.impl.parser.listener; + +import fr.inria.corese.core.next.impl.parser.antlr.SparqlParser; +import fr.inria.corese.core.next.query.impl.parser.SparqlAstBuilder; +import fr.inria.corese.core.next.query.impl.sparql.ast.IriAst; + +public class DatasetClauseFeature extends AbstractSparqlFeature { + public DatasetClauseFeature(SparqlAstBuilder builder) { + super(builder); + } + + @Override + public void exitDefaultGraphClause(SparqlParser.DefaultGraphClauseContext ctx) { + builder().addFromGraph((IriAst) builder().termFromIriRef(ctx.sourceSelector().iriRef())); + } + + @Override + public void exitNamedGraphClause(SparqlParser.NamedGraphClauseContext ctx) { + builder().addFromNamedGraph((IriAst) builder().termFromIriRef(ctx.sourceSelector().iriRef())); + } +} diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserAskQueryTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserAskQueryTest.java index 017918fe6..0c0d641ef 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserAskQueryTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserAskQueryTest.java @@ -208,4 +208,126 @@ public void shouldIgnoreCommentInTheMiddleOfQuery() { assertInstanceOf(VarAst.class, bgpAst.triples().getLast().object()); assertEquals("o", ((VarAst)bgpAst.triples().getLast().object()).name()); } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + ASK + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(1, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + ASK + FROM + FROM + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(3, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[2]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + ASK + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + ASK + FROM NAMED + FROM NAMED + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(3, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[2]); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleMixedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + ASK + FROM + FROM NAMED + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(2, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + } } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDescribeTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDescribeTest.java index b09685929..01f071def 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDescribeTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDescribeTest.java @@ -11,7 +11,7 @@ /** * Integration tests for DESCRIBE query parsing via {@link SparqlParser}. */ -class SparqlParserDescribeTest { +class SparqlParserDescribeTest extends AbstractSparqlParserFeatureTest { private SparqlParser parser; @@ -135,4 +135,126 @@ void variableNamesAreInOrder() { assertEquals("p", ((VarAst) result.described().get(1)).name()); } } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + DESCRIBE ?s + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(1, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + DESCRIBE ?s + FROM + FROM + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(3, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[2]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(0, queryAst.datasetClause().namedGraphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + DESCRIBE ?s + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleNamedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + DESCRIBE ?s + FROM NAMED + FROM NAMED + FROM NAMED + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(3, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[1]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[2]); + assertEquals(0, queryAst.datasetClause().graphs().size()); + } + + @Test + @DisplayName("a list of From graphs can be inserted") + public void fromMultipleMixedGraphs() { + SparqlParser parser = newParserDefault(); + String commentedQuery = """ + DESCRIBE ?s + FROM + FROM NAMED + FROM + WHERE { + ?s a ?c ; + ?o . + } + """; + QueryAst queryAst = parser.parse(commentedQuery); + assertNotNull(queryAst); + assertNotNull(queryAst.datasetClause()); + assertNotNull(queryAst.datasetClause().graphs()); + assertEquals(2, queryAst.datasetClause().graphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); + assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); + assertNotNull(queryAst.datasetClause().namedGraphs()); + assertEquals(1, queryAst.datasetClause().namedGraphs().size()); + assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); + } } \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java index c24e7519e..f03b7da0c 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserSelectQueryTest.java @@ -776,7 +776,7 @@ public void fromNamedGraphs() { QueryAst queryAst = parser.parse(commentedQuery); assertNotNull(queryAst); assertNotNull(queryAst.datasetClause()); - assertNotNull(queryAst.datasetClause().graphs()); + assertNotNull(queryAst.datasetClause().namedGraphs()); assertEquals(1, queryAst.datasetClause().namedGraphs().size()); assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); assertNotNull(queryAst.datasetClause().graphs()); @@ -801,11 +801,11 @@ public void fromMultipleNamedGraphs() { assertNotNull(queryAst); assertNotNull(queryAst.datasetClause()); assertNotNull(queryAst.datasetClause().graphs()); + assertNotNull(queryAst.datasetClause().namedGraphs()); assertEquals(3, queryAst.datasetClause().namedGraphs().size()); assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[0]); assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[1]); assertInstanceOf(IriAst.class, queryAst.datasetClause().namedGraphs().toArray()[2]); - assertNotNull(queryAst.datasetClause().graphs()); assertEquals(0, queryAst.datasetClause().graphs().size()); } @@ -827,7 +827,7 @@ public void fromMultipleMixedGraphs() { assertNotNull(queryAst); assertNotNull(queryAst.datasetClause()); assertNotNull(queryAst.datasetClause().graphs()); - assertEquals(3, queryAst.datasetClause().graphs().size()); + assertEquals(2, queryAst.datasetClause().graphs().size()); assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[0]); assertInstanceOf(IriAst.class, queryAst.datasetClause().graphs().toArray()[1]); assertNotNull(queryAst.datasetClause().namedGraphs()); From 007e9e954292bf4e36be0c1a31ddd3aef274f421 Mon Sep 17 00:00:00 2001 From: Pierre Maillot Date: Fri, 20 Mar 2026 11:36:48 +0100 Subject: [PATCH 4/4] Fixing datasetClause immutability --- .../next/query/impl/parser/SparqlAstBuilder.java | 14 ++++++++------ .../next/query/impl/sparql/ast/AskQueryAst.java | 3 +++ .../query/impl/sparql/ast/DatasetClauseAst.java | 12 ++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) 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 df077aa58..28b642e82 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 @@ -67,7 +67,8 @@ public final class SparqlAstBuilder { /** * Dataset clause (FROM/FROM NAMED) */ - private DatasetClauseAst datasets = DatasetClauseAst.none(); + private final Set datasetDefaultGraphs = new LinkedHashSet<>(); + private final Set datasetNamedGraphs = new LinkedHashSet<>(); /** SELECT DISTINCT / REDUCED. Set by SelectQueryFeature when parsing SELECT (DISTINCT | REDUCED)? ... */ private boolean distinct; @@ -164,11 +165,11 @@ public void setReduced(boolean reduced) { } public void addFromGraph(IriAst graph) { - this.datasets.graphs().add(graph); + this.datasetDefaultGraphs.add(graph); } public void addFromNamedGraph(IriAst graph) { - this.datasets.namedGraphs().add(graph); + this.datasetNamedGraphs.add(graph); } /** @@ -291,11 +292,12 @@ public QueryAst getResult() { if (whereClause == null) { throw new IllegalStateException("No WHERE clause: did you call exitGroup() for the top-level GroupGraphPattern?"); } + DatasetClauseAst datasetClauseAst = new DatasetClauseAst(datasetDefaultGraphs, datasetNamedGraphs); return switch (this.queryType) { - case DESCRIBE -> new DescribeQueryAst(datasets, describeResources, whereClause); + case DESCRIBE -> new DescribeQueryAst(datasetClauseAst, describeResources, whereClause); case CONSTRUCT -> null; - case ASK -> new AskQueryAst(datasets, whereClause); - case SELECT -> new SelectQueryAst(projection, datasets, whereClause, buildSolutionModifier()); + case ASK -> new AskQueryAst(datasetClauseAst, whereClause); + case SELECT -> new SelectQueryAst(projection, datasetClauseAst, whereClause, buildSolutionModifier()); case UNDEFINED -> throw new QueryEvaluationException("Could not determine the type of query during parsing"); }; } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java index 243246219..f7f51ed00 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AskQueryAst.java @@ -20,5 +20,8 @@ public record AskQueryAst(DatasetClauseAst datasetClause, GroupGraphPatternAst w if (whereClause == null) { whereClause = new GroupGraphPatternAst(List.of()); } + if(datasetClause == null) { + datasetClause = DatasetClauseAst.none(); + } } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java index 150a118a0..10571fd68 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DatasetClauseAst.java @@ -1,20 +1,16 @@ package fr.inria.corese.core.next.query.impl.sparql.ast; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; public record DatasetClauseAst(Set graphs, Set namedGraphs) { public DatasetClauseAst { - if (graphs == null) { - graphs = new HashSet<>(); - } - if (namedGraphs == null) { - namedGraphs = new HashSet<>(); - } + graphs = graphs == null ? Set.of() : Set.copyOf(new LinkedHashSet<>(graphs)); + namedGraphs = namedGraphs == null ? Set.of() : Set.copyOf(new LinkedHashSet<>(namedGraphs)); } public static DatasetClauseAst none() { - return new DatasetClauseAst(new HashSet<>(), new HashSet<>()); + return new DatasetClauseAst(Set.of(), Set.of()); } }