-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/384 bridge where compiler #466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| package fr.inria.corese.core.next.query.impl.sparql.bridge; | ||
|
|
||
| import fr.inria.corese.core.next.query.kgram.api.core.Edge; | ||
| import fr.inria.corese.core.next.query.kgram.api.core.Node; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * A query-side {@link Edge} produced by the WHERE compiler from a | ||
| * {@link fr.inria.corese.core.next.query.impl.sparql.ast.TriplePatternAst}. | ||
| */ | ||
| final class AstBackedEdge implements Edge { | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For variable predicates, @Test
void edgeExposesVariablePredicateAsEdgeVariable() {
Exp bgp = new WhereCompiler().compile(new BgpAst(List.of(
new TriplePatternAst(new VarAst("s"), new VarAst("p"), new VarAst("o")))));
Exp edgeExp = bgp.get(0);
assertNotNull(edgeExp.getEdge().getEdgeVariable(), "variable predicate should be exposed as edge variable");
assertTrue(edgeExp.getEdge().getEdgeVariable().isVariable());
} |
||
| private final Node subject; | ||
| private final Node predicate; | ||
| private final Node object; | ||
| private final Node graph; | ||
|
|
||
| AstBackedEdge(Node subject, Node predicate, Node object) { | ||
| this(subject, predicate, object, null); | ||
| } | ||
|
|
||
| AstBackedEdge(Node subject, Node predicate, Node object, Node graph) { | ||
| this.subject = Objects.requireNonNull(subject, "subject"); | ||
| this.predicate = Objects.requireNonNull(predicate, "predicate"); | ||
| this.object = Objects.requireNonNull(object, "object"); | ||
| this.graph = graph; | ||
| } | ||
|
|
||
| @Override | ||
| public Node getNode(int i) { | ||
| return switch (i) { | ||
| case 0 -> subject; | ||
| case 1 -> object; | ||
| default -> throw new IndexOutOfBoundsException( | ||
| "Triple pattern edge has nodes 0 (subject) and 1 (object), got: " + i); | ||
| }; | ||
| } | ||
|
|
||
| @Override | ||
| public Node getProperty() { | ||
| return predicate; | ||
| } | ||
|
|
||
| @Override | ||
| public Node getEdgeVariable() { | ||
| return predicate.isVariable() ? predicate : null; | ||
| } | ||
|
|
||
| @Override | ||
| public String getEdgeLabel() { | ||
| return predicate.getLabel(); | ||
| } | ||
|
|
||
| /** | ||
| * The graph this triple pattern is matched in, or {@code null} for the default graph. | ||
| * | ||
| */ | ||
| @Override | ||
| public Node getGraph() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it return something ? either the default graph or the encompassing graph The The |
||
| return graph; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean contains(Node node) { | ||
| if (node == null) { | ||
| return false; | ||
| } | ||
| for (int i = 0; i < nbNode(); i++) { | ||
| Node n = getNode(i); | ||
| if (n != null && (n == node || n.same(node))) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public Edge getEdge() { | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return subject + " " + predicate + " " + object; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| package fr.inria.corese.core.next.query.impl.sparql.bridge; | ||
|
|
||
| import fr.inria.corese.core.next.query.impl.sparql.ast.*; | ||
| import fr.inria.corese.core.next.query.kgram.api.core.Edge; | ||
| import fr.inria.corese.core.next.query.kgram.api.core.ExpType.Type; | ||
| import fr.inria.corese.core.next.query.kgram.api.core.Filter; | ||
| import fr.inria.corese.core.next.query.kgram.api.core.Node; | ||
| import fr.inria.corese.core.next.query.kgram.core.Exp; | ||
| import fr.inria.corese.core.next.query.kgram.core.Query; | ||
| import fr.inria.corese.core.next.query.kgram.tool.NodeImpl; | ||
| import fr.inria.corese.core.sparql.triple.parser.Atom; | ||
| import fr.inria.corese.core.sparql.triple.parser.Expression; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| /** | ||
| * Compiles the content of a SPARQL {@code WHERE} clause — a tree of | ||
| * {@link PatternAst} nodes — into a KGRAM {@link Exp} body that the engine can | ||
| * evaluate. | ||
| */ | ||
| public final class WhereCompiler { | ||
|
|
||
| public WhereCompiler() { | ||
| } | ||
|
|
||
| public Exp compile(GroupGraphPatternAst where) { | ||
| Objects.requireNonNull(where, "where"); | ||
| return compileGroup(where); | ||
| } | ||
|
|
||
| /** | ||
| * Dispatch a single {@link PatternAst} element to its KGRAM {@link Exp}. | ||
| */ | ||
| public Exp compile(PatternAst pattern) { | ||
| Objects.requireNonNull(pattern, "pattern"); | ||
| return switch (pattern) { | ||
| case BgpAst bgp -> compileBgp(bgp); | ||
| case FilterAst filter -> compileFilter(filter); | ||
| case UnionAst union -> compileUnion(union); | ||
| case OptionalAst optional -> compileOptional(optional); | ||
| case MinusAst minus -> compileMinus(minus); | ||
| case BindAst bind -> compileBind(bind); | ||
| case ServiceAst service -> compileService(service); | ||
| case GroupGraphPatternAst group -> compileGroup(group); | ||
| default -> throw new UnsupportedOperationException( | ||
| "WHERE compilation not yet supported for: " + pattern.getClass().getSimpleName()); | ||
| }; | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * {@code { e1 e2 ... en }} becomes an {@code AND} of the compiled elements. | ||
| */ | ||
| private Exp compileGroup(GroupGraphPatternAst group) { | ||
| Exp body = Exp.create(Type.AND); | ||
| for (PatternAst element : group.patterns()) { | ||
| if (element instanceof OptionalAst(PatternAst ast)) { | ||
| Exp left = body; | ||
| Exp right = compile(ast); | ||
| Exp optionalExp = Exp.create(Type.OPTIONAL, left, right); | ||
| body = Exp.create(Type.AND); | ||
| body.add(optionalExp); | ||
| } else if (element instanceof MinusAst(GroupGraphPatternAst pattern)) { | ||
| Exp left = body; | ||
| Exp right = compile(pattern); | ||
| Exp minusExp = Exp.create(Type.MINUS, left, right); | ||
| body = Exp.create(Type.AND); | ||
| body.add(minusExp); | ||
| } else { | ||
| body.add(compile(element)); | ||
| } | ||
| } | ||
| return body; | ||
| } | ||
|
|
||
|
|
||
| private Exp compileBgp(BgpAst bgp) { | ||
| Exp pattern = Exp.create(Type.BGP); | ||
| for (TriplePatternAst triple : bgp.triples()) { | ||
| pattern.add(toEdge(triple)); | ||
| } | ||
| return pattern; | ||
| } | ||
|
|
||
| private Edge toEdge(TriplePatternAst triple) { | ||
| Node subject = toNode(triple.subject()); | ||
| Node predicate = toNode(triple.predicate()); | ||
| Node object = toNode(triple.object()); | ||
| return new AstBackedEdge(subject, predicate, object); | ||
| } | ||
|
|
||
| private Node toNode(TermAst term) { | ||
| Expression expression = SparqlAstToExpression.convert(term); | ||
| if (expression instanceof Atom atom) { | ||
| return new NodeImpl(atom); | ||
| } | ||
| throw new IllegalArgumentException( | ||
| "A triple-pattern term must be a variable, IRI or literal, got: " | ||
| + term.getClass().getSimpleName()); | ||
| } | ||
|
|
||
|
|
||
| private Exp compileFilter(FilterAst filter) { | ||
| Filter nextFilter = SparqlAstToExpression.toNextFilter(filter); | ||
| return Exp.create(Type.FILTER, nextFilter); | ||
| } | ||
|
|
||
|
|
||
| private Exp compileUnion(UnionAst union) { | ||
| Exp left = compile(union.left()); | ||
| Exp right = compile(union.right()); | ||
| return Exp.create(Type.UNION, left, right); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Compiles a bare {@link OptionalAst}, i.e. one that is not folded with a | ||
| * preceding pattern by {@link #compileGroup(GroupGraphPatternAst)} (for | ||
| * instance a lone {@code { OPTIONAL { ... } }}). The mandatory left part is | ||
| * then the empty pattern, matching SPARQL's {@code {} OPTIONAL { ... }}. | ||
| */ | ||
| private Exp compileOptional(OptionalAst optional) { | ||
| Exp left = Exp.create(Type.AND); | ||
| Exp right = compile(optional.ast()); | ||
| return Exp.create(Type.OPTIONAL, left, right); | ||
| } | ||
|
|
||
| /** | ||
| * Compiles a bare {@link MinusAst} not folded with a preceding pattern. | ||
| * The mandatory left part is the empty pattern, matching {@code {} MINUS { ... }}. | ||
| */ | ||
| private Exp compileMinus(MinusAst minus) { | ||
| Exp left = Exp.create(Type.AND); | ||
| Exp right = compile(minus.pattern()); | ||
| return Exp.create(Type.MINUS, left, right); | ||
| } | ||
|
|
||
| /** | ||
| * Compiles {@code BIND(expression AS ?var)} into a KGRAM {@link Exp}. | ||
| */ | ||
| private Exp compileBind(BindAst bind) { | ||
| Filter filter = SparqlAstToExpression.toNextFilter(bind.expression()); | ||
| Node variable = toNode(bind.variable()); | ||
| Exp exp = Exp.create(Type.BIND); | ||
| exp.setFilter(filter); | ||
| exp.setFunctional(filter.isFunctional()); | ||
| exp.setNode(variable); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@Test
void bindShouldPropagateFunctionalFlag() {
BindAst bind = new BindAst(
new FunctionCallAst(new IriAst("<http://example.org/unnest>"), List.of(new VarAst("x"))),
new VarAst("y"));
Exp bindExp = new WhereCompiler().compile(bind);
assertTrue(bindExp.getFilter().isFunctional());
assertTrue(bindExp.isFunctional(), "BIND exp should propagate functional flag");
} |
||
| return exp; | ||
| } | ||
|
|
||
| /** | ||
| * Compiles {@code SERVICE <endpoint> { ... }} into a KGRAM {@link Exp}. | ||
| */ | ||
| private Exp compileService(ServiceAst service) { | ||
| Node endpoint = toNode(service.endpoint()); | ||
| Exp endpointNode = Exp.create(Type.NODE, endpoint); | ||
| Query body = Query.create(compile(service.pattern())); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@Test
void serviceBodyShouldBeMarkedAsServiceQuery() {
ServiceAst service = new ServiceAst(
new IriAst("<http://example.org/>"),
false,
group(new BgpAst(List.of(
new TriplePatternAst(new VarAst("s"), new VarAst("p"), new VarAst("o"))))));
Exp serviceExp = new WhereCompiler().compile(service);
assertTrue(serviceExp.rest().getQuery().isService(),
"SERVICE body query should be marked as service");
}private Exp compileService(ServiceAst service) {
Node endpoint = toNode(service.endpoint());
Exp endpointNode = Exp.create(Type.NODE, endpoint);
Query body = Query.create(compile(service.pattern()));
body.setService(true);
body.setSilent(service.silent());
Exp exp = Exp.create(Type.SERVICE, endpointNode, body);
exp.setSilent(service.silent());
return exp;
} |
||
| Exp exp = Exp.create(Type.SERVICE, endpointNode, body); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Replace @Test
void serviceRestShouldExposeAQueryForRuntime() {
ServiceAst service = new ServiceAst(
new IriAst("<http://example.org/>"),
false,
group(new BgpAst(List.of(
new TriplePatternAst(new VarAst("s"), new VarAst("p"), new VarAst("o"))))));
Exp serviceExp = new WhereCompiler().compile(service);
assertTrue(serviceExp.isService());
assertNotNull(serviceExp.rest().getQuery(), "SERVICE runtime expects a query body");
} |
||
| exp.setSilent(service.silent()); | ||
| return exp; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package fr.inria.corese.core.next.query.impl.sparql.bridge; | ||
|
|
||
| import fr.inria.corese.core.next.query.kgram.api.core.Node; | ||
| import fr.inria.corese.core.next.query.kgram.tool.NodeImpl; | ||
| import fr.inria.corese.core.sparql.triple.parser.Constant; | ||
| import fr.inria.corese.core.sparql.triple.parser.Variable; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.*; | ||
|
|
||
| @DisplayName("AstBackedEdge: contains(), getEdgeVariable() and getGraph()") | ||
| class AstBackedEdgeTest { | ||
|
|
||
| @Test | ||
| @DisplayName("contains() recognises the subject and the object") | ||
| void edgeContainsSubjectAndObject() { | ||
| Node subject = new NodeImpl(Variable.create("s")); | ||
| Node predicate = new NodeImpl(Variable.create("p")); | ||
| Node object = new NodeImpl(Variable.create("o")); | ||
|
|
||
| AstBackedEdge edge = new AstBackedEdge(subject, predicate, object); | ||
|
|
||
| assertTrue(edge.contains(subject)); | ||
| assertTrue(edge.contains(object)); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("contains() recognises a variable by name (not only the same instance)") | ||
| void edgeContainsByVariableName() { | ||
| AstBackedEdge edge = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), | ||
| new NodeImpl(Variable.create("p")), | ||
| new NodeImpl(Variable.create("o"))); | ||
|
|
||
| assertTrue(edge.contains(new NodeImpl(Variable.create("s")))); | ||
| assertTrue(edge.contains(new NodeImpl(Variable.create("o")))); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("contains() returns false for an absent variable and for null") | ||
| void edgeDoesNotContainOtherNode() { | ||
| AstBackedEdge edge = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), | ||
| new NodeImpl(Variable.create("p")), | ||
| new NodeImpl(Variable.create("o"))); | ||
|
|
||
| assertFalse(edge.contains(new NodeImpl(Variable.create("x")))); | ||
| assertFalse(edge.contains(null)); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("getEdgeVariable() exposes the predicate when it is a variable, null otherwise") | ||
| void edgeVariableExposedOnlyForVariablePredicate() { | ||
| Node predicateVar = new NodeImpl(Variable.create("p")); | ||
| AstBackedEdge withVarPredicate = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), predicateVar, new NodeImpl(Variable.create("o"))); | ||
| assertSame(predicateVar, withVarPredicate.getEdgeVariable()); | ||
|
|
||
| AstBackedEdge withIriPredicate = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), | ||
| new NodeImpl(Constant.createResource("http://example.org/p")), | ||
| new NodeImpl(Variable.create("o"))); | ||
| assertNull(withIriPredicate.getEdgeVariable()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("getGraph() is null for the default graph (engine represents it as null)") | ||
| void defaultGraphIsNull() { | ||
| AstBackedEdge edge = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), | ||
| new NodeImpl(Variable.create("p")), | ||
| new NodeImpl(Variable.create("o"))); | ||
|
|
||
| assertNull(edge.getGraph()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("getGraph() returns the graph node passed to the constructor") | ||
| void explicitGraphIsReturned() { | ||
| Node graph = new NodeImpl(Constant.createResource("http://example.org/g")); | ||
| AstBackedEdge edge = new AstBackedEdge( | ||
| new NodeImpl(Variable.create("s")), | ||
| new NodeImpl(Variable.create("p")), | ||
| new NodeImpl(Variable.create("o")), | ||
| graph); | ||
|
|
||
| assertSame(graph, edge.getGraph()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AstBackedEdgecurrently inherits the defaultEdge.contains(Node)implementation, which always returnsfalse. For query edges we probably needcontains()to match subject/object, otherwise some runtime logic may not see that this pattern carries these variables.