/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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 General Public License for more details. You should have received a copy of the GNU 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 */ /* * Created on Sep 10, 2011 */ package com.bigdata.rdf.sparql.ast.optimizers; import java.util.LinkedHashMap; import java.util.Map; import org.apache.log4j.Logger; import org.openrdf.model.URI; import org.openrdf.query.algebra.Compare.CompareOp; import com.bigdata.bop.BOpContextBase; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IValueExpression; import com.bigdata.rdf.internal.IV; import com.bigdata.rdf.internal.constraints.CompareBOp; import com.bigdata.rdf.internal.constraints.RangeBOp; import com.bigdata.rdf.sparql.ast.ConstantNode; import com.bigdata.rdf.sparql.ast.FilterNode; import com.bigdata.rdf.sparql.ast.FunctionNode; import com.bigdata.rdf.sparql.ast.FunctionRegistry; import com.bigdata.rdf.sparql.ast.GlobalAnnotations; import com.bigdata.rdf.sparql.ast.IQueryNode; import com.bigdata.rdf.sparql.ast.JoinGroupNode; import com.bigdata.rdf.sparql.ast.QueryHints; import com.bigdata.rdf.sparql.ast.RangeNode; import com.bigdata.rdf.sparql.ast.StatementPatternNode; import com.bigdata.rdf.sparql.ast.StaticAnalysis; import com.bigdata.rdf.sparql.ast.ValueExpressionNode; import com.bigdata.rdf.sparql.ast.VarNode; import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext; import com.bigdata.rdf.sparql.ast.eval.AST2BOpUtility; /** * Attach range bops. */ public class ASTRangeOptimizer extends AbstractJoinGroupOptimizer implements IASTOptimizer { private static final transient Logger log = Logger.getLogger(ASTRangeOptimizer.class); public ASTRangeOptimizer() { super(false /* childFirst */, true /* optimizeServiceNodes */); } /** * Optimize the join group, attach range nodes. */ // @SuppressWarnings("rawtypes") @Override protected void optimizeJoinGroup(final AST2BOpContext ctx, final StaticAnalysis sa, final IBindingSet[] bSets, final JoinGroupNode group) { /* * First see if we have anything "rangeable". */ boolean rangeSafe = false; for (IQueryNode node : group) { if (!(node instanceof StatementPatternNode)) continue; final StatementPatternNode sp = (StatementPatternNode) node; rangeSafe |= sp.getQueryHintAsBoolean(QueryHints.RANGE_SAFE, false); } if (!rangeSafe) return; final Map<VarNode, RangeNode> ranges = new LinkedHashMap<VarNode, RangeNode>(); for (IQueryNode node : group) { if (!(node instanceof FilterNode)) continue; final FilterNode filter = (FilterNode) node; if (log.isDebugEnabled()) log.debug(filter); if (!(filter.getValueExpressionNode() instanceof FunctionNode)) continue; final FunctionNode function = (FunctionNode) filter.getValueExpressionNode(); if (log.isDebugEnabled()) log.debug(function); processFunction(function, ranges); } // didn't find any if (ranges.isEmpty()) return; final GlobalAnnotations globals = new GlobalAnnotations( ctx.getLexiconNamespace(), ctx.getTimestamp()); for (IQueryNode node : group) { if (!(node instanceof StatementPatternNode)) continue; final StatementPatternNode sp = (StatementPatternNode) node; if (!sp.getQueryHintAsBoolean(QueryHints.RANGE_SAFE, false)) continue; if (!sp.o().isVariable()) continue; final VarNode v = (VarNode) sp.o(); if (!ranges.containsKey(v)) continue; final RangeNode range = ranges.get(v); final RangeBOp bop = toRangeBOp(ctx.getBOpContext(), range, globals); if (log.isDebugEnabled()) { log.debug("attaching a range:\n" + range + "\n to statement pattern: " + sp); } range.setRangeBOp(bop); sp.setRange(range); } } /** * Public static facilitates the test cases. */ @SuppressWarnings("rawtypes") public static RangeBOp toRangeBOp( final BOpContextBase context, final RangeNode range, final GlobalAnnotations globals) { // final IVariable<? extends IV> var = range.var().getValueExpression(); final RangeBOp bop = new RangeBOp(); final ValueExpressionNode from = range.from(); if (from != null) { final IValueExpression<? extends IV> ve = AST2BOpUtility.toVE(context, globals, from); bop.setFrom(ve); } final ValueExpressionNode to = range.to(); if (to != null) { final IValueExpression<? extends IV> ve = AST2BOpUtility.toVE(context, globals, to); bop.setTo(ve); } return bop; } private void processFunction( final FunctionNode function, final Map<VarNode, RangeNode> ranges) { final URI uri = function.getFunctionURI(); if (uri.equals(FunctionRegistry.AND)) { final ValueExpressionNode left = (ValueExpressionNode) function.get(0); if (left instanceof FunctionNode) { processFunction((FunctionNode) left, ranges); } final ValueExpressionNode right = (ValueExpressionNode) function.get(1); if (right instanceof FunctionNode) { processFunction((FunctionNode) right, ranges); } } else if (uri.equals(FunctionRegistry.GT) || uri.equals(FunctionRegistry.GE)) { final ValueExpressionNode left = (ValueExpressionNode) function.get(0); final ValueExpressionNode right = (ValueExpressionNode) function.get(1); // ?left > ?right if (left instanceof VarNode) { addLowerBoundIfConstant((VarNode) left, right, ranges); } if (right instanceof VarNode) { addUpperBoundIfConstant((VarNode) right, left, ranges); } } else if (uri.equals(FunctionRegistry.LT) || uri.equals(FunctionRegistry.LE)) { final ValueExpressionNode left = (ValueExpressionNode) function.get(0); final ValueExpressionNode right = (ValueExpressionNode) function.get(1); // ?left < ?right if (left instanceof VarNode) { addUpperBoundIfConstant((VarNode) left, right, ranges); } if (right instanceof VarNode) { addLowerBoundIfConstant((VarNode) right, left, ranges); } } } @SuppressWarnings("unchecked") private void addUpperBoundIfConstant(final VarNode var, final ValueExpressionNode ve, final Map<VarNode, RangeNode> ranges) { /** * If the node is not a constant, adding it as a range restriction won't * help us: com.bigdata.rdf.internal.constraints.RangeBOp.isToBound(), * which is called when deciding whether to apply a range scane, will * simply ignore non-constant range nodes. */ if (!(ve instanceof ConstantNode)) { return; } RangeNode range = ranges.get(var); if (range == null) { range = new RangeNode(var); ranges.put(var, range); } ValueExpressionNode to = range.to(); if (to == null) { to = (ValueExpressionNode) ve.clone(); } else { try { ConstantNode cOld = (ConstantNode)to; ConstantNode cNew = (ConstantNode)ve; if (CompareBOp.compare( cNew.getValueExpression().get(), cOld.getValueExpression().get(), CompareOp.LT)) { to = (ValueExpressionNode) ve.clone(); // override } } catch (Exception e) { // ignore: this may be some non materialized stuff or the like, in which // case we just can't do better here, so let's stick with the old value } } if (to!=null) range.setTo(to); } @SuppressWarnings("unchecked") private void addLowerBoundIfConstant(final VarNode var, final ValueExpressionNode ve, final Map<VarNode, RangeNode> ranges) { /** * If the node is not a constant, adding it as a range restriction won't * help us: com.bigdata.rdf.internal.constraints.RangeBOp.isFromBound(), * which is called when deciding whether to apply a range scan, will * simply ignore non-constant range nodes. See BLZG-1635. */ if (!(ve instanceof ConstantNode)) { return; } RangeNode range = ranges.get(var); if (range == null) { range = new RangeNode(var); ranges.put(var, range); } ValueExpressionNode from = range.from(); if (from == null) { from = (ValueExpressionNode) ve.clone(); } else { try { ConstantNode cOld = (ConstantNode)ve; ConstantNode cNew = (ConstantNode)ve; if (CompareBOp.compare( cNew.getValueExpression().get(), cOld.getValueExpression().get(), CompareOp.GT)) { from = (ValueExpressionNode) ve.clone(); // override } } catch (Exception e) { // ignore: this may be some non materialized stuff or the like, in which // case we just can't do better here, so let's stick with the old value } } if (from!=null) range.setFrom(from); } }