/** 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.Enumeration; import java.util.Properties; import com.bigdata.bop.BOp; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.PipelineOp; import com.bigdata.rdf.sparql.ast.GraphPatternGroup; import com.bigdata.rdf.sparql.ast.GroupNodeBase; import com.bigdata.rdf.sparql.ast.IBindingProducerNode; import com.bigdata.rdf.sparql.ast.IGroupMemberNode; import com.bigdata.rdf.sparql.ast.IGroupNode; import com.bigdata.rdf.sparql.ast.IJoinNode; import com.bigdata.rdf.sparql.ast.IQueryNode; import com.bigdata.rdf.sparql.ast.JoinGroupNode; import com.bigdata.rdf.sparql.ast.NamedSubqueriesNode; import com.bigdata.rdf.sparql.ast.NamedSubqueryRoot; import com.bigdata.rdf.sparql.ast.QueryBase; import com.bigdata.rdf.sparql.ast.QueryNodeBase; import com.bigdata.rdf.sparql.ast.QueryNodeWithBindingSet; import com.bigdata.rdf.sparql.ast.QueryRoot; import com.bigdata.rdf.sparql.ast.UnionNode; import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext; /** * Eliminate semantically empty join group nodes which are the sole child of * another join groups. Such nodes either do not specify a context or they * specify the same context as the parent. * * <pre> * { { ... } } => { ... } * </pre> * * and for non-graph groups: * * <pre> * { ... {} } => { ... } * </pre> * * This is also done when the child is a UNION. * * <pre> * { UNION {...} } => UNION {...} * </pre> * * Or * * <pre> * { GRAPH ?g {...} } => GRAPH ?g {...} * </pre> * * Note: An empty <code>{}</code> matches a single empty solution. Since we * always push in an empty solution and the join of anything with an empty * solution is that source solution, this is the same as not running the group, * so we just eliminate the empty group. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id: ASTEmptyGroupOptimizer.java 5177 2011-09-12 17:49:44Z * thompsonbry $ */ public class ASTEmptyGroupOptimizer implements IASTOptimizer { // private static final Logger log = Logger // .getLogger(ASTEmptyGroupOptimizer.class); @Override public QueryNodeWithBindingSet optimize( final AST2BOpContext context, final QueryNodeWithBindingSet input) { final IQueryNode queryNode = input.getQueryNode(); final IBindingSet[] bindingSets = input.getBindingSets(); if (!(queryNode instanceof QueryRoot)) return new QueryNodeWithBindingSet(queryNode, bindingSets); final QueryRoot queryRoot = (QueryRoot) queryNode; // Main WHERE clause { @SuppressWarnings("unchecked") final GraphPatternGroup<IGroupMemberNode> whereClause = (GraphPatternGroup<IGroupMemberNode>) queryRoot .getWhereClause(); if (whereClause != null) { eliminateEmptyGroups(queryRoot, whereClause); // removeEmptyChildGroups((GraphPatternGroup<?>) whereClause); } } // Named subqueries if (queryRoot.getNamedSubqueries() != null) { final NamedSubqueriesNode namedSubqueries = queryRoot .getNamedSubqueries(); /* * Note: This loop uses the current size() and get(i) to avoid * problems with concurrent modification during visitation. */ for (int i = 0; i < namedSubqueries.size(); i++) { final NamedSubqueryRoot namedSubquery = (NamedSubqueryRoot) namedSubqueries .get(i); @SuppressWarnings("unchecked") final GraphPatternGroup<IGroupMemberNode> whereClause = (GraphPatternGroup<IGroupMemberNode>) namedSubquery .getWhereClause(); if (whereClause != null) { eliminateEmptyGroups(namedSubquery, whereClause); // removeEmptyChildGroups((GraphPatternGroup<?>) whereClause); } } } // log.error("\nafter rewrite:\n" + queryNode); return new QueryNodeWithBindingSet(queryNode, bindingSets); } private static void eliminateEmptyGroups(final QueryBase queryBase, final GraphPatternGroup<?> op) { /* * Recursion, but only into group nodes (including within subqueries). */ for (int i = 0; i < op.arity(); i++) { final BOp child = op.get(i); if (child instanceof GraphPatternGroup<?>) { @SuppressWarnings("unchecked") final GraphPatternGroup<IGroupMemberNode> childGroup = (GraphPatternGroup<IGroupMemberNode>) child; eliminateEmptyGroups(queryBase, childGroup); /* * If we pruned the child, then we need to decrement the index * so that we don't skip one. */ if (i < op.arity() && op.get(i) != child) { i--; } } else if (child instanceof QueryBase) { final QueryBase subquery = (QueryBase) child; @SuppressWarnings("unchecked") final GraphPatternGroup<IGroupMemberNode> childGroup = (GraphPatternGroup<IGroupMemberNode>) subquery .getWhereClause(); eliminateEmptyGroups(subquery, childGroup); /* * If we pruned the child, then we need to decrement the index * so that we don't skip one. */ if (i < op.arity() && op.get(i) != child) { i--; } } } final int arity = op.arity(); if (arity == 0 && op.getContext() == null && op.getParent() != null && !isUnion(op.getParent())) { /* * If this is an empty graph pattern group then we can just prune it * out entirely, unless it is the where clause. * * Also, do not prune GRAPH ?g {} or GRAPH uri {}. Those * constructions have special semantics. * * Note: Do not eliminate an empty join group which is the child of * a UNION. See http://sourceforge.net/apps/trac/bigdata/ticket/504 * (UNION with Empty Group Pattern) */ final IGroupNode<IGroupMemberNode> parent = op.getParent(); parent.removeChild(op); op.setParent(null); } else if (arity == 1 && op instanceof JoinGroupNode && op.get(0) instanceof JoinGroupNode) { final JoinGroupNode parent = (JoinGroupNode) op; final JoinGroupNode child = (JoinGroupNode) op.get(0); if ( (!parent.isMinus() && !child.isMinus()) // fix for trac 712 && (parent.isOptional() || !child.isOptional()) ) { /* * We can always merge two JoinGroupNodes into one, but we have * to make sure we get the optionality right. * * Note: This is not true for MINUS. MINUS can only be combined * with a child join group which is using a normal join. */ /* 1. JoinGroup1 [optional=false] { JoinGroup2 [optional=false] { ... } } -> JoinGroup2 [optional=false] { ... } 2. JoinGroup1 [optional=true] { JoinGroup2 [optional=true] { ... } } -> JoinGroup2 [optional=true] { ... } 3. JoinGroup1 [optional=true] { JoinGroup2 [optional=false] { ... } } -> JoinGroup2 [optional=true] { ... } This case 4 appears to be misconceived: Jeremy Carroll. 4. JoinGroup1 [optional=false] { JoinGroup2 [optional=true] { ... } } -> JoinGroup2 [optional=true] { ... } */ if (parent.isOptional() && !child.isOptional()) { child.setOptional(true); } swap(queryBase, parent, child); } } else if (arity == 1 && op instanceof UnionNode && op.get(0) instanceof JoinGroupNode) { /* * We can always replace a UnionNode that has a single JoinGroupNode * with the JoinGroupNode itself. */ final UnionNode parent = (UnionNode) op; final JoinGroupNode child = (JoinGroupNode) op.get(0); swap(queryBase, parent, child); } else if (arity == 1 && op instanceof JoinGroupNode && !op.isOptional() && !op.isMinus() && op.get(0) instanceof UnionNode) { /* * If a JoinGroupNode contains a single UnionNode, we can lift the * UnionNode unless the JoinGroupNode is optional or minus. * The MINUS case was added as part of BLZG-852. */ final JoinGroupNode parent = (JoinGroupNode) op; final UnionNode child = (UnionNode) op.get(0); swap(queryBase, parent, child); } else if (arity == 1 && // op.get(0) instanceof IBindingProducerNode && // op.getParent() != null && // !op.isOptional() && // !op.isMinus() && // !isUnion(op.getParent()) && // // !(((IGroupNode<?>) op.getParent()) instanceof UnionNode) && !isMinus(op.getParent()) && // !(((IGroupNode<?>) op.getParent()) instanceof JoinGroupNode && !(((JoinGroupNode) op // .getParent()).isMinus())) && op.getContext() == op.getParent().getContext()) { /* * The child is something which produces bindings (hence, * not a FILTER) and is neither a JoinGroupNode nor a * UnionNode and the operator is neither the top level of * the WHERE clause nor a UnionNode or MINUS. * * Just replace the parent JoinGroupNode (op) with the * child. */ // inherit query hints copyQueryHints(op.get(0),op.getQueryHints()); ((GroupNodeBase<?>) op.getParent()) .replaceWith(op, (BOp) op.get(0)); } // if (op instanceof GraphPatternGroup<?>) { // // removeEmptyChildGroups((GraphPatternGroup<?>) op); // // } } /** * Return true if the operator is a UNION. * * @param op * The operator. */ static private boolean isUnion(final IGroupNode<?> op) { if (op instanceof UnionNode) return true; return false; } /** * Return true if the operator is a MINUS node. * * @param op * The operator. */ static private boolean isMinus(final IGroupNode<?> op) { if (op instanceof IJoinNode) { final IJoinNode g = (IJoinNode) op; if (g.isMinus()) return true; } return false; } // /** // * Return true if the operator is an OPTIONAL node. // * // * @param op // * The operator. // */ // private boolean isOptional(final IGroupNode<?> op) { // if (op instanceof IJoinNode) { // final IJoinNode g = (IJoinNode) op; // if (g.isOptional()) // return true; // } // return false; // } /** * Swap the parent with the child inside the grandparent. If there is no * grandparent, assume the parent is the where clause in the query base, * and replace it with the child. */ static private void swap(final QueryBase queryBase, final GraphPatternGroup<?> parent, final GraphPatternGroup<?> child) { if (parent.getParent() == null) { /* * If the parent has no parent, the parent must * currently be the where clause. Set the child to * be the new where clause instead. */ queryBase.setWhereClause(child); } else { /* * If the parent has a parent, then remove the parent * from the grandparent and replace it with the child. */ final GroupNodeBase<?> grandparent = (GroupNodeBase<?>) parent .getParent(); // inherit query hints copyQueryHints(child,parent.getQueryHints()); grandparent.replaceWith(parent, child); } parent.setParent(null); } /** * Copies the query hints to the given node, if not specified there. * If the given node already has the query hint, it is left unmodified. * * @param node * @param queryHints */ private static void copyQueryHints(BOp node, final Properties queryHints) { if (queryHints == null) return; if (!(node instanceof QueryNodeBase)) return; final QueryNodeBase nodeAsQN = (QueryNodeBase)node; final Enumeration<?> pnames = queryHints.propertyNames(); while (pnames.hasMoreElements()) { final String name = (String) pnames.nextElement(); final String value = queryHints.getProperty(name); // copy over query hint, ignoring conflicting ones final String curHint = nodeAsQN.getQueryHint(name); if (curHint==null) { nodeAsQN.setQueryHint(name, value); } } } }