/* * Copyright (C) INRIA, 2012-2013 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package fr.inrialpes.tyrexmo.queryanalysis; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.HashSet; import java.util.Stack; //import com.hp.hpl.jena.sparql.algebra.op.OpQuad; import com.hp.hpl.jena.graph.Triple; import com.hp.hpl.jena.graph.Node; import com.hp.hpl.jena.query.Query; import com.hp.hpl.jena.query.QueryFactory; import com.hp.hpl.jena.query.SortCondition; import com.hp.hpl.jena.sparql.ARQNotImplemented; import com.hp.hpl.jena.sparql.algebra.Algebra; import com.hp.hpl.jena.sparql.algebra.Op; import com.hp.hpl.jena.sparql.algebra.OpVisitor; import com.hp.hpl.jena.sparql.algebra.op.*; import com.hp.hpl.jena.sparql.core.BasicPattern; import com.hp.hpl.jena.sparql.core.Var; import com.hp.hpl.jena.sparql.core.VarExprList; import com.hp.hpl.jena.sparql.expr.Expr; import com.hp.hpl.jena.sparql.expr.ExprList; import com.hp.hpl.jena.sparql.syntax.*; /** Convert an Op expression in SPARQL syntax, that is, the reverse of algebra generation */ public class TransformAlgebra { private Stack<Object> lst; private Query query; /* private boolean containsOpt; private boolean containsFilter; private boolean containsUn; */ private Converter conv; public TransformAlgebra( Query q ) { query = q; Op op = Algebra.compile( query ); transformAlgebra( op ); } public TransformAlgebra( String q ) { query = QueryFactory.create( q); Op op = Algebra.compile( query ); transformAlgebra( op ); } private void transformAlgebra( Op op ) { conv = new Converter( query ); op.visit( conv ); lst = conv.getQueryElems(); } public boolean containsOpt () { return conv.containsOptional(); } public boolean hasFilter () { return conv.containsFilter(); } public boolean hasUnion () { return conv.containsUnion(); } public Stack<Object> getQueryPattern() { return lst; } /********************************************************/ // Distinguished variables // JE: I think the the new Jena has: // getProjectVars() getResultVars() getValuesVariables() public List<Var> getResultVars() { // I am fed up creating the query each time if ( query.isQueryResultStar() ) { List<Var> list = new ArrayList<Var>(); list.addAll( getAllVariables() ); return list; } else { return query.getProjectVars(); } } // Distinguished variable names (useless?) public List<String> getProjectVars() { return query.getResultVars(); } public Set<Var> getAllVariables() { return conv.variables(); /* // should be possible to ask it to conv // Remember distinct subjects in this final Set<Var> vars = new HashSet<Var>(); // This will walk through all parts of the query ElementWalker.walk(query.getQueryPattern(), // For each element... new ElementVisitorBase() { // ...when it's a block of triples... public void visit(ElementPathBlock el) { // ...go through all the triples... Iterator<TriplePath> triples = el.patternElts(); while (triples.hasNext()) { // ...and grab the subject subjects.add(triples.next().getSubject()); } } } ); return vars; */ } // non-distinguished vars public Collection<Var> getNonDistVars() { return difference( getResultVars(), getAllVariables() ); } //set difference of distinguished vars and all the vars in a query private Collection<Var> difference(Collection<Var> dVars, Collection<Var> allVars) { Collection<Var> result = new ArrayList<Var>(allVars); result.removeAll(dVars); return result; } /** * triples that made up the query in a list * @return */ public List<Triple> getTriples() { Object [] triples = lst.toArray(); List<Triple> t = new ArrayList<Triple>(); for ( int i = 0; i < lst.size(); i++ ) { if (triples[i] instanceof Triple) t.add((Triple)triples[i]); } return t; } public static class Converter implements OpVisitor { private Query query; private ElementGroup currentGroup = null; private Stack<ElementGroup> stack = new Stack<ElementGroup>(); private Stack<Object> elems = new Stack<Object>(); private Set<Var> allVars = new HashSet<Var>(); private boolean containsOptional = false; private boolean containsFilter = false; private boolean containsUnion = false; public Converter(Query q) { query = q; currentGroup = new ElementGroup(); } public Stack<Object> getQueryElems() { return elems; } public boolean containsOptional () { return containsOptional; } public Set<Var> variables() { return allVars; } public boolean containsFilter () { return containsFilter; } public boolean containsUnion () { return containsUnion; } 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(); } public void visit( OpBGP opBGP ) { currentGroup().addElement( process( opBGP.getPattern() ) ); //System.out.println("ETB"+process(opBGP.getPattern()).toString()); //elems.add(process(opBGP.getPattern())); } public void visit( OpTriple opTriple ) { currentGroup().addElement(process(opTriple.getTriple())); //System.out.println(opTriple.getTriple().toString()); } private ElementTriplesBlock process( BasicPattern pattern ) { int twoAdded = pattern.size(); ElementTriplesBlock e = new ElementTriplesBlock(); for ( Triple t : pattern ) { // Leave bNode variables as they are // Query serialization will deal with them. e.addTriple(t); // add each triple pattern into the stack elems.add(t); twoAdded--; if (twoAdded >= 1) elems.add("AND"); /* JE 2013/07 */ Node s = t.getSubject(); if ( s.isVariable() && s instanceof Var ) allVars.add( (Var)s ); Node o = t.getObject(); if ( o.isVariable() && o instanceof Var ) allVars.add( (Var)o ); Node p = t.getPredicate(); if ( p.isVariable() && p instanceof Var ) allVars.add( (Var)p ); } //elems.add(e); return e; } private ElementTriplesBlock process(Triple triple) { // Unsubtle ElementTriplesBlock e = new ElementTriplesBlock(); e.addTriple(triple); //System.out.println(triple.toString()); elems.push(triple); return e; } public void visit(OpQuadPattern quadPattern) { throw new ARQNotImplemented("OpQuadPattern"); } public void visit(OpPath opPath) { throw new ARQNotImplemented("OpPath"); } public void visit(OpJoin opJoin) { // Keep things clearly separated. elems.add("("); Element eLeft = asElement(opJoin.getLeft()); elems.add("AND"); Element eRight = asElementGroup(opJoin.getRight()); elems.add(")"); ElementGroup g = currentGroup(); g.addElement(eLeft); //elems.push(eLeft); //elems.add("AND"); g.addElement(eRight); //elems.push(eRight); return; } private static boolean emptyGroup(Element element) { if ( ! ( element instanceof ElementGroup ) ) return false; ElementGroup eg = (ElementGroup)element; return eg.isEmpty(); } /*************************************************************/ public void visit( OpLeftJoin opLeftJoin ) { containsOptional = true; // JE: to be discussed 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); g.addElement( new ElementOptional(eRight) ); } /*************************************************************/ public void visit( OpDiff opDiff ) { throw new ARQNotImplemented("OpDiff"); } public void visit( OpMinus opMinus ) { // throw new ARQNotImplemented("OpMinus"); // Keep things clearly separated. elems.add("("); Element eLeft = asElement(opMinus.getLeft()); elems.add("MINUS"); Element eRight = asElementGroup(opMinus.getRight()); elems.add(")"); ElementGroup g = currentGroup(); g.addElement(eLeft); //elems.push(eLeft); //elems.add("AND"); g.addElement(eRight); //elems.push(eRight); return; } public void visit(OpUnion opUnion) { containsUnion = true; elems.add("("); Element eLeft = asElementGroup(opUnion.getLeft()); elems.add("UNION"); Element eRight = asElementGroup(opUnion.getRight()); elems.add(")"); if ( eLeft instanceof ElementUnion ) { ElementUnion elUnion = (ElementUnion)eLeft; //elems.add("UNION"); 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); // elems.add(eLeft); //elems.add("UNION"); // elems.add(eRight); elUnion.addElement(eRight); currentGroup().addElement(elUnion); } public void visit(OpConditional opCondition) { throw new ARQNotImplemented("OpCondition"); } public void visit( OpFilter opFilter ) { containsFilter = true; // (filter .. (filter ( ... )) (non-canonicalizing OpFilters) // Inner gets Grouped unnecessarily. Element e = asElement(opFilter.getSubOp()); if ( currentGroup() != e ) currentGroup().addElement(e); currentGroup(); ExprList exprs = opFilter.getExprs(); for ( Expr expr : exprs ) { ElementFilter f = new ElementFilter(expr); currentGroup().addElement(f); } } public void visit(OpGraph opGraph) { startSubGroup(); Element e = asElement(opGraph.getSubOp()); ElementGroup g = endSubGroup(); Element graphElt = new ElementNamedGraph(opGraph.getNode(), e); currentGroup().addElement(graphElt); } public void visit(OpService opService) { throw new ARQNotImplemented("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 ); currentGroup().addElement(elt); */ } public void visit(OpDatasetNames dsNames) { throw new ARQNotImplemented("OpDatasetNames"); } public void visit(OpTable opTable) { // This will go in a group so simply forget it. if ( opTable.isJoinIdentity() ) return; throw new ARQNotImplemented("OpTable"); } public void visit(OpExt opExt) { // Op op = opExt.effectiveOp(); // // This does not work in all cases. // op.visit(this); throw new ARQNotImplemented("OpExt"); } public void visit(OpNull opNull) { throw new ARQNotImplemented("OpNull"); } public void visit(OpLabel opLabel) { /* No action */ } public void visit(OpAssign opAssign) { for ( Var v : opAssign.getVarExprList().getVars() ) { Element elt = new ElementAssign(v, opAssign.getVarExprList().getExpr(v)); ElementGroup g = currentGroup(); g.addElement(elt); } } public void visit(OpList opList) { /* No action */ } public void visit(OpOrder opOrder) { List<SortCondition> x = opOrder.getConditions(); for ( SortCondition sc : x ) query.addOrderBy(sc); opOrder.getSubOp().visit(this); } public void visit(OpProject opProject) { query.setQueryResultStar(false); Iterator<Var> iter = opProject.getVars().iterator(); for (; iter.hasNext(); ) { Var v = iter.next(); query.addResultVar(v); } opProject.getSubOp().visit(this); } public void visit(OpReduced opReduced) { query.setReduced(true); opReduced.getSubOp().visit(this); } public void visit(OpDistinct opDistinct) { query.setDistinct(true); opDistinct.getSubOp().visit(this); } 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); } /* JE: 2013/07/05. These are for Jena 2.10*/ public void visit( OpTopN op ) { throw new ARQNotImplemented("OpTopN"); } public void visit( OpGroup op ) { throw new ARQNotImplemented("OpGroup"); } // public void visit( OpQuad op ) { // throw new ARQNotImplemented("OpQuad"); // } public void visit( OpExtend op ) { throw new ARQNotImplemented("OpExtend"); } 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); } public void visit(OpProcedure opProcedure) { throw new ARQNotImplemented("OpProcedure"); } public void visit(OpPropFunc opPropFunc) { throw new ARQNotImplemented("OpPropFunc"); } public void visit(OpSequence opSequence) { throw new ARQNotImplemented("OpSequence"); } public void visit(OpDisjunction opDisjunction) { throw new ARQNotImplemented("OpDisjunction"); } } }