From 6c6e0434f7f228251c5cd1d26aceb8e0726a7f68 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Wed, 22 Jul 2026 08:54:24 +0200 Subject: [PATCH 1/5] [SPARQL 1.1 Graph Management] - Parser and AST : ADD Query --- .../impl/parser/SparqlListenerDispatcher.java | 10 ++++ .../impl/parser/SparqlQueryAnalyzer.java | 1 + .../impl/parser/SparqlUpdateAstBuilder.java | 7 +++ .../parser/listener/AddUpdateAstListener.java | 20 ++++++++ .../query/impl/sparql/ast/AddRequestAst.java | 27 ++++++++++ .../impl/sparql/ast/UpdateRequestUnitAst.java | 2 +- .../impl/parser/AddUpdateAstBuilderTest.java | 49 +++++++++++++++++++ 7 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java create mode 100644 src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java create mode 100644 src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListenerDispatcher.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListenerDispatcher.java index 8849fb3fb..b325eeb14 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListenerDispatcher.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlListenerDispatcher.java @@ -312,4 +312,14 @@ public void enterDrop(SparqlParser.DropContext ctx) { public void exitDrop(SparqlParser.DropContext ctx) { for (var d : delegates) d.exitDrop(ctx); } + + @Override + public void enterAdd(SparqlParser.AddContext ctx) { + for (var d : delegates) d.enterAdd(ctx); + } + + @Override + public void exitAdd(SparqlParser.AddContext ctx) { + for (var d : delegates) d.exitAdd(ctx); + } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlQueryAnalyzer.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlQueryAnalyzer.java index a4c845f75..99b6a1fff 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlQueryAnalyzer.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlQueryAnalyzer.java @@ -162,6 +162,7 @@ private QueryAst buildAst( new CreateUpdateAstListener(updateBuilder), new DropUpdateAstListener(updateBuilder), new LoadUpdateAstListener(updateBuilder), + new AddUpdateAstListener(updateBuilder), new BgpAstListener(updateBuilder), new BindAstListener(updateBuilder), new FilterAstListener(updateBuilder), diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java index 4a77a2031..2a7a42441 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java @@ -83,4 +83,11 @@ public CreateRequestAst createToAst(SparqlParser.CreateContext ctx) { public void addRequest(UpdateRequestUnitAst ast) { this.updateRequestAst.add(ast); } + + public AddRequestAst addtoAst(SparqlParser.AddContext ctx) { + return new AddRequestAst( + graphRefFromGraphOrDefault(ctx.graphOrDefault(0)), + graphRefFromGraphOrDefault(ctx.graphOrDefault(1)), + ctx.SILENT() != null); + } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java new file mode 100644 index 000000000..2da3561af --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java @@ -0,0 +1,20 @@ +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.SparqlUpdateAstBuilder; +import fr.inria.corese.core.next.query.impl.sparql.ast.AddRequestAst; + +/** + * AST feature listener for ADD SPARQL update query. + */ +public class AddUpdateAstListener extends AbstractSparqlUpdateAstListener { + public AddUpdateAstListener(SparqlUpdateAstBuilder builder) { + super(builder); + } + + @Override + public void enterAdd(SparqlParser.AddContext ctx) { + AddRequestAst ast = this.updateBuilder().addtoAst(ctx); + this.updateBuilder().addRequest(ast); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java new file mode 100644 index 000000000..967d35677 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java @@ -0,0 +1,27 @@ +package fr.inria.corese.core.next.query.impl.sparql.ast; + +import fr.inria.corese.core.next.query.impl.parser.semantic.support.AstVisitor; + +/** + * Represents the ADD operation as defined in the SPARQL 1.1 recommendation. + * @param source source graph ({@code GRAPH } or {@code DEFAULT}) + * @param destination destination graph ({@code GRAPH } or {@code DEFAULT}) + * @param silent Determine if the resolution of the query must be resolved silently or not. + */ +public record AddRequestAst(GraphRefAst source, GraphRefAst destination, boolean silent) implements UpdateRequestUnitAst { + /** + * Construct a ADD query with a silent flag to false. + * @param source source graph + * @param destination destination graph + */ + public AddRequestAst(GraphRefAst source, GraphRefAst destination) { + this(source, destination, false); + } + + @Override + public void accept(AstVisitor visitor) { + visitor.visit(this); + this.source.accept(visitor); + this.destination.accept(visitor); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/UpdateRequestUnitAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/UpdateRequestUnitAst.java index 52dc6ca05..770afd6a7 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/UpdateRequestUnitAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/UpdateRequestUnitAst.java @@ -5,5 +5,5 @@ /** * Root interface for all operations related to the SPARQL Update operations listed in SPARQL 1.1 Update. */ -public sealed interface UpdateRequestUnitAst extends VisitableAst permits LoadRequestAst, ClearRequestAst, CreateRequestAst, DropRequestAst { +public sealed interface UpdateRequestUnitAst extends VisitableAst permits LoadRequestAst, ClearRequestAst, CreateRequestAst, DropRequestAst, AddRequestAst { } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java new file mode 100644 index 000000000..2009a3233 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java @@ -0,0 +1,49 @@ +package fr.inria.corese.core.next.query.impl.parser; + +import fr.inria.corese.core.next.query.impl.sparql.ast.AddRequestAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.UpdateRequestAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.UpdateRequestUnitAst; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AddUpdateAstBuilderTest { + + private UpdateRequestUnitAst parseUnit(String query) { + QueryAst ast = new SparqlParser().parse(query); + UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); + assertEquals(1, update.operations().size(), "one update operation"); + return update.operations().get(0); + } + + @Test + @DisplayName("ADD GRAPH TO GRAPH : source and destination named graphs, not silent") + void addGraphToGraph() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD GRAPH TO GRAPH ")); + assertFalse(add.silent()); + assertNotNull(add.source().graph()); + assertNotNull(add.destination().graph()); + } + + @Test + @DisplayName("ADD SILENT DEFAULT TO GRAPH : silent, source is DEFAULT") + void addSilentDefaultToGraph() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD SILENT DEFAULT TO GRAPH ")); + assertTrue(add.silent()); + assertTrue(add.source().defaultGraph()); + assertNotNull(add.destination().graph()); + } + + @Test + @DisplayName("ADD GRAPH TO DEFAULT: destination is DEFAULT") + void addGraphToDefault() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD GRAPH TO DEFAULT")); + assertNotNull(add.source().graph()); + assertTrue(add.destination().defaultGraph()); + } +} \ No newline at end of file From 8be1707b6f9f4a263b9961f90e15cb1aa160ffcd Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Wed, 22 Jul 2026 08:55:20 +0200 Subject: [PATCH 2/5] [SPARQL 1.1 Graph Management] - Parser and AST : ADD Query --- .../core/next/query/impl/parser/AddUpdateAstBuilderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java index 2009a3233..61a1442f5 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java @@ -15,7 +15,7 @@ private UpdateRequestUnitAst parseUnit(String query) { QueryAst ast = new SparqlParser().parse(query); UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); assertEquals(1, update.operations().size(), "one update operation"); - return update.operations().get(0); + return update.operations().getFirst(); } @Test From c441e7858c301fdd70888374451251193ed56af8 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Wed, 22 Jul 2026 14:38:48 +0200 Subject: [PATCH 3/5] [SPARQL 1.1 Graph Management] - Parser and AST : ADD Query --- .../impl/parser/AddUpdateAstBuilderTest.java | 82 +++++++++++++++++-- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java index 61a1442f5..2c6b1024f 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java @@ -1,5 +1,6 @@ package fr.inria.corese.core.next.query.impl.parser; +import fr.inria.corese.core.next.query.api.exception.QuerySyntaxException; import fr.inria.corese.core.next.query.impl.sparql.ast.AddRequestAst; import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst; import fr.inria.corese.core.next.query.impl.sparql.ast.UpdateRequestAst; @@ -9,10 +10,10 @@ import static org.junit.jupiter.api.Assertions.*; -class AddUpdateAstBuilderTest { +class AddUpdateAstBuilderTest extends AbstractSparqlParserFeatureTest { private UpdateRequestUnitAst parseUnit(String query) { - QueryAst ast = new SparqlParser().parse(query); + QueryAst ast = newParserDefault().parse(query); UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); assertEquals(1, update.operations().size(), "one update operation"); return update.operations().getFirst(); @@ -24,8 +25,8 @@ void addGraphToGraph() { AddRequestAst add = assertInstanceOf(AddRequestAst.class, parseUnit("ADD GRAPH TO GRAPH ")); assertFalse(add.silent()); - assertNotNull(add.source().graph()); - assertNotNull(add.destination().graph()); + assertEquals("", add.source().graph().raw()); + assertEquals("", add.destination().graph().raw()); } @Test @@ -35,7 +36,7 @@ void addSilentDefaultToGraph() { parseUnit("ADD SILENT DEFAULT TO GRAPH ")); assertTrue(add.silent()); assertTrue(add.source().defaultGraph()); - assertNotNull(add.destination().graph()); + assertEquals("", add.destination().graph().raw()); } @Test @@ -43,7 +44,74 @@ void addSilentDefaultToGraph() { void addGraphToDefault() { AddRequestAst add = assertInstanceOf(AddRequestAst.class, parseUnit("ADD GRAPH TO DEFAULT")); - assertNotNull(add.source().graph()); + assertFalse(add.silent()); + assertEquals("", add.source().graph().raw()); + assertTrue(add.destination().defaultGraph()); + } + + @Test + @DisplayName("ADD DEFAULT TO DEFAULT: both source and destination are DEFAULT") + void addDefaultToDefault() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD DEFAULT TO DEFAULT")); + assertFalse(add.silent()); + assertTrue(add.source().defaultGraph()); assertTrue(add.destination().defaultGraph()); } -} \ No newline at end of file + + @Test + @DisplayName("ADD SILENT DEFAULT TO DEFAULT: silent with both DEFAULT") + void addSilentDefaultToDefault() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD SILENT DEFAULT TO DEFAULT")); + assertTrue(add.silent()); + assertTrue(add.source().defaultGraph()); + assertTrue(add.destination().defaultGraph()); + } + + @Test + @DisplayName("chained ADD operations separated by ';' produce two update operations") + void chainedAddProducesTwoOperations() { + QueryAst ast = newParserDefault().parse( + "ADD GRAPH TO GRAPH ; " + + "ADD SILENT DEFAULT TO GRAPH "); + UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); + assertEquals(2, update.operations().size()); + + AddRequestAst first = assertInstanceOf(AddRequestAst.class, update.operations().get(0)); + assertEquals("", first.source().graph().raw()); + assertEquals("", first.destination().graph().raw()); + assertFalse(first.silent()); + + AddRequestAst second = assertInstanceOf(AddRequestAst.class, update.operations().get(1)); + assertTrue(second.source().defaultGraph()); + assertEquals("", second.destination().graph().raw()); + assertTrue(second.silent()); + } + + @Test + @DisplayName("PREFIX declaration is preserved in the prologue of an ADD query") + void addMustKeepItsPrologue() { + QueryAst ast = newParserDefault().parse( + "PREFIX ex: ADD GRAPH ex:s TO GRAPH ex:d"); + UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); + assertEquals(1, update.operations().size()); + assertInstanceOf(AddRequestAst.class, update.operations().getFirst()); + assertNotNull(update.prologue()); + assertEquals(1, update.prologue().prefixDeclarations().size()); + } + + @Test + @DisplayName("ADD without TO keyword should throw a syntax exception") + void addMissingToShouldFail() { + assertThrows(QuerySyntaxException.class, + () -> newParserDefault().parse("ADD GRAPH ")); + } + + @Test + @DisplayName("ADD without source should throw a syntax exception") + void addMissingSourceShouldFail() { + assertThrows(QuerySyntaxException.class, + () -> newParserDefault().parse("ADD TO GRAPH ")); + } +} From 47841f716e86d59839ee2fe42eba8dadfa54a5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Wed, 22 Jul 2026 15:36:56 +0200 Subject: [PATCH 4/5] style(query): align ADD AST builder naming --- .../core/next/query/impl/parser/SparqlUpdateAstBuilder.java | 2 +- .../next/query/impl/parser/listener/AddUpdateAstListener.java | 2 +- .../corese/core/next/query/impl/sparql/ast/AddRequestAst.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java index 2a7a42441..f3476dfa2 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/SparqlUpdateAstBuilder.java @@ -84,7 +84,7 @@ public void addRequest(UpdateRequestUnitAst ast) { this.updateRequestAst.add(ast); } - public AddRequestAst addtoAst(SparqlParser.AddContext ctx) { + public AddRequestAst addToAst(SparqlParser.AddContext ctx) { return new AddRequestAst( graphRefFromGraphOrDefault(ctx.graphOrDefault(0)), graphRefFromGraphOrDefault(ctx.graphOrDefault(1)), diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java index 2da3561af..1fbe132e9 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/AddUpdateAstListener.java @@ -14,7 +14,7 @@ public AddUpdateAstListener(SparqlUpdateAstBuilder builder) { @Override public void enterAdd(SparqlParser.AddContext ctx) { - AddRequestAst ast = this.updateBuilder().addtoAst(ctx); + AddRequestAst ast = this.updateBuilder().addToAst(ctx); this.updateBuilder().addRequest(ast); } } diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java index 967d35677..8e794130e 100644 --- a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/AddRequestAst.java @@ -10,7 +10,7 @@ */ public record AddRequestAst(GraphRefAst source, GraphRefAst destination, boolean silent) implements UpdateRequestUnitAst { /** - * Construct a ADD query with a silent flag to false. + * Construct an ADD query with the silent flag set to false. * @param source source graph * @param destination destination graph */ From c07140b7e55de9d496db6450aeb548d375e0bbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20C=C3=A9r=C3=A8s?= Date: Wed, 22 Jul 2026 15:37:44 +0200 Subject: [PATCH 5/5] test(query): cover ADD graph IRI shorthand --- .../next/query/impl/parser/AddUpdateAstBuilderTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java index 2c6b1024f..b962c8ebd 100644 --- a/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/AddUpdateAstBuilderTest.java @@ -29,6 +29,15 @@ void addGraphToGraph() { assertEquals("", add.destination().graph().raw()); } + @Test + @DisplayName("ADD accepts the SPARQL shorthand graph IRI form without GRAPH") + void addAcceptsGraphIriShorthand() { + AddRequestAst add = assertInstanceOf(AddRequestAst.class, + parseUnit("ADD TO ")); + assertEquals("", add.source().graph().raw()); + assertEquals("", add.destination().graph().raw()); + } + @Test @DisplayName("ADD SILENT DEFAULT TO GRAPH : silent, source is DEFAULT") void addSilentDefaultToGraph() {