[Mulgara-svn] r1897 - trunk/src/jar/tuples/java/org/mulgara/store/tuples
pag at mulgara.org
pag at mulgara.org
Wed Jan 27 19:24:30 UTC 2010
Author: pag
Date: 2010-01-27 11:24:29 -0800 (Wed, 27 Jan 2010)
New Revision: 1897
Added:
trunk/src/jar/tuples/java/org/mulgara/store/tuples/ExpandedProjection.java
trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftFiltered.java
Modified:
trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftJoin.java
trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java
Log:
Changed the evaluation of Optional to fully conform to the SPARQL algebra
Added: trunk/src/jar/tuples/java/org/mulgara/store/tuples/ExpandedProjection.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/ExpandedProjection.java (rev 0)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/ExpandedProjection.java 2010-01-27 19:24:29 UTC (rev 1897)
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2010 Duraspace, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mulgara.store.tuples;
+
+// Java 2 standard packages
+import java.util.*;
+
+// Locally written packages
+import org.apache.log4j.Logger;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+import org.mulgara.util.functional.C;
+
+/**
+ * Projection to include new columns that will be unbound. This is a thin wrapper
+ * and will maintain almost all the properties of the wrapped Tuples.
+ *
+ * @created 2010-01-06
+ * @author Paul Gearon
+ * @copyright © 2010 <a href="http://www.fedora-commons.org/">Fedora Commons</a>
+ */
+class ExpandedProjection extends AbstractTuples {
+
+ /** Logger. */
+ private final static Logger logger = Logger.getLogger(ExpandedProjection.class);
+
+ /** The proposition to project. */
+ private final Tuples operand;
+
+ /** Projected variable array. */
+ private final Variable[] variables;
+
+ /** Width of the original tuples. */
+ private final int opWidth;
+
+ /**
+ * Eliminate columns from a {@link Tuples}. This does not eliminate
+ * duplicates; {@link DistinctTuples} should be used to produce a formal
+ * relational projection.
+ *
+ * @param operand the tuples to project
+ * @param newVars the columns to add
+ * @throws IllegalArgumentException if <var>operand</var> is <code>null</code>
+ */
+ ExpandedProjection(Tuples operand, List<Variable> newVars) {
+
+ assert operand != null;
+ assert newVars != null;
+
+ assert !newVars.isEmpty();
+
+ // get the existing a new variables
+ Variable[] opVars = operand.getVariables();
+
+ assert C.intersect(newVars, opVars).isEmpty();
+
+ if (logger.isDebugEnabled() && newVars.isEmpty()) {
+ logger.debug("No extra variables in tuples expansion");
+ }
+
+ // Initialize fields
+ opWidth = opVars.length;
+ this.operand = (Tuples)operand.clone();
+ this.variables = new Variable[opWidth + newVars.size()];
+
+ // copy the original columns to the start of the variables array
+ System.arraycopy(opVars, 0, this.variables, 0, opWidth);
+ // copy the new "unbound" columns to the end of the variables array
+ int i = opWidth;
+ for (Variable v: newVars) this.variables[i++] = v;
+ assert i == this.variables.length;
+ }
+
+ /**
+ * Cloning constructor.
+ * @param parent The original Tuples to be cloned from
+ */
+ private ExpandedProjection(ExpandedProjection parent) {
+ operand = (Tuples)parent.operand.clone();
+ variables = parent.variables;
+ opWidth = parent.opWidth;
+ }
+
+ /**
+ * Gets the ColumnValue attribute of the OrderedProjection object
+ * @param column Specifies the column number to get the value for.
+ * @return The value for the variable binding, unbound if in an expanded column.
+ * @throws TuplesException Error accessing the underlying Tuples
+ */
+ public long getColumnValue(int column) throws TuplesException {
+ assert (column >= 0) && (column < variables.length) : "Invalid column " + column;
+ return column < opWidth ? operand.getColumnValue(column) : UNBOUND;
+ }
+
+ /**
+ * Gets the Comparator attribute of the OrderedProjection object
+ * @return The Comparator value
+ */
+ public RowComparator getComparator() {
+ return operand.getComparator();
+ }
+
+ /**
+ * Gets the RowCount attribute of the OrderedProjection object
+ * @return The RowCount value
+ * @throws TuplesException Error on underlying data
+ */
+ public long getRowCount() throws TuplesException {
+ return operand.getRowCount();
+ }
+
+ public long getRowUpperBound() throws TuplesException {
+ return operand.getRowUpperBound();
+ }
+
+ public long getRowExpectedCount() throws TuplesException {
+ return operand.getRowExpectedCount();
+ }
+
+ /**
+ * Gets the Variables attribute of the OrderedProjection object
+ * @return The Variables value
+ */
+ public Variable[] getVariables() {
+ return variables;
+ }
+
+ /**
+ * A column may be unbound if it's unbound in the projection operand.
+ */
+ public boolean isColumnEverUnbound(int column) throws TuplesException {
+ assert (column >= 0) && (column < variables.length) : "Invalid column " + column;
+ // yes there's a boolean expression for the following, but this is easier to read
+ return column < opWidth ? operand.isColumnEverUnbound(column) : true;
+ }
+
+ /**
+ * Gets the Materialized attribute of the OrderedProjection object
+ * @return The Materialized value
+ */
+ public boolean isMaterialized() {
+ return operand.isMaterialized();
+ }
+
+ /**
+ * @return whether every operand is unconstrained
+ * @throws TuplesException Error in the underlying data
+ */
+ public boolean isUnconstrained() throws TuplesException {
+ return operand.isUnconstrained();
+ }
+
+
+ public List<Tuples> getOperands() {
+ return Collections.singletonList(operand);
+ }
+
+
+ //
+ // Methods implementing Tuples
+ //
+
+ /**
+ * Go the the first binding that matches a given prefix.
+ * @param prefix A pattern specifying the first thing to test against.
+ * @param suffixTruncation Not accepted. Must be 0.
+ * @throws TuplesException Error in the underlying Tuples, or a non-zero suffixTruncation.
+ */
+ public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
+ if (suffixTruncation != 0) throw new TuplesException("Suffix truncation not implemented");
+ // truncate the prefix if it is wider than the first operand. It can't match to UNBOUND anyway.
+ if (prefix.length > opWidth) {
+ long[] tmp = prefix;
+ prefix = new long[opWidth];
+ System.arraycopy(tmp, 0, prefix, 0, opWidth);
+ }
+ operand.beforeFirst(prefix, 0);
+ }
+
+ /**
+ * Closes this resource and all attached resources.
+ * @throws TuplesException Error in the underlying tuples.
+ */
+ public void close() throws TuplesException {
+ operand.close();
+ }
+
+ /**
+ * Tests if this tuples has duplicate rows
+ * @return <code>true</code> iff the underyling tuples has no duplicates.
+ * @throws TuplesException Error in the underlying Tuples
+ */
+ public boolean hasNoDuplicates() throws TuplesException {
+ return operand.hasNoDuplicates();
+ }
+
+ /**
+ * Move to the next binding on this Tuples.
+ * @return <code>true</code> if there is more data to move to.
+ * @throws TuplesException Error in the udnerlying Tuples
+ */
+ public boolean next() throws TuplesException {
+ return operand.next();
+ }
+
+ //
+ // Methods overriding Object
+ //
+
+ /**
+ * Clones this object
+ * @return A new and identical tuples
+ */
+ public Object clone() {
+ return new ExpandedProjection(this);
+ }
+}
Added: trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftFiltered.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftFiltered.java (rev 0)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftFiltered.java 2010-01-27 19:24:29 UTC (rev 1897)
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2008 Fedora Commons, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.mulgara.store.tuples;
+
+// Java 2 standard packages
+import java.util.*;
+
+// Third party packages
+import org.apache.log4j.*;
+
+// Locally written packages
+import org.mulgara.query.Constraint;
+import org.mulgara.query.QueryException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+import org.mulgara.query.filter.Context;
+import org.mulgara.query.filter.ContextOwner;
+import org.mulgara.query.filter.Filter;
+import org.mulgara.resolver.spi.QueryEvaluationContext;
+import org.mulgara.resolver.spi.TuplesContext;
+import org.mulgara.store.tuples.AbstractTuples;
+
+/**
+ * Left Filtering operation.
+ *
+ * This operation is used for the case where an OPTIONAL join is being filtered
+ * based on variables that appear in both the LHS and the RHS of the OPTIONAL.
+ *
+ * According to SPARQL:
+ * Diff(Ω1, Ω2, expr) = { μ | μ in Ω1 such that for all μ′ in Ω2,
+ * either μ and μ′ are not compatible or μ and μ' are compatible and
+ * expr(merge(μ, μ')) has an effective boolean value of false }
+ * http://www.w3.org/TR/rdf-sparql-query/#defn_algDiff
+ *
+ * In this case, no variables are common, which simplifies our situation to being
+ * compatible (since all rows are compatible when no variables are shared). So
+ * we just need every μ from Ω1 where every μ' yields an expression of false.
+
+ * The join is performed by iterating over the lhs, and searching on the
+ * RHS for true rows. If one is found, then the lhs must iterate again.
+ *
+ * @created 2009-12-18
+ * @author <a href="mailto:pgearon at users.sourceforge.net">Paul Gearon</a>
+ */
+public class LeftFiltered extends AbstractTuples implements ContextOwner {
+
+ private static Logger logger = Logger.getLogger(LeftFiltered.class.getName());
+
+ /** The set of tuples to return all row from. */
+ protected Tuples lhs;
+
+ /** The set of tuples to add to the lhs. */
+ protected Tuples rhs;
+
+ /** The filter to apply. */
+ private Filter filter;
+
+ /** The tuples context */
+ protected TuplesContext context = null;
+
+ /** A list of context owners that this owner provides the context for. */
+ private List<ContextOwner> contextListeners = new ArrayList<ContextOwner>();
+
+ /** A collection of the variables on the LHS */
+ private ArrayList<Variable> lhsVars;
+
+ /** The offset for indexing into the RHS, while avoiding LHS variables */
+ private int rhsOffset;
+
+ /** Indicates that the current row is OK, and {@link #next()} will return true. */
+ private boolean currentRowValid = false;
+
+ /**
+ * Configure a filtering join on the left hand side.
+ *
+ * @param lhs The original tuples, including the rows to be removed.
+ * @param rhs The tuples to be joined in cross product for testing.
+ * @param filter The filter that must return FALSE for everything in order to have a LHS row returned.
+ * @throws IllegalArgumentException If the <var>lhs</var> and <var>rhs</var>
+ * contain variables in common.
+ */
+ @SuppressWarnings("unchecked")
+ LeftFiltered(Tuples lhs, Tuples rhs, Filter filter, QueryEvaluationContext queryContext) throws TuplesException, IllegalArgumentException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Filtering " + lhs + " by " + rhs + " with expression=" + filter);
+ }
+ // store the operands
+ this.lhs = (Tuples)lhs.clone();
+ this.rhs = (Tuples)rhs.clone();
+ this.filter = filter;
+ if (this.filter == null || filter.getVariables().size() == 0) throw new IllegalArgumentException("No need to filter on unfiltered data");
+ this.context = new TuplesContext(this, queryContext.getResolverSession());
+ this.filter.setContextOwner(this);
+
+ // get the variables to merge on
+ Set<Variable> commonVars = Collections.unmodifiableSet((Set<Variable>)TuplesOperations.getMatchingVars(lhs, rhs));
+
+ // This is more common than we expected, so just log a debug message
+ if (!commonVars.isEmpty()) throw new IllegalArgumentException("Cannot left filter when data has non-trivial compatability.");
+
+ // set the variables for this optional conjunction
+ lhsVars = new ArrayList<Variable>(Arrays.asList(lhs.getVariables()));
+ ArrayList<Variable> vars = (ArrayList<Variable>)lhsVars.clone();
+ ArrayList<Variable> rhsVars = new ArrayList<Variable>(Arrays.asList(rhs.getVariables()));
+ vars.addAll(rhsVars);
+ setVariables(vars);
+
+ // set the column offset for indexing into the RHS
+ rhsOffset = lhsVars.size();
+ assert rhsOffset > 0;
+ }
+
+
+ //
+ // Methods implementing Tuples
+ //
+
+ /** {@inheritDoc} */
+ public long getColumnValue(int column) throws TuplesException {
+ int nrLeftVars = lhs.getNumberOfVariables();
+ return (column < nrLeftVars) ? lhs.getColumnValue(column) : UNBOUND;
+ }
+
+
+ /** {@inheritDoc} */
+ public long getRawColumnValue(int column) throws TuplesException {
+ int nrLeftVars = lhs.getNumberOfVariables();
+ if (column < nrLeftVars) return lhs.getColumnValue(column);
+ return rhs.getColumnValue(column - rhsOffset);
+ }
+
+
+ /** {@inheritDoc} */
+ public long getRowUpperBound() throws TuplesException {
+ return lhs.getRowUpperBound();
+ }
+
+
+ /** {@inheritDoc} */
+ public long getRowExpectedCount() throws TuplesException {
+ // TODO: work out a better expected value. Maybe add about 10%
+ return lhs.getRowExpectedCount();
+ }
+
+
+ /** {@inheritDoc} Relies on the lhs of the optional. */
+ public boolean isColumnEverUnbound(int column) throws TuplesException {
+ int nrLeftVars = lhs.getNumberOfVariables();
+ return (column >= nrLeftVars) || lhs.isColumnEverUnbound(column);
+ }
+
+
+ /** {@inheritDoc} */
+ public int getColumnIndex(Variable variable) throws TuplesException {
+ if (lhsVars.contains(variable)) return lhs.getColumnIndex(variable);
+ return rhs.getColumnIndex(variable) + rhsOffset;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * @return Always <code>false</code>.
+ */
+ public boolean isMaterialized() {
+ return false;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNoDuplicates() throws TuplesException {
+ return lhs.hasNoDuplicates();
+ }
+
+
+ /** {@inheritDoc} */
+ public RowComparator getComparator() {
+ return lhs.getComparator();
+ }
+
+
+ /** {@inheritDoc} */
+ public List<Tuples> getOperands() {
+ return Collections.unmodifiableList(Arrays.asList(new Tuples[] {lhs, rhs}));
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean isUnconstrained() throws TuplesException {
+ return lhs.isUnconstrained();
+ }
+
+
+ /** {@inheritDoc} */
+ public void renameVariables(Constraint constraint) {
+ lhs.renameVariables(constraint);
+ rhs.renameVariables(constraint);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * This method matches what it can on the LHS, and saves the rest for later searches
+ * on the RHS. Searches on the RHS only happen when the LHS iterates to valid data.
+ */
+ public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
+ int lhsVars = lhs.getNumberOfVariables();
+ int tailLen = prefix.length - lhsVars;
+ if (tailLen <= 0) {
+ // search on the LHS only
+ lhs.beforeFirst(prefix, suffixTruncation);
+ } else {
+ // looking for something that doesn't exist
+ lhs.beforeFirst(new long[] {-1}, suffixTruncation);
+ }
+ currentRowValid = false;
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean next() throws TuplesException {
+ while ((currentRowValid = lhs.next())) {
+ if (!testRhs()) break;
+ }
+ return currentRowValid;
+ }
+
+ /**
+ * Tests if any row on the right is true
+ * @return true if any row on the right evaluates to true
+ * @throws TuplesException If the RHS cannot be tested.
+ */
+ private boolean testRhs() throws TuplesException {
+ rhs.beforeFirst();
+ while (rhs.next()) {
+ if (testFilter()) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Tests a filter using the current context.
+ * @return The test result.
+ * @throws QueryException If there was an error accessing data needed for the test.
+ */
+ private boolean testFilter() {
+ // re-root the filter expression to this Tuples
+ filter.setContextOwner(this);
+ try {
+ return filter.test(context);
+ } catch (QueryException qe) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Closes all the operands.
+ * @throws TuplesException If either the lhs or the rhs can't be closed.
+ */
+ public void close() throws TuplesException {
+ lhs.close();
+ rhs.close();
+ }
+
+
+ /**
+ * @return {@inheritDoc}
+ */
+ public Object clone() {
+ LeftFiltered cloned = (LeftFiltered)super.clone();
+
+ // Copy mutable fields by value
+ cloned.lhs = (Tuples)lhs.clone();
+ cloned.rhs = (Tuples)rhs.clone();
+ cloned.context = (context == null) ? null : new TuplesContext(cloned, context);
+ if (cloned.filter == null) throw new IllegalStateException("Unexpectedly lost a filter: " + filter);
+
+ return cloned;
+ }
+
+ /**
+ * Tells a filter what the current context is.
+ * @see org.mulgara.query.filter.ContextOwner#getCurrentContext()
+ */
+ public Context getCurrentContext() {
+ return context;
+ }
+
+
+ /**
+ * Allows the context to be set manually. This is not expected.
+ * @see org.mulgara.query.filter.ContextOwner#setCurrentContext(org.mulgara.query.filter.Context)
+ */
+ public void setCurrentContext(Context context) {
+ if (!(context instanceof TuplesContext)) throw new IllegalArgumentException("LeftJoin can only accept a TuplesContext.");
+ this.context = (TuplesContext)context;
+ for (ContextOwner l: contextListeners) l.setCurrentContext(context);
+ }
+
+
+ /**
+ * This provides a context, and does not need to refer to a parent.
+ * @see org.mulgara.query.filter.ContextOwner#getContextOwner()
+ */
+ public ContextOwner getContextOwner() {
+ throw new IllegalStateException("Should never be asking for the context owner of a Tuples");
+ }
+
+
+ /**
+ * The owner of the context for a Tuples is never needed, since it is always provided by the Tuples.
+ * @see org.mulgara.query.filter.ContextOwner#setContextOwner(org.mulgara.query.filter.ContextOwner)
+ */
+ public void setContextOwner(ContextOwner owner) {
+ }
+
+ /**
+ * Adds a context owner as a listener so that it will be updated with its context
+ * when this owner gets updated.
+ * @param l The context owner to register.
+ */
+ public void addContextListener(ContextOwner l) {
+ contextListeners.add(l);
+ }
+
+}
Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftJoin.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftJoin.java 2010-01-27 19:23:39 UTC (rev 1896)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/LeftJoin.java 2010-01-27 19:24:29 UTC (rev 1897)
@@ -1,13 +1,17 @@
/*
- * The contents of this file are subject to the Open Software License
- * Version 3.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- * http://www.opensource.org/licenses/osl-3.0.txt
+ * Copyright 2008 Fedora Commons, Inc.
*
- * Software distributed under the License is distributed on an "AS IS"
- * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- * the License for the specific language governing rights and limitations
- * under the License.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package org.mulgara.store.tuples;
@@ -46,8 +50,6 @@
*
* @created 2008-04-04
* @author <a href="mailto:pgearon at users.sourceforge.net">Paul Gearon</a>
- * @copyright © 2005 <A href="mailto:pgearon at users.sourceforge.net">Paul Gearon</A>
- * @licence <a href="{@docRoot}/../../LICENCE">Open Software License v3.0</a>
*/
public class LeftJoin extends AbstractTuples implements ContextOwner {
Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java 2010-01-27 19:23:39 UTC (rev 1896)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java 2010-01-27 19:24:29 UTC (rev 1897)
@@ -38,6 +38,7 @@
// Local packages
import org.mulgara.query.*;
import org.mulgara.query.filter.Filter;
+import org.mulgara.query.filter.Inverse;
import org.mulgara.query.filter.RDFTerm;
import org.mulgara.resolver.spi.*;
import org.mulgara.util.StackTrace;
@@ -453,18 +454,32 @@
// check for empty parameters
if (standard.getRowCardinality() == Cursor.ZERO && logger.isDebugEnabled()) logger.debug("Nothing to the left of an optional");
+ // If the Optional clause does not have matching variables with the LHS
+ // then this is the equivalent to a normal join as a cartesian product
+ if (matchingVars.isEmpty()) {
+
+ // get the cartesian product
+ Tuples filteredProduct = filter(join(standard, optional), filter, context);
+ Tuples invertedStd;
+ if (intersects(optional.getVariables(), filter.getVariables())) {
+ invertedStd = new LeftFiltered(standard, optional, filter, context);
+ } else {
+ // this is correct, since 'optional' is independent of the filter
+ invertedStd = filter(standard, new Inverse(filter), context);
+ }
+ return append(filteredProduct, invertedStd);
+ }
+
if (optional.getRowCardinality() == Cursor.ZERO) {
// need to return standard, projected out to the extra variables
if (optional.getNumberOfVariables() == 0) {
// This may be empty due to having zero rows (since the columns are truncated in this case)
return (Tuples)standard.clone();
+ } else {
+ return project(standard, optional.getVariables());
}
}
- // If the Optional clause does not constrain the RHS
- // then this is the equivalent to a normal join as a cartesian product
- if (matchingVars.isEmpty()) return join(standard, optional);
-
// check if there are variables which should not be considered when sorting
if (!checkForExtraVariables(optional, matchingVars)) {
// there were no extra variables in the optional
@@ -497,6 +512,18 @@
/**
+ * Convenience method to see if an array and a collection share any elements in common.
+ * @param <T> The type of elements in both containers.
+ * @param lhs An array of elements.
+ * @param rhs A collection of elements.
+ * @return <code>true</code> iff there is 1 or more elements present in both containers.
+ */
+ private static final <T> boolean intersects(T[] lhs, Collection<T> rhs) {
+ for (T elt: lhs) if (rhs.contains(elt)) return true;
+ return false;
+ }
+
+ /**
* Flattens any nested joins to allow polyadic join operations.
* @param operands A list of Tuples which may in turn be nested operations.
* @return A flattened list of flattened Tuples.
@@ -845,6 +872,33 @@
/**
+ * Project a tuples out to extra columns that will always be unbound.
+ * @param tuples The original tuples to expand.
+ * @param expansionVars The new set of variables to expand to.
+ * These may intersect the existing variables, but this is unexpected.
+ * @return A Tuples with the original bindings, plus any specified new columns that will be unbound.
+ */
+ public static Tuples project(Tuples tuples, Variable[] expansionVars) {
+ if (tuples == null) throw new IllegalArgumentException("Projection on Null \"tuples\"");
+ if (expansionVars == null) throw new IllegalArgumentException("Projection with Null expansion variables");
+
+ // test if no expansion, and short circuit if there isn't one
+ if (expansionVars.length == 0) return (Tuples)tuples.clone();
+
+ // test for overlapping variables
+ Variable[] opVars = tuples.getVariables();
+ List<Variable> newVars = new ArrayList<Variable>();
+ for (Variable v: expansionVars) newVars.add(v);
+ for (Variable v: opVars) newVars.remove(v);
+
+ // test again for no expansion, and short circuit if there isn't one
+ if (newVars.isEmpty()) return (Tuples)tuples.clone();
+
+ return new ExpandedProjection(tuples, newVars);
+ }
+
+
+ /**
* Creates a new restriction tuples, based on a normal Tuples and a restriction predicate.
* @param tuples The tuples to restrict.
* @param pred The predicate describing the restriction.
More information about the Mulgara-svn
mailing list