/* * 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. */ package org.apache.jena.sparql.algebra; import java.util.* ; import org.apache.jena.atlas.lib.Lib ; import org.apache.jena.atlas.lib.Pair ; import org.apache.jena.atlas.logging.Log ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.Triple ; import org.apache.jena.query.ARQ ; import org.apache.jena.query.Query ; import org.apache.jena.query.SortCondition ; import org.apache.jena.sparql.ARQInternalErrorException ; import org.apache.jena.sparql.algebra.op.* ; import org.apache.jena.sparql.algebra.optimize.TransformSimplify ; import org.apache.jena.sparql.core.* ; import org.apache.jena.sparql.engine.binding.Binding ; import org.apache.jena.sparql.expr.* ; import org.apache.jena.sparql.path.PathLib ; import org.apache.jena.sparql.syntax.* ; import org.apache.jena.sparql.util.Context ; /** * Class used to compile SPARQL queries into SPARQL algebra. * This is the SPARQL standard defined process of abstract syntax to algebra. */ public class AlgebraGenerator { // Fixed filter position means leave exactly where it is syntactically (illegal SPARQL) // Helpful only to write exactly what you mean and test the full query compiler. private boolean fixedFilterPosition = false ; private Context context ; private final int subQueryDepth ; // simplifyInAlgebraGeneration=true is the alternative reading of // the DAWG Algebra translation algorithm. // If we simplify during algebra generation, it changes the SPARQL for OPTIONAL {{ FILTER }} // The {{}} results in (join unit (filter ...)) the filter is not moved // into the LeftJoin. static final private boolean applySimplification = true ; // False allows raw algebra to be generated (testing) static final private boolean simplifyTooEarlyInAlgebraGeneration = false ; // False is the correct setting. /** * Create a new generator * @param context Context */ public AlgebraGenerator(Context context) { this (context != null ? context : ARQ.getContext().copy(), 0) ; } /** * Create a new generator */ public AlgebraGenerator() { this(null) ; } protected AlgebraGenerator(Context context, int depth) { this.context = context ; this.subQueryDepth = depth ; } //-- Public operations. Do not call recursively (call compileElement). // These operations apply the simplification step which is done, once, at the end. /** * Compile a query * <p> * <strong>DO NOT</strong> call recursively * </p> * @param query Query to compile * @return Compiled algebra */ public Op compile(Query query) { Op op = compile(query.getQueryPattern()) ; // Not compileElement - may need to apply simplification. op = compileModifiers(query, op) ; return op ; } protected static Transform simplify = new TransformSimplify() ; /** * Compile any structural element * @param elt Element * @return Compiled algebra */ public Op compile(Element elt) { Op op = compileElement(elt) ; Op op2 = op ; if ( ! simplifyTooEarlyInAlgebraGeneration && applySimplification && simplify != null ) op2 = simplify(op) ; return op2; } protected static Op simplify(Op op) { return Transformer.transform(simplify, op) ; } // This is the operation to call for recursive application. protected Op compileElement(Element elt) { if ( elt instanceof ElementGroup ) return compileElementGroup((ElementGroup)elt) ; if ( elt instanceof ElementUnion ) return compileElementUnion((ElementUnion)elt) ; if ( elt instanceof ElementNamedGraph ) return compileElementGraph((ElementNamedGraph)elt) ; if ( elt instanceof ElementService ) return compileElementService((ElementService)elt) ; // This is only here for queries built programmatically // (triple patterns not in a group) if ( elt instanceof ElementTriplesBlock ) return compileBasicPattern(((ElementTriplesBlock)elt).getPattern()) ; // Ditto. if ( elt instanceof ElementPathBlock ) return compilePathBlock(((ElementPathBlock)elt).getPattern()) ; if ( elt instanceof ElementSubQuery ) return compileElementSubquery((ElementSubQuery)elt) ; if ( elt instanceof ElementData ) return compileElementData((ElementData)elt) ; if ( elt == null ) return OpNull.create() ; return compileUnknownElement(elt, "compile(Element)/Not a structural element: "+Lib.className(elt)); } /** * Future extension point to allow the algebra generator to be more easily extended to understand new {@link Element} implementations * that user defined language extensions may introduce. * <p> * This default implementation will throw an error * </p> * @param elt Element * @param error Error message if unable to compile the given element type * @return Algebra */ protected Op compileUnknownElement(Element elt, String error) { broken(error) ; return null; } //Produce the algebra for a single group. //<a href="http://www.w3.org/TR/rdf-sparql-query/#sparqlQuery">Translation to the SPARQL Algebra</a> // // Step : (URI resolving and triple pattern syntax forms) was done during parsing // Step : Collection FILTERS [prepareGroup] // Step : (Paths) e.g. simple links become triple patterns. Done later in [compileOneInGroup] // Step : (BGPs) Merge PathBlocks - these are SPARQL 1.1's TriplesBlock [prepareGroup] // Step : (BIND/LET) Associate with BGP [??] // Step : (Groups and unions) Was done during parsing to get ElementUnion. // Step : Graph Patterns [compileOneInGroup] // Step : Filters [here] // Simplification: Done later // If simplification is done now, it changes OPTIONAL { { ?x :p ?w . FILTER(?w>23) } } because it removes the // (join Z (filter...)) that in turn stops the filter getting moved into the LeftJoin. // It need a depth of 2 or more {{ }} for this to happen. protected Op compileElementGroup(ElementGroup groupElt) { Pair<List<Expr>, List<Element>> pair = prepareGroup(groupElt) ; List<Expr> filters = pair.getLeft() ; List<Element> groupElts = pair.getRight() ; // Compile the consolidated group elements. // "current" is the completed part only - there may be thing pushed into the accumulator. Op current = OpTable.unit() ; Deque<Op> acc = new ArrayDeque<>() ; for ( Element elt : groupElts ) { if ( elt != null ) { current = compileOneInGroup( elt, current, acc ); } } // Deal with any remaining ops. //current = joinOpAcc(current, acc) ; if ( filters != null ) { // Put filters round the group. for ( Expr expr : filters ) current = OpFilter.filter(expr, current) ; } return current ; } /* Extract filters, merge adjacent BGPs, do BIND. * When extracting filters, BGP or PathBlocks may become adjacent * so merge them into one. * Return a list of elements with any filters at the end. */ protected Pair<List<Expr>, List<Element>> prepareGroup(ElementGroup groupElt) { List<Element> groupElts = new ArrayList<>() ; PathBlock currentPathBlock = null ; List<Expr> filters = null ; for (Element elt : groupElt.getElements() ) { if ( ! fixedFilterPosition && elt instanceof ElementFilter ) { // For fixed position filters, drop through to general element processing. // It's also illegal SPARQL - filters operate over the whole group. ElementFilter f = (ElementFilter)elt ; if ( filters == null ) filters = new ArrayList<>() ; filters.add(f.getExpr()) ; // Collect filters but do not place them yet. continue ; } // The parser does not generate ElementTriplesBlock (SPARQL 1.1) // but SPARQL 1.0 does and also we cope for programmatically built queries if ( elt instanceof ElementTriplesBlock ) { ElementTriplesBlock etb = (ElementTriplesBlock)elt ; if ( currentPathBlock == null ) { ElementPathBlock etb2 = new ElementPathBlock() ; currentPathBlock = etb2.getPattern() ; groupElts.add(etb2) ; } for ( Triple t : etb.getPattern()) currentPathBlock.add(new TriplePath(t)) ; continue ; } // To PathLib if ( elt instanceof ElementPathBlock ) { ElementPathBlock epb = (ElementPathBlock)elt ; if ( currentPathBlock == null ) { ElementPathBlock etb2 = new ElementPathBlock() ; currentPathBlock = etb2.getPattern() ; groupElts.add(etb2) ; } currentPathBlock.addAll(epb.getPattern()) ; continue ; } // else // Not BGP, path or filters. // Clear any BGP-related triple accumulators. currentPathBlock = null ; // Add this element groupElts.add(elt) ; } return Pair.create(filters, groupElts) ; } // /** Flush the op accumulator - and clear it */ // private void accumulate(Deque<Op> acc, Op op) { acc.addLast(op) ; } // // /** Accumulate stored ops, return unit if none. */ // private Op popAccumulated(Deque<Op> acc) // { // if ( acc.size() == 0 ) // return OpTable.unit() ; // // Op joined = null ; // // First first to last. // for ( Op op : acc ) // joined = OpJoin.create(joined,op) ; // acc.clear() ; // return joined ; // } // // /** Join stored ops to the current state */ // private Op joinOpAcc(Op current, Deque<Op> acc) // { // if ( acc.size() == 0 ) return current ; // Op joined = current ; // // First first to last. // for ( Op op : acc ) // joined = OpJoin.create(joined,op) ; // acc.clear() ; // return joined ; // } protected Op compileOneInGroup(Element elt, Op current, Deque<Op> acc) { // // Coming into the general block. // if ( elt instanceof ElementTriplesBlock ) // { // ElementTriplesBlock etb = (ElementTriplesBlock)elt ; // Op op = compileBasicPattern(etb.getPattern()) ; // return join(current, op) ; // } // // if ( elt instanceof ElementPathBlock ) // { // ElementPathBlock epb = (ElementPathBlock)elt ; // Op op = compilePathBlock(epb.getPattern()) ; // return join(current, op) ; // } // // Elements that group so far and evaluate over that. if ( elt instanceof ElementAssign ) { ElementAssign assign = (ElementAssign)elt ; return OpAssign.assign(current, assign.getVar(), assign.getExpr()) ; } if ( elt instanceof ElementBind ) { ElementBind bind = (ElementBind)elt ; return OpExtend.create(current, bind.getVar(), bind.getExpr()) ; } if ( elt instanceof ElementOptional ) { ElementOptional eltOpt = (ElementOptional)elt ; return compileElementOptional(eltOpt, current) ; } if ( elt instanceof ElementMinus ) { ElementMinus elt2 = (ElementMinus)elt ; Op op = compileElementMinus(current, elt2) ; return op ; } // All elements that simply "join" into the algebra. if ( elt instanceof ElementGroup || elt instanceof ElementNamedGraph || elt instanceof ElementService || elt instanceof ElementUnion || elt instanceof ElementSubQuery || elt instanceof ElementData || elt instanceof ElementTriplesBlock || elt instanceof ElementPathBlock ) { Op op = compileElement(elt) ; return join(current, op) ; } // Specials. if ( elt instanceof ElementExists ) { ElementExists elt2 = (ElementExists)elt ; Op op = compileElementExists(current, elt2) ; return op ; } if ( elt instanceof ElementNotExists ) { ElementNotExists elt2 = (ElementNotExists)elt ; Op op = compileElementNotExists(current, elt2) ; return op ; } // Filters were collected together by prepareGroup // This only handles filters left in place by some magic. if ( elt instanceof ElementFilter ) { ElementFilter f = (ElementFilter)elt ; return OpFilter.filter(f.getExpr(), current) ; } // // SPARQL 1.1 UNION -- did not make it into SPARQL // if ( elt instanceof ElementUnion ) // { // ElementUnion elt2 = (ElementUnion)elt ; // if ( elt2.getElements().size() == 1 ) // { // Op op = compileElementUnion(current, elt2) ; // return op ; // } // } return compileUnknownElement(elt, "compile/Element not recognized: "+Lib.className(elt)); } protected Op compileElementUnion(ElementUnion el) { Op current = null ; for ( Element subElt: el.getElements() ) { Op op = compileElement(subElt) ; current = union(current, op) ; } return current ; } protected Op compileElementNotExists(Op current, ElementNotExists elt2) { Op op = compile(elt2.getElement()) ; // "compile", not "compileElement" -- do simpliifcation Expr expr = new E_Exists(elt2, op) ; expr = new E_LogicalNot(expr) ; return OpFilter.filter(expr, current) ; } protected Op compileElementExists(Op current, ElementExists elt2) { Op op = compile(elt2.getElement()) ; // "compile", not "compileElement" -- do simpliifcation Expr expr = new E_Exists(elt2, op) ; return OpFilter.filter(expr, current) ; } protected Op compileElementMinus(Op current, ElementMinus elt2) { Op op = compile(elt2.getMinusElement()) ; Op opMinus = OpMinus.create(current, op) ; return opMinus ; } protected Op compileElementData(ElementData elt) { return OpTable.create(elt.getTable()) ; } protected Op compileElementUnion(Op current, ElementUnion elt2) { // Special SPARQL 1.1 case. Op op = compile(elt2.getElements().get(0)) ; Op opUnion = OpUnion.create(current, op) ; return opUnion ; } protected Op compileElementOptional(ElementOptional eltOpt, Op current) { Element subElt = eltOpt.getOptionalElement() ; Op op = compileElement(subElt) ; ExprList exprs = null ; if ( op instanceof OpFilter ) { OpFilter f = (OpFilter)op ; //f = OpFilter.tidy(f) ; // Collapse filter(filter(..)) Op sub = f.getSubOp() ; if ( sub instanceof OpFilter ) broken("compile/Optional/nested filters - unfinished") ; exprs = f.getExprs() ; op = sub ; } current = OpLeftJoin.create(current, op, exprs) ; return current ; } protected Op compileBasicPattern(BasicPattern pattern) { return new OpBGP(pattern) ; } protected Op compilePathBlock(PathBlock pathBlock) { // Empty path block : the parser does not generate this case. if ( pathBlock.size() == 0 ) return OpTable.unit() ; // Always turns the most basic paths to triples. return PathLib.pathToTriples(pathBlock) ; } protected Op compileElementGraph(ElementNamedGraph eltGraph) { Node graphNode = eltGraph.getGraphNameNode() ; Op sub = compileElement(eltGraph.getElement()) ; return new OpGraph(graphNode, sub) ; } protected Op compileElementService(ElementService eltService) { Node serviceNode = eltService.getServiceNode() ; Op sub = compileElement(eltService.getElement()) ; return new OpService(serviceNode, sub, eltService, eltService.getSilent()) ; } protected Op compileElementSubquery(ElementSubQuery eltSubQuery) { AlgebraGenerator gen = new AlgebraGenerator(context, subQueryDepth+1) ; return gen.compile(eltSubQuery.getQuery()) ; } /** Compile query modifiers */ protected Op compileModifiers(Query query, Op pattern) { /* The modifier order in algebra is: * * Limit/Offset * Distinct/reduce * project * OrderBy * Bindings * having * select expressions * group */ // Preparation: sort SELECT clause into assignments and projects. VarExprList projectVars = query.getProject() ; VarExprList exprs = new VarExprList() ; // Assignments to be done. List<Var> vars = new ArrayList<>() ; // projection variables Op op = pattern ; // ---- GROUP BY if ( query.hasGroupBy() ) { // When there is no GroupBy but there are some aggregates, it's a group of no variables. op = new OpGroup(op, query.getGroupBy(), query.getAggregators()) ; } //---- Assignments from SELECT and other places (so available to ORDER and HAVING) // Now do assignments from expressions // Must be after "group by" has introduced it's variables. // Look for assignments in SELECT expressions. if ( ! projectVars.isEmpty() && ! query.isQueryResultStar()) { // Don't project for QueryResultStar so initial bindings show // through in SELECT * if ( projectVars.size() == 0 && query.isSelectType() ) Log.warn(this,"No project variables") ; // Separate assignments and variable projection. for ( Var v : query.getProject().getVars() ) { Expr e = query.getProject().getExpr(v) ; if ( e != null ) { Expr e2 = ExprLib.replaceAggregateByVariable(e) ; exprs.add(v, e2) ; } // Include in project vars.add(v) ; } } // ---- Assignments from SELECT and other places (so available to ORDER and HAVING) for ( Var v : exprs.getVars() ) { Expr e = exprs.getExpr(v) ; op = OpExtend.create(op, v, e) ; } // ---- HAVING if ( query.hasHaving() ) { for (Expr expr : query.getHavingExprs()) { // HAVING expression to refer to the aggregate via the variable. Expr expr2 = ExprLib.replaceAggregateByVariable(expr) ; op = OpFilter.filter(expr2 , op) ; } } // ---- VALUES if ( query.hasValues() ) { Table table = TableFactory.create(query.getValuesVariables()) ; for ( Binding binding : query.getValuesData() ) table.addBinding(binding) ; OpTable opTable = OpTable.create(table) ; op = OpJoin.create(op, opTable) ; } // ---- ToList if ( context.isTrue(ARQ.generateToList) ) // Listify it. op = new OpList(op) ; // ---- ORDER BY if ( query.getOrderBy() != null ) { List<SortCondition> scList = new ArrayList<>() ; // Aggregates in ORDER BY for ( SortCondition sc : query.getOrderBy() ) { Expr e = sc.getExpression() ; e = ExprLib.replaceAggregateByVariable(e) ; scList.add(new SortCondition(e, sc.getDirection())) ; } op = new OpOrder(op, scList) ; } // ---- PROJECT // No projection => initial variables are exposed. // Needed for CONSTRUCT and initial bindings + SELECT * if ( vars.size() > 0 ) op = new OpProject(op, vars) ; // ---- DISTINCT if ( query.isDistinct() ) op = OpDistinct.create(op) ; // ---- REDUCED if ( query.isReduced() ) op = OpReduced.create(op) ; // ---- LIMIT/OFFSET if ( query.hasLimit() || query.hasOffset() ) op = new OpSlice(op, query.getOffset() /*start*/, query.getLimit()/*length*/) ; return op ; } // -------- protected static Op join(Op current, Op newOp) { if ( simplifyTooEarlyInAlgebraGeneration && applySimplification ) return OpJoin.createReduce(current, newOp) ; return OpJoin.create(current, newOp) ; } protected Op sequence(Op current, Op newOp) { return OpSequence.create(current, newOp) ; } protected Op union(Op current, Op newOp) { return OpUnion.create(current, newOp) ; } protected final void broken(String msg) { //System.err.println("AlgebraGenerator: "+msg) ; throw new ARQInternalErrorException(msg) ; } }