package org.aksw.jena_sparql_api.views; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.apache.jena.graph.Node; import org.apache.jena.graph.Triple; import org.apache.jena.query.Query; import org.apache.jena.query.QueryFactory; import org.apache.jena.query.SortCondition; import org.apache.jena.query.Syntax; import org.apache.jena.sparql.ARQInternalErrorException; import org.apache.jena.sparql.ARQNotImplemented; import org.apache.jena.sparql.algebra.Op; import org.apache.jena.sparql.algebra.OpVars; import org.apache.jena.sparql.algebra.OpVisitor; import org.apache.jena.sparql.algebra.op.OpAssign; import org.apache.jena.sparql.algebra.op.OpBGP; import org.apache.jena.sparql.algebra.op.OpConditional; import org.apache.jena.sparql.algebra.op.OpDatasetNames; import org.apache.jena.sparql.algebra.op.OpDiff; import org.apache.jena.sparql.algebra.op.OpDisjunction; import org.apache.jena.sparql.algebra.op.OpDistinct; import org.apache.jena.sparql.algebra.op.OpExt; import org.apache.jena.sparql.algebra.op.OpExtend; import org.apache.jena.sparql.algebra.op.OpFilter; import org.apache.jena.sparql.algebra.op.OpGraph; import org.apache.jena.sparql.algebra.op.OpGroup; import org.apache.jena.sparql.algebra.op.OpJoin; import org.apache.jena.sparql.algebra.op.OpLabel; import org.apache.jena.sparql.algebra.op.OpLeftJoin; import org.apache.jena.sparql.algebra.op.OpList; import org.apache.jena.sparql.algebra.op.OpMinus; import org.apache.jena.sparql.algebra.op.OpNull; import org.apache.jena.sparql.algebra.op.OpOrder; import org.apache.jena.sparql.algebra.op.OpPath; import org.apache.jena.sparql.algebra.op.OpProcedure; import org.apache.jena.sparql.algebra.op.OpProject; import org.apache.jena.sparql.algebra.op.OpPropFunc; import org.apache.jena.sparql.algebra.op.OpQuad; import org.apache.jena.sparql.algebra.op.OpQuadBlock; import org.apache.jena.sparql.algebra.op.OpQuadPattern; import org.apache.jena.sparql.algebra.op.OpReduced; import org.apache.jena.sparql.algebra.op.OpSequence; import org.apache.jena.sparql.algebra.op.OpService; import org.apache.jena.sparql.algebra.op.OpSlice; import org.apache.jena.sparql.algebra.op.OpTable; import org.apache.jena.sparql.algebra.op.OpTopN; import org.apache.jena.sparql.algebra.op.OpTriple; import org.apache.jena.sparql.algebra.op.OpUnion; import org.apache.jena.sparql.core.BasicPattern; import org.apache.jena.sparql.core.Quad; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.core.VarExprList; import org.apache.jena.sparql.expr.Expr; import org.apache.jena.sparql.expr.ExprAggregator; import org.apache.jena.sparql.expr.ExprList; import org.apache.jena.sparql.expr.ExprTransformCopy; import org.apache.jena.sparql.expr.ExprTransformer; import org.apache.jena.sparql.expr.ExprVar; import org.apache.jena.sparql.pfunction.PropFuncArg; import org.apache.jena.sparql.syntax.Element; import org.apache.jena.sparql.syntax.ElementAssign; import org.apache.jena.sparql.syntax.ElementBind; import org.apache.jena.sparql.syntax.ElementFilter; import org.apache.jena.sparql.syntax.ElementGroup; import org.apache.jena.sparql.syntax.ElementNamedGraph; import org.apache.jena.sparql.syntax.ElementOptional; import org.apache.jena.sparql.syntax.ElementPathBlock; import org.apache.jena.sparql.syntax.ElementService; import org.apache.jena.sparql.syntax.ElementSubQuery; import org.apache.jena.sparql.syntax.ElementTriplesBlock; import org.apache.jena.sparql.syntax.ElementUnion; import org.apache.jena.sparql.util.graph.GraphList; import org.apache.jena.vocabulary.RDF; /** Convert an Op expression in SPARQL syntax, that is, the reverse of algebra generation */ public class MyOpAsQuery { public static Query asQuery(Op op) { return asQuery(op, Dialect.DEFAULT); } public static Query asQuery(Op op, Dialect dialect) { Query query = QueryFactory.make() ; Converter v = new Converter(query, dialect) ; //OpWalker.walk(op, v) ; op.visit(v) ; List<Var> vars = v.projectVars; query.setQueryResultStar(vars.isEmpty()); // SELECT * unless we are projecting Iterator<Var> iter = vars.iterator(); for (; iter.hasNext();) { Var var = iter.next(); if (v.varExpression.containsKey(var)) query.addResultVar(var, v.varExpression.get(var)); else query.addResultVar(var); } ElementGroup eg = v.currentGroup ; query.setQueryPattern(eg) ; query.setQuerySelectType() ; query.setResultVars() ; return query ; } public static class Converter implements OpVisitor { private Query query ; private Element element = null ; private ElementGroup currentGroup = null ; private Stack<ElementGroup> stack = new Stack<ElementGroup>() ; private List<Var> projectVars = Collections.emptyList() ; private Map<Var, Expr> varExpression = new HashMap<Var, Expr>() ; private Dialect dialect; public Converter(Query query, Dialect dialect) { this.dialect = dialect; this.query = query ; currentGroup = new ElementGroup() ; } Element asElement(Op op) { ElementGroup g = asElementGroup(op) ; if ( g.getElements().size() == 1 ) return g.getElements().get(0) ; return g ; } ElementGroup asElementGroup(Op op) { startSubGroup() ; op.visit(this) ; return endSubGroup() ; } @Override public void visit(OpBGP opBGP) { currentGroup().addElement(process(opBGP.getPattern())) ; } // public void visit(OpPropFunc opPropFunc) // { // OpBGP opBGP = opPropFunc.getBGP() ; // currentGroup().addElement(process(opBGP.getPattern())) ; // } @Override public void visit(OpTriple opTriple) { currentGroup().addElement(process(opTriple.getTriple())) ; } @Override public void visit(OpQuad opQuad) { throw new ARQNotImplemented("OpQuad") ; } @Override public void visit(OpProcedure opProcedure) { throw new ARQNotImplemented("OpProcedure") ; } @Override public void visit(OpPropFunc opPropFunc) { Node s = processPropFuncArg(opPropFunc.getSubjectArgs()) ; Node o = processPropFuncArg(opPropFunc.getObjectArgs()) ; Triple t = new Triple(s, opPropFunc.getProperty(), o) ; currentGroup().addElement(process(t)) ; } private Node processPropFuncArg(PropFuncArg args) { if ( args.isNode() ) return args.getArg() ; // List ... List<Node> list = args.getArgList() ; if ( list.size() == 0 ) return RDF.Nodes.nil ; BasicPattern bgp = new BasicPattern() ; Node head = GraphList.listToTriples(list, bgp) ; currentGroup().addElement(process(bgp)) ; return head ; } @Override public void visit(OpSequence opSequence) { ElementGroup g = currentGroup() ; boolean nestGroup = ! g.isEmpty() ; if ( nestGroup ) { startSubGroup() ; g = currentGroup() ; } Iterator<Op> iter = opSequence.iterator() ; for ( ; iter.hasNext() ; ) { Op op = iter.next() ; Element e = asElement(op) ; g.addElement(e) ; } if ( nestGroup ) endSubGroup() ; return ; } @Override public void visit(OpDisjunction opDisjunction) { if(opDisjunction.getElements().isEmpty()) { return; } Op a = null; for(Op b : opDisjunction.getElements()) { if(a == null) { a = b; continue; } a = new OpUnion(a, b); } a.visit(this); //throw new ARQNotImplemented("OpDisjunction") ; } private Element process(BasicPattern pattern) { // The different SPARQL versions use different internal structures for BGPs. if ( query.getSyntax() == Syntax.syntaxSPARQL_10 ) { ElementTriplesBlock e = new ElementTriplesBlock() ; for (Triple t : pattern) // Leave bNode variables as they are // Query serialization will deal with them. e.addTriple(t) ; return e ; } if ( query.getSyntax() == Syntax.syntaxSPARQL_11 || query.getSyntax() == Syntax.syntaxARQ ) { ElementPathBlock e = new ElementPathBlock() ; for (Triple t : pattern) // Leave bNode variables as they are // Query serialization will deal with them. e.addTriple(t) ; return e ; } throw new ARQInternalErrorException("Unrecognized syntax: "+query.getSyntax()) ; } private ElementTriplesBlock process(Triple triple) { // Unsubtle ElementTriplesBlock e = new ElementTriplesBlock() ; e.addTriple(triple) ; return e ; } @Override public void visit(OpQuadPattern quadPattern) { BasicPattern pattern = quadPattern.getBasicPattern(); if(!Quad.isDefaultGraph(quadPattern.getGraphNode())) { OpGraph opGraph = new OpGraph(quadPattern.getGraphNode(), new OpBGP(pattern)); visit(opGraph); } else { currentGroup().addElement(process(pattern)) ; } //throw new ARQNotImplemented("OpQuadPattern") ; } @Override public void visit(OpPath opPath) { throw new ARQNotImplemented("OpPath") ; } @Override public void visit(OpJoin opJoin) { // Keep things clearly separated. Element eLeft = asElement(opJoin.getLeft()) ; Element eRight = asElementGroup(opJoin.getRight()) ; ElementGroup g = currentGroup() ; g.addElement(eLeft) ; g.addElement(eRight) ; return ; } private static boolean emptyGroup(Element element) { if ( ! ( element instanceof ElementGroup ) ) return false ; ElementGroup eg = (ElementGroup)element ; return eg.isEmpty() ; } @Override public void visit(OpLeftJoin opLeftJoin) { Element eLeft = asElement(opLeftJoin.getLeft()) ; ElementGroup eRight = asElementGroup(opLeftJoin.getRight()) ; if ( opLeftJoin.getExprs() != null ) { for ( Expr expr : opLeftJoin.getExprs() ) { ElementFilter f = new ElementFilter(expr) ; eRight.addElement(f) ; } } ElementGroup g = currentGroup() ; if ( ! emptyGroup(eLeft) ) g.addElement(eLeft) ; ElementOptional opt = new ElementOptional(eRight) ; g.addElement(opt) ; } @Override public void visit(OpDiff opDiff) { throw new ARQNotImplemented("OpDiff") ; } @Override public void visit(OpMinus opMinus) { throw new ARQNotImplemented("OpMinus") ; } @Override public void visit(OpUnion opUnion) { Element eLeft = asElementGroup(opUnion.getLeft()) ; Element eRight = asElementGroup(opUnion.getRight()) ; if ( eLeft instanceof ElementUnion ) { ElementUnion elUnion = (ElementUnion)eLeft ; elUnion.addElement(eRight) ; return ; } // if ( eRight instanceof ElementUnion ) // { // ElementUnion elUnion = (ElementUnion)eRight ; // elUnion.getElements().add(0, eLeft) ; // return ; // } ElementUnion elUnion = new ElementUnion() ; elUnion.addElement(eLeft) ; elUnion.addElement(eRight) ; currentGroup().addElement(elUnion) ; } @Override public void visit(OpConditional opCondition) { throw new ARQNotImplemented("OpCondition") ; } @Override public void visit(OpFilter opFilter) { // (filter .. (filter ( ... )) (non-canonicalizing OpFilters) // Inner gets Grouped unnecessarily. Element e = asElement(opFilter.getSubOp()) ; if ( currentGroup() != e ) currentGroup().addElement(e) ; element = currentGroup() ; // Was cleared by asElement. ExprList exprs = opFilter.getExprs() ; for ( Expr expr : exprs ) { ElementFilter f = new ElementFilter(expr) ; currentGroup().addElement(f) ; } } @Override public void visit(OpGraph opGraph) { startSubGroup() ; Element e = asElement(opGraph.getSubOp()) ; ElementGroup g = endSubGroup() ; Element graphElt = new ElementNamedGraph(opGraph.getNode(), e) ; currentGroup().addElement(graphElt) ; } @Override public void visit(OpService opService) { // Hmm - if the subnode has been optimized, we may fail. Op op = opService.getSubOp() ; Element x = asElement(opService.getSubOp()) ; Element elt = new ElementService(opService.getService(), x, opService.getSilent()) ; currentGroup().addElement(elt) ; } @Override public void visit(OpDatasetNames dsNames) { throw new ARQNotImplemented("OpDatasetNames") ; } @Override public void visit(OpTable opTable) { // This will go in a group so simply forget it. if ( opTable.isJoinIdentity() ) return ; throw new ARQNotImplemented("OpTable") ; } @Override public void visit(OpExt opExt) { Op op = opExt.effectiveOp() ; if(op == null) { throw new RuntimeException("Effective Op is null for: " + opExt.getClass()); } // // This does not work in all cases. op.visit(this) ; //throw new ARQNotImplemented("OpExt") ; } @Override public void visit(OpNull opNull) { throw new ARQNotImplemented("OpNull") ; } @Override public void visit(OpLabel opLabel) { if ( opLabel.hasSubOp() ) opLabel.getSubOp().visit(this) ; } @Override public void visit(OpAssign opAssign) { opAssign.getSubOp().visit(this) ; // Go through each var and get the assigned expression for ( Var v : opAssign.getVarExprList().getVars() ) { Expr e = opAssign.getVarExprList().getExpr(v); // Substitute group aggregate expressions in for generated vars SubExprForVar sefr = new SubExprForVar(varExpression); Expr tr = ExprTransformer.transform(sefr, e); // If in top level we defer assignment to SELECT section // This also covers the GROUP recombine // NOTE: this means we can't round trip top-level BINDs if (inTopLevel()) { varExpression.put(v, tr); } else { Element elt = new ElementAssign(v, e) ; ElementGroup g = currentGroup() ; g.addElement(elt) ; } } } @Override public void visit(OpExtend opExtend) { if(!inTopLevel() && dialect.equals(Dialect.VIRTUOSO)) { Set<Var> vars = new HashSet<Var>(OpVars.mentionedVars(opExtend)); OpProject project = new OpProject(opExtend, new ArrayList<Var>(vars)); Query query = MyOpAsQuery.asQuery(project, dialect); //System.out.println(query); ElementSubQuery elt = new ElementSubQuery(query); ElementGroup g = currentGroup() ; g.addElement(elt) ; return; } opExtend.getSubOp().visit(this) ; // Go through each var and get the assigned expression for ( Var v : opExtend.getVarExprList().getVars() ) { Expr e = opExtend.getVarExprList().getExpr(v); // Substitute group aggregate expressions in for generated vars Expr tr = ExprTransformer.transform(new SubExprForVar(varExpression), e); // If in top level we defer assignment to SELECT section // This also covers the GROUP recombine // NOTE: this means we can't round trip top-level BINDs if (inTopLevel()) { varExpression.put(v, tr); } else { Element elt = new ElementBind(v, tr) ; ElementGroup g = currentGroup() ; g.addElement(elt) ; } } } @Override public void visit(OpList opList) { /* No action */ } @Override public void visit(OpOrder opOrder) { List<SortCondition> x = opOrder.getConditions() ; for ( SortCondition sc : x ) query.addOrderBy(sc); opOrder.getSubOp().visit(this) ; } @Override public void visit(OpProject opProject) { /* if(dialect.equals(Dialect.VIRTUOSO)) { if(opProject.getSubOp() instanceof OpExtend) { //Set<Var> vars = GetVarsMentioned.getVarsMentioned(opProject.getSubOp()); //OpProject project = new OpProject(opExtend, new ArrayList<Var>(vars)); Query query = MyOpAsQuery.asQuery(opProject); //System.out.println(query); ElementSubQuery elt = new ElementSubQuery(query); ElementGroup g = currentGroup() ; g.addElement(elt) ; return; } }*/ // Defer adding result vars until the end. // OpGroup generates dupes otherwise this.projectVars = opProject.getVars(); opProject.getSubOp().visit(this) ; } @Override public void visit(OpReduced opReduced) { query.setReduced(true) ; opReduced.getSubOp().visit(this) ; } @Override public void visit(OpDistinct opDistinct) { query.setDistinct(true) ; opDistinct.getSubOp().visit(this) ; } @Override public void visit(OpSlice opSlice) { if ( opSlice.getStart() != Query.NOLIMIT ) query.setOffset(opSlice.getStart()) ; if ( opSlice.getLength() != Query.NOLIMIT ) query.setLimit(opSlice.getLength()) ; opSlice.getSubOp().visit(this) ; } @Override public void visit(OpGroup opGroup) { List<ExprAggregator> a = opGroup.getAggregators(); // Aggregators are broken up in the algebra, split between a // group and an assignment (extend or assign) using a generated var. // We record them here and insert later. for (ExprAggregator ea : a) { // Substitute generated var for actual Var givenVar = ea.getAggVar().asVar(); // Copy aggregator across (?) Expr myAggr = query.allocAggregate(ea.getAggregator()); varExpression.put(givenVar, myAggr); } VarExprList b = opGroup.getGroupVars(); for (Var v : b.getVars()) { Expr e = b.getExpr(v); if (e != null) { query.addGroupBy(v, e); } else { query.addGroupBy(v); } } opGroup.getSubOp().visit(this); } @Override public void visit(OpTopN opTop) { throw new ARQNotImplemented("OpTopN") ; } private Element lastElement() { ElementGroup g = currentGroup ; if ( g == null || g.getElements().size() == 0 ) return null ; int len = g.getElements().size() ; return g.getElements().get(len-1) ; } private void startSubGroup() { push(currentGroup) ; ElementGroup g = new ElementGroup() ; currentGroup = g ; } private ElementGroup endSubGroup() { ElementGroup g = pop() ; ElementGroup r = currentGroup ; currentGroup = g ; return r ; } // private void endCurrentGroup() // { // currentGroup = null ; // element = null ; //?? // } private ElementGroup currentGroup() { // if ( currentGroup == null ) // startSubGroup() ; return currentGroup ; } private ElementGroup peek() { if ( stack.size() == 0 ) return null ; return stack.peek(); } private ElementGroup pop() { return stack.pop(); } private void push(ElementGroup el) { stack.push(el); } private boolean inTopLevel() { return stack.size() == 0; } @Override public void visit(OpQuadBlock op) { throw new RuntimeException("Not implemented"); //op. // TODO Auto-generated method stub } } /** * This class is used to take substitute an expressions for variables * in another expression. It is used to stick grouping expressions back * together. */ public static class SubExprForVar extends ExprTransformCopy { private final Map<Var, Expr> varExpr; private boolean subOccurred = false; public SubExprForVar(Map<Var, Expr> varExpr) { this.varExpr = varExpr; } public boolean didChange() { return subOccurred; } @Override public Expr transform(ExprVar var) { if (varExpr.containsKey(var.asVar())) { subOccurred = true; return varExpr.get(var.asVar()).deepCopy(); } else return var.deepCopy(); } } }