[Mulgara-svn] r1970 - in trunk/src/jar: query/java/org/mulgara/query resolver/java/org/mulgara/resolver

pag at mulgara.org pag at mulgara.org
Sun Aug 8 02:49:17 UTC 2010


Author: pag
Date: 2010-08-08 02:49:17 +0000 (Sun, 08 Aug 2010)
New Revision: 1970

Modified:
   trunk/src/jar/query/java/org/mulgara/query/ConstructQuery.java
   trunk/src/jar/query/java/org/mulgara/query/GraphAnswer.java
   trunk/src/jar/query/java/org/mulgara/query/Query.java
   trunk/src/jar/query/java/org/mulgara/query/Variable.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/LocalQueryResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java
Log:
Corrected graph construction with CONSTRUCT. Blank nodes now being handled correctly. Also Variables can identify if they are being used to generate blank nodes, based on the fact that they have the illegal * character in their name. Added some generics cleanup to LocalQueryResolver

Modified: trunk/src/jar/query/java/org/mulgara/query/ConstructQuery.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/ConstructQuery.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/query/java/org/mulgara/query/ConstructQuery.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -16,7 +16,10 @@
 
 package org.mulgara.query;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.mulgara.connection.Connection;
 import org.mulgara.connection.Connection.SessionOp;
@@ -39,6 +42,8 @@
   /** Required serialization ID */
   private static final long serialVersionUID = -6024259961466362580L;
 
+  private final Set<Variable> bNodes;
+
   public ConstructQuery(List<? extends SelectElement> variableList, GraphExpression graphExpression,
         ConstraintExpression constraintExpression,
         List<Order> orderList, Integer limit, int offset) {
@@ -49,9 +54,22 @@
         offset,
         true,
         new UnconstrainedAnswer());
+    Set<Variable> bn = new HashSet<Variable>();
+    for (SelectElement e: variableList) {
+      if (e instanceof Variable && ((Variable)e).isBnodeVar()) bn.add((Variable)e);
+    }
+    bNodes = Collections.unmodifiableSet(bn);
   }
 
   /**
+   * Gets the set of variables that represent bnodes being generated by this query.
+   * @return An unmodifiable set of variables that are bNode generators.
+   */
+  public Set<Variable> getBnodeVars() {
+    return bNodes;
+  }
+  
+  /**
    * Executes this query on a connection.
    * @param conn The connection to a database session to execute the query against.
    * @return The answer to this query.  This must be closed by the calling code.

Modified: trunk/src/jar/query/java/org/mulgara/query/GraphAnswer.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/GraphAnswer.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/query/java/org/mulgara/query/GraphAnswer.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -13,9 +13,14 @@
 package org.mulgara.query;
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
 
 import org.jrdf.graph.BlankNode;
 import org.jrdf.graph.Literal;
+import org.mulgara.query.rdf.BlankNodeImpl;
 
 /**
  * An Answer that represents a graph.
@@ -57,9 +62,21 @@
   /** The column counter for emulating rows. */
   private int colOffset = 0;
 
-  /** The number of rows per column. */
-  private final int rowsPerCol;
+  /** The number of rows per solution. A solution is a row from the raw answer. */
+  private final int rowsPerSoln;
 
+  /** The normal variables that must be bound. */
+  private Map<Variable,List<Integer>> stdVarIndexes;
+
+  /** The bnode generating variables. */
+  private Map<Variable,List<Integer>> bnodeVars;
+
+  /** All the nodes generated for a single solution */
+  private Object[] rowNodes;
+
+  /** Internal counter for generating blank node identifiers */
+  private long blankNodeId = 0;
+
   /**
    * Constructs a new BooleanAnswer.
    * @param rawAnswer The result this answer represents.
@@ -67,15 +84,46 @@
   public GraphAnswer(Answer rawAnswer) {
     int cols = rawAnswer.getNumberOfVariables();
     if (cols % 3 != 0) throw new IllegalArgumentException("Cannot construct a graph with " + cols + " columns.");
-    rowsPerCol = cols / 3;
+    rowsPerSoln = cols / 3;
     this.rawAnswer = rawAnswer;
+    rowNodes = new Object[cols];
+
+    stdVarIndexes = new HashMap<Variable,List<Integer>>();
+    bnodeVars = new HashMap<Variable,List<Integer>>();
+    Variable[] vars = rawAnswer.getVariables();
+    for (int i = 0; i < vars.length; i++) {
+      Variable v = vars[i];
+      List<Integer> nodeColumns;
+      if (v.isBnodeVar()) {
+        // record which columns this bnode will generate
+        nodeColumns = bnodeVars.get(v);
+        if (nodeColumns == null) {
+          // first time this bnode was encountered
+          nodeColumns = new ArrayList<Integer>();
+          bnodeVars.put(v, nodeColumns);
+        }
+      } else {
+        // record which columns this bnode will generate
+        nodeColumns = stdVarIndexes.get(v);
+        if (nodeColumns == null) {
+          // first time this variable was encountered
+          nodeColumns = new ArrayList<Integer>();
+          stdVarIndexes.put(v, nodeColumns);
+        }
+      }
+      nodeColumns.add(i);
+    }
+    resetBlankNodes();
   }
 
   /**
    * @see org.mulgara.query.Answer#getObject(int)
    */
   public Object getObject(int column) throws TuplesException {
-    return rawAnswer.getObject(column + colOffset);
+    int c = column + colOffset;
+    assert (rawAnswer.getVariables()[c].isBnodeVar() && rowNodes[c] instanceof BlankNode) ||
+           !rawAnswer.getVariables()[c].isBnodeVar();
+    return rowNodes[c];
   }
 
   /**
@@ -83,16 +131,17 @@
    */
   public Object getObject(String columnName) throws TuplesException {
     // use an unrolled loop
-    if (CONSTANT_VAR_SUBJECT.equals(columnName)) return rawAnswer.getObject(colOffset);
-    if (CONSTANT_VAR_PREDICATE.equals(columnName)) return rawAnswer.getObject(1 + colOffset);
-    if (CONSTANT_VAR_OBJECT.equals(columnName)) return rawAnswer.getObject(2 + colOffset);
+    if (CONSTANT_VAR_SUBJECT.equals(columnName)) return getObject(0);
+    if (CONSTANT_VAR_PREDICATE.equals(columnName)) return getObject(1);
+    if (CONSTANT_VAR_OBJECT.equals(columnName)) return getObject(2);
     throw new TuplesException("Unknown variable: " + columnName);
   }
 
   /** @see org.mulgara.query.Cursor#beforeFirst() */
   public void beforeFirst() throws TuplesException {
     rawAnswer.beforeFirst();
-    colOffset = (rowsPerCol - 1) * 3;
+    colOffset = (rowsPerSoln - 1) * 3;
+    resetBlankNodes();
   }
 
   /** @see org.mulgara.query.Cursor#close() */
@@ -122,7 +171,7 @@
    * @see org.mulgara.query.Cursor#getRowCardinality()
    */
   public int getRowCardinality() throws TuplesException {
-    int rawCardinality = rawAnswer.getRowCardinality() * rowsPerCol;
+    int rawCardinality = rawAnswer.getRowCardinality() * rowsPerSoln;
     if (rawCardinality == 0) return 0;
     // get a copy to work with
     GraphAnswer answerCopy = (GraphAnswer)clone();
@@ -132,7 +181,7 @@
       if (!answerCopy.next()) return 0;
       // test if we know it can't be more than 1, or if there is no second row
       if (rawCardinality == 1) return 1;
-      if (!answerCopy.next()) return rowsPerCol;
+      if (!answerCopy.next()) return rowsPerSoln;
       // Return the raw cardinality
       return rawCardinality;
     } finally {
@@ -158,7 +207,7 @@
       answerCopy.beforeFirst();
       long result = 0;
       while (answerCopy.next()) result++;
-      return result * rowsPerCol;
+      return result * rowsPerSoln;
     } finally {
       answerCopy.close();
     }
@@ -168,14 +217,14 @@
    * @see org.mulgara.query.Cursor#getRowUpperBound()
    */
   public long getRowUpperBound() throws TuplesException {
-    return rawAnswer.getRowUpperBound() * rowsPerCol;
+    return rawAnswer.getRowUpperBound() * rowsPerSoln;
   }
 
   /**
    * @see org.mulgara.query.Cursor#getRowExpectedCount()
    */
   public long getRowExpectedCount() throws TuplesException {
-    return rawAnswer.getRowExpectedCount() * rowsPerCol;
+    return rawAnswer.getRowExpectedCount() * rowsPerSoln;
   }
 
   /**
@@ -224,11 +273,38 @@
    * @throws TuplesException Due to an error in the underlying rawAnswer.
    */
   private boolean internalNext() throws TuplesException {
-    if ((colOffset += 3) < (rowsPerCol * 3)) return true;
+    if ((colOffset += 3) < (rowsPerSoln * 3)) return true;
     colOffset = 0;
-    return rawAnswer.next();
+    // test if the next solution can be used
+    // this requires that there are no unbound variables
+    boolean nextResult;
+    do {
+      nextResult = rawAnswer.next();
+    } while (hasUnboundVar());
+    generateBlanks();
+    return nextResult;
   }
 
+  /**
+   * Tests if the current row of the raw answer has any unbound variables.
+   * This has a side effect of filling the row with all the tested values,
+   * meaning that there is no longer a need to call rawAnswer.getObject on any values.
+   * @return <code>true</code> if at least one variable is unbound. If there is an
+   *         unbound variable then the contents of rowNodes are not defined.
+   * @throws TuplesException If there is an error reading a variable.
+   */
+  private boolean hasUnboundVar() throws TuplesException {
+    for (Map.Entry<Variable,List<Integer>> varNodes: stdVarIndexes.entrySet()) {
+      List<Integer> objIndexes = varNodes.getValue();
+      // get the value for the first occurrence
+      Object o = rawAnswer.getObject(objIndexes.get(0));
+      // short circuit on invalid rows
+      if (o == null) return true;
+      // fill in the row
+      for (int v: objIndexes) rowNodes[v] = o;
+    }
+    return false;
+  }
 
   /**
    * Test if the current row is expressible as a graph row.
@@ -236,8 +312,27 @@
    * @throws TuplesException The row could not be accessed.
    */
   private boolean graphable() throws TuplesException {
-    if (rawAnswer.getObject(colOffset) instanceof Literal) return false;
-    Object predicate = rawAnswer.getObject(1 + colOffset);
+    if (rowNodes[colOffset] instanceof Literal) return false;
+    Object predicate = rowNodes[1 + colOffset];
     return !(predicate instanceof Literal || predicate instanceof BlankNode);
   }
+
+  /**
+   * Resets the blank node identifier to the start of the document.
+   */
+  private  void resetBlankNodes() {
+    blankNodeId = 1;
+  }
+
+  /**
+   * Creates all the generated blank nodes for this row.
+   */
+  private void generateBlanks() {
+    for (Map.Entry<Variable,List<Integer>> varNodes: bnodeVars.entrySet()) {
+      // generate the blank node for this variable
+      BlankNode b = new BlankNodeImpl(blankNodeId++);
+      // put this blank node in every position the variable appears in
+      for (int i: varNodes.getValue()) rowNodes[i] = b;
+    }
+  }
 }

Modified: trunk/src/jar/query/java/org/mulgara/query/Query.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Query.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/query/java/org/mulgara/query/Query.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -181,11 +181,15 @@
       for (Object o: variableList) {
         if (o instanceof Variable) {
           Variable var = (Variable)o;
-          if (!variableSet.contains(var)) {
+          if (!var.isBnodeVar() && !variableSet.contains(var)) {
             if (logger.isDebugEnabled()) logger.debug("Failed to find " + var + " in " + variableSet);
             throw new IllegalArgumentException("Failed to constrain all variables: " + var +
                 " not constrained in WHERE or GIVEN clauses");
           }
+          if (var.isBnodeVar() && !(this instanceof ConstructQuery)) {
+            if (logger.isDebugEnabled()) logger.debug("BNode variable in non-CONSTRUCT query: " + var);
+            throw new IllegalArgumentException("BNode variable in non-CONSTRUCT query: " + var);
+          }
         }
       }
     }

Modified: trunk/src/jar/query/java/org/mulgara/query/Variable.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Variable.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/query/java/org/mulgara/query/Variable.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -52,14 +52,16 @@
    * NOTE : update this serialVersionUID when a method or a public member is
    * deleted.
    */
-  static final long serialVersionUID = -3242803845875986693L;
+ static final long serialVersionUID = 205307605615376038L;
 
   /** Description of the Field */
   public final static Variable FROM = new Variable("_from");
 
   /** The <var>name</var> property. */
-  private String name;
+  private final String name;
 
+  /** Indicates the the variable is an internal variable for representing blanks nodes */
+  private final boolean bNodeVar;
 
   /**
    * Create a new variable.
@@ -69,7 +71,8 @@
   public Variable(String name) {
     // Validate "name" parameter
     if (name == null) throw new IllegalArgumentException("Null \"name\" parameter");
-    if (name.indexOf(" ") != -1)  throw new IllegalArgumentException("\"" + name + "\" is a not a variable name");
+    if (name.indexOf(" ") != -1) throw new IllegalArgumentException("\"" + name + "\" is a not a variable name");
+    bNodeVar = (name.indexOf("*") != -1);
 
     this.name = name;
   }
@@ -83,6 +86,14 @@
     return name;
   }
 
+  /**
+   * Tests if this variable represents a BNode. This is equivalent to
+   * testing if the variable name contains an asterisk.
+   * @return <code>true</code> if this variable represents a bnode.
+   */
+  public boolean isBnodeVar() {
+    return bNodeVar;
+  }
 
   /**
    * Clones this variable.

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LocalQueryResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LocalQueryResolver.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LocalQueryResolver.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -82,7 +82,6 @@
  *   Technology, Inc</a>
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
- at SuppressWarnings({ "unchecked"})
 class LocalQueryResolver implements QueryEvaluationContext {
   /** Logger.  */
   private static final Logger logger = Logger.getLogger(LocalQueryResolver.class.getName());
@@ -228,7 +227,7 @@
     return oldValue;
   }
 
-  Tuples resolveMap(Query query, Map outerBindings) throws QueryException {
+  Tuples resolveMap(Query query, Map<Variable,Value> outerBindings) throws QueryException {
     try {
       Query newQuery = new Query(
           query.getVariableList(),
@@ -252,16 +251,16 @@
 
   // FIXME: This method should be using a LiteralTuples.  Also I believe MULGARA_IS is now preallocated.
   // Someone needs to try making the change and testing.
-  private ConstraintExpression constrainBindings(Map bindings) throws LocalizeException {
-    List args = new ArrayList();
-    Iterator i = bindings.entrySet().iterator();
+  private ConstraintExpression constrainBindings(Map<Variable,Value> bindings) throws LocalizeException {
+    List<ConstraintExpression> args = new ArrayList<ConstraintExpression>();
+    Iterator<Map.Entry<Variable,Value>> i = bindings.entrySet().iterator();
     logger.info("FIXME:localize should be lookup, need to preallocate MULGARA_IS");
     while (i.hasNext()) {
-      Map.Entry entry = (Map.Entry)i.next();
+      Map.Entry<Variable,Value> entry = i.next();
       args.add(ConstraintIs.newLocalConstraintIs(
-                  (Variable)entry.getKey(),
+                  entry.getKey(),
                   new LocalNode(resolverSession.localize(ConstraintIs.MULGARA_IS)),
-                  (Value)entry.getValue(),
+                  entry.getValue(),
                   null));
     }
 
@@ -314,7 +313,7 @@
     if (!result.isEmpty()) {
       Tuples tmp = result;
       try {
-        List variables = new ArrayList();
+        List<Variable> variables = new ArrayList<Variable>();
 
       /*
        * Note that this code need not concern itself with the order of the select-list,
@@ -351,9 +350,9 @@
     return result;
   }
 
-  private List filterSubqueries(List select) {
-    List result = new ArrayList();
-    for (Object o : select) {
+  private List<SelectElement> filterSubqueries(List<SelectElement> select) {
+    List<SelectElement> result = new ArrayList<SelectElement>();
+    for (SelectElement o : select) {
       if (!(o instanceof Subquery)) {
         result.add(o);
       }
@@ -375,9 +374,8 @@
     return result;
   }
 
-
   private Tuples orderResult(Query query, Tuples result) throws TuplesException, QueryException {
-    List orderList = query.getOrderList();
+    List<Order> orderList = query.getOrderList();
     if (orderList.size() > 0 && result.getRowCardinality() > Cursor.ONE) {
       Tuples tmp = result;
       result = TuplesOperations.sort(result,

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java	2010-07-20 13:54:47 UTC (rev 1969)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java	2010-08-08 02:49:17 UTC (rev 1970)
@@ -134,6 +134,7 @@
 
         // Validate the variable
         try {
+          // if (!empty && !variables[i].isBnodeVar()) tuples.getColumnIndex(variables[i]);
           if (!empty) tuples.getColumnIndex(variables[i]);
         } catch (TuplesException e) {
           unboundVars.add(variables[i]);
@@ -190,6 +191,7 @@
 
     if (element instanceof Variable) {
       Variable var = (Variable)element;
+      //if (unboundVars.contains(var) || var.isBnodeVar()) return null;
       if (unboundVars.contains(var)) return null;
       return super.getObject(super.getColumnIndex(var));
     } else if (element instanceof ConstantValue) {



More information about the Mulgara-svn mailing list