[Mulgara-svn] r254 - in trunk: data src/jar/resolver/java/org/mulgara/resolver src/jar/tuples/java/org/mulgara/store/tuples src/jar/util/java/org/mulgara/util

andrae at mulgara.org andrae at mulgara.org
Thu Apr 26 06:06:17 UTC 2007


Author: andrae
Date: 2007-04-26 01:06:17 -0500 (Thu, 26 Apr 2007)
New Revision: 254

Added:
   trunk/data/prefix-unbound.rdf
Modified:
   trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/Assignment.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/LiteralTuples.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperationsUnitTest.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoin.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoinUnitTest.java
   trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnorderedProjection.java
   trunk/src/jar/util/java/org/mulgara/util/FileUtil.java
Log:
Fix for MGR-34 http://mulgara.org/jira/browse/MGR-34 merged from
branches/nuc-disj into trunk/

merge -r 209:253 https://mulgara.org/svn/mulgara/branches/nuc-disj



Copied: trunk/data/prefix-unbound.rdf (from rev 253, branches/nuc-disj/data/prefix-unbound.rdf)

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -79,6 +79,7 @@
   private static final URI model2URI;
   private static final URI model3URI;
   private static final URI model4URI;
+  private static final URI model5URI;
 
   static {
     try {
@@ -88,6 +89,7 @@
       model2URI      = new URI("local:database#model2");
       model3URI      = new URI("local:database#model3");
       model4URI      = new URI("local:database#model4");
+      model5URI      = new URI("local:database#model5");
     } catch (URISyntaxException e) {
       throw new Error("Bad hardcoded URI", e);
     }
@@ -114,6 +116,7 @@
     suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitRollbackIsolationQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitCommitIsolationQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testImplicitCommitQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testPrefixingWithUnbound"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testDatabaseDelete"));
 
     return suite;
@@ -1105,7 +1108,7 @@
           assertFalse(answer.next());
           answer.close();
 
-          session1.removeModel(model3URI);
+          session1.removeModel(model4URI);
         } finally {
           session2.close();
         }
@@ -1118,6 +1121,86 @@
   }
 
 
+  public void testPrefixingWithUnbound() throws URISyntaxException
+  {
+    logger.warn("testPrefixingWithUnbound");
+    URI fileURI  = new File("data/prefix-unbound.rdf").toURI();
+
+    try {
+      Session session = database.newSession();
+      try {
+        session.createModel(model5URI, null);
+        session.setModel(model5URI, new ModelResource(fileURI));
+
+        Variable varA   = new Variable("a");
+        Variable varB   = new Variable("b");
+        Variable varT = new Variable("t");
+
+        List selectList = new ArrayList(2);
+        selectList.add(varA);
+        selectList.add(varT);
+
+        // Check data loaded
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model5URI),                      // FROM
+          new ConstraintConjunction(Arrays.asList(
+              new ConstraintExpression[] {
+                  new ConstraintImpl(varB,
+                      new URIReferenceImpl(new URI("test:p01")),
+                      new URIReferenceImpl(new URI("test:o01"))),
+                  new ConstraintImpl(varB,
+                      new URIReferenceImpl(new URI("test:p02")),
+                      varA),
+                  new ConstraintDisjunction(
+                      new ConstraintConjunction(
+                          new ConstraintImpl(varB,
+                              new URIReferenceImpl(new URI("test:p03")),
+                              new URIReferenceImpl(new URI("test:o03"))),
+                          new ConstraintIs(varT,
+                              new URIReferenceImpl(new URI("result:0")))),
+                      new ConstraintIs(varT,
+                              new URIReferenceImpl(new URI("result:1")))),
+              })),                                          // WHERE
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(varA, true),
+            new Order(varT, true),
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        String[][] results = {
+          { "test:o02", "result:0" },
+          { "test:o02", "result:1" },
+          { "test:o04", "result:0" },
+          { "test:o04", "result:1" },
+        };
+        answer.beforeFirst();
+        String s = "comparing results:\n" + answer.getVariables();
+        while (answer.next()) {
+          s += "\n[";
+          for (int i = 0; i < answer.getNumberOfVariables(); i++) {
+            s += " " + answer.getObject(i);
+          }
+          s += " ]";
+        }
+
+        compareResults(results, answer);
+        answer.close();
+
+        session.removeModel(model5URI);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
   public void testDatabaseDelete() {
     database.delete();
     database = null;
@@ -1128,17 +1211,22 @@
   //
 
   private void compareResults(String[][] expected, Answer answer) throws Exception {
-    answer.beforeFirst();
-    for (int i = 0; i < expected.length; i++) {
-      assertTrue("Answer short at row " + i, answer.next());
-      assertEquals(expected[i].length, answer.getNumberOfVariables());
-      for (int j = 0; j < expected[i].length; j++) {
-        URIReferenceImpl uri = new URIReferenceImpl(
-            new URI(expected[i][j]));
-        assertEquals(uri, answer.getObject(j));
+    try {
+      answer.beforeFirst();
+      for (int i = 0; i < expected.length; i++) {
+        assertTrue("Answer short at row " + i, answer.next());
+        assertEquals(expected[i].length, answer.getNumberOfVariables());
+        for (int j = 0; j < expected[i].length; j++) {
+          URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
+          assertEquals(uri, answer.getObject(j));
+        }
       }
+      assertFalse(answer.next());
+    } catch (Exception e) {
+      logger.error("Failed test - " + answer);
+      answer.close();
+      throw e;
     }
-    assertFalse(answer.next());
   }
 
   private void compareResults(Answer answer1, Answer answer2) throws Exception {

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -408,8 +408,6 @@
         logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
         throw new MulgaraTransactionException(
             "Attempt to suspend write transaction without setting AutoCommit Off");
-      } else {
-//        logger.error("Suspended transaction: ac=" + autoCommit + " t=" + transaction + "ut=" + userTransaction);
       }
 
       Transaction xa = transactionManager.suspend();

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/Assignment.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/Assignment.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/Assignment.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -283,6 +283,10 @@
     return new ArrayList(0);
   }
 
+  public RowComparator getRowComparator() {
+    return DefaultRowComparator.getInstance();
+  }
+
   /**
    * METHOD TO DO
    *

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/LiteralTuples.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/LiteralTuples.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/LiteralTuples.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -74,12 +74,23 @@
   private int currentTuple;
   private boolean[] columnContainsUnbound;
   private long[] prefix;
+  private boolean sorted;
 
+  public LiteralTuples(String[] variableNames, boolean sorted) {
+    List vars = new ArrayList();
+    for (int i = 0; i < variableNames.length; i++) {
+      Variable v = new Variable(variableNames[i]);
+      assert!vars.contains(v);
+      vars.add(v);
+    }
+    init((Variable[]) vars.toArray(new Variable[0]), sorted);
+  }
+
   /**
    * Creates a literal tuples with specified variables.
    */
   public LiteralTuples(Variable[] variables) {
-    init(variables);
+    init(variables, false);
   }
 
   /**
@@ -87,22 +98,17 @@
    * Variables created to match variableNames[].
    */
   public LiteralTuples(String[] variableNames) {
-    List vars = new ArrayList();
-    for (int i = 0; i < variableNames.length; i++) {
-      Variable v = new Variable(variableNames[i]);
-      assert!vars.contains(v);
-      vars.add(v);
-    }
-    init((Variable[]) vars.toArray(new Variable[0]));
+    this(variableNames, false);
   }
 
-  private void init(Variable[] variables) {
+  private void init(Variable[] variables, boolean sorted) {
     tuples = new ArrayList();
     currentTuple = 0;
     tupleIterator = null;
     setVariables(Arrays.asList(variables));
     columnContainsUnbound = new boolean[variables.length];
     Arrays.fill(columnContainsUnbound, false);
+    this.sorted = sorted;
   }
 
   /**
@@ -170,7 +176,11 @@
   }
 
   public RowComparator getComparator() {
-    return null;
+    if (sorted) {
+      return DefaultRowComparator.getInstance();
+    } else {
+      return null;
+    }
   }
 
   //
@@ -223,7 +233,7 @@
   }
 
   public boolean hasNoDuplicates() throws TuplesException {
-    return isUnconstrained();
+    return sorted || isUnconstrained();
   }
 
   public Object clone() {

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperations.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -204,7 +204,9 @@
       while (i.hasNext()) {
         Tuples operand = (Tuples)i.next();
         Tuples proj = project(operand, variables);
-        projected.add(proj);
+        Tuples sorted = sort(proj);
+        projected.add(sorted);
+        proj.close();
         operand.close();
       }
 
@@ -218,21 +220,6 @@
   }
 
 
-  private static String printArgs(String header, List args) {
-    StringBuffer buff = new StringBuffer(header + "[");
-    Iterator i = args.iterator();
-    if (i.hasNext()) {
-      buff.append(tuplesSummary((Tuples)i.next()));
-    }
-
-    while (i.hasNext()) {
-      buff.append(", " + tuplesSummary((Tuples)i.next()));
-    }
-    buff.append("]");
-    return buff.toString();
-  }
-
-
   /**
    * This is approximately a conjunction.
    */
@@ -559,8 +546,11 @@
       }
 
       // Add all variables that don't contain UNBOUND to boundVars set.
-      // Note that the inefficiency this introduces for distributed results
+      // Note: the inefficiency this introduces for distributed results
       // can only be eliminated by propagating isColumnEverUnbound through Answer.
+      // Note: this is required to ensure that a subsequent operand will not
+      // rely on this variable when selecting an index as if it is UNBOUND in a
+      // left-operand it becomes unprefixed.
       Variable[] vars = bestTuples.getVariables();
       for (int i = 0; i < vars.length; i++) {
         if (!bestTuples.isColumnEverUnbound(i)) {
@@ -986,6 +976,21 @@
   }
 
 
+  private static String printArgs(String header, List args) {
+    StringBuffer buff = new StringBuffer(header + "[");
+    Iterator i = args.iterator();
+    if (i.hasNext()) {
+      buff.append(tuplesSummary((Tuples)i.next()));
+    }
+
+    while (i.hasNext()) {
+      buff.append(", " + tuplesSummary((Tuples)i.next()));
+    }
+    buff.append("]");
+    return buff.toString();
+  }
+
+
   private static StringBuffer indentedTuplesTree(Tuples tuples, String indent) {
 
     StringBuffer buff = new StringBuffer();

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperationsUnitTest.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperationsUnitTest.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/TuplesOperationsUnitTest.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -33,6 +33,9 @@
 // Log4J
 import org.apache.log4j.Logger;
 
+// Mulgara
+import org.mulgara.query.Variable;
+
 /**
  * Test case for {@link TuplesOperationsUnitTest}.
  *
@@ -76,8 +79,11 @@
    * @return The test suite
    */
   public static Test suite() {
+    TestSuite suite = new TestSuite();
 
-    return new TestSuite(TuplesOperationsUnitTest.class);
+    suite.addTest(new TuplesOperationsUnitTest("testReorderedAppend"));
+
+    return suite;
   }
 
   /**
@@ -99,8 +105,42 @@
    *
    * @throws Exception if query fails when it should have succeeded
    */
-  public void testFoo() throws Exception {
+  public void testReorderedAppend() throws Exception {
+    LiteralTuples lhs = new LiteralTuples(new String[] {"x"}, true);
+    LiteralTuples rhs1 = new LiteralTuples(new String[] {"x", "y"}, true);
+    LiteralTuples rhs2 = new LiteralTuples(new String[] {"y", "x"}, true);
 
-    // not yet implemented
+    lhs.appendTuple(new long[] { 1 });
+    lhs.appendTuple(new long[] { 6 });
+
+    rhs1.appendTuple(new long[] { 1, 2 });
+    rhs1.appendTuple(new long[] { 1, 3 });
+    rhs1.appendTuple(new long[] { 4, 1 });
+
+    rhs2.appendTuple(new long[] { 5, 1 });
+    rhs2.appendTuple(new long[] { 6, 1 });
+    rhs2.appendTuple(new long[] { 1, 7 });
+
+    Tuples append = TuplesOperations.append(rhs1, rhs2);
+    Tuples join = TuplesOperations.join(lhs, append);
+    append.close();
+
+    logger.warn("join - " + TuplesOperations.formatTuplesTree(join));
+
+    Variable[] vars = join.getVariables();
+    assertEquals(2, vars.length);
+    assertEquals(new Variable("x"), vars[0]);
+    assertEquals(new Variable("y"), vars[1]);
+
+    join.beforeFirst();
+
+    TuplesTestingUtil.testTuplesRow(join, new long[] { 1, 2 });
+    TuplesTestingUtil.testTuplesRow(join, new long[] { 1, 3 });
+    TuplesTestingUtil.testTuplesRow(join, new long[] { 1, 5 });
+    TuplesTestingUtil.testTuplesRow(join, new long[] { 1, 6 });
+
+    assertFalse(join.next());
+
+    TuplesTestingUtil.closeTuples(new Tuples[] { join });
   }
 }

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoin.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoin.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoin.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -16,7 +16,9 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *  Various bug fixes copyright Netymon Pty Ltd <info at netymon.com> under
+ *  contract to The Topaz Foundation <info at topazproject.org>
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -32,7 +34,7 @@
 import java.math.BigInteger;
 
 // Third party packages
-import org.apache.log4j.Category;
+import org.apache.log4j.Logger;
 
 // Locally written packages
 import org.mulgara.query.TuplesException;
@@ -59,8 +61,6 @@
  *
  * @modified $Date: 2005/03/07 19:42:40 $
  *
- * @maintenanceAuthor $Author: newmana $
- *
  * @company <A href="mailto:info at PIsoftware.com">Plugged In Software</A>
  *
  * @copyright &copy; 2003 <A href="http://www.PIsoftware.com/">Plugged In
@@ -68,13 +68,100 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-public class UnboundJoin extends JoinTuples {
+public class UnboundJoin extends AbstractTuples {
 
-  static {
-    logger = Category.getInstance(UnboundJoin.class.getName());
-  }
+  /** Logger.  */
+  private static final Logger logger =
+    Logger.getLogger(UnboundJoin.class.getName());
 
   /**
+   * Version of {@link #operandBinding}} including only columns to the left of
+   * the first unbound column.
+   */
+  protected long[][] operandBindingPrefix;
+
+  /**
+   * For each column of the joined result, which operand contains the first
+   * occurrence of that variable.
+   */
+  protected int[] mapOperand;
+
+  /**
+   * For each column of the joined result, which column of the operand
+   * determined by {@link #mapOperand} contains the first occurrence of that
+   * variable.
+   */
+  protected int[] mapColumn;
+
+  /**
+   * Magic value within the {@link #fooOperand} array, indicating that a column
+   * is bound to one of the columns of the <var>prefix</var> parameter to
+   * {@link #next}.
+   */
+  protected static final int PREFIX = -1;
+
+  /**
+   * For each column of each operand, which operand contains the first
+   * occurrence of that variable, or {@link #PREFIX} if the prefix specified
+   * to {@link #next} contains the occurrence.
+   */
+  protected int[][] fooOperand;
+
+  /**
+   * For each column of each operand, which column of the operand determined by
+   * {@link #fooOperand} contains the first occurrence of that variable, or if
+   * the corresponding value of {@link #fooOperand} is {@link #PREFIX}, which
+   * column of the prefix specified to {@link #next} contains the occurrence.
+   */
+  protected int[][] fooColumn;
+
+  /**
+   * Whether each column of this instance might contain {@link #UNBOUND} rows.
+   */
+  protected boolean[] columnEverUnbound;
+
+  /**
+   * The propositions to conjoin.
+   */
+  protected Tuples[] operands;
+
+  /**
+   * The required values of the columns of each operand. A value of {@link
+   * Tuples#UNBOUND} indicates that the column is free to vary.
+   */
+  protected long[][] operandBinding;
+
+  /**
+   * For each operand, for each variable, which output column contains the same variable.
+   */
+  protected int[][] operandOutputMap;
+
+  /**
+   * Do any of the operands with variables matching this output variable contain UNBOUND?
+   */
+  protected boolean[][] columnOperandEverUnbound;
+
+  /**
+   * Flag indicating that the cursor is before the first row.
+   */
+  protected boolean isBeforeFirst = true;
+
+  /**
+   * Flag indicating that the cursor is after the last row.
+   */
+  protected boolean isAfterLast = false;
+
+  /**
+   * Do any of the operands contain duplicates.  Used to shortcircuit hasNoDuplicates.
+   */
+  protected boolean operandsContainDuplicates;
+
+  /**
+   * The prefix of the index.
+   */
+  protected long[] prefix = null;
+
+  /**
    * Conjoin a list of propositions.
    *
    * @param operands the propositions to conjoin; the order affects efficiency,
@@ -84,9 +171,142 @@
    * @throws TuplesException EXCEPTION TO DO
    */
   UnboundJoin(Tuples[] operands) throws TuplesException {
-    init(operands);
+    // Validate "operands" parameter
+    if (operands == null) {
+        throw new IllegalArgumentException("Null \"operands\" parameter");
+    }
+
+    // Initialize fields
+    this.operands = clone(operands);
+    operandBinding = new long[operands.length][];
+    operandBindingPrefix = new long[operands.length][];
+    this.operandsContainDuplicates = false;
+    for (int i = 0; i < operands.length; i++) {
+      // Debug
+      if (logger.isDebugEnabled()) {
+        logger.debug("Operands " + i + " : " + operands[i]);
+        logger.debug("Operands variables " + i + " : " + Arrays.asList(operands[i].getVariables()));
+        logger.debug("Ooperands types " + i + " : " + operands[i].getClass());
+      }
+      operandBinding[i] = new long[operands[i].getVariables().length];
+      if (!operands[i].hasNoDuplicates()) {
+        this.operandsContainDuplicates = true;
+      }
+    }
+
+    fooOperand = new int[operands.length][];
+    fooColumn = new int[operands.length][];
+    operandOutputMap = new int[operands.length][];
+
+    // Calculate the variables present and their mappings from operand
+    // columns to result columns
+    List variableList = new ArrayList();
+    List mapOperandList = new ArrayList();
+    List mapColumnList = new ArrayList();
+    List fooOperandList = new ArrayList();
+    List fooColumnList = new ArrayList();
+
+    for (int i = 0; i < operands.length; i++) {
+      fooOperandList.clear();
+      fooColumnList.clear();
+
+      Variable[] operandVariables = operands[i].getVariables();
+
+      operandOutputMap[i] = new int[operandVariables.length];
+
+      for (int j = 0; j < operandVariables.length; j++) {
+        int k = variableList.indexOf(operandVariables[j]);
+
+        if (k == -1) {
+          mapOperandList.add(new Integer(i));
+          mapColumnList.add(new Integer(j));
+          fooOperandList.add(new Integer(PREFIX));
+          fooColumnList.add(new Integer(variableList.size()));
+          variableList.add(operandVariables[j]);
+          operandOutputMap[i][j] = j;
+        } else {
+          fooOperandList.add(mapOperandList.get(k));
+          fooColumnList.add(mapColumnList.get(k));
+          operandOutputMap[i][j] = k;
+        }
+      }
+
+      // Convert per-operand lists into arrays
+      assert fooOperandList.size() == fooColumnList.size();
+      fooOperand[i] = new int[fooOperandList.size()];
+      fooColumn[i] = new int[fooColumnList.size()];
+
+      for (int j = 0; j < fooOperand[i].length; j++) {
+        fooOperand[i][j] = ((Integer) fooOperandList.get(j)).intValue();
+        fooColumn[i][j] = ((Integer) fooColumnList.get(j)).intValue();
+      }
+    }
+
+    // Convert column mappings from lists to arrays
+    setVariables(variableList);
+
+    mapOperand = new int[mapOperandList.size()];
+    mapColumn = new int[mapColumnList.size()];
+
+    for (int i = 0; i < mapOperand.length; i++) {
+      mapOperand[i] = ((Integer) mapOperandList.get(i)).intValue();
+      mapColumn[i] = ((Integer) mapColumnList.get(i)).intValue();
+    }
+
+    // Determine which columns are ever unbound
+    columnEverUnbound = new boolean[variableList.size()];
+    columnOperandEverUnbound = new boolean[operands.length][variableList.size()];
+    Arrays.fill(columnEverUnbound, true);
+
+    for (int i = 0; i < operands.length; i++) {
+      Arrays.fill(columnOperandEverUnbound[i], false);
+      Variable[] variables = operands[i].getVariables();
+      for (int j = 0; j < variables.length; j++) {
+        if (!operands[i].isColumnEverUnbound(j)) {
+          columnEverUnbound[getColumnIndex(variables[j])] = false;
+        } else {
+          columnOperandEverUnbound[i][getColumnIndex(variables[j])] = true;
+        }
+      }
+    }
   }
 
+  /**
+   * @return {@inheritDoc}  This occurs if and only if every one of the
+   *   {@link #operands} is unconstrained.
+   * @throws TuplesException {@inheritDoc}
+   */
+  public boolean isUnconstrained() throws TuplesException {
+    for (int i = 0; i < operands.length; i++) {
+      if (!operands[i].isUnconstrained()) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  public List getOperands() {
+    return Arrays.asList(operands);
+  }
+
+  public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
+    if (prefix == null) {
+      throw new IllegalArgumentException("Null \"prefix\" parameter");
+    }
+
+    if (suffixTruncation != 0) {
+      throw new TuplesException("Suffix truncation not implemented");
+    }
+
+    assert operands != null;
+    assert operandBinding != null;
+
+    isBeforeFirst = true;
+    isAfterLast = false;
+    this.prefix = prefix;
+  }
+
   //
   // Methods implementing Tuples
   //
@@ -140,9 +360,7 @@
     BigInteger rowCount = BigInteger.valueOf(operands[0].getRowUpperBound());
 
     for (int i = 1; i < operands.length; i++) {
-      rowCount = rowCount.multiply(BigInteger.valueOf(
-          operands[i].getRowUpperBound()
-          ));
+      rowCount = rowCount.multiply(BigInteger.valueOf(operands[i].getRowUpperBound()));
       if (rowCount.bitLength() > 63)
         return Long.MAX_VALUE;
     }
@@ -150,16 +368,10 @@
     return rowCount.longValue();
   }
 
-  /**
-   * This method is not yet implemented, and always returns <code>true</code>.
-   *
-   * @return <code>true</code>
-   */
   public boolean isColumnEverUnbound(int column) throws TuplesException {
     try {
       return columnEverUnbound[column];
-    }
-    catch (ArrayIndexOutOfBoundsException e) {
+    } catch (ArrayIndexOutOfBoundsException e) {
       throw new TuplesException("No such column " + column, e);
     }
   }
@@ -195,8 +407,7 @@
       }
 
       return true;
-    }
-    else {
+    } else {
       // We know at this point that we're on a row satisfying the current
       // prefix.  Advance the rightmost operand and let rollover do any
       // right-to-left advancement required
@@ -263,13 +474,10 @@
     for (int j = 0; j < operandBinding[i].length; j++) {
       if (fooOperand[i][j] == PREFIX) {
         // Variable first bound to a next method parameter prefix column passed to beforeFirst.
-        operandBinding[i][j] = (j < prefix.length) ? prefix[fooColumn[i][j]] :
-            Tuples.UNBOUND;
-      }
-      else {
+        operandBinding[i][j] = (j < prefix.length) ? prefix[fooColumn[i][j]] : Tuples.UNBOUND;
+      } else {
         // Variable first bound to a leftward operand column
-        operandBinding[i][j] = operands[fooOperand[i][j]].getColumnValue(
-            fooColumn[i][j]);
+        operandBinding[i][j] = operands[fooOperand[i][j]].getColumnValue(fooColumn[i][j]);
       }
     }
 
@@ -277,25 +485,21 @@
     int prefixLength = 0;
     while ((prefixLength < operandBinding[i].length) &&
         (operandBinding[i][prefixLength] != Tuples.UNBOUND) &&
-        (columnOperandEverUnbound[operandOutputMap[i][prefixLength]] == false)) {
+        (columnOperandEverUnbound[i][operandOutputMap[i][prefixLength]] == false)) {
       prefixLength++;
     }
 
     assert prefixLength >= 0;
     assert prefixLength <= operandBinding[i].length;
-//    assert (prefixLength == operandBinding[i].length) ||
-//           (operandBinding[i][prefixLength] == Tuples.UNBOUND);
 
     // Generate the advancement prefix
     assert operandBindingPrefix != null;
 
-    if ((operandBindingPrefix[i] == null) ||
-        (operandBindingPrefix[i].length != prefixLength)) {
+    if ((operandBindingPrefix[i] == null) || (operandBindingPrefix[i].length != prefixLength)) {
       operandBindingPrefix[i] = new long[prefixLength];
     }
 
-    System.arraycopy(operandBinding[i], 0, operandBindingPrefix[i], 0,
-        prefixLength);
+    System.arraycopy(operandBinding[i], 0, operandBindingPrefix[i], 0, prefixLength);
   }
 
   /**
@@ -317,8 +521,7 @@
           isAfterLast = true;
           prefix = null;
           return false;
-        }
-        else {
+        } else {
           // roll the leftward row
           if (!advance(i - 1)) {
             return false;
@@ -333,8 +536,7 @@
       }
 
       // Check that any suffix conditions are satisfied
-      for (int j = operandBindingPrefix[i].length;
-          j < operandBinding[i].length; j++) {
+      for (int j = operandBindingPrefix[i].length; j < operandBinding[i].length; j++) {
         if ((operandBinding[i][j] != Tuples.UNBOUND) &&
             (operandBinding[i][j] != operands[i].getColumnValue(j)) &&
             (operands[i].getColumnValue(j) != Tuples.UNBOUND)) {

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoinUnitTest.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoinUnitTest.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnboundJoinUnitTest.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -121,6 +121,7 @@
     testSuite.addTest(new UnboundJoinUnitTest("testNullPrefixBoundInSuffix"));
     testSuite.addTest(new UnboundJoinUnitTest("testNullPropagation"));
     testSuite.addTest(new UnboundJoinUnitTest("testLeadingPrefixNull"));
+    testSuite.addTest(new UnboundJoinUnitTest("testPartialMGR36"));
 
     return testSuite;
 
@@ -577,4 +578,47 @@
 
     TuplesTestingUtil.closeTuples(new Tuples[] { actual, lhs, rhs });
   }
+
+  /**
+   * Test {@link UnboundJoin}.n the expected final join of MGR-36 case 2.
+   * <pre>
+   *   s          s p o x       s p o x
+   * | 1 | join | 1 2 3 * | = | 1 2 3 * |
+   *            | 2 4 5 * |   | 1 5 3 2 |
+   *            | 2 4 6 * |   | 1 6 3 2 |
+   *            | 1 5 3 2 |
+   *            | 1 6 3 2 |
+   * </pre>
+   */
+  public void testPartialMGR36() throws Exception {
+    String[] lvars = new String[] { "s" };
+    final long[][] lhsValues = new long[][] {
+        new long[] { 1 } };
+
+    String[] rvars = new String[] { "s", "p", "o", "x" };
+    final long[][] rhsValues = new long[][] {
+        new long[] { 1, 2, 3, Tuples.UNBOUND },
+        new long[] { 1, 5, 3, 2 },
+        new long[] { 1, 6, 3, 2 },
+        new long[] { 2, 4, 5, Tuples.UNBOUND },
+        new long[] { 2, 4, 6, Tuples.UNBOUND } };
+
+
+    LiteralTuples lhs = LiteralTuples.create(lvars, lhsValues);
+    LiteralTuples rhs = LiteralTuples.create(rvars, rhsValues);
+
+    Tuples actual = TuplesOperations.sort(new UnboundJoin(new Tuples[] {lhs, rhs}));
+
+    logger.warn("testPartialMGR36 result = " + actual);
+
+    actual.beforeFirst();
+
+    TuplesTestingUtil.testTuplesRow(actual, new long[] { 1, 2, 3, Tuples.UNBOUND } );
+    TuplesTestingUtil.testTuplesRow(actual, new long[] { 1, 5, 3, 2 } );
+    TuplesTestingUtil.testTuplesRow(actual, new long[] { 1, 6, 3, 2 } );
+
+    assertTrue(!actual.next());
+
+    TuplesTestingUtil.closeTuples(new Tuples[] { actual, lhs, rhs });
+  }
 }

Modified: trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnorderedProjection.java
===================================================================
--- trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnorderedProjection.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/tuples/java/org/mulgara/store/tuples/UnorderedProjection.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -256,6 +256,11 @@
       throw new TuplesException("Suffix truncation not supported");
     }
 
+    if (prefix.length > 0) {
+      throw new TuplesException("Prefix not supported in UnorderedProjection" +
+          "- use TuplesOperations.sort() to provide prefix support");
+    }
+
     operand.beforeFirst(prefix, 0);
   }
 

Modified: trunk/src/jar/util/java/org/mulgara/util/FileUtil.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/FileUtil.java	2007-04-25 04:33:21 UTC (rev 253)
+++ trunk/src/jar/util/java/org/mulgara/util/FileUtil.java	2007-04-26 06:06:17 UTC (rev 254)
@@ -51,6 +51,9 @@
  */
 public abstract class FileUtil {
 
+  /** Logger.  */
+  private static Logger logger = Logger.getLogger(FileUtil.class.getName());
+
   /**
    * Recursively delete a file or directory.
    *
@@ -61,18 +64,14 @@
    * @return whether the directory was successfully deleted
    */
   public static boolean deleteDirectory(File directory) {
-
     File[] files = directory.listFiles();
     if (files != null) {
-
       for (int i = 0; i < files.length; ++i) {
-
         if (files[i].isFile()) {
-
-          files[i].delete();
-        }
-        else {
-
+          if (!files[i].delete()) {
+            logger.warn("Failed to delete " + files[i]);
+          }
+        } else {
           deleteDirectory(files[i]);
         }
       }




More information about the Mulgara-svn mailing list