/**
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 Oct 25, 2011
*/
package com.bigdata.rdf.sparql.ast.optimizers;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
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.FilterNode;
import com.bigdata.rdf.sparql.ast.GraphPatternGroup;
import com.bigdata.rdf.sparql.ast.IGroupMemberNode;
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.QueryNodeWithBindingSet;
import com.bigdata.rdf.sparql.ast.QueryRoot;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.StaticAnalysis;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
/**
* Optimizer assigns join variables to sub-groups.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class ASTSubGroupJoinVarOptimizer implements IASTOptimizer {
@SuppressWarnings("unchecked")
@Override
public QueryNodeWithBindingSet optimize(
final AST2BOpContext context, final QueryNodeWithBindingSet input) {
final IQueryNode queryNode = input.getQueryNode();
final IBindingSet[] bindingSets = input.getBindingSets();
final QueryRoot queryRoot = (QueryRoot) queryNode;
final StaticAnalysis sa = new StaticAnalysis(queryRoot, context);
// First, process any pre-existing named subqueries.
{
final NamedSubqueriesNode namedSubqueries = queryRoot
.getNamedSubqueries();
if (namedSubqueries != null) {
for (NamedSubqueryRoot namedSubquery : namedSubqueries) {
assignJoinVars(sa, namedSubquery.getWhereClause());
}
}
}
// Now process the main where clause.
assignJoinVars(sa, queryRoot.getWhereClause());
return new QueryNodeWithBindingSet(queryRoot, bindingSets);
}
/**
* Assign the join variable(s) to the group. The join variables are those
* variables which are definitely bound by the time the group runs, so this
* depends on the order of the nodes in the parent group. There may be zero
* or more join variables. Child groups are processed recursively.
*
* @param sa
* @param group
*/
private void assignJoinVars(final StaticAnalysis sa,
final GraphPatternGroup<IGroupMemberNode> group) {
if (group.getParentGraphPatternGroup() != null) {
/*
* The variables which will be definitely bound based on an analysis
* of the group.
*/
final Set<IVariable<?>> boundByGroup = sa
.getDefinitelyProducedBindings(group,
new LinkedHashSet<IVariable<?>>(), true/* recursive */);
/*
* Find the set of variables which will be definitely bound by the
* time the group is evaluated.
*/
final Set<IVariable<?>> incomingBindings = sa
.getDefinitelyIncomingBindings(
(GraphPatternGroup<?>) group,
new LinkedHashSet<IVariable<?>>());
/*
* This is only those variables which are bound on entry into the group
* in which the SERVICE join appears *and* which are "must" bound
* variables projected by the SERVICE.
*/
boundByGroup.retainAll(incomingBindings);
@SuppressWarnings("rawtypes")
final IVariable[] joinVars = boundByGroup.toArray(new IVariable[0]);
group.setJoinVars(joinVars);
/*
* The variables that will definitely be bound inside the subquery.
*/
final Set<IVariable<?>> definitelyBoundInGroup =
sa.getDefinitelyProducedBindings(
group, new LinkedHashSet<IVariable<?>>(), true);
/*
* Find the set of variables which have appeared in the query and
* may be bound by the time the group is evaluated.
*/
final Set<IVariable<?>> maybeIncomingBindings = sa
.getMaybeIncomingBindings(
(GraphPatternGroup<?>) group,
new LinkedHashSet<IVariable<?>>());
/**
* Add the variables that are used inside filters in the OPTIONAL,
* since the SPARQL 1.1 semantics lifts these filters to the
* upper level in case the join succeeds.
*
* However, note that this may not be valid in the general case,
* i.e. it is unclear how to deal with ill designed patterns, where
* the inner subgroup contains an optional join reusing the
* variables.
*/
if (group instanceof JoinGroupNode) {
final JoinGroupNode jgn = (JoinGroupNode)group;
if (jgn.isOptional()) {
final Set<FilterNode> filters = new LinkedHashSet<FilterNode>();
for (BOp node : jgn.args()) {
final Iterator<BOp> it = BOpUtility.preOrderIterator(node);
while (it.hasNext()) {
final BOp bop = it.next();
if (bop instanceof FilterNode) {
filters.add((FilterNode)bop);
} else if (node instanceof StatementPatternNode) {
final StatementPatternNode nodeAsSP =
(StatementPatternNode)node;
final List<FilterNode> attachedFilters =
nodeAsSP.getAttachedJoinFilters();
for (final FilterNode filter : attachedFilters) {
filters.add(filter);
}
}
}
}
for (FilterNode fn : filters) {
definitelyBoundInGroup.addAll(sa.getSpannedVariables(fn,
true/* filters */, new LinkedHashSet<IVariable<?>>()));
}
}
}
/*
* Retain the defintely bound variables that have already
* appeared previously in the query up to this point.
*/
definitelyBoundInGroup.retainAll(maybeIncomingBindings);
@SuppressWarnings("rawtypes")
final IVariable[] projectInVars =
definitelyBoundInGroup.toArray(new IVariable[0]);
group.setProjectInVars(projectInVars);
}
/*
* Recursion.
*/
for (IGroupMemberNode child : group) {
if (child instanceof GraphPatternGroup<?>) {
@SuppressWarnings("unchecked")
final GraphPatternGroup<IGroupMemberNode> subGroup = (GraphPatternGroup<IGroupMemberNode>) child;
assignJoinVars(sa, subGroup);
} else if (child instanceof QueryBase) {
final QueryBase subquery = (QueryBase) child;
@SuppressWarnings("unchecked")
final GraphPatternGroup<IGroupMemberNode> subGroup = (GraphPatternGroup<IGroupMemberNode>) subquery
.getWhereClause();
assignJoinVars(sa, subGroup);
}
}
}
}