com.bigdata.rdf.sparql.ast.StaticAnalysisBase Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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 20, 2011
*/
package com.bigdata.rdf.sparql.ast;
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.Constant;
import com.bigdata.bop.IConstant;
import com.bigdata.bop.IVariable;
import com.bigdata.rdf.sparql.ast.eval.IEvaluationContext;
import com.bigdata.rdf.sparql.ast.service.ServiceNode;
import com.bigdata.rdf.sparql.ast.ssets.ISolutionSetManager;
/**
* Base class for static analysis.
*
* @author Bryan Thompson
* @version $Id$
*/
public class StaticAnalysisBase {
// private static final Logger log = Logger.getLogger(StaticAnalysisBase.class);
protected final QueryRoot queryRoot;
protected final IEvaluationContext evaluationContext;
/**
* Return the {@link QueryRoot} parameter given to the constructor.
*/
public QueryRoot getQueryRoot() {
return queryRoot;
}
/**
*
* @param queryRoot
* The root of the query. We need to have this on hand in order
* to resolve {@link NamedSubqueryInclude}s during static
* analysis.
* @param evaluationContext
* The evaluation context provides access to the
* {@link ISolutionSetStats} and the {@link ISolutionSetManager} for
* named solution sets.
*/
protected StaticAnalysisBase(final QueryRoot queryRoot,
final IEvaluationContext evaluationContext) {
if(queryRoot == null)
throw new IllegalArgumentException();
this.queryRoot = queryRoot;
this.evaluationContext = evaluationContext;
}
/**
* Return the distinct variables in the operator tree, including on those on
* annotations attached to operators. Variables projected by a subquery are
* included, but not variables within the WHERE clause of the subquery.
* Variables projected by a {@link NamedSubqueryInclude} are also reported,
* but not those used within the WHERE clause of the corresponding
* {@link NamedSubqueryRoot}.
*
* @param op
* An operator.
* @param varSet
* The variables are inserted into this {@link Set}.
*
* @return The caller's {@link Set}.
*/
public Set> getSpannedVariables(final BOp op,
final Set> varSet) {
return getSpannedVariables(op, true/* filters */, varSet);
}
/**
* Return the distinct variables in the operator tree. Variables projected
* by a subquery are included, but not variables within the WHERE clause of
* the subquery. Variables projected by a {@link NamedSubqueryInclude} are
* also reported, but not those used within the WHERE clause of the
* corresponding {@link NamedSubqueryRoot}.
*
* @param op
* An operator.
* @param filters
* When true
, variables on {@link FilterNode}s are
* also reported.
* @param varSet
* The variables are inserted into this {@link Set}.
*
* @return The caller's {@link Set}.
*
* TODO Unit tests for different kinds of AST nodes to make sure
* that we always get/ignore the variables in filters as appropriate.
* For example, an optional {@link StatementPatternNode} can have
* filter nodes attached.
*/
public Set> getSpannedVariables(final BOp op,
final boolean filters, final Set> varSet) {
if (op == null) {
return varSet;
}
// System.err.println("vars=" + varSet + ", op=" + op.toShortString());
if (op instanceof IVariable>) {
varSet.add((IVariable>)op);
} else if(op instanceof IConstant>) {
final IConstant> c = (IConstant>)op;
final IVariable> var = (IVariable> )c.getProperty(Constant.Annotations.VAR);
if( var != null) {
varSet.add(var);
}
} else if (op instanceof ArbitraryLengthPathNode) {
varSet.addAll(((ArbitraryLengthPathNode) op).getMaybeProducedBindings());
// do not recurse
return varSet;
} else if (op instanceof ZeroLengthPathNode) {
varSet.addAll(((ZeroLengthPathNode) op).getProducedBindings());
// do not recurse
return varSet;
} else if (op instanceof ServiceNode) {
// @see http://trac.blazegraph.com/ticket/816
final ServiceNode serviceNode = (ServiceNode) op;
// Look for the SERVICE URI, it might be a variable as well.
final TermNode uriRef = serviceNode.getServiceRef();
if (uriRef instanceof VarNode) {
varSet.add(((VarNode) uriRef).getValueExpression());
}
// pick up anything in the group graph pattern.
getSpannedVariables(serviceNode.getGraphPattern(), filters, varSet);
// fall through - look for attached filters.
} else if (op instanceof BindingsClause) {
final BindingsClause bc = (BindingsClause)op;
varSet.addAll(bc.getDeclaredVariables());
return varSet;
} else if (op instanceof FilterNode && !filters) {
// DO NOT RECURSE INTO THE FILTER!
return varSet;
} else if (op instanceof SubqueryRoot) {
/*
* Do not recurse into a subquery, but report any variables
* projected by that subquery.
*/
final SubqueryRoot subquery = (SubqueryRoot) op;
subquery.getProjectedVars(varSet);
// addProjectedVariables(subquery, varSet);
// DO NOT RECURSE INTO THE SUBQUERY!
return varSet;
} else if (op instanceof NamedSubqueryInclude) {
final NamedSubqueryInclude namedInclude = (NamedSubqueryInclude) op;
final String name = namedInclude.getName();
final NamedSubqueryRoot subquery = getNamedSubqueryRoot(name);
if(subquery != null) {
subquery.getProjectedVars(varSet);
// addProjectedVariables(subquery, varSet);
} else {
final ISolutionSetStats stats = getSolutionSetStats(name);
/*
* Note: This is all variables which are bound in ANY solution.
* It MAY include variables which are NOT bound in some
* solutions.
*/
varSet.addAll(stats.getUsedVars());
}
// DO NOT RECURSE INTO THE SUBQUERY!
return varSet;
// } else {
//
// throw new AssertionError("Not handled: " + op);
}
if (filters && op instanceof IJoinNode) {
/*
* Join nodes may have attached filters.
*/
final IJoinNode t = (IJoinNode) op;
final List list = t.getAttachedJoinFilters();
if (list != null) {
for (FilterNode f : list) {
getSpannedVariables(f, filters, varSet);
}
}
}
/*
* Recursion.
*/
final int arity = op.arity();
for (int i = 0; i < arity; i++) {
final BOp child = op.get(i);
getSpannedVariables(child, filters, varSet);
}
return varSet;
}
/**
* Return the corresponding {@link NamedSubqueryRoot}.
*
* @param name
* The name of the solution set.
*
* @return The {@link NamedSubqueryRoot} and never null
.
*
* @throws RuntimeException
* if no {@link NamedSubqueryRoot} was found for the named set
* associated with this {@link NamedSubqueryInclude}.
*
* @deprecated Caller's MUST BE CHANGED to look for both a
* {@link NamedSubqueryRoot} and an {@link ISolutionSetStats}
* and then handle these as appropriate. In one case, that means
* static analysis of the {@link NamedSubqueryRoot}. In the
* other, the relevant information are present in pre-computed
* metadata on the {@link ISolutionSetStats}.
*/
protected NamedSubqueryRoot getRequiredNamedSubqueryRoot(final String name) {
final NamedSubqueryRoot nsr = getNamedSubqueryRoot(name);
if(nsr == null)
throw new RuntimeException(
"Named subquery does not exist for namedSet: " + name);
return nsr;
}
/**
* Return the corresponding {@link NamedSubqueryRoot}.
*
* Note: You can not resolve pre-existing named solution sets with this
* method, only those which are defined within the scope of a query by a
* {@link NamedSubqueryRoot}.
*
* Note: Typically, callers MUST look for both a {@link NamedSubqueryRoot}
* and an {@link ISolutionSetStats} and then handle these as appropriate. In
* one case, that means static analysis of the {@link NamedSubqueryRoot}. In
* the other, the relevant information are present in pre-computed metadata
* on the {@link ISolutionSetStats}.
*
* @param name
* The name of the solution set.
*
* @return The {@link NamedSubqueryRoot} -or- null
if none was
* found.
*
* @see #getSolutionSetStats(String)
*/
public NamedSubqueryRoot getNamedSubqueryRoot(final String name) {
final NamedSubqueriesNode namedSubqueries = queryRoot
.getNamedSubqueries();
if (namedSubqueries == null)
return null;
for (NamedSubqueryRoot namedSubquery : namedSubqueries) {
if(name.equals(namedSubquery.getName()))
return namedSubquery;
}
return null;
}
/**
* Return the {@link ISolutionSetStats} for the named solution set.
*
* Note: This does NOT report on {@link NamedSubqueryRoot}s for the query.
* It only checks the {@link ISolutionSetManager}.
*
* Note: Typically, callers MUST look for both a {@link NamedSubqueryRoot}
* and an {@link ISolutionSetStats} and then handle these as appropriate. In
* one case, that means static analysis of the {@link NamedSubqueryRoot}. In
* the other, the relevant information are present in pre-computed metadata
* on the {@link ISolutionSetStats}.
*
* @param name
* The name of the solution set.
*
* @return The {@link ISolutionSetStats} and never null
.
*
* @throws RuntimeException
* if there is no such pre-existing named solution set.
*
* @see #getNamedSubqueryRoot(String)
*/
public ISolutionSetStats getSolutionSetStats(final String name) {
return evaluationContext.getSolutionSetStats(name);
}
/**
* Add all variables spanned by the operator.
*
* WARNING: This method does not consider the variable
* scoping rules. It is safe to use with a FILTER as all variables will be
* in scope, but it is not safe to use with a {@link SubqueryBase} as only
* the projected variables will be in scope.
*
* @param bindings
* The set to which the variables will be added.
* @param op
* The operator.
*/
protected void addAll(final Set> bindings,
final IGroupMemberNode op) {
final Iterator> it = BOpUtility
.getSpannedVariables((BOp) op);
while (it.hasNext()) {
bindings.add(it.next());
}
}
// /**
// * Add all variables on the {@link ProjectionNode} of the subquery to the
// * set of distinct variables visible within the scope of the parent query.
// *
// * @param subquery
// * @param varSet
// */
// static private void addProjectedVariables(final QueryBase subquery,
// final Set> varSet) {
//
// final ProjectionNode proj = subquery.getProjection();
//
// if (proj.isWildcard()) {
// /* The subquery's projection should already have been rewritten. */
// throw new AssertionError();
// }
//
// proj.getProjectionVars(varSet);
//
//// for (IVariable> var : proj.getProjectionVars()) {
////
//// varSet.add(var);
////
//// }
//
// }
/**
* Return true
if the {@link FilterNode} is fully bound for the
* given variables.
*
* @param f
* The {@link FilterNode}
* @param vars
* Some collection of variables which are known to be bound.
* @return true
if all variables on which the filter depends
* are present in that collection.
*/
public boolean isFullyBound(final FilterNode f, final Set> vars) {
final Set> fvars = getSpannedVariables(f,
true/* filters */, new LinkedHashSet>());
fvars.removeAll(vars);
if (fvars.isEmpty()) {
/*
* The variables for this filter are all present in the given set of
* variables.
*/
return true;
}
return false;
}
/**
* Return any variables appearing in the Subject, Predicate, or Object
* position (the Context position is ignored).
*
* @param sp
* The statement pattern.
*
* @return The SPO variables in that statement pattern.
*/
public static Set> getSPOVariables(
final StatementPatternNode sp) {
final LinkedHashSet> set = new LinkedHashSet>();
for (int i = 0; i < 3; i++) {
final TermNode tmp = (TermNode) sp.get(0);
if (tmp.isVariable()) {
set.add((IVariable>) tmp.getValueExpression());
}
}
return set;
}
}