package com.bigdata.rdf.sparql.ast; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.openrdf.query.algebra.StatementPattern.Scope; import com.bigdata.bop.BOp; import com.bigdata.bop.Constant; import com.bigdata.bop.IVariable; import com.bigdata.bop.NV; import com.bigdata.rdf.sparql.ast.optimizers.ASTGraphGroupOptimizer; /** * A node in the AST representing a triple pattern with a property path. The * predicate for this triple pattern will be a {@link PathNode} instead of a * {@link TermNode} as in a normal statement pattern. This is modeled closely * after StatementPatternNode. */ public class PropertyPathNode extends GroupMemberNodeBase<PropertyPathNode> implements IBindingProducerNode { /** * */ private static final long serialVersionUID = -1697848422480337886L; public interface Annotations extends GroupMemberNodeBase.Annotations { /** * The {@link Scope} (required). * * @see ASTGraphGroupOptimizer */ String SCOPE = "scope"; } /** * Constructor required for {@link com.bigdata.bop.BOpUtility#deepCopy(FilterNode)}. */ public PropertyPathNode(final PropertyPathNode op) { super(op); } /** * Required shallow copy constructor. */ public PropertyPathNode(final BOp[] args, final Map<String, Object> anns) { super(args, anns); } /** * A triple pattern. The {@link Scope} will be * {@link Scope#DEFAULT_CONTEXTS}, the context will be <code>null</code>. * * @param s * @param p * @param o * * @see #PropertyPathNode(TermNode, PathNode, TermNode, TermNode, Scope) */ public PropertyPathNode(final TermNode s, final PathNode p, final TermNode o) { this(s, p, o, null/* context */, Scope.DEFAULT_CONTEXTS); } /** * A quad pattern. * <p> * Note: When a {@link PropertyPathNode} appears in a WHERE clause, the * {@link Scope} should be marked as {@link Scope#DEFAULT_CONTEXTS} if it is * NOT embedded within a GRAPH clause and otherwise as * {@link Scope#NAMED_CONTEXTS}. * <p> * The context position of the statement should be <code>null</code> unless * it is embedded within a GRAPH clause, in which case the context is the * context specified for the parent GRAPH clause. * <p> * A <code>null</code> context in {@link Scope#DEFAULT_CONTEXTS} is * interpreted as the RDF merge of the graphs in the defaultGraph (as * specified by the {@link DatasetNode}). When non-<code>null</code> (it can * be bound by the SPARQL UPDATE <em>WITH</em> clause), the defaultGraph * declared by the {@link DatasetNode} is ignored and the context is bound * to the constant specified in that <em>WITH</em> clause. * <p> * Absent any other constraints on the query, an unbound variable context in * {@link Scope#NAMED_CONTEXTS} may be bound to any named graph specified by * the {@link DatasetNode}. * * @param s * The subject (variable or constant; required). * @param p * The property path (required). * @param o * The subject (variable or constant; required). * @param c * The context (variable or constant; optional). * @param scope * Either {@link Scope#DEFAULT_CONTEXTS} or * {@link Scope#NAMED_CONTEXTS} (required). * * @throws IllegalArgumentException * if <i>s</i>, <i>p</i>, or <i>o</i> is <code>null</code>. * @throws IllegalArgumentException * if <i>scope</i> is <code>null</code>. * @throws IllegalArgumentException * if <i>scope</i> is {@link Scope#NAMED_CONTEXTS} and <i>c</i> * is <code>null</code>. */ public PropertyPathNode(final TermNode s, final PathNode p, final TermNode o, final TermNode c, final Scope scope) { super(new BOp[] { s, p, o, c }, scope == null ? null/* anns */: NV .asMap(new NV(Annotations.SCOPE, scope))); if (scope == null) throw new IllegalArgumentException(); if (s == null || p == null || o == null) throw new IllegalArgumentException(); if (scope == Scope.NAMED_CONTEXTS && c == null) throw new IllegalArgumentException(); } /** * The variable or constant for the subject position (required). */ final public TermNode s() { return (TermNode) get(0); } /** * The property path (required). */ final public PathNode p() { return (PathNode) get(1); } /** * The variable or constant for the object position (required). */ final public TermNode o() { return (TermNode) get(2); } /** * The variable or constant for the context position (required iff in quads * mode). */ final public TermNode c() { return (TermNode) get(3); } final public void setC(final TermNode c) { this.setArg(3, c); } /** * The scope for this statement pattern (either named graphs or default * graphs). * * @see Annotations#SCOPE * @see Scope */ final public Scope getScope() { return (Scope) getRequiredProperty(Annotations.SCOPE); } final public void setScope(final Scope scope) { if (scope == null) throw new IllegalArgumentException(); setProperty(Annotations.SCOPE, scope); } public String toString(final int indent) { final StringBuilder sb = new StringBuilder(); sb.append("\n").append(indent(indent)).append(toShortString()); if (getQueryHints() != null && !getQueryHints().isEmpty()) { sb.append("\n"); sb.append(indent(indent + 1)); sb.append(Annotations.QUERY_HINTS); sb.append("="); sb.append(getQueryHints().toString()); } return sb.toString(); } @Override public String toShortString() { final StringBuilder sb = new StringBuilder(); final Integer id = (Integer)getProperty(BOp.Annotations.BOP_ID); sb.append("PropertyPathNode"); if (id != null) { sb.append("[").append(id.toString()).append("]"); } sb.append("("); sb.append(s()).append(", "); sb.append(p()).append(", "); sb.append(o()); final TermNode c = c(); if (c != null) { sb.append(", ").append(c); } sb.append(")"); final Scope scope = getScope(); if (scope != null) { sb.append(" [scope=" + scope + "]"); } return sb.toString(); } /** * Return the variables used by the predicate - i.e. what this node will * attempt to bind when run. */ public Set<IVariable<?>> getProducedBindings() { final Set<IVariable<?>> producedBindings = new LinkedHashSet<IVariable<?>>(); final TermNode s = s(); final TermNode o = o(); final TermNode c = c(); addProducedBindings(s, producedBindings); addProducedBindings(o, producedBindings); addProducedBindings(c, producedBindings); return producedBindings; } /** * This handles the special case where we've wrapped a Var with a Constant * because we know it's bound, perhaps by the exogenous bindings. If we * don't handle this case then we get the join vars wrong. * * @see StaticAnalysis._getJoinVars */ private void addProducedBindings(final TermNode t, final Set<IVariable<?>> producedBindings) { if (t instanceof VarNode) { producedBindings.add(((VarNode) t).getValueExpression()); } else if (t instanceof ConstantNode) { final ConstantNode cNode = (ConstantNode) t; final Constant<?> c = (Constant<?>) cNode.getValueExpression(); final IVariable<?> var = c.getVar(); if (var != null) { producedBindings.add(var); } } } @Override public Set<IVariable<?>> getRequiredBound(StaticAnalysis sa) { return new HashSet<IVariable<?>>(); } @Override public Set<IVariable<?>> getDesiredBound(StaticAnalysis sa) { return sa.getSpannedVariables(this, true, new HashSet<IVariable<?>>()); } }