blob: f97929a729dec0c94e036f42b7d50a817614ead3 [file] [log] [blame]
/*
* 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.optimize ;
import java.util.* ;
import org.apache.jena.atlas.lib.Lib ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.sparql.algebra.Op ;
import org.apache.jena.sparql.algebra.OpVars ;
import org.apache.jena.sparql.algebra.TransformCopy ;
import org.apache.jena.sparql.algebra.op.* ;
import org.apache.jena.sparql.core.BasicPattern ;
import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.expr.Expr ;
import org.apache.jena.sparql.expr.ExprLib ;
import org.apache.jena.sparql.expr.ExprList ;
import org.apache.jena.sparql.expr.ExprVars;
import org.apache.jena.sparql.pfunction.PropFuncArg ;
import org.apache.jena.sparql.util.VarUtils ;
/**
* Rewrite an algebra expression to put filters as close to their bound
* variables.
* <p>Process BGP (whether triples or quads) is left as a separate step (but after this transform)
* because it can desirable to reorder the BGP before placing filters,
* or afterwards.
*/
public class TransformFilterPlacement extends TransformCopy {
public static class Placement {
final public Op op ;
final public ExprList unplaced ;
public Placement(Op op, ExprList remaining) {
this.op = op ;
this.unplaced = remaining ;
}
@Override
public String toString() { return ""+op+" : "+unplaced ; }
@Override
public int hashCode() {
return 31*Lib.hashCodeObject(op,1) + Lib.hashCodeObject(unplaced) ;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) return true ;
if ( obj == null ) return false ;
if ( getClass() != obj.getClass() ) return false ;
Placement other = (Placement)obj ;
return Objects.equals(op, other.op) && Objects.equals(unplaced, other.unplaced) ;
}
}
// Empty, immutable ExprList
static final ExprList emptyList = ExprList.emptyList ;
// No placement performed
static final Placement noChangePlacement = null ; //new Placement(null, null) ;
private static Placement result(Op op, ExprList remaining) {
if ( op == null )
return noChangePlacement ;
return new Placement(op, remaining) ;
}
private Placement resultNoChange(Op original) {
return noChangePlacement ;
}
private boolean isNoChange(Placement placement) {
return ! isChange(placement) ;
}
private boolean isChange(Placement placement) {
return placement != noChangePlacement ;
}
/** Apply filter placement to a BGP */
public static Op transform(ExprList exprs, BasicPattern bgp) {
Placement placement = placeBGP(exprs, bgp) ;
Op op = ( placement == null ) ? new OpBGP(bgp) : placement.op ;
if ( placement != null )
op = buildFilter(placement.unplaced, op) ;
return op ;
}
/** Apply filter placement to a named graph BGP */
public static Op transform(ExprList exprs, Node graphNode, BasicPattern bgp) {
Placement placement = placeQuadPattern(exprs, graphNode, bgp) ;
Op op = ( placement == null ) ? new OpQuadPattern(graphNode, bgp) : placement.op ;
if ( placement != null )
op = buildFilter(placement.unplaced, op) ;
return op ;
}
private final boolean includeBGPs ;
public TransformFilterPlacement() { this(true) ; }
public TransformFilterPlacement(boolean includeBGPs)
{ this.includeBGPs = includeBGPs ; }
/** Operation exposes the filter placement mechanism
* so that investigation of filter placement issues
* can be done from outside this class.
* <i>Do not use in application code : subject to removal or change at any time.</i>
*/
public static Placement filterPlacement$(ExprList exprs, Op op) {
TransformFilterPlacement t = new TransformFilterPlacement() ;
return t.transform(exprs, op) ;
}
@Override
public Op transform(OpFilter opFilter, Op x) {
ExprList exprs = opFilter.getExprs() ;
// Extract any expressions with "nasty" cases (RAND, UUID, STRUUID and BNODE)
// which are not true functions (they return a different value every call so
// number of calls matters. NOW is safe (returns a fixed time point for the whole
// query.
// Phase one - check to see if work needed.
ExprList exprs2 = null ;
for ( Expr expr : exprs ) {
if ( ! ExprLib.isStable(expr) ) {
if ( exprs2 == null )
exprs2 = new ExprList() ;
exprs2.add(expr) ;
}
}
// Phase 2 - if needed, split.
if ( exprs2 != null ) {
ExprList exprs1 = new ExprList() ;
for ( Expr expr : exprs ) {
// We are assuming fixup is rare.
if ( ExprLib.isStable(expr) )
exprs1.add(expr) ;
}
exprs = exprs1 ;
}
Placement placement = transform(exprs, x) ;
if ( isNoChange(placement) )
// Didn't do anything.
return super.transform(opFilter, x) ;
Op op = buildFilter(placement) ;
if ( exprs2 != null )
// Add back the non-deterministic expressions
op = OpFilter.filterBy(exprs2, op );
return op ;
}
/** Transform and always at least wrap the op with the exprs */
private Op transformOpAlways(ExprList exprs, Op x) {
Placement placement = transform(exprs, x) ;
if ( isNoChange(placement) )
return buildFilter(exprs, x) ;
return buildFilter(placement) ;
}
/** Transform and return a placement */
private Placement transform(ExprList exprs, Op input) {
// Dispatch by visitor??
Placement placement = noChangePlacement ;
if ( input instanceof OpBGP )
placement = placeOrWrapBGP(exprs, (OpBGP)input) ;
else if ( input instanceof OpQuadPattern )
placement = placeOrWrapQuadPattern(exprs, (OpQuadPattern)input) ;
else if ( input instanceof OpSequence )
placement = placeSequence(exprs, (OpSequence)input) ;
else if ( input instanceof OpJoin )
placement = placeJoin(exprs, (OpJoin)input) ;
else if ( input instanceof OpConditional )
placement = placeConditional(exprs, (OpConditional)input) ;
else if ( input instanceof OpDisjunction )
placement = placeDisjunction(exprs, (OpDisjunction)input) ;
else if ( input instanceof OpLeftJoin )
placement = placeLeftJoin(exprs, (OpLeftJoin)input) ;
else if ( input instanceof OpFilter )
placement = placeFilter(exprs, (OpFilter)input) ;
else if ( input instanceof OpUnion )
placement = placeUnion(exprs, (OpUnion)input) ;
else if ( input instanceof OpPropFunc )
placement = placePropertyFunction(exprs, (OpPropFunc)input) ;
else if ( input instanceof OpProcedure )
placement = placeProcedure(exprs, (OpProcedure)input) ;
// These are operations where changing the order of operations
// does not in itself make a difference but enables expressions
// to be pushed down to where they might make a difference.
// Otherwise these would be blockers.
else if ( input instanceof OpExtend )
placement = placeExtend(exprs, (OpExtend)input) ;
else if ( input instanceof OpAssign )
placement = placeAssign(exprs, (OpAssign)input) ;
// Modifiers
// else if ( input instanceof OpGroup ) {
// placement = noChangePlacement ;
// }
// else if ( input instanceof OpSlice ) {
// // Not sure what the best choice is here.
// placement = noChangePlacement ;
// }
// else if ( input instanceof OpTopN ) {
// // Not sure what the best choice is here.
// placement = noChangePlacement ;
// }
else if ( input instanceof OpProject )
placement = placeProject(exprs, (OpProject)input) ;
else if ( input instanceof OpDistinctReduced )
placement = placeDistinctReduced(exprs, (OpDistinctReduced)input) ;
else if ( input instanceof OpTable )
placement = placeTable(exprs, (OpTable)input) ;
return placement ;
}
private Placement placeFilter(ExprList exprs, OpFilter input) {
// If input.getSubOp is itself a filter, it has already been
// processed because the Transform is applied bottom-up.
// We must not let the filter's expressions go back as "unplaced"
// as they are scoped to the input and if "unplaced" are available
// out of that scope.
Op op = input.getSubOp() ;
ExprList exprsInner = input.getExprs() ;
ExprList exprsOuter = exprs ;
// Outer
Placement p = transform(exprsOuter, input.getSubOp()) ;
if ( isChange(p) ) {
op = p.op ;
exprsOuter = p.unplaced ;
}
// Put inner round the modified Op.
// If op is also a filter, a single filter is created with
// exprsInner now after placed filters.
// ("after" means later in the exprList of the filter).
Op f = OpFilter.filterBy(exprsInner, op) ;
return new Placement(f, exprsOuter) ;
}
private Placement placeOrWrapBGP(ExprList exprs, OpBGP x) {
return placeOrWrapBGP(exprs, x.getPattern()) ;
}
/** Either just wrap the BGP with possible expressions or also consider breaking up the BGP */
private Placement placeOrWrapBGP(ExprList exprsIn, BasicPattern pattern) {
if ( includeBGPs )
return placeBGP(exprsIn, pattern) ;
else
return wrapBGP(exprsIn, pattern) ;
}
// See also placeQuadPattern.
//
// An improvement might be to put any filters that apply to exactly one triple
// directly on the triple pattern. At the moment, the filter is put over
// the block leading up to the triple pattern.
private static Placement placeBGP(ExprList exprsIn, BasicPattern pattern) {
ExprList exprs = ExprList.copy(exprsIn) ;
Set<Var> patternVarsScope = new HashSet<>() ;
// Any filters that depend on no variables.
Op op = insertAnyFilter$(exprs, patternVarsScope, null) ;
for (Triple triple : pattern) {
OpBGP opBGP = getBGP(op) ;
if ( opBGP == null ) {
// Last thing was not a BGP (so it likely to be a filter)
// Need to pass the results from that into the next triple.
opBGP = new OpBGP() ;
op = OpSequence.create(op, opBGP) ;
}
opBGP.getPattern().add(triple) ;
// Update variables in scope.
VarUtils.addVarsFromTriple(patternVarsScope, triple) ;
op = insertAnyFilter$(exprs, patternVarsScope, op) ;
}
return result(op, exprs) ;
}
/** Wrap the Basic Pattern with any applicable expressions from the ExprList
* but do not break up the BasicPattern in any way.
*/
private Placement wrapBGP(ExprList exprsIn, BasicPattern pattern) {
Set<Var> vs = new HashSet<>();
VarUtils.addVars(vs, pattern);
ExprList pushed = new ExprList();
ExprList unpushed = new ExprList();
for (Expr e : exprsIn) {
Set<Var> eVars = e.getVarsMentioned();
if (vs.containsAll(eVars))
pushed.add(e);
else
unpushed.add(e);
}
// Can't push anything into a filter around this BGP
if (pushed.size() == 0)
return noChangePlacement ;
// Safe to place some conditions around the BGP
Op opx = OpFilter.filterBy(pushed, new OpBGP(pattern)) ;
return result(opx, unpushed);
}
/** Find the current OpBGP, or return null. */
private static OpBGP getBGP(Op op) {
if ( op instanceof OpBGP )
return (OpBGP)op ;
if ( op instanceof OpSequence ) {
// Is last in OpSequence an BGP?
OpSequence opSeq = (OpSequence)op ;
List<Op> x = opSeq.getElements() ;
if ( x.size() > 0 ) {
Op opTop = x.get(x.size() - 1) ;
if ( opTop instanceof OpBGP )
return (OpBGP)opTop ;
// Drop through
}
}
// Can't find.
return null ;
}
private Placement placeOrWrapQuadPattern(ExprList exprs, OpQuadPattern pattern) {
return placeOrWrapQuadPattern(exprs, pattern.getGraphNode(), pattern.getBasicPattern()) ;
}
private Placement placeOrWrapQuadPattern(ExprList exprsIn, Node graphNode, BasicPattern pattern) {
if ( includeBGPs )
return placeQuadPattern(exprsIn, graphNode, pattern) ;
else
return wrapQuadPattern(exprsIn, graphNode, pattern) ;
}
private static Placement placeQuadPattern(ExprList exprsIn, Node graphNode, BasicPattern pattern) {
ExprList exprs = ExprList.copy(exprsIn) ;
Set<Var> patternVarsScope = new HashSet<>() ;
// Any filters that depend on no variables.
Op op = insertAnyFilter$(exprs, patternVarsScope, null) ;
if ( Var.isVar(graphNode) ) {
// Add in the graph node of the quad block.
VarUtils.addVar(patternVarsScope, Var.alloc(graphNode)) ;
}
for (Triple triple : pattern) {
OpQuadPattern opQuad = getQuads(op) ;
if ( opQuad == null ) {
opQuad = new OpQuadPattern(graphNode, new BasicPattern()) ;
op = OpSequence.create(op, opQuad) ;
}
opQuad.getBasicPattern().add(triple) ;
// Update variables in scope.
VarUtils.addVarsFromTriple(patternVarsScope, triple) ;
op = insertAnyFilter$(exprs, patternVarsScope, op) ;
}
return result(op, exprs) ;
}
/** Wrap the Graph node, Basic Pattern with any applicable expressions from the ExprList
* but do not break up the BasicPattern in any way.
*/
private static Placement wrapQuadPattern(ExprList exprsIn, Node graphNode, BasicPattern pattern) {
Set<Var> vs = new HashSet<>();
VarUtils.addVars(vs, pattern);
if (Var.isVar(graphNode))
vs.add(Var.alloc(graphNode));
ExprList pushed = new ExprList();
ExprList unpushed = new ExprList();
for (Expr e : exprsIn) {
Set<Var> eVars = e.getVarsMentioned();
if (vs.containsAll(eVars)) {
pushed.add(e);
} else {
unpushed.add(e);
}
}
// Can't push anything into a filter around this quadpattern
if (pushed.size() == 0) return null;
// Safe to place some conditions around the quadpattern
return new Placement(OpFilter.filterBy(pushed, new OpQuadPattern(graphNode, pattern)), unpushed);
}
/** Find the current OpQuadPattern, or return null. */
private static OpQuadPattern getQuads(Op op) {
if ( op instanceof OpQuadPattern )
return (OpQuadPattern)op ;
if ( op instanceof OpSequence ) {
// Is last in OpSequence an BGP?
OpSequence opSeq = (OpSequence)op ;
List<Op> x = opSeq.getElements() ;
if ( x.size() > 0 ) {
Op opTop = x.get(x.size() - 1) ;
if ( opTop instanceof OpQuadPattern )
return (OpQuadPattern)opTop ;
// Drop through
}
}
return null ;
}
private Placement placePropertyFunction(ExprList exprsIn, OpPropFunc input) {
Set<Var> argVars = new HashSet<>() ;
PropFuncArg.addVars(argVars, input.getSubjectArgs()) ;
PropFuncArg.addVars(argVars, input.getObjectArgs()) ;
return placePropertyFunctionProcedure(exprsIn, argVars, input) ;
}
private Placement placeProcedure(ExprList exprsIn, OpProcedure input) {
Set<Var> argVars = new HashSet<>() ;
ExprVars.varsMentioned(argVars, input.getArgs());
return placePropertyFunctionProcedure(exprsIn, argVars, input) ;
}
private Placement placePropertyFunctionProcedure(ExprList exprsIn, Set<Var> varScope, Op1 op) {
ExprList exprListPlaceable = new ExprList() ;
ExprList exprListRetain = new ExprList() ;
for ( Expr expr : exprsIn ) {
Set<Var> mentioned = expr.getVarsMentioned() ;
if ( Collections.disjoint(varScope, mentioned) )
exprListPlaceable.add(expr);
else
exprListRetain.add(expr);
}
if ( ! exprListPlaceable.isEmpty() ) {
Placement p = transform(exprListPlaceable, op.getSubOp()) ;
if ( isNoChange(p) )
return resultNoChange(op);
Op newOp = op.copy(p.op) ;
p.unplaced.addAll(exprListRetain);
return result(newOp, p.unplaced) ;
}
return resultNoChange(op);
}
/*
* A Sequence is a number of joins where scoping means the LHS can be
* substituted into the right, i.e. there are no scoping issues. Assuming a
* substitution join is going to be done, filtering once as soon as the
* accumulated variables cover the filter is a good thing to do. It is
* effectively pusing on the left side only - the right side, by
* substitution, will never see the variables. The variable can not be
* reintroduced (it will have been renamed away if it's the same name,
* different scope, which is a different variable with the same name in the
* orginal query).
*/
private Placement placeSequence(ExprList exprsIn, OpSequence opSequence) {
ExprList exprs = ExprList.copy(exprsIn) ;
Set<Var> varScope = new HashSet<>() ;
List<Op> ops = opSequence.getElements() ;
Op op = null ;
for ( Op op1 : ops )
{
op = insertAnyFilter$( exprs, varScope, op );
Op seqElt = op1;
Placement p = transform( exprs, seqElt );
if ( isChange(p) ) {
exprs = p.unplaced;
seqElt = p.op;
}
varScope.addAll( fixedVars( seqElt ) );
op = OpSequence.create( op, seqElt );
}
return result(op, exprs) ;
}
// Whether to push a covered filter into the RHS even if pushed into the LHS.
// If this is run after join->sequence, then this is good to do.
static boolean pushRightAsWellAsLeft = true ;
private Placement placeJoin(ExprList exprs, OpJoin opJoin) {
Op left = opJoin.getLeft() ;
Op right = opJoin.getRight() ;
Collection<Var> leftVars = fixedVars(left) ;
Collection<Var> rightVars = fixedVars(right) ;
// More sophisticated - consider optionl variables as well.
// This code check the two ways to get fixed vars yields the same
// and it does for the test suite.
// //---
// VarFinder vfLeft = VarFinder.process(left) ;
// VarFinder vfRight = VarFinder.process(right) ;
// if ( ! CollectionUtils.sameElts(leftVars, vfLeft.getFixed() ) )
// System.err.println("Left: "+leftVars+" : "+vfLeft.getFixed() ) ;
// if ( ! CollectionUtils.sameElts(rightVars, vfRight.getFixed() ) )
// System.err.println("Right: "+rightVars+" : "+vfRight.getFixed() ) ;
// //---
ExprList unpushed = new ExprList() ;
ExprList pushLeft = new ExprList() ;
ExprList pushRight = new ExprList() ;
for (Expr expr : exprs) {
Set<Var> vars = expr.getVarsMentioned() ;
boolean pushed = false ;
if ( leftVars.containsAll(vars) ) {
pushLeft.add(expr) ;
pushed = true ;
}
if ( pushed && ! pushRightAsWellAsLeft )
continue ;
// If left only, make this "else if" of left test, remove "continue"
if ( rightVars.containsAll(vars) ) {
// Push right
pushRight.add(expr) ;
pushed = true ;
}
if ( !pushed )
unpushed.add(expr) ;
}
if ( pushLeft.isEmpty() && pushRight.isEmpty() )
return null ;
Op opLeftNew = left ;
if ( !pushLeft.isEmpty() )
opLeftNew = transformOpAlways(pushLeft, opLeftNew) ;
Op opRightNew = right ;
if ( !pushRight.isEmpty() )
opRightNew = transformOpAlways(pushRight, opRightNew) ;
Op op = OpJoin.create(opLeftNew, opRightNew) ;
return result(op, unpushed) ;
}
/* A conditional is left join without scoping complications. */
private Placement placeConditional(ExprList exprs, OpConditional opConditional) {
Op left = opConditional.getLeft() ;
Op right = opConditional.getRight() ;
Placement nLeft = transform(exprs, left) ;
if ( isNoChange(nLeft) )
return result(opConditional, exprs) ;
Op op = new OpConditional(nLeft.op, right) ;
return result(op, nLeft.unplaced) ;
}
private Placement placeLeftJoin(ExprList exprs, OpLeftJoin opLeftJoin) {
// Push LHS only. RHS may result in no matches - is that safe to push into?
Op left = opLeftJoin.getLeft() ;
Op right = opLeftJoin.getRight() ;
Placement nLeft = transform(exprs, left) ;
if ( isNoChange(nLeft) )
return result(opLeftJoin, exprs) ;
Op op = OpLeftJoin.create(nLeft.op, right, opLeftJoin.getExprs()) ;
return result(op, nLeft.unplaced) ;
}
private Placement placeUnion(ExprList exprs, OpUnion input) {
if ( false ) {
// Push into both sides without thinking.
// Left as a safety fallback.
Op left = input.getLeft() ;
Placement pLeft = transform(exprs, left) ;
Op right = input.getRight() ;
Placement pRight = transform(exprs, right) ;
if ( pLeft != null && ! pLeft.unplaced.isEmpty() )
return noChangePlacement ;
if ( pRight != null && ! pRight.unplaced.isEmpty() )
return noChangePlacement ;
// Must be guarded by the above.
left = transformOpAlways(exprs, left) ;
right = transformOpAlways(exprs, right) ;
Op op2 = OpUnion.create(left, right) ;
return result(op2, emptyList) ;
}
Op left = input.getLeft() ;
Placement pLeft = transform(exprs, left) ;
Op right = input.getRight() ;
Placement pRight = transform(exprs, right) ;
// If it's placed in neither arm it should be passed back out for placement.
//
// If it's done in both arms, then expression can be left pushed in
// and not passed back out for placement.
// If it is done in one arm and not the other, then it can be left pushed
// in but needs to be redone for the other arm as if it were no placed at all.
// A filter applied twice is safe.
ExprList exprs2 = null ;
for ( Expr expr : exprs ) {
boolean unplacedLeft = ( isNoChange(pLeft) || pLeft.unplaced.getList().contains(expr) ) ;
boolean unplacedRight = ( isNoChange(pRight) || pRight.unplaced.getList().contains(expr) ) ;
// if ( unplacedLeft && unplacedRight ) {
// System.out.println("Unplaced: "+expr) ;
// } else if ( unplacedLeft ) {
// System.out.println("Unplaced(L): "+expr) ;
// } else if ( unplacedRight ) {
// System.out.println("Unplaced(R): "+expr) ;
// } else
// System.out.println("Placed(L+R): "+expr) ;
boolean placed = !unplacedLeft && !unplacedRight ;
if ( placed )
// Went into both arms - expression has been handled completely.
continue ;
if ( exprs2 == null )
exprs2 = new ExprList() ;
exprs2.add(expr) ;
}
Op newLeft = (pLeft == null ) ? left : pLeft.op ;
Op newRight = (pRight == null ) ? right : pRight.op ;
if ( exprs2 == null )
exprs2 = emptyList ;
//Op op2 = OpUnion.create(newLeft, newRight) ;
Op op2 = input.copy(newLeft, newRight) ;
return result(op2, exprs2) ;
}
private Placement placeDisjunction(ExprList exprs, OpDisjunction input) {
// Do on each arm.
// better (neater) would be to pass out exprs not placed anywhere.
// Combine with union.
if ( false ) {
// Push everything, always
// Left as a safty fall back.
List<Op> x = new ArrayList<>() ;
input.getElements().forEach(op->{
Placement p = transform(exprs, op) ;
if ( isNoChange(p) ) {
x.add(buildFilter(exprs, op)) ;
} else {
Op op1 = buildFilter(p) ;
x.add(op1) ;
}
});
return result(input.copy(x), emptyList) ;
}
// Don't push any expressions that aren't used in any of the arms of the disjunction.
// This is more about being tidy.
List<Expr> unplaced = new ArrayList<>(exprs.getList()) ;
//List<Placement> x = input.getElements().stream().map(op->transform(exprs, op)).collect(Collectors.toList()) ;
List<Placement> placements = new ArrayList<>(exprs.size()) ;
Boolean someChange = Boolean.FALSE ;
for ( Op op : input.getElements() ) {
Placement p = transform(exprs, op) ;
if ( isChange(p) ) {
unplaced.retainAll(p.unplaced.getList()) ;
someChange = Boolean.TRUE ;
} else
p = result(op, exprs) ;
placements.add(p) ;
};
if ( ! someChange )
return noChangePlacement ;
List<Expr> retained = new ArrayList<>(exprs.getList()) ;
retained.removeAll(unplaced) ;
// Mutate placements to remove the expres going outside.
List<Op> ops = new ArrayList<>(input.size()) ;
for ( Placement p : placements ) {
// No "noChange" at this point.
p.unplaced.getListRaw().removeAll(unplaced) ;
ops.add(buildFilter(p)) ;
} ;
return result(input.copy(ops), new ExprList(unplaced)) ;
}
private Placement placeExtend(ExprList exprs, OpExtend input) {
return processExtendAssign(exprs, input) ;
}
private Placement placeAssign(ExprList exprs, OpAssign input) {
return processExtendAssign(exprs, input) ;
}
/** Try to optimize (filter (extend ...)) , (filter (let ...)) */
private Placement processExtendAssign(ExprList exprs, OpExtendAssign input) {
// We assume that each (extend) and (assign) is usually in simple form -
// always one assignment. We cope with the general form (multiple
// assignments) but do not attempt reordering of assignments.
// There are three cases:
// 1 - expressions that can be pushed into the subop.
// 2 - expressions that are covered when the extend/assign has applied. [wrapping]
// 3 - expressions that are not covered even at the outermost level. [unplaced]
List<Var> vars1 = input.getVarExprList().getVars() ;
Op subOp = input.getSubOp() ;
// Case 1 : Do as much inner placement as possible.
ExprList remaining = exprs ;
Placement p = transform(exprs, input.getSubOp()) ;
if ( isChange(p) ) {
subOp = p.op ;
remaining = p.unplaced ;
}
// Case 2 : wrapping
// Case 3 : unplaced
// Variables in subop and introduced by (extend)/(assign)
Set<Var> subVars = OpVars.fixedVars(subOp) ;
subVars.addAll(input.getVarExprList().getVars()) ;
ExprList wrapping = new ExprList() ;
ExprList unplaced = new ExprList() ;
for ( Expr expr : remaining ) {
Set<Var> exprVars = expr.getVarsMentioned() ;
if ( subVars.containsAll(exprVars) )
wrapping.add(expr) ;
else
unplaced.add(expr) ;
}
Op result = input.copy(subOp) ;
if ( ! wrapping.isEmpty() )
result = OpFilter.filterBy(wrapping, result) ;
return result(result, unplaced) ;
}
private Placement placeProject(ExprList exprs, OpProject input) {
Collection<Var> varsProject = input.getVars() ;
ExprList pushed = new ExprList() ;
ExprList unpushed = new ExprList() ;
for ( Expr expr : exprs ) {
Set<Var> exprVars = expr.getVarsMentioned() ;
if ( varsProject.containsAll(exprVars) )
pushed.add(expr);
else
unpushed.add(expr) ;
}
if ( pushed.isEmpty() )
return resultNoChange(input) ;
// (filter (project ...)) ===> (project (filter ...))
return processSubOp1(pushed, unpushed, input) ;
}
// For a modifier without expressions (distinct, reduced), we could
// push that inside the modifier if that were all there was. But the
// expressions may be processed elsewhere in the overall algebra.
// Putting them inside the modifier would lock them here as they don't
// get returned in the Placement as "unplaced."
// This is the cause of JENA-874.
/* Complete processing for an Op1.
* Having split expressions into pushed and unpushed at this point,
* try to push "pushed" down further into the subOp.
*/
private Placement processSubOp1(ExprList pushed, ExprList unpushed, Op1 input) {
Op opSub = input.getSubOp() ;
Placement subPlacement = transform(pushed, opSub) ;
if ( isNoChange(subPlacement) ) {
// (Same as if a placement of the exprlist and op passed in is given).
// Didn't make any changes below, so add a filter for the 'pushed' and
// return a placement for the unpushed.
Op op1 = input.getSubOp() ;
if ( pushed != null &&! pushed.isEmpty() )
op1 = OpFilter.filterBy(pushed, op1) ;
Op op2 = input.copy(op1) ;
return result(op2, unpushed) ;
}
// We did make changes below. Add filter for these (which includes the
// "pushed" at this level, now in the p.op or left in p.unplaced.
Op op_a = OpFilter.filterBy(subPlacement.unplaced, subPlacement.op) ;
op_a = input.copy(op_a) ;
return result(op_a, unpushed) ;
}
private Placement placeDistinctReduced(ExprList exprs, OpDistinctReduced input) {
Op subOp = input.getSubOp() ;
Placement p = transform(exprs, subOp) ;
if ( isNoChange(p) )
// No effect - we do not manage to make a change.
return resultNoChange(input) ;
// Rebuild.
// We managed to place at least some expressions.
Op op = p.op ;
// Put back distinct/reduced
op = input.copy(op) ;
// Return with unplaced filters.
return result(op, p.unplaced) ;
}
private Placement placeTable(ExprList exprs, OpTable input) {
exprs = ExprList.copy(exprs) ;
Op op = insertAnyFilter$(exprs, input.getTable().getVars(), input) ;
return result(op, exprs) ;
}
private Set<Var> fixedVars(Op op) {
return OpVars.fixedVars(op) ;
}
/** For any expression now in scope, wrap the op with a filter. */
private static Op insertAnyFilter$(ExprList unplacedExprs, Collection<Var> patternVarsScope, Op op) {
for (Iterator<Expr> iter = unplacedExprs.iterator(); iter.hasNext();) {
Expr expr = iter.next() ;
// Cache
Set<Var> exprVars = expr.getVarsMentioned() ;
if ( patternVarsScope.containsAll(exprVars) ) {
if ( op == null )
op = OpTable.unit() ;
op = OpFilter.filter(expr, op) ;
iter.remove() ;
}
}
return op ;
}
private static <T> boolean disjoint(Collection<T> collection, Collection<T> possibleElts) {
return Collections.disjoint(collection, possibleElts);
}
/** Place expressions around an Op */
private static Op buildFilter(Placement placement) {
if ( placement == null )
return null ;
if ( placement.unplaced.isEmpty() )
return placement.op ;
return buildFilter(placement.unplaced, placement.op) ;
}
private static Op buildFilter(ExprList exprs, Op op) {
if ( exprs == null || exprs.isEmpty() )
return op ;
for ( Expr expr : exprs ) {
if ( op == null )
op = OpTable.unit() ;
op = OpFilter.filter(expr, op) ;
}
return op ;
}
}