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());
}
}