/** 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 Aug 23, 2011 */ package com.bigdata.rdf.sparql.ast.optimizers; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Properties; import java.util.Set; import java.util.UUID; import org.apache.log4j.Logger; import org.openrdf.model.URI; import com.bigdata.bop.BOp; import com.bigdata.bop.BOpUtility; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IVariable; import com.bigdata.rdf.sparql.ast.ArbitraryLengthPathNode; import com.bigdata.rdf.sparql.ast.FilterNode; import com.bigdata.rdf.sparql.ast.GlobalAnnotations; import com.bigdata.rdf.sparql.ast.GroupMemberNodeBase; import com.bigdata.rdf.sparql.ast.IGroupMemberNode; import com.bigdata.rdf.sparql.ast.IValueExpressionNode; import com.bigdata.rdf.sparql.ast.JoinGroupNode; import com.bigdata.rdf.sparql.ast.StatementPatternNode; import com.bigdata.rdf.sparql.ast.StaticAnalysis; import com.bigdata.rdf.sparql.ast.TermNode; import com.bigdata.rdf.sparql.ast.UnionNode; import com.bigdata.rdf.sparql.ast.VarNode; import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext; import com.bigdata.rdf.sparql.ast.eval.AST2BOpUtility; import com.bigdata.rdf.sparql.ast.hints.BasicBooleanQueryHint; import com.bigdata.rdf.sparql.ast.hints.BasicIntQueryHint; import com.bigdata.rdf.sparql.ast.hints.BasicStringQueryHint; import com.bigdata.rdf.sparql.ast.hints.QueryHintRegistry; import com.bigdata.rdf.sparql.ast.service.ServiceNode; import com.bigdata.rdf.store.BD; /** * This ALP SERVICE {@link IASTOptimizer} provides a rewrite of a SERVICE * expression that makes it possible to specify limits (minimum, maximum path * length), directionality of traversal, etc. * <p> * A sample query is: * * <pre> * #e.g. "Go three hops out in either direction from vertex <id:v0> where the edge is of type <test:foo> and the edge has <some:prop>=someVal." * * SELECT * WHERE { * SERVICE bd:alp { " + * <id:v0> ?edge ?to . " + * hint:Prior hint:alp.pathExpr true . * ?edge rdf:type <test:foo> . * ?edge <some:prop> "someVal" . * hint:Group hint:alp.lowerBound 1 . * hint:Group hint:alp.upperBound 3 . * hint:Group hint:alp.bidirectional true . * } * } * </pre> * * @see <a href="http://trac.blazegraph.com/ticket/1072"> Configurable ALP Service </a> * @see <a href="http://trac.blazegraph.com/ticket/1117"> Document the ALP Service </a> * * @author bryan */ public class ASTALPServiceOptimizer extends AbstractJoinGroupOptimizer implements IASTOptimizer { private static final transient Logger log = Logger.getLogger(ASTALPServiceOptimizer.class); /** * The well-known URI of the ALP SERVICE extension {@value #ALP}. */ public static final URI ALP = BD.ALP_SERVICE; /** * */ public static final String PATH_EXPR = "alp.pathExpr"; public static final String LOWER_BOUND = "alp.lowerBound"; public static final String UPPER_BOUND = "alp.upperBound"; public static final String BIDIRECTIONAL = "alp.bidirectional"; public static final String EDGE_VAR = "alp.edgeVar"; static { QueryHintRegistry.add(new BasicBooleanQueryHint(PATH_EXPR, false)); QueryHintRegistry.add(new BasicIntQueryHint(LOWER_BOUND, 1)); QueryHintRegistry.add(new BasicIntQueryHint(UPPER_BOUND, Integer.MAX_VALUE)); QueryHintRegistry.add(new BasicBooleanQueryHint(BIDIRECTIONAL, false)); QueryHintRegistry.add(new BasicStringQueryHint(EDGE_VAR, null)); } /** * Optimize the join group. */ protected void optimizeJoinGroup(final AST2BOpContext ctx, final StaticAnalysis sa, final IBindingSet[] bSets, final JoinGroupNode group) { final GlobalAnnotations globals = new GlobalAnnotations( ctx.getLexiconNamespace(), ctx.getTimestamp() ); for (ServiceNode node : group.getChildren(ServiceNode.class)) { if (log.isDebugEnabled()) { log.debug(node); } final TermNode serviceRef = node.getServiceRef(); if (!serviceRef.isConstant() || !serviceRef.getValue().equals(ALP)) { /* * Not our service. */ continue; } final JoinGroupNode subgroup = (JoinGroupNode) node.getGraphPattern(); if (log.isDebugEnabled()) { log.debug("found an alp service to optimize:\n"+subgroup); } final Properties hints = subgroup.getQueryHints(); if (!hints.containsKey(LOWER_BOUND)) { throw new RuntimeException("missing: " + LOWER_BOUND); } if (!hints.containsKey(UPPER_BOUND)) { throw new RuntimeException("missing: " + UPPER_BOUND); } for (IGroupMemberNode child : subgroup.getChildren()) { if (!(child instanceof StatementPatternNode || child instanceof FilterNode)) { throw new RuntimeException("Complex groups not allowed in alp service"); } } final Set<VarNode> dropVars = new LinkedHashSet<>(); final int lowerBound = Integer.valueOf(subgroup.getQueryHint(LOWER_BOUND)); final int upperBound = Integer.valueOf(subgroup.getQueryHint(UPPER_BOUND)); final VarNode tVarLeft = new VarNode("-tVarLeft-"+UUID.randomUUID().toString()); tVarLeft.setAnonymous(true); final VarNode tVarRight = new VarNode("-tVarRight-"+UUID.randomUUID().toString()); tVarRight.setAnonymous(true); dropVars.add(tVarLeft); dropVars.add(tVarRight); final boolean bidirectional = subgroup.getQueryHintAsBoolean(BIDIRECTIONAL, false); final String evHint = subgroup.getQueryHint(EDGE_VAR); VarNode edgeVar = null; if (evHint != null) { if (!(evHint.length() > 1 && evHint.charAt(0) == '?')) { throw new IllegalArgumentException("Illegal hint for "+EDGE_VAR+": " + evHint); } edgeVar = new VarNode(evHint.substring(1)); } TermNode left = null; TermNode right = null; IGroupMemberNode pathExpr = null; JoinGroupNode group1 = null; JoinGroupNode group2 = null; TermNode middle = null; for (StatementPatternNode child : subgroup.getStatementPatterns()) { if (child.getQueryHintAsBoolean(PATH_EXPR, false)) { if (pathExpr != null) { throw new RuntimeException("Only one " + PATH_EXPR + " allowed"); } left = child.s(); right = child.o(); middle = child.p(); if (child.p() instanceof VarNode) { final VarNode v = (VarNode) child.p(); v.setAnonymous(true); dropVars.add(v); } if (bidirectional) { final StatementPatternNode forward = new StatementPatternNode( tVarLeft, child.p(), tVarRight, child.c(), child.getScope() ); forward.setQueryHint(PATH_EXPR, "true"); group1 = new JoinGroupNode(); group1.addChild(forward); final StatementPatternNode reverse = new StatementPatternNode( tVarRight, child.p(), tVarLeft, child.c(), child.getScope() ); reverse.setQueryHint(PATH_EXPR, "true"); group2 = new JoinGroupNode(); group2.addChild(reverse); final UnionNode union = new UnionNode(); union.addArg(group1); union.addArg(group2); pathExpr = union; } else { final StatementPatternNode sp = new StatementPatternNode( tVarLeft, child.p(), tVarRight, child.c(), child.getScope() ); sp.setQueryHint(PATH_EXPR, "true"); pathExpr = sp; } subgroup.removeChild(child); } } IVariable<?> leftVar = null; if (left instanceof VarNode) { leftVar = ((VarNode) left).getValueExpression(); } IVariable<?> rightVar = null; if (right instanceof VarNode) { rightVar = ((VarNode) right).getValueExpression(); } /* * Remap any filters on the left/right vars onto the transitive * left/right vars instead. */ for (FilterNode f : subgroup.getChildren(FilterNode.class)) { final Iterator<BOp> it = BOpUtility.preOrderIteratorWithAnnotations(f); boolean swap = false; while (it.hasNext()) { final BOp bop = it.next(); if (!(bop instanceof VarNode)) { continue; } final VarNode v = (VarNode) bop; final IVariable<?> ve = v.getValueExpression(); if (leftVar != null && leftVar.equals(ve)) { v.setValueExpression(tVarLeft.getValueExpression()); swap = true; } else if (rightVar != null && rightVar.equals(ve)) { v.setValueExpression(tVarRight.getValueExpression()); swap = true; } } /* * If we've swapped any vars, also re-generate the underlying * value expression on the filter node. */ if (swap) { final IValueExpressionNode veNode = f.getValueExpressionNode(); veNode.setValueExpression(null); AST2BOpUtility.toVE(ctx.context, globals, veNode); } } final ArbitraryLengthPathNode alpNode = new ArbitraryLengthPathNode( left, right, tVarLeft, tVarRight, lowerBound, upperBound ); if (edgeVar != null) { alpNode.setEdgeVar(edgeVar, middle); } alpNode.subgroup().addChild(pathExpr); for (@SuppressWarnings("rawtypes") GroupMemberNodeBase child : subgroup.getChildren(GroupMemberNodeBase.class)) { if (!child.getQueryHintAsBoolean(PATH_EXPR, false)) { /* * Make all variables in the ALP service (other than the * left and right) anonymous, since they cannot be projected * out in a meaningful fashion. */ final Iterator<BOp> it = BOpUtility.preOrderIteratorWithAnnotations(child); while (it.hasNext()) { final BOp bop = it.next(); if (bop instanceof VarNode) { final VarNode v = (VarNode) bop; v.setAnonymous(true); dropVars.add(v); } } child.setQueryHints(new Properties()); subgroup.removeChild(child); if (bidirectional) { group1.addChild((IGroupMemberNode) child.clone()); group2.addChild((IGroupMemberNode) child.clone()); } else { alpNode.subgroup().addChild(child); } } } alpNode.setDropVars(dropVars); if (log.isDebugEnabled()) { log.debug("optimized alpNode:\n"+alpNode); } group.removeChild(node); group.addChild(alpNode); // optimize(ctx, sa, group, node); } } }