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 327b66be5..8849fb3fb 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 @@ -302,4 +302,14 @@ public void enterCreate(SparqlParser.CreateContext ctx) { public void exitCreate(SparqlParser.CreateContext ctx) { for (var d : delegates) d.exitCreate(ctx); } + + @Override + public void enterDrop(SparqlParser.DropContext ctx) { + for (var d : delegates) d.enterDrop(ctx); + } + + @Override + public void exitDrop(SparqlParser.DropContext ctx) { + for (var d : delegates) d.exitDrop(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 372982ca7..a4c845f75 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 @@ -160,6 +160,7 @@ private QueryAst buildAst( SparqlListenerDispatcher updateListener = new SparqlListenerDispatcher(List.of( new ClearUpdateAstListener(updateBuilder), new CreateUpdateAstListener(updateBuilder), + new DropUpdateAstListener(updateBuilder), new LoadUpdateAstListener(updateBuilder), new BgpAstListener(updateBuilder), new BindAstListener(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 ac4270542..4a77a2031 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 @@ -58,6 +58,19 @@ public ClearRequestAst cleartoAst(SparqlParser.ClearContext ctx) { } } + public DropRequestAst dropToAst(SparqlParser.DropContext ctx) { + GraphRefAst targetGraphRef = null; + boolean silentFlag = ctx.SILENT() != null; + if (ctx.graphRefAll() != null) { + targetGraphRef = this.graphRefFromGraphRefAll(ctx.graphRefAll()); + } + if (targetGraphRef != null) { + return new DropRequestAst(targetGraphRef, silentFlag); + } else { + throw new QueryEvaluationException("No target graph reference found in DROP query"); + } + } + public CreateRequestAst createToAst(SparqlParser.CreateContext ctx) { boolean silentFlag = ctx.SILENT() != null; if (ctx.graphRef() != null) { diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DropUpdateAstListener.java b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DropUpdateAstListener.java new file mode 100644 index 000000000..e12b2bf8e --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/parser/listener/DropUpdateAstListener.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.DropRequestAst; + +/** + * AST feature listener for DROP SPARQL update query + */ +public class DropUpdateAstListener extends AbstractSparqlUpdateAstListener { + public DropUpdateAstListener(SparqlUpdateAstBuilder builder) { + super(builder); + } + + @Override + public void enterDrop(SparqlParser.DropContext ctx) { + DropRequestAst dropRequestAst = this.updateBuilder().dropToAst(ctx); + this.updateBuilder().addRequest(dropRequestAst); + } +} diff --git a/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DropRequestAst.java b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DropRequestAst.java new file mode 100644 index 000000000..bf071d511 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/next/query/impl/sparql/ast/DropRequestAst.java @@ -0,0 +1,24 @@ +package fr.inria.corese.core.next.query.impl.sparql.ast; + +import fr.inria.corese.core.next.query.impl.parser.semantic.support.AstVisitor; + +/** + * Represents the DROP operation as defined in the SPARQL 1.1 recommendation. + * @param graphRef targeted graph to be dropped + * @param silent Determine if the resolution of the query must be resolved silently or not. + */ +public record DropRequestAst(GraphRefAst graphRef, boolean silent) implements UpdateRequestUnitAst { + /** + * Construct DROP query with a silent flag to false. + * @param graphRef targeted graph to be dropped + */ + public DropRequestAst(GraphRefAst graphRef) { + this(graphRef, false); + } + + @Override + public void accept(AstVisitor visitor) { + visitor.visit(this); + this.graphRef.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 e68694223..52dc6ca05 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 { +public sealed interface UpdateRequestUnitAst extends VisitableAst permits LoadRequestAst, ClearRequestAst, CreateRequestAst, DropRequestAst { } diff --git a/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDropQueryTest.java b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDropQueryTest.java new file mode 100644 index 000000000..267334da7 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/next/query/impl/parser/SparqlParserDropQueryTest.java @@ -0,0 +1,215 @@ +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.parser.QueryParser; +import fr.inria.corese.core.next.query.impl.sparql.ast.DropRequestAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.IriAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.QueryAst; +import fr.inria.corese.core.next.query.impl.sparql.ast.UpdateRequestAst; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class SparqlParserDropQueryTest extends AbstractSparqlParserFeatureTest { + + @Test + @DisplayName("should parse DROP GRAPH ") + void graphQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP GRAPH + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + IriAst graphIri = dropQueryAst.graphRef().graph(); + assertEquals("", graphIri.raw()); + assertFalse(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP SILENT GRAPH and set the silent flag") + void graphSilentQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP SILENT GRAPH + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + IriAst graphIri = dropQueryAst.graphRef().graph(); + assertEquals("", graphIri.raw()); + assertTrue(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP NAMED") + void namedQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP NAMED + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().named()); + assertFalse(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP SILENT NAMED and set the silent flag") + void namedSilentQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP SILENT NAMED + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().named()); + assertTrue(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP DEFAULT") + void defaultQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP DEFAULT + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().defaultGraph()); + assertFalse(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP SILENT DEFAULT and set the silent flag") + void defaultSilentQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP SILENT DEFAULT + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().defaultGraph()); + assertTrue(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP ALL") + void allQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP ALL + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().all()); + assertFalse(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse DROP SILENT ALL and set the silent flag") + void allSilentQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP SILENT ALL + """; + + QueryAst queryAst = parser.parse(query); + assertInstanceOf(UpdateRequestAst.class, queryAst); + UpdateRequestAst updateRequestAst = (UpdateRequestAst) queryAst; + assertEquals(1, updateRequestAst.operations().size()); + assertInstanceOf(DropRequestAst.class, updateRequestAst.operations().getFirst()); + DropRequestAst dropQueryAst = (DropRequestAst) updateRequestAst.operations().getFirst(); + assertNotNull(dropQueryAst.graphRef()); + assertTrue(dropQueryAst.graphRef().all()); + assertTrue(dropQueryAst.silent()); + } + + @Test + @DisplayName("should parse two chained DROP operations separated by ';'") + void chainedDropShouldCreateTwoUpdateOperations() { + QueryParser parser = newParserDefault(); + String query = """ + DROP GRAPH ; + DROP SILENT GRAPH + """; + + QueryAst queryAst = parser.parse(query); + UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, queryAst); + assertEquals(2, update.operations().size()); + + DropRequestAst first = assertInstanceOf(DropRequestAst.class, update.operations().getFirst()); + assertEquals("", first.graphRef().graph().raw()); + assertFalse(first.silent()); + + DropRequestAst second = assertInstanceOf(DropRequestAst.class, update.operations().get(1)); + assertEquals("", second.graphRef().graph().raw()); + assertTrue(second.silent()); + } + + @Test + @DisplayName("should keep the prologue when DROP uses a prefixed graph name") + void dropMustKeepItsPrologue() { + QueryParser parser = newParserDefault(); + QueryAst ast = parser.parse("PREFIX ex: DROP GRAPH ex:g"); + UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast); + + assertEquals(1, update.operations().size()); + assertInstanceOf(DropRequestAst.class, update.operations().getFirst()); + assertNotNull(update.prologue()); + assertEquals(1, update.prologue().prefixDeclarations().size()); + } + + @Test + @DisplayName("should reject invalid DROP syntax with multiple graph references") + void failingTooManyQueryTest() { + QueryParser parser = newParserDefault(); + String query = """ + DROP SILENT ALL NAMED GRAPH + """; + + assertThrows(QuerySyntaxException.class, () -> parser.parse(query)); + } +}