Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,14 @@ public void enterAdd(SparqlParser.AddContext ctx) {
public void exitAdd(SparqlParser.AddContext ctx) {
for (var d : delegates) d.exitAdd(ctx);
}

@Override
public void enterCopy(SparqlParser.CopyContext ctx) {
for (var d : delegates) d.enterCopy(ctx);
}

@Override
public void exitCopy(SparqlParser.CopyContext ctx) {
for (var d : delegates) d.exitCopy(ctx);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ private QueryAst buildAst(
new DropUpdateAstListener(updateBuilder),
new LoadUpdateAstListener(updateBuilder),
new AddUpdateAstListener(updateBuilder),
new CopyUpdateAstListener(updateBuilder),
new BgpAstListener(updateBuilder),
new BindAstListener(updateBuilder),
new FilterAstListener(updateBuilder),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,11 @@ public AddRequestAst addToAst(SparqlParser.AddContext ctx) {
graphRefFromGraphOrDefault(ctx.graphOrDefault(1)),
ctx.SILENT() != null);
}

public CopyRequestAst copyToAst(SparqlParser.CopyContext ctx) {
return new CopyRequestAst(
graphRefFromGraphOrDefault(ctx.graphOrDefault(0)),
graphRefFromGraphOrDefault(ctx.graphOrDefault(1)),
ctx.SILENT() != null);
}
}
Original file line number Diff line number Diff line change
@@ -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.CopyRequestAst;

/**
* AST feature listener for COPY SPARQL update query.
*/
public class CopyUpdateAstListener extends AbstractSparqlUpdateAstListener {
public CopyUpdateAstListener(SparqlUpdateAstBuilder builder) {
super(builder);
}

@Override
public void enterCopy(SparqlParser.CopyContext ctx) {
CopyRequestAst ast = this.updateBuilder().copyToAst(ctx);
this.updateBuilder().addRequest(ast);
}
}
Original file line number Diff line number Diff line change
@@ -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 COPY operation as defined in the <a href="https://www.w3.org/TR/2013/REC-sparql11-update-20130321/#copy">SPARQL 1.1 recommendation</a>.
* @param source source graph ({@code GRAPH <iri>} or {@code DEFAULT})
* @param destination destination graph ({@code GRAPH <iri>} or {@code DEFAULT})
* @param silent Determine if the resolution of the query must be resolved silently or not.
*/
public record CopyRequestAst(GraphRefAst source, GraphRefAst destination, boolean silent) implements UpdateRequestUnitAst {
/**
* Construct a COPY query with a silent flag to false.
* @param source source graph
* @param destination destination graph
*/
public CopyRequestAst(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
/**
* Root interface for all operations related to the SPARQL Update operations listed in <a href="https://www.w3.org/TR/sparql11-update/">SPARQL 1.1 Update</a>.
*/
public sealed interface UpdateRequestUnitAst extends VisitableAst permits LoadRequestAst, ClearRequestAst, CreateRequestAst, DropRequestAst, AddRequestAst {
public sealed interface UpdateRequestUnitAst extends VisitableAst permits LoadRequestAst, ClearRequestAst, CreateRequestAst, DropRequestAst, AddRequestAst, CopyRequestAst {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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.CopyRequestAst;
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 SparqlParserCopyQueryTest extends AbstractSparqlParserFeatureTest {

private UpdateRequestUnitAst parseUnit(String query) {
QueryAst ast = newParserDefault().parse(query);
UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast);
assertEquals(1, update.operations().size(), "one update operation");
return update.operations().getFirst();
}

@Test
@DisplayName("COPY GRAPH <s> TO GRAPH <d>: source and destination named graphs, not silent")
void copyGraphToGraph() {
CopyRequestAst copy = assertInstanceOf(CopyRequestAst.class,
parseUnit("COPY GRAPH <http://example.org/s> TO GRAPH <http://example.org/d>"));
assertFalse(copy.silent());
assertEquals("<http://example.org/s>", copy.source().graph().raw());
assertEquals("<http://example.org/d>", copy.destination().graph().raw());
}

@Test
@DisplayName("COPY SILENT DEFAULT TO GRAPH <d>: silent, source is DEFAULT")
void copySilentDefaultToGraph() {
CopyRequestAst copy = assertInstanceOf(CopyRequestAst.class,
parseUnit("COPY SILENT DEFAULT TO GRAPH <http://example.org/d>"));
assertTrue(copy.silent());
assertTrue(copy.source().defaultGraph());
assertEquals("<http://example.org/d>", copy.destination().graph().raw());
}

@Test
@DisplayName("COPY GRAPH <s> TO DEFAULT: destination is DEFAULT")
void copyGraphToDefault() {
CopyRequestAst copy = assertInstanceOf(CopyRequestAst.class,
parseUnit("COPY GRAPH <http://example.org/s> TO DEFAULT"));
assertFalse(copy.silent());
assertEquals("<http://example.org/s>", copy.source().graph().raw());
assertTrue(copy.destination().defaultGraph());
}

@Test
@DisplayName("COPY DEFAULT TO DEFAULT: both source and destination are DEFAULT")
void copyDefaultToDefault() {
CopyRequestAst copy = assertInstanceOf(CopyRequestAst.class,
parseUnit("COPY DEFAULT TO DEFAULT"));
assertFalse(copy.silent());
assertTrue(copy.source().defaultGraph());
assertTrue(copy.destination().defaultGraph());
}

@Test
@DisplayName("COPY SILENT DEFAULT TO DEFAULT: silent with both DEFAULT")
void copySilentDefaultToDefault() {
CopyRequestAst copy = assertInstanceOf(CopyRequestAst.class,
parseUnit("COPY SILENT DEFAULT TO DEFAULT"));
assertTrue(copy.silent());
assertTrue(copy.source().defaultGraph());
assertTrue(copy.destination().defaultGraph());
}

@Test
@DisplayName("chained COPY operations separated by ';' produce two update operations")
void chainedCopyProducesTwoOperations() {
QueryAst ast = newParserDefault().parse(
"COPY GRAPH <http://example.org/s> TO GRAPH <http://example.org/d> ; " +
"COPY SILENT DEFAULT TO GRAPH <http://example.org/d2>");
UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast);
assertEquals(2, update.operations().size());

CopyRequestAst first = assertInstanceOf(CopyRequestAst.class, update.operations().get(0));
assertEquals("<http://example.org/s>", first.source().graph().raw());
assertEquals("<http://example.org/d>", first.destination().graph().raw());
assertFalse(first.silent());

CopyRequestAst second = assertInstanceOf(CopyRequestAst.class, update.operations().get(1));
assertTrue(second.source().defaultGraph());
assertEquals("<http://example.org/d2>", second.destination().graph().raw());
assertTrue(second.silent());
}

@Test
@DisplayName("PREFIX declaration is preserved in the prologue of a COPY query")
void copyMustKeepItsPrologue() {
QueryAst ast = newParserDefault().parse(
"PREFIX ex: <http://example.org/> COPY GRAPH ex:s TO GRAPH ex:d");
UpdateRequestAst update = assertInstanceOf(UpdateRequestAst.class, ast);
assertEquals(1, update.operations().size());
assertInstanceOf(CopyRequestAst.class, update.operations().getFirst());
assertNotNull(update.prologue());
assertEquals(1, update.prologue().prefixDeclarations().size());
}

@Test
@DisplayName("COPY without TO keyword should throw a syntax exception")
void copyMissingToShouldFail() {
assertThrows(QuerySyntaxException.class,
() -> newParserDefault().parse("COPY GRAPH <http://example.org/s>"));
}

@Test
@DisplayName("COPY without source should throw a syntax exception")
void copyMissingSourceShouldFail() {
assertThrows(QuerySyntaxException.class,
() -> newParserDefault().parse("COPY TO GRAPH <http://example.org/d>"));
}
}
Loading