| /* |
| * 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 org.apache.jena.sparql.algebra.Op ; |
| import org.apache.jena.sparql.algebra.TransformCopy ; |
| import org.apache.jena.sparql.algebra.op.* ; |
| |
| /** |
| * Optimizer that ensures that <strong>table empty</strong> is promoted as high |
| * up a query as is possible |
| * <p> |
| * <strong>table empty</strong> is an operator that may be introduced into the |
| * algebra by other optimizations and represents the case where the optimizer |
| * can a priori determine that some portion of a query will produce no results. |
| * The classic example of this are {@code FILTER} clauses that may be determined |
| * to always return false and thus negate the need to evaluate their inner |
| * operations. |
| * </p> |
| * <p> |
| * Other optimizers introduce <strong>table empty</strong> at the point in the |
| * query where they are optimizing, often its presence in a query may render a |
| * larger portion of even the entire query superfluous so this optimizer is |
| * designed to promote it up through the query as necessary. |
| * </p> |
| * <p> |
| * As detailed below this is not guaranteed to eliminate all portions of a query |
| * rather it aims to eliminate portions where not doing so can cause expensive |
| * and unnecessary evaluation to happen e.g. evaluating the left hand side of a |
| * join (which may itself be a deeply nested operator) only to join it with |
| * table empty and thus discard all the work that had been done. |
| * </p> |
| * <h3>Table Empty Promotions</h3> |
| * <p> |
| * The optimizer makes the following promotions: |
| * </p> |
| * <ul> |
| * <li>Graph over table empty {@literal =>} table empty</li> |
| * <li>Assign/Extend over table empty {@literal =>} table empty</li> |
| * <li>Join where either side is table empty {@literal =>} table empty</li> |
| * <li>Left Join: |
| * <ul> |
| * <li>If LHS is table empty {@literal =>} table empty</li> |
| * <li>If RHS is table empty {@literal =>} LHS</li> |
| * </ul> |
| * </li> |
| * <li>Union: |
| * <ul> |
| * <li>If both sides are table empty {@literal =>} table empty</li> |
| * <li>If one side is table empty {@literal =>} other side</li> |
| * </ul> |
| * </li> |
| * <li>Minus: |
| * <ul> |
| * <li>If LHS is table empty {@literal =>} table empty</li> |
| * <li>If RHS is table empty {@literal =>} LHS</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * <p> |
| * All other operators are left untouched either because it cannot be promoted |
| * through them or because doing so has no clear benefit since applying them |
| * over table empty should be minimal work anyway. |
| * </p> |
| */ |
| public class TransformPromoteTableEmpty extends TransformCopy { |
| |
| @Override |
| public Op transform(OpGraph opGraph, Op subOp) { |
| if (isTableEmpty(subOp)) { |
| return subOp; |
| } |
| return super.transform(opGraph, subOp); |
| } |
| |
| @Override |
| public Op transform(OpAssign opAssign, Op subOp) { |
| if (isTableEmpty(subOp)) { |
| return subOp; |
| } |
| return super.transform(opAssign, subOp); |
| } |
| |
| @Override |
| public Op transform(OpExtend opExtend, Op subOp) { |
| if (isTableEmpty(subOp)) { |
| return subOp; |
| } |
| return super.transform(opExtend, subOp); |
| } |
| |
| @Override |
| public Op transform(OpJoin opJoin, Op left, Op right) { |
| // If either side is table empty return table empty |
| if (isTableEmpty(left) || isTableEmpty(right)) { |
| return OpTable.empty(); |
| } |
| return super.transform(opJoin, left, right); |
| } |
| |
| @Override |
| public Op transform(OpLeftJoin opLeftJoin, Op left, Op right) { |
| // If LHS is table empty return table empty |
| // If RHS is table empty can eliminate left join and just leave LHS |
| if (isTableEmpty(left)) { |
| return OpTable.empty(); |
| } else if (isTableEmpty(right)) { |
| return left; |
| } |
| return super.transform(opLeftJoin, left, right); |
| } |
| |
| @Override |
| public Op transform(OpMinus opMinus, Op left, Op right) { |
| // If LHS is table empty return table empty |
| // If RHS is table empty can eliminate minus and just leave LHS since no |
| // shared variables means no effect |
| if (isTableEmpty(left)) { |
| return OpTable.empty(); |
| } else if (isTableEmpty(right)) { |
| return left; |
| } |
| return super.transform(opMinus, left, right); |
| } |
| |
| @Override |
| public Op transform(OpUnion opUnion, Op left, Op right) { |
| // If one and only one side is table empty return other side |
| // If both are table empty return table empty |
| if (isTableEmpty(left)) { |
| if (isTableEmpty(right)) { |
| return OpTable.empty(); |
| } else { |
| return right; |
| } |
| } else if (isTableEmpty(right)) { |
| return left; |
| } |
| return super.transform(opUnion, left, right); |
| } |
| |
| private boolean isTableEmpty(Op op) { |
| if (op instanceof OpTable) { |
| return ((OpTable) op).getTable().isEmpty(); |
| } else { |
| return false; |
| } |
| } |
| |
| } |