com.bigdata.rdf.sparql.ast.optimizers.AbstractOptimizerTestCase Maven / Gradle / Ivy
Show all versions of bigdata-rdf-test Show documentation
/**
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
*/
package com.bigdata.rdf.sparql.ast.optimizers;
import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.query.algebra.StatementPattern.Scope;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IValueExpression;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.ModifiableBOpBase;
import com.bigdata.journal.ITx;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.constraints.IsBoundBOp;
import com.bigdata.rdf.internal.constraints.OrBOp;
import com.bigdata.rdf.sparql.ast.ASTBase;
import com.bigdata.rdf.sparql.ast.ASTContainer;
import com.bigdata.rdf.sparql.ast.AbstractASTEvaluationTestCase;
import com.bigdata.rdf.sparql.ast.ArbitraryLengthPathNode;
import com.bigdata.rdf.sparql.ast.AssignmentNode;
import com.bigdata.rdf.sparql.ast.ConstantNode;
import com.bigdata.rdf.sparql.ast.ExistsNode;
import com.bigdata.rdf.sparql.ast.FilterNode;
import com.bigdata.rdf.sparql.ast.FunctionNode;
import com.bigdata.rdf.sparql.ast.FunctionRegistry;
import com.bigdata.rdf.sparql.ast.GlobalAnnotations;
import com.bigdata.rdf.sparql.ast.GraphPatternGroup;
import com.bigdata.rdf.sparql.ast.GroupMemberNodeBase;
import com.bigdata.rdf.sparql.ast.IGroupMemberNode;
import com.bigdata.rdf.sparql.ast.IQueryNode;
import com.bigdata.rdf.sparql.ast.IValueExpressionNode;
import com.bigdata.rdf.sparql.ast.JoinGroupNode;
import com.bigdata.rdf.sparql.ast.NamedSubqueryInclude;
import com.bigdata.rdf.sparql.ast.NamedSubqueryRoot;
import com.bigdata.rdf.sparql.ast.NotExistsNode;
import com.bigdata.rdf.sparql.ast.PathNode;
import com.bigdata.rdf.sparql.ast.PathNode.PathAlternative;
import com.bigdata.rdf.sparql.ast.PathNode.PathElt;
import com.bigdata.rdf.sparql.ast.PathNode.PathMod;
import com.bigdata.rdf.sparql.ast.PathNode.PathSequence;
import com.bigdata.rdf.sparql.ast.ProjectionNode;
import com.bigdata.rdf.sparql.ast.PropertyPathNode;
import com.bigdata.rdf.sparql.ast.PropertyPathUnionNode;
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.QueryType;
import com.bigdata.rdf.sparql.ast.StatementPatternNode;
import com.bigdata.rdf.sparql.ast.SubqueryRoot;
import com.bigdata.rdf.sparql.ast.TermNode;
import com.bigdata.rdf.sparql.ast.UnionNode;
import com.bigdata.rdf.sparql.ast.ValueExpressionNode;
import com.bigdata.rdf.sparql.ast.VarNode;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpContext;
import com.bigdata.rdf.sparql.ast.eval.AST2BOpUtility;
import com.bigdata.rdf.sparql.ast.explainhints.ExplainHints;
import com.bigdata.rdf.sparql.ast.service.ServiceNode;
/**
* A helper class that can make it easier to write {@link IASTOptimizer} tests.
*
* @author jeremycarroll
*/
public abstract class AbstractOptimizerTestCase extends
AbstractASTEvaluationTestCase {
public interface Annotations extends
com.bigdata.rdf.sparql.ast.GraphPatternGroup.Annotations,
com.bigdata.rdf.sparql.ast.ArbitraryLengthPathNode.Annotations,
com.bigdata.rdf.sparql.ast.eval.AST2BOpBase.Annotations,
com.bigdata.rdf.sparql.ast.StatementPatternNode.Annotations
{
}
enum HelperFlag {
ZERO_OR_ONE {
@Override
public void apply(ASTBase sp) {
setPathMod(((ArbitraryLengthPathNode) sp), PathMod.ZERO_OR_ONE);
}
},
ZERO_OR_MORE {
@Override
public void apply(ASTBase sp) {
setPathMod(((ArbitraryLengthPathNode) sp), PathMod.ZERO_OR_MORE);
}
},
ONE_OR_MORE {
@Override
public void apply(ASTBase sp) {
setPathMod(((ArbitraryLengthPathNode) sp), PathMod.ONE_OR_MORE);
}
},
DEFAULT_CONTEXTS {
@Override
public void apply(ASTBase sp) {
((StatementPatternNode) sp).setScope(Scope.DEFAULT_CONTEXTS);
}
},
NAMED_CONTEXTS {
@Override
public void apply(ASTBase sp) {
((StatementPatternNode) sp).setScope(Scope.DEFAULT_CONTEXTS);
}
},
OPTIONAL {
@Override
public void apply(ASTBase sp) {
((ModifiableBOpBase) sp)
.setProperty(Annotations.OPTIONAL, true);
}
},
DISTINCT {
@Override
public void apply(ASTBase rslt) {
((QueryBase) rslt).getProjection().setDistinct(true);
}
},
REDUCED {
@Override
public void apply(ASTBase rslt) {
((QueryBase) rslt).getProjection().setReduced(true);
}
},
NOT_DISTINCT {
@Override
public void apply(ASTBase rslt) {
((QueryBase) rslt).getProjection().setDistinct(false);
}
},
NOT_REDUCED {
@Override
public void apply(ASTBase rslt) {
((QueryBase) rslt).getProjection().setReduced(false);
}
};
/**
*
* @param target
* @throws ClassCastException
* If there is a mismatch between the flag and its usage.
*/
abstract public void apply(ASTBase target);
private static void setPathMod(ArbitraryLengthPathNode alp, PathMod mod ) {
alp.setProperty(Annotations.LOWER_BOUND, mod == PathMod.ONE_OR_MORE ? 1L : 0L);
alp.setProperty(Annotations.UPPER_BOUND,mod == PathMod.ZERO_OR_ONE ? 1L : Long.MAX_VALUE);
}
};
protected AbstractOptimizerTestCase(final String name) {
super(name);
}
protected AbstractOptimizerTestCase() {
super();
}
/**
* Return the {@link IASTOptimizer} to be evaluated by the {@link Helper}.
* This may be an {@link ASTOptimizerList} if you need to chain multiple
* optimizers together in order to evaluate their behavior.
*/
abstract IASTOptimizer newOptimizer() ;
/**
* The purpose of this class is to make the tests look like the old
* comments. The first example
* {@link TestASTStaticJoinOptimizer#test_simpleOptional01A()} is based on
* the comments of
* {@link TestASTStaticJoinOptimizer#test_simpleOptional01()} and
* demonstrates that the comment is out of date.
*
* NB: Given this goal, several Java naming conventions are ignored. e.g.
* methods whose names are ALLCAPS or the same as ClassNames
*
* Also, note that the intent is that this class be used in
* anonymous subclasses with a single invocation of the {@link #test()} method,
* and the two fields {@link #given} and {@link #expected} initialized
* in the subclasses constructor (i.e. inside a second pair of braces).
*
* All of the protected members are wrappers around constructors,
* to allow the initialization of these two fields, to have a style
* much more like Prolog than Java.
*
* @author jeremycarroll
*/
@SuppressWarnings("rawtypes")
public abstract class Helper {
/**
* Wrapper for the annotation property name-value.
*/
protected class StatementPatternProperty {
private String annotation;
private Object value;
public StatementPatternProperty(String annotation, Object value) {
this.annotation = annotation;
this.value = value;
}
public String getAnnotation() {
return annotation;
}
public Object getValue() {
return value;
}
}
/**
* The given AST is the input to the {@link IASTOptimizer}.
*/
protected QueryRoot given;
/**
* The expected AST output from the {@link IASTOptimizer}.
*/
protected QueryRoot expected;
/**
* Field for construction of arbitrary AST nodes.
*/
protected ASTBase tmp;
/**
* Variables
*/
protected final String w = "w", x = "x", y = "y", z = "z", s = "s",
p = "p", o = "o";
/**
* Constants ...
*/
protected final IV a = iv("a"), b = iv("b"), c = iv("c"), d = iv("d"),
e = iv("e"), f = iv("f"), g = iv("g"), h = iv("h");
private VarNode rightVar;
private VarNode leftVar;
int varCount = 0;
private GlobalAnnotations globals = new GlobalAnnotations(getName(), ITx.READ_COMMITTED);
/**
* Execute the test comparing the rewrite of the {@link #given} AST by
* the {@link IASTOptimizer} with the {@link #expected} AST.
*/
public void test() {
final IASTOptimizer rewriter = newOptimizer();
final AST2BOpContext context = new AST2BOpContext(new ASTContainer(
given), store);
final IQueryNode actual = rewriter.optimize(context,
new QueryNodeWithBindingSet(given, new IBindingSet[] {})).
getQueryNode();
assertSameAST(expected, actual);
}
/**
* Execute the test comparing the rewrite of the {@link #given} AST by
* the {@link IASTOptimizer} with the {@link #expected} AST. Thereby,
* explain hints attached to the computed AST are ignored when comparing
* the ASTs.
*/
public void testWhileIgnoringExplainHints() {
final IASTOptimizer rewriter = newOptimizer();
final AST2BOpContext context = new AST2BOpContext(new ASTContainer(
given), store);
final IQueryNode actual = rewriter.optimize(context,
new QueryNodeWithBindingSet(given, new IBindingSet[] {})).
getQueryNode();
// remove explain hints, to ease comparison
ExplainHints.removeExplainHintAnnotationsFromBOp(actual);
assertSameAST(expected, actual);
}
private IV iv(final String id) {
return makeIV(new URIImpl("http://example/" + id));
}
protected VarNode wildcard() {
return varNode("*");
}
/**
* Create a top-level SELECT query.
*
* @param varNodes
* The projected variables.
* @param namedSubQuery
* A named subquery that is declared to that top-level query.
* @param where
* The WHERE clause of the top-level query.
* @param flags
* The flags to be applied to the resulting AST.
*/
protected QueryRoot select(final VarNode[] varNodes,
final NamedSubqueryRoot namedSubQuery,
final JoinGroupNode where, final HelperFlag... flags) {
final QueryRoot rslt = select(varNodes, where, flags);
rslt.getNamedSubqueriesNotNull().add(namedSubQuery);
return rslt;
}
/**
*
* @param varNodes
* The variables that will appear in the projection.
* @param where
* The top-level WHERE clause.
* @param flags
* Zero or more flags that are applied to the operations.
*
* @return The {@link QueryRoot}.
*/
protected QueryRoot select(final VarNode[] varNodes,
final JoinGroupNode where, final HelperFlag... flags) {
// // Create QueryRoot.
// final QueryRoot select = new QueryRoot(QueryType.SELECT);
// // Setup projection.
// final ProjectionNode projection = new ProjectionNode();
// for (VarNode varNode : varNodes)
// projection.addProjectionVar(varNode);
return select(projection(varNodes), where, flags);
// // Set Projection and Where clause on QueryRoot.
// select.setProjection(projection);
// select.setWhereClause(where);
//
// // Apply helper flags.
// for (HelperFlag flag : flags)
// flag.apply(select);
//
// return select;
}
/**
* Return a PROJECTION node.
*
* @param varNodes
* The variables that will appear in the projection.
*/
protected ProjectionNode projection(final VarNode... varNodes) {
// Setup projection.
final ProjectionNode projection = new ProjectionNode();
for (VarNode varNode : varNodes)
projection.addProjectionVar(varNode);
return projection;
}
/**
* Return a PROJECTION node.
*
* @param assignmentNodes
* The BIND()s that will appear in the projection.
*/
protected ProjectionNode projection(final AssignmentNode... assignmentNodes) {
// Setup projection.
final ProjectionNode projection = new ProjectionNode();
for (AssignmentNode varNode : assignmentNodes)
projection.addProjectionExpression(varNode);
return projection;
}
/**
*
* @param projection
* The projection.
* @param where
* The top-level WHERE clause.
* @param flags
* Zero or more flags that are applied to the operations.
*
* @return The {@link QueryRoot}.
*/
protected QueryRoot select(final ProjectionNode projection,
final JoinGroupNode where, final HelperFlag... flags) {
final QueryRoot select = new QueryRoot(QueryType.SELECT);
return select(select, projection, where, flags);
}
protected QueryRoot select(final VarNode varNode,
final NamedSubqueryRoot namedSubQuery,
final JoinGroupNode where, final HelperFlag... flags) {
return select(new VarNode[] { varNode }, namedSubQuery, where,
flags);
}
protected QueryRoot select(final VarNode varNode,
final JoinGroupNode where, final HelperFlag... flags) {
return select(new VarNode[] { varNode }, where, flags);
}
protected SubqueryRoot selectSubQuery(final ProjectionNode projection,
final JoinGroupNode where, final HelperFlag... flags) {
final SubqueryRoot rslt = new SubqueryRoot(QueryType.SELECT);
return select(rslt, projection, where, flags);
}
private T select(T select, final ProjectionNode projection,
final JoinGroupNode where, final HelperFlag... flags) {
assert projection != null;
// Set Projection and Where clause on QueryRoot.
select.setProjection(projection);
select.setWhereClause(where);
// Apply helper flags.
for (HelperFlag flag : flags)
flag.apply(select);
return select;
}
/**
* Return an ASK subquery.
*
* @param varNode
* The "ASK" variable. See
* {@link SubqueryRoot.Annotations#ASK_VAR}.
* @param where
* The WHERE clause.
*
* @return the ASK subquery.
*/
protected SubqueryRoot ask(final VarNode varNode,
final JoinGroupNode where) {
final SubqueryRoot rslt = new SubqueryRoot(QueryType.ASK);
final ProjectionNode projection = new ProjectionNode();
varNode.setAnonymous(true);
rslt.setProjection(projection);
projection.addProjectionExpression(new AssignmentNode(varNode, varNode));
rslt.setWhereClause(where);
rslt.setAskVar(toValueExpression(varNode));
return rslt;
}
/**
* Return a named subquery.
*
* @param name
* The name associated with the named subquery result.
* @param varNode
* The projected variable.
* @param where
* The where clause.
*
* @return The named subquyery.
*/
protected NamedSubqueryRoot namedSubQuery(final String name,
final VarNode varNode, final JoinGroupNode where) {
final NamedSubqueryRoot namedSubquery = new NamedSubqueryRoot(
QueryType.SELECT, name);
final ProjectionNode projection = new ProjectionNode();
namedSubquery.setProjection(projection);
projection.addProjectionExpression(new AssignmentNode(varNode,
new VarNode(varNode)));
namedSubquery.setWhereClause(where);
return namedSubquery;
}
protected GroupMemberNodeBase namedSubQueryInclude(final String name) {
return new NamedSubqueryInclude(name);
}
protected VarNode leftVar() {
leftVar = newAlppVar("-tVarLeft-",leftVar);
return leftVar;
}
protected VarNode rightVar() {
rightVar = newAlppVar("-tVarRight-",rightVar);
return rightVar;
}
private VarNode newAlppVar(final String prefix, VarNode v) {
if (v != null) {
return v;
}
v = varNode(prefix+varCount++);
v.setAnonymous(true);
return v ;
}
protected ArbitraryLengthPathNode arbitartyLengthPropertyPath(TermNode left, TermNode right,
HelperFlag card, JoinGroupNode joinGroupNode) {
assert leftVar != null;
assert rightVar != null;
ArbitraryLengthPathNode rslt = new ArbitraryLengthPathNode(left, right, leftVar, rightVar, PathMod.ONE_OR_MORE);
card.apply(rslt);
rslt.setArg(0, joinGroupNode);
leftVar = null;
rightVar = null;
return rslt;
}
protected VarNode[] varNodes(String... names) {
VarNode rslt[] = new VarNode[names.length];
for (int i = 0; i < names.length; i++)
rslt[i] = varNode(names[i]);
return rslt;
}
protected VarNode varNode(String varName) {
return new VarNode(varName);
}
protected TermNode constantNode(IV iv) {
return new ConstantNode(iv);
}
protected TermNode constantNode(String c) {
return new ConstantNode(iv(c));
}
protected PropertyPathNode propertyPathNode(final TermNode s, String pattern, final TermNode o) {
return new PropertyPathNode(s, pathNode(pattern), o);
}
/**
* This method is only implemented in part. The issue is what patterns are supported.
* The desired support as of the intended contract of this method is that pattern
* can be any sparql property path expression without any whitespace, and where
* every property in the path is reduced to a single letter a-z.
* e.g. "(a/b?/c)+|a|c*"
*
* The current support is that parenthesis, alternatives and negated properties are not supported.
*
* @param pattern
* @return
*/
protected PathNode pathNode(String pattern) {
final String seq[] = pattern.split("/");
final PathElt elements[] = new PathElt[seq.length];
PathMod mod = null;
for (int i =0;i
* A context node: must be first (i.e. fourth)
* integer cardinality
* HelperFlag's
*
* @param s
* @param p
* @param o
* @param more
* @return
*/
protected StatementPatternNode statementPatternNode(//
final TermNode s,
final TermNode p,
final TermNode o,
final Object ... more) {
final StatementPatternNode rslt = newStatementPatternNode(s, p, o);
if (more.length > 0) {
int i = 0;
if (more[0] instanceof TermNode) {
rslt.setC((TermNode) more[0]);
i = 1;
}
for (; i < more.length; i++) {
if (more[i] instanceof Integer) {
rslt.setProperty(Annotations.ESTIMATED_CARDINALITY,
Long.valueOf((Integer) more[i]));
} else if(more[i] instanceof StatementPatternProperty) {
StatementPatternProperty prop = (StatementPatternProperty) more[i];
rslt.setProperty(prop.annotation, prop.value);
} else {
final HelperFlag flag = (HelperFlag) more[i];
flag.apply(rslt);
}
}
}
return rslt;
}
@SuppressWarnings("unchecked")
private > T initGraphPatternGroup(
T rslt, Object... statements) {
for (Object mem : statements) {
if (mem instanceof IGroupMemberNode) {
rslt.addChild((E) mem);
} else {
((HelperFlag) mem).apply(rslt);
}
}
return rslt;
}
/**
* Return a graph join group.
*
* @param context
* The graph (named graph variable or IRI).
* @param statements
* The children (including any optional {@link HelperFlag}s).
*
* @return The group join group node.
*/
protected JoinGroupNode joinGroupNode(final TermNode context,
final Object... statements) {
final JoinGroupNode rslt = joinGroupNode(statements);
rslt.setContext(context);
return rslt;
}
/**
* Wrap the arguments in a {@link JoinGroupNode} and apply any
* inter-mixed {@link HelperFlag}s.
*
* @param statements
* The arguments (group group members or {@link HelperFlag}s)
*
* @return The {@link JoinGroupNode}.
*/
protected JoinGroupNode joinGroupNode(final Object... statements) {
return initGraphPatternGroup(new JoinGroupNode(), statements);
}
/**
* Wrap the triple patterns in a WHERE clause (aka join group).
*
* @param statements
* The triple patterns.
*
* @return The {@link JoinGroupNode}
*/
protected JoinGroupNode where(final IGroupMemberNode... statements) {
return joinGroupNode((Object[]) statements);
}
protected PropertyPathUnionNode propertyPathUnionNode(Object... statements) {
return initGraphPatternGroup(new PropertyPathUnionNode(),
statements);
}
protected UnionNode unionNode(Object... statements) {
return initGraphPatternGroup(new UnionNode(), statements);
}
protected FunctionNode bound(final VarNode varNode) {
final FunctionNode rslt = new FunctionNode(FunctionRegistry.BOUND,
null,
new ValueExpressionNode[] {
varNode
} );
rslt.setValueExpression(new IsBoundBOp(varNode.getValueExpression()));
return rslt;
}
protected FunctionNode knownUnbound(final VarNode varNode) {
final VarNode vv = varNode("-unbound-var-"+varNode.getValueExpression().getName()+"-0");
final FunctionNode rslt = new FunctionNode(FunctionRegistry.BOUND, null, vv);
rslt.setValueExpression(new IsBoundBOp(vv.getValueExpression()));
return rslt;
}
protected FilterNode filter(final IValueExpressionNode f) {
return new FilterNode(f);
}
/**
* Return a {@link FunctionNode}
*
* @param uri
* the function URI. see {@link FunctionRegistry}
* @param args
* the arguments to the function.
* @return
*/
// * @param scalarValues
// * One or more scalar values that are passed to the function
protected IValueExpressionNode functionNode(//
final String uri,//
final ValueExpressionNode... args//
) {
return functionNode(new URIImpl(uri), args);
}
/**
* Return a {@link FunctionNode}
*
* @param uri
* the function URI. see {@link FunctionRegistry}
* @param args
* the arguments to the function.
* @return
*/
// * @param scalarValues
// * One or more scalar values that are passed to the function
protected IValueExpressionNode functionNode(//
final URI uri,//
final ValueExpressionNode... args//
) {
return new FunctionNode(uri, null, args);
}
protected ServiceNode service(//
final TermNode serviceRef,
final GraphPatternGroup groupNode//
) {
return new ServiceNode(serviceRef, groupNode);
}
/**
* BIND(expression AS variable)
*
* @param valueNode
* The expression
* @param varNode
* The variable.
*/
protected AssignmentNode bind(final IValueExpressionNode valueNode,
final VarNode varNode) {
return new AssignmentNode(varNode, valueNode);
}
/**
* Logical OR of two value expressions.
*/
protected FunctionNode or(final ValueExpressionNode v1,
final ValueExpressionNode v2) {
final FunctionNode rslt = FunctionNode.OR(v1, v2);
rslt.setValueExpression(new OrBOp(//
v1.getValueExpression(), //
v2.getValueExpression()));
return rslt;
}
protected ExistsNode exists(VarNode v, GraphPatternGroup jg) {
v.setAnonymous(true);
ExistsNode existsNode = new ExistsNode(v, jg);
existsNode.setValueExpression(toValueExpression(v));
return existsNode;
}
private IVariable extends IV> toValueExpression(VarNode v) {
return (IVariable extends IV>) AST2BOpUtility.toVE(getBOpContext(), globals, v);
}
private IValueExpression extends IV> toValueExpression(FunctionNode n) {
return AST2BOpUtility.toVE(getBOpContext(), globals, n);
}
protected NotExistsNode notExists(VarNode v, GraphPatternGroup jg) {
return new NotExistsNode(v, jg);
}
protected ASTBase getTmp() {
return tmp;
}
}
protected static final class ASTPropertyPathOptimizerInTest extends ASTPropertyPathOptimizer {
private int counter = 0;
@Override
protected VarNode anonVar(final String anon) {
VarNode v = new VarNode(anon+counter++);
v.setAnonymous(true);
return v;
}
}
protected StatementPatternNode newStatementPatternNode(final TermNode s,
final TermNode p, final TermNode o) {
return newStatementPatternNode(s, p, o, -1, false);
}
protected StatementPatternNode newStatementPatternNode(final TermNode s,
final TermNode p, final TermNode o, final long cardinality) {
return newStatementPatternNode(s, p, o, cardinality, false);
}
/**
* Return a new triple pattern.
*
* @param s
* The subject.
* @param p
* The predicate.
* @param o
* The object.
* @param cardinality
* The estimated cardinality -or- -1
to NOT
* associate cardinality with the triple pattern.
* @param optional
* true
iff the triple pattern should be marked as a
* simple OPTIONAL.
*/
protected StatementPatternNode newStatementPatternNode(
final TermNode s, final TermNode p, final TermNode o,
final long cardinality, final boolean optional) {
final StatementPatternNode sp = new StatementPatternNode(s, p, o);
if (cardinality != -1) {
sp.setProperty(Annotations.ESTIMATED_CARDINALITY, cardinality);
}
if (optional) {
sp.setOptional(true);
}
return sp;
}
}