/**
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 29, 2011
*/
package com.bigdata.rdf.sparql.ast.optimizers;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.bset.ConditionalRoutingOp;
import com.bigdata.rdf.sparql.ast.ArbitraryLengthPathNode;
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.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.QueryNodeWithBindingSet;
import com.bigdata.rdf.sparql.ast.QueryRoot;
import com.bigdata.rdf.sparql.ast.QueryType;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.StaticAnalysis;
import com.bigdata.rdf.sparql.ast.SubqueryRoot;
import com.bigdata.rdf.sparql.ast.UnionNode;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
import com.bigdata.rdf.sparql.ast.eval.IEvaluationContext;
/**
* Optimizer attaches {@link FilterNode}s which will run as "join filters" to
* {@link StatementPatternNode}s. The joins must already be in the order in
* which they will be evaluated. Join filters which are already attached to
* required joins will be pick up and reattached as appropriate for the current
* join evaluation order.
* <p>
* Note: Even though a {@link FilterNode} is attached to a given join, the
* {@link FilterNode} may have materialization requirements which make it
* impossible to evaluate the constraint on the physical JOIN operator. In such
* cases, a materialization pattern will be used to ensure that the necessary
* variables have been materialized before the constraint runs. The
* materialization pipeline, of necessity, runs after the join and the
* constraint will be modeled as a {@link ConditionalRoutingOp}. However, this
* optimizer is NOT responsible for those decisions. It just attaches filters to
* join based on when their variables become bound, not when their variables are
* known to satisify the materialization requirements for the filter.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id: ASTAttachJoinFiltersOptimizer.java 5455 2011-10-29 19:43:53Z
* thompsonbry $
*/
public class ASTAttachJoinFiltersOptimizer 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) {
attachJoinFilters(context, sa, namedSubquery.getWhereClause());
}
}
}
// Now process the main where clause.
attachJoinFilters(context, sa, queryRoot.getWhereClause());
return new QueryNodeWithBindingSet(queryRoot, bindingSets);
}
/**
* Recursively process groups.
*
* @param context
* @param sa
* @param group
*/
@SuppressWarnings("unchecked")
private void attachJoinFilters(final IEvaluationContext context,
final StaticAnalysis sa,
final GraphPatternGroup<IGroupMemberNode> group) {
/*
* Recursion.
*/
for (IGroupMemberNode child : group) {
if (child instanceof GraphPatternGroup<?>) {
attachJoinFilters(context, sa,
(GraphPatternGroup<IGroupMemberNode>) child);
} else if (child instanceof SubqueryRoot) {
attachJoinFilters(context, sa,
((SubqueryRoot) child).getWhereClause());
} else if (child instanceof ArbitraryLengthPathNode) {
attachJoinFilters(context, sa,
((ArbitraryLengthPathNode) child).subgroup());
}
}
if (group instanceof JoinGroupNode) {
/*
* Filter attachment for this join group.
*/
attachJoinFilters2(context, sa, (JoinGroupNode) group);
}
}
/**
* Figure out which filters will be attached to which statement patterns.
* This only inspects the required statement patterns. Simple optionals are
* handled by {@link ASTSimpleOptionalOptimizer}.
* <p>
* Note: This handles re-attach by collecting previously attached FILTERS
* from required joins.
*/
private void attachJoinFilters2(final IEvaluationContext context,
final StaticAnalysis sa, final JoinGroupNode group) {
/*
* Collect all required joins and all join filters.
*/
// The join path (required joins only).
final List<IJoinNode> requiredJoins = new LinkedList<IJoinNode>();
// The join filters.
final List<FilterNode> joinFilters = new LinkedList<FilterNode>(
sa.getJoinFilters(group));
for (IGroupMemberNode child : group) {
if (!(child instanceof IJoinNode)) {
continue;
}
final IJoinNode aJoinNode = (IJoinNode) child;
if (aJoinNode.isOptional()) {
/*
* Note: We do not attach filters to OPTIONAL joins here.
*
* Note: The ASTSimpleOptionalOptimizer is responsible for
* filter attachment to optional statement pattern nodes. It
* looks for optional JoinGroupNodes which it can rewrite into
* an optional StatementPatternNode. The join filter (if any)
* attached to that optional StatementPatternNode MUST NOT be
* detached and reassigned even if the join order changes since
* it is really part of an optional group which was rewritten
* into an optional statement pattern.
*/
continue;
}
if (aJoinNode instanceof SubqueryRoot &&
((SubqueryRoot)aJoinNode).getQueryType().equals(QueryType.ASK)) {
/*
* Note: we also do not add to ASK subqueries generated by
* FILTER and/or FILTER NOT EXISTS nodes. The reason is that
* they may interact with other FILTERS (namely the FILTERs
* carrying the NotExists and Exists nodes), i.e. if we
* attach filters they would be evaluated *prior* to evaluating
* the other filters, which can cause problems. See BLZG-1284.
*
* Also note that it makes no sense to attach FILTERs here;
* these ASK subqueries are logically binary, they are not
* supposed to contribute variable bindings referred to by
* other FILTERs.
*/
continue;
}
if (aJoinNode instanceof UnionNode) {
/*
* Note: the translation for union nodes currently does not
* support inlined filters. This is an edge case anyway, since
* FILTERs are typically pushed inside UNION nodes (wherever
* possible).
*
* See https://jira.blazegraph.com/browse/BLZG-1494.
*/
continue;
}
requiredJoins.add(aJoinNode);
final List<FilterNode> ownJoinFilters = aJoinNode
.getAttachedJoinFilters();
if (ownJoinFilters != null) {
// Pick up any join filters already attached to this join node.
joinFilters.addAll(ownJoinFilters);
aJoinNode.setAttachedJoinFilters(null);
}
}
if (requiredJoins.isEmpty()) {
// Nothing to do.
return;
}
// The join filters that become attached to joins.
final List<FilterNode> attachedFilters = new LinkedList<FilterNode>();
/*
* Figure out which filters are attached to which joins.
*/
{
final int requiredJoinCount = requiredJoins.size();
final IJoinNode[] path = requiredJoins
.toArray(new IJoinNode[requiredJoinCount]);
final Set<IVariable<?>> knownBound = sa
.getDefinitelyIncomingBindings(group,
new LinkedHashSet<IVariable<?>>());
final FilterNode[][] assignedConstraints = sa
.getJoinGraphConstraints(path, joinFilters
.toArray(new FilterNode[joinFilters.size()]),
knownBound, false/* pathIsComplete */);
/*
* Attach the join filters.
*/
for (int i = 0; i < requiredJoinCount; i++) {
final IJoinNode tmp = path[i];
final FilterNode[] filters = assignedConstraints[i];
if (filters.length > 0) {
tmp.setAttachedJoinFilters(Arrays.asList(filters));
attachedFilters.addAll(Arrays.asList(filters));
}
}
}
/*
* Remove all join filters from the group. They have all been attached
* to the joins.
*/
for (FilterNode joinFilter : attachedFilters) {
/*
* the attachedFilters array contains identical FilterNode
* expressions (as of Java equals method) only once, but in the
* AST they may occur multiple times. Therefore, we iteratively
* remove them until all filter expressions have been discarded.
*/
boolean checkNext = true;
while (checkNext) {
checkNext = group.removeArg(joinFilter);
}
}
}
}