com.bigdata.relation.rule.Rule 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
*/
package com.bigdata.relation.rule;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.log4j.Logger;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IConstraint;
import com.bigdata.bop.IPredicate;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.IVariableOrConstant;
import com.bigdata.bop.Var;
import com.bigdata.bop.bindingSet.EmptyBindingSet;
import com.bigdata.relation.rule.eval.ActionEnum;
import com.bigdata.relation.rule.eval.IRuleTaskFactory;
/**
* Default impl.
*
* @author Bryan Thompson
* @version $Id$
*/
public class Rule implements IRule {
/**
*
*/
private static final long serialVersionUID = -3834383670300306143L;
final static transient protected Logger log = Logger.getLogger(Rule.class);
final static transient protected boolean INFO = log.isInfoEnabled();
final static transient protected boolean DEBUG = log.isDebugEnabled();
/**
* Singleton factory for {@link Var}s (delegates to {@link Var#var(String)}).
*
* @see Var#var(String)
*
* @todo it is a good idea to use this factory rather than
* {@link Var#var(String)} as the latter MAY be replaced by
* per-rule-instance variables rather than globally canonical
* variables
*
* the only problem with this is that we need to access the variables
* in a bindingset by name after high-level query since the rule may
* not be available to the caller, e.g., the match rule uses
* dynamically generated rules that are not visible to the caller who
* only sees the binding sets.
*/
static protected Var var(String name) {
return Var.var(name);
}
/**
* Name of the rule.
*/
final private String name;
/**
* The head of the rule.
*/
final private IPredicate head;
/**
* The body of the rule -or- null
if the body of the rule
* is empty.
*/
final private IPredicate[] tail;
// /**
// * true
iff a DISTINCT constraint will be imposed when the
// * rule is evaluated as a query.
// */
// final private boolean distinct;
/**
* Options that effect query evaluation.
*/
final private IQueryOptions queryOptions;
/**
* Optional constraints on the bindings.
*/
final private IConstraint[] constraints;
/**
* The optional override for rule evaluation.
*/
final private IRuleTaskFactory taskFactory;
/**
* The bound constants (if any).
*/
final private IBindingSet constants;
/**
* The set of distinct variables declared by the rule.
*/
final private Set vars;
/**
* The set of distinct required variables declared by the rule. These
* are used in the projection (select or construct) and for aggregation,
* and thus can never be dropped from the binding sets.
*/
final private Set requiredVars;
final public int getVariableCount() {
return vars.size();
}
final public Iterator getVariables() {
return vars.iterator();
}
final public int getRequiredVariableCount() {
return requiredVars.size();
}
final public Iterator getRequiredVariables() {
return requiredVars.iterator();
}
final public int getTailCount() {
return tail.length;
}
final public IPredicate getHead() {
return head;
}
final public Iterator getTail() {
return Arrays.asList(tail).iterator();
}
final public IPredicate getTail(int index) {
return tail[index];
}
final public IQueryOptions getQueryOptions() {
return queryOptions;
}
// final public boolean isDistinct() {
//
// return distinct;
//
// }
final public int getConstraintCount() {
return constraints == null ? 0 : constraints.length;
}
final public IConstraint getConstraint(int index) {
if (constraints == null)
throw new IndexOutOfBoundsException();
return constraints[index];
}
final public Iterator getConstraints() {
return Arrays.asList(constraints).iterator();
}
public final IBindingSet getConstants() {
return constants;
}
final public String getName() {
return name;
}
public String toString() {
return toString(null);
}
public String toString(final IBindingSet bindingSet) {
final StringBuilder sb = new StringBuilder();
sb.append(getName());
sb.append(" : ");
// write out bindings for the tail.
for (int i = 0; i < tail.length; i++) {
sb.append(tail[i].toString(bindingSet));
if (i + 1 < tail.length) {
sb.append(", ");
}
}
sb.append(" -> ");
// write out bindings for the head.
if (head != null) {
sb.append(head.toString(bindingSet));
}
if(!(constants instanceof EmptyBindingSet)) {
sb.append(", where ");
sb.append(constants);
}
if (queryOptions != null) {
sb.append(", queryOptions=" + queryOptions);
}
return sb.toString();
}
/**
* Rule ctor.
*
* @param name
* A label for the rule.
* @param head
* The subset of bindings that are selected by the rule.
* @param tail
* The tail (aka body) of the rule.
* @param constraints
* An array of constraints on the legal states of the bindings
* materialized for the rule.
*
* @throws IllegalArgumentException
* if the name is null
.
* @throws IllegalArgumentException
* if the head is null
.
* @throws IllegalArgumentException
* if the tail is null
.
* @throws IllegalArgumentException
* if any element of the tail is null
.
* @throws IllegalArgumentException
* if any element of the optional constraints is
* null
.
* @throws IllegalArgumentException
* if the tail is empty.
* @throws IllegalArgumentException
* if the head declares any variables that are not
* declared in the tail.
*/
// * @throws IllegalArgumentException
// * if the head names more the one relation in its view
// * (tails may name more than one, which is interpreted as a
// * fused view of the named relations).
public Rule(String name, IPredicate head, IPredicate[] tail,
IConstraint[] constraints) {
this(name, head, tail, QueryOptions.NONE, constraints,
null/* constants */, null/* taskFactory */);
}
/**
* Rule ctor.
*
* @param name
* A label for the rule.
* @param head
* The subset of bindings that are selected by the rule.
* @param tail
* The tail (aka body) of the rule.
* @param queryOptions
* Options that effect evaluation of the rule as a query.
* @param constraints
* An array of constraints on the legal states of the bindings
* materialized for the rule.
*
* @throws IllegalArgumentException
* if the name is null
.
* @throws IllegalArgumentException
* if the head is null
.
* @throws IllegalArgumentException
* if the tail is null
.
* @throws IllegalArgumentException
* if any element of the tail is null
.
* @throws IllegalArgumentException
* if any element of the optional constraints is
* null
.
* @throws IllegalArgumentException
* if the tail is empty.
* @throws IllegalArgumentException
* if the head declares any variables that are not
* declared in the tail.
*/
// * @throws IllegalArgumentException
// * if the head names more the one relation in its view
// * (tails may name more than one, which is interpreted as a
// * fused view of the named relations).
public Rule(String name, IPredicate head, IPredicate[] tail,
final IQueryOptions queryOptions, IConstraint[] constraints) {
this(name, head, tail, queryOptions, constraints,
null/* constants */, null/* taskFactory */, null/* requiredVars*/);
}
public Rule(String name, IPredicate head, IPredicate[] tail,
IQueryOptions queryOptions, IConstraint[] constraints,
IBindingSet constants, IRuleTaskFactory taskFactory) {
this(name, head, tail, queryOptions, constraints,
null/* constants */, taskFactory, null /* requiredVars */);
}
/**
* Fully specified ctor.
*
* @param name
* The name of the rule.
* @param head
* The head of the rule. This is optional for rules that will be
* evaluated using {@link ActionEnum#Query} but required for
* rules that will be evaluated using a mutation
* {@link ActionEnum}.
* @param tail
* The predicates in the tail of the rule.
* @param queryOptions
* Additional constraints on the evaluate of a rule as a query
* (required, but see {@link QueryOptions#NONE}).
* @param constraints
* The constraints on the rule (optional).
* @param constants
* Bindings for variables that are bound as constants for the
* rule (optional).
* @param taskFactory
* Optional override for rule evaluation (MAY be
* null
).
* @param requiredVars
* Optional set of required variables. If null
,
* all variables are assumed to be required.
*/
public Rule(String name, IPredicate head, IPredicate[] tail,
IQueryOptions queryOptions, IConstraint[] constraints,
IBindingSet constants, IRuleTaskFactory taskFactory,
IVariable[] requiredVars) {
if (name == null)
throw new IllegalArgumentException();
// if (head == null)
// throw new IllegalArgumentException();
if (tail == null)
throw new IllegalArgumentException();
if (queryOptions == null)
throw new IllegalArgumentException();
if (constants == null) {
constants = EmptyBindingSet.INSTANCE;
}
this.name = name;
// the predicate declarations for the body.
this.tail = tail;
final Set vars = new HashSet();
for (int i = 0; i < tail.length; i++) {
final IPredicate pred = tail[i];
if (pred == null)
throw new IllegalArgumentException();
final int arity = pred.arity();
for (int j = 0; j < arity; j++) {
final IVariableOrConstant t = pred.get(j);
if (t.isVar()) {
vars.add((IVariable) t);
}
}
if (pred instanceof IStarJoin) {
final IStarJoin starJoin = (IStarJoin) pred;
final Iterator it = starJoin.getConstraintVariables();
while (it.hasNext()) {
IVariable v = it.next();
vars.add(v);
}
}
}
// the head of the rule - all variables must occur in the tail.
this.head = head;
if(head != null) {
if (head.getRelationCount() != 1) {
throw new IllegalArgumentException(
"Expecting a single relation identifier for the head: head="
+ head);
}
final int arity = head.arity();
for (int j = 0; j < arity; j++) {
final IVariableOrConstant t = head.get(j);
if (t.isVar()) {
if (!vars.contains((IVariable) t)) {
throw new IllegalArgumentException(
"Variable not declared in the tail: " + t);
}
}
}
}
// make the collection immutable.
this.vars = Collections.unmodifiableSet(vars);
this.queryOptions = queryOptions;
// constraint(s) on the variable bindings (MAY be null).
this.constraints = constraints;
if (constraints != null) {
for (int i = 0; i < constraints.length; i++) {
assert constraints[i] != null;
}
}
// NOT NULL.
this.constants = constants;
// MAY be null
this.taskFactory = taskFactory;
// required variables may be null
final Set s = new HashSet();
if (requiredVars == null) {
s.addAll(vars);
} else {
for (IVariable v : requiredVars) {
s.add(v);
}
}
this.requiredVars = Collections.unmodifiableSet(s);
}
public IRule specialize(IBindingSet bindingSet, IConstraint[] constraints) {
return specialize(getName() + "'", bindingSet, constraints);
}
public IRule specialize(String name, IBindingSet bindingSet,
IConstraint[] constraints) {
if (name == null)
throw new IllegalArgumentException();
if (bindingSet == null)
throw new IllegalArgumentException();
/*
* Setup the new head and the body for the new rule by applying the
* bindings.
*/
final IPredicate newHead = (head == null ? null : head
.asBound(bindingSet));
final IPredicate[] newTail = bind(tail, bindingSet);
// final IPredicate newHead = head;
//
// final IPredicate[] newTail = tail;
/*
* Setup the new constraints. We do not test for whether or not two
* constraints are the same, we just append the new constraints to the
* end of the old constraints. The rest of the logic just covers the
* edge cases where one or the other of the constraint arrays is null or
* empty.
*/
final IConstraint[] newConstraint;
if (constraints == null || constraints.length == 0) {
newConstraint = this.constraints;
} else if (this.constraints == null || this.constraints.length == 0) {
newConstraint = constraints;
} else {
int len = constraints.length + this.constraints.length;
newConstraint = new IConstraint[len];
System.arraycopy(this.constraints, 0, newConstraint, 0,
this.constraints.length);
System.arraycopy(constraints, 0, newConstraint,
this.constraints.length, constraints.length);
}
/*
* Pass on the task factory - it is up to it to be robust to
* specialization of the rule.
*/
final IRuleTaskFactory taskFactory = getTaskFactory();
final IRule newRule = new Rule(name, newHead, newTail,
queryOptions, newConstraint, bindingSet, taskFactory,
requiredVars.toArray(new IVariable[requiredVars.size()]));
return newRule;
}
private IPredicate[] bind(IPredicate[] predicates, IBindingSet bindingSet) {
final IPredicate[] tmp = new IPredicate[predicates.length];
for(int i=0; i> getSharedVars(int index1, int index2) {
if (index1 == index2) {
throw new IllegalArgumentException();
}
return getSharedVars(tail[index1], tail[index2]);
}
/**
* Return the variables in common for two {@link IPredicate}s.
*
* @param p1
* A predicate.
*
* @param p2
* A different predicate.
*
* @return The variables in common -or- null
iff there are no
* variables in common.
*
* @throws IllegalArgumentException
* if the two predicates are the same reference.
*/
public static Set> getSharedVars(final IPredicate p1, final IPredicate p2) {
final Set> vars = new HashSet>();
final int arity1 = p1.arity();
final int arity2 = p2.arity();
for(int i=0; i avar = p1.get(i);
if(avar.isConstant()) continue;
for (int j = 0; j < arity2; j++) {
if (p2.get(j) == avar) {
vars.add((Var>) avar);
}
}
}
return vars;
}
public int getVariableCount(int index, IBindingSet bindingSet) {
// if (index < 0 || index >= body.length)
// throw new IndexOutOfBoundsException();
if (bindingSet == null)
throw new IllegalArgumentException();
final IPredicate pred = tail[index];
final int arity = pred.arity();
int nbound = 0;
for(int j=0; j= body.length)
// throw new IndexOutOfBoundsException();
if (bindingSet == null)
throw new IllegalArgumentException();
final IPredicate pred = tail[index];
final int arity = pred.arity();
for(int j=0; j