/**
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 org.apache.log4j.Logger;
import com.bigdata.bop.BOp;
import com.bigdata.bop.IBindingSet;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.sparql.ast.ConstantNode;
import com.bigdata.rdf.sparql.ast.GraphPatternGroup;
import com.bigdata.rdf.sparql.ast.GroupNodeBase;
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.QueryNodeWithBindingSet;
import com.bigdata.rdf.sparql.ast.QueryRoot;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.UnionNode;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
/**
* Pruning rules for unknown IVs in statement patterns:
*
* If an optional join is known to fail, then remove the optional group in which
* it appears from the group (which could be an optional group, a join group, or
* a union).
*
* If a statement pattern contains an unknown term, a with this statement
* pattern will certainly fail. Thus the group in which the statement pattern
* appears (the parent) will also fail. Continue recursively up the parent
* hierarchy until we hit a UNION or an OPTIONAL parent. If we reach the root
* of the where clause for a subquery, then continue up the groups in which the
* subquery appears.
*
* If the parent is a UNION, then remove the child from the UNION.
*
* If a UNION has one child, then replace the UNION with the child.
*
* If a UNION is empty, then fail the group in which it fails (unions are not
* optional).
*
* These rules should be triggered if a join is known to fail, which includes
* the case of an unknown IV in a statement pattern as well
* <code>GRAPH uri {}</code> where uri is not a named graph.
*
* <pre>
*
* TODO From BigdataEvaluationStrategyImpl3#945
*
* Prunes the sop tree of optional join groups containing values
* not in the lexicon.
*
* sopTree = stb.pruneGroups(sopTree, groupsToPrune);
*
*
* If after pruning groups with unrecognized values we end up with a
* UNION with no subqueries, we can safely just return an empty
* iteration.
*
* if (SOp2BOpUtility.isEmptyUnion(sopTree.getRoot())) {
* return new EmptyIteration<BindingSet, QueryEvaluationException>();
* }
* </pre>
*
* and also if we encounter a value not in the lexicon, we can still continue
* with the query if the value is in either an optional tail or an optional join
* group (i.e. if it appears on the right side of a LeftJoin). We can also
* continue if the value is in a UNION. Otherwise we can stop evaluating right
* now.
*
* <pre>
* } catch (UnrecognizedValueException ex) {
* if (sop.getGroup() == SOpTreeBuilder.ROOT_GROUP_ID) {
* throw new UnrecognizedValueException(ex);
* } else {
* groupsToPrune.add(sopTree.getGroup(sop.getGroup()));
* }
* }
* </pre>
*
*
* ASTPruneUnknownTerms : If an unknown terms appears in a StatementPatternNode
* then we get to either fail the query or prune that part of the query. If it
* appears in an optional, then prune the optional. if it appears in union, the
* prune that part of the union. if it appears at the top-level then there are
* no solutions for that query. This is part of what
* BigdataEvaluationStrategyImpl3#toPredicate(final StatementPattern
* stmtPattern) is doing. Note that toVE() as called from that method will throw
* an UnknownValueException if the term is not known to the database.
*
* FIXME Isolate pruning logic since we need to use it in more than one place.
*/
public class ASTUnknownTermOptimizer implements IASTOptimizer {
private static final Logger log = Logger
.getLogger(ASTUnknownTermOptimizer.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
{
final GroupNodeBase<IGroupMemberNode> whereClause = (GroupNodeBase<IGroupMemberNode>) queryRoot
.getWhereClause();
if (whereClause != null) {
eliminateGroupsWithUnknownTerms(queryRoot, 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);
final GroupNodeBase<IGroupMemberNode> whereClause = (GroupNodeBase<IGroupMemberNode>) namedSubquery
.getWhereClause();
if (whereClause != null) {
eliminateGroupsWithUnknownTerms(queryRoot, whereClause);
}
}
}
// log.error("\nafter rewrite:\n" + queryNode);
return new QueryNodeWithBindingSet(queryNode, bindingSets);
}
/**
* If the group has an unknown term, simply prune it out from its parent.
* If the group happens to be the top-level of the where, simply replace
* the where with an empty join group.
*
* @param op
*/
private static void eliminateGroupsWithUnknownTerms(final QueryRoot queryRoot,
final GroupNodeBase<IGroupMemberNode> op) {
/*
* Check the statement patterns.
*/
for (int i = 0; i < op.arity(); i++) {
final BOp sp = op.get(i);
if (!(sp instanceof StatementPatternNode)) {
continue;
}
for (int j = 0; j < sp.arity(); j++) {
final BOp term = sp.get(j);
if (term instanceof ConstantNode) {
final IV iv = ((ConstantNode) term).getValue().getIV();
if (iv == null || iv.isNullIV()) {
pruneGroup(queryRoot, 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 GroupNodeBase<?>) {
@SuppressWarnings("unchecked")
final GroupNodeBase<IGroupMemberNode> childGroup = (GroupNodeBase<IGroupMemberNode>) child;
eliminateGroupsWithUnknownTerms(queryRoot, childGroup);
} else if (child instanceof QueryBase) {
final QueryBase subquery = (QueryBase) child;
final GroupNodeBase<IGroupMemberNode> childGroup = (GroupNodeBase<IGroupMemberNode>) subquery
.getWhereClause();
eliminateGroupsWithUnknownTerms(queryRoot, childGroup);
}
}
}
private static void pruneGroup(final QueryRoot queryRoot,
final GroupNodeBase<IGroupMemberNode> op) {
final IGroupNode parent = op.getParent();
if (op.getParent() == null) {
/*
* We've reached the end of the line. Prune out the Where clause
* from the QueryRoot.
*/
queryRoot.setWhereClause(new JoinGroupNode());
} else if (parent instanceof UnionNode
|| ((op instanceof IJoinNode) && ((IJoinNode) op).isOptional())) {
/*
* We've reached a stopping point - we can prune an optional group
* and we can prune the child of a UNION.
*/
parent.removeChild(op);
} else {
/*
* Continue onwards and upwards.
*/
pruneGroup(queryRoot, (GroupNodeBase) parent);
}
}
/**
* Remove any empty (non-GRAPH) groups (normal groups and UNIONs, but not
* GRAPH {}).
*/
static private void removeEmptyChildGroups(final GraphPatternGroup<?> op) {
int n = op.arity();
for (int i = 0; i < n; i++) {
final BOp child = op.get(i);
if (!(child instanceof GroupNodeBase<?>))
continue;
if (((GroupNodeBase<?>) child).getContext() != null) {
/*
* Do not prune GRAPH ?g {} or GRAPH uri {}. Those constructions
* have special semantics.
*/
continue;
}
if (child.arity() == 0) {
// remove an empty child group.
op.removeArg(child);
// one less child to visit.
n--;
}
}
}
}