[dna-commits] DNA SVN: r1294 - in trunk: dna-common/src/test/java/org/jboss/dna/common/collection and 10 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Thu Oct 15 15:57:20 EDT 2009


Author: rhauch
Date: 2009-10-15 15:57:20 -0400 (Thu, 15 Oct 2009)
New Revision: 1294

Added:
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
Modified:
   trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java
   trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java
   trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
   trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java
   trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
Log:
DNA-467 Added more query optimizer rules, and changed the way that the query plans are handled so that access nodes always are in the shape of a valid query.

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/AbstractProblems.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -47,17 +47,17 @@
         addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, null, null, throwable));
     }
 
-    public void addError( I18n message,
-                          String resource,
+    public void addError( String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, resource, location, null));
     }
 
     public void addError( Throwable throwable,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
     }
@@ -76,18 +76,18 @@
     }
 
     public void addError( int code,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         addProblem(new Problem(Problem.Status.ERROR, code, message, params, resource, location, null));
     }
 
     public void addError( Throwable throwable,
                           int code,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         addProblem(new Problem(Problem.Status.ERROR, code, message, params, resource, location, throwable));
     }
@@ -103,17 +103,17 @@
         addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, null, null, throwable));
     }
 
-    public void addWarning( I18n message,
-                            String resource,
+    public void addWarning( String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, resource, location, null));
     }
 
     public void addWarning( Throwable throwable,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
     }
@@ -132,18 +132,18 @@
     }
 
     public void addWarning( int code,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         addProblem(new Problem(Problem.Status.WARNING, code, message, params, resource, location, null));
     }
 
     public void addWarning( Throwable throwable,
                             int code,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         addProblem(new Problem(Problem.Status.WARNING, code, message, params, resource, location, throwable));
     }
@@ -159,17 +159,17 @@
         addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, null, null, throwable));
     }
 
-    public void addInfo( I18n message,
-                         String resource,
+    public void addInfo( String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, resource, location, null));
     }
 
     public void addInfo( Throwable throwable,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, params, resource, location, throwable));
     }
@@ -188,18 +188,18 @@
     }
 
     public void addInfo( int code,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         addProblem(new Problem(Problem.Status.INFO, code, message, params, resource, location, null));
     }
 
     public void addInfo( Throwable throwable,
                          int code,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         addProblem(new Problem(Problem.Status.INFO, code, message, params, resource, location, throwable));
     }

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/ImmutableProblems.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -54,12 +54,12 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addError(org.jboss.dna.common.i18n.I18n, java.lang.String, java.lang.String,
+     * @see org.jboss.dna.common.collection.Problems#addError(java.lang.String, java.lang.String, org.jboss.dna.common.i18n.I18n,
      *      java.lang.Object[])
      */
-    public void addError( I18n message,
-                          String resource,
+    public void addError( String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -78,13 +78,12 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addError(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
-     *      java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addError(int, String, String, I18n, Object...)
      */
     public void addError( int code,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -104,13 +103,13 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n,java.lang.Object[])
      */
     public void addError( Throwable throwable,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -131,14 +130,14 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addError(java.lang.Throwable, int, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
     public void addError( Throwable throwable,
                           int code,
-                          I18n message,
                           String resource,
                           String location,
+                          I18n message,
                           Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -156,12 +155,12 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addInfo(org.jboss.dna.common.i18n.I18n, java.lang.String, java.lang.String,
+     * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.String, java.lang.String, org.jboss.dna.common.i18n.I18n,
      *      java.lang.Object[])
      */
-    public void addInfo( I18n message,
-                         String resource,
+    public void addInfo( String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -180,13 +179,13 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addInfo(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
-     *      java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addInfo(int, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
     public void addInfo( int code,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -206,13 +205,13 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
     public void addInfo( Throwable throwable,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -233,14 +232,14 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addInfo(java.lang.Throwable, int, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
     public void addInfo( Throwable throwable,
                          int code,
-                         I18n message,
                          String resource,
                          String location,
+                         I18n message,
                          Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -258,12 +257,12 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addWarning(org.jboss.dna.common.i18n.I18n, java.lang.String,
-     *      java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
-    public void addWarning( I18n message,
-                            String resource,
+    public void addWarning( String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -282,13 +281,13 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addWarning(int, org.jboss.dna.common.i18n.I18n, java.lang.String,
-     *      java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addWarning(int, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n, java.lang.Object[])
      */
     public void addWarning( int code,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -308,13 +307,13 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, java.lang.String, java.lang.String ,
+     *      org.jboss.dna.common.i18n.I18n,java.lang.Object[])
      */
     public void addWarning( Throwable throwable,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         throw new UnsupportedOperationException();
     }
@@ -335,14 +334,14 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, int, org.jboss.dna.common.i18n.I18n,
-     *      java.lang.String, java.lang.String, java.lang.Object[])
+     * @see org.jboss.dna.common.collection.Problems#addWarning(java.lang.Throwable, int, java.lang.String, java.lang.String,
+     *      org.jboss.dna.common.i18n.I18n,java.lang.Object[])
      */
     public void addWarning( Throwable throwable,
                             int code,
-                            I18n message,
                             String resource,
                             String location,
+                            I18n message,
                             Object... params ) {
         throw new UnsupportedOperationException();
     }

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -56,14 +56,14 @@
      * Add an error message with a description of the resource, its location, and the parameters that should be used when
      * localizing the message
      * 
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
-    void addError( I18n message,
-                   String resource,
+    void addError( String resource,
                    String location,
+                   I18n message,
                    Object... params );
 
     /**
@@ -71,15 +71,15 @@
      * when localizing the message
      * 
      * @param throwable the exception that represents the error; may be null
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addError( Throwable throwable,
-                   I18n message,
                    String resource,
                    String location,
+                   I18n message,
                    Object... params );
 
     /**
@@ -111,15 +111,15 @@
      * localizing the message
      * 
      * @param code the error code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addError( int code,
-                   I18n message,
                    String resource,
                    String location,
+                   I18n message,
                    Object... params );
 
     /**
@@ -128,16 +128,16 @@
      * 
      * @param throwable the exception that represents the error; may be null
      * @param code the error code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addError( Throwable throwable,
                    int code,
-                   I18n message,
                    String resource,
                    String location,
+                   I18n message,
                    Object... params );
 
     /**
@@ -164,14 +164,14 @@
      * Add a warning message with a description of the resource, its location, and the parameters that should be used when
      * localizing the message
      * 
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
-    void addWarning( I18n message,
-                     String resource,
+    void addWarning( String resource,
                      String location,
+                     I18n message,
                      Object... params );
 
     /**
@@ -179,15 +179,15 @@
      * used when localizing the message
      * 
      * @param throwable the exception that represents the warning; may be null
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addWarning( Throwable throwable,
-                     I18n message,
                      String resource,
                      String location,
+                     I18n message,
                      Object... params );
 
     /**
@@ -219,15 +219,15 @@
      * localizing the message
      * 
      * @param code the problem code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addWarning( int code,
-                     I18n message,
                      String resource,
                      String location,
+                     I18n message,
                      Object... params );
 
     /**
@@ -236,16 +236,16 @@
      * 
      * @param throwable the exception that represents the warning; may be null
      * @param code the problem code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addWarning( Throwable throwable,
                      int code,
-                     I18n message,
                      String resource,
                      String location,
+                     I18n message,
                      Object... params );
 
     /**
@@ -272,14 +272,14 @@
      * Add an informational message with a description of the resource, its location, and the parameters that should be used when
      * localizing the message
      * 
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
-    void addInfo( I18n message,
-                  String resource,
+    void addInfo( String resource,
                   String location,
+                  I18n message,
                   Object... params );
 
     /**
@@ -287,15 +287,15 @@
      * be used when localizing the message
      * 
      * @param throwable the exception that represents the problem; may be null
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addInfo( Throwable throwable,
-                  I18n message,
                   String resource,
                   String location,
+                  I18n message,
                   Object... params );
 
     /**
@@ -327,15 +327,15 @@
      * localizing the message
      * 
      * @param code the problem code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addInfo( int code,
-                  I18n message,
                   String resource,
                   String location,
+                  I18n message,
                   Object... params );
 
     /**
@@ -344,16 +344,16 @@
      * 
      * @param throwable the exception that represents the problem; may be null
      * @param code the problem code
-     * @param message the internationalized message describing the problem
      * @param resource the description of the resource; may be null
      * @param location the location of the resource; may be null
+     * @param message the internationalized message describing the problem
      * @param params the values for the parameters in the message
      */
     void addInfo( Throwable throwable,
                   int code,
-                  I18n message,
                   String resource,
                   String location,
+                  I18n message,
                   Object... params );
 
     /**

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/SimpleProblems.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -23,6 +23,7 @@
  */
 package org.jboss.dna.common.collection;
 
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import net.jcip.annotations.NotThreadSafe;
@@ -57,4 +58,23 @@
     protected List<Problem> getProblems() {
         return this.problems != null ? problems : EMPTY_PROBLEMS;
     }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        Iterator<Problem> iter = getProblems().iterator();
+        if (iter.hasNext()) {
+            sb.append(iter.next());
+            while (iter.hasNext()) {
+                sb.append("\n");
+                sb.append(iter.next());
+            }
+        }
+        return sb.toString();
+    }
 }

Modified: trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/collection/AbstractProblemsTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -104,7 +104,7 @@
     @Test
     public void shouldAddErrorByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
         error = new Problem(Status.ERROR, Problem.DEFAULT_CODE, message, new Object[] {"error msg"}, resource, location, null);
-        problems.addError(error.getMessage(), error.getResource(), error.getLocation(), error.getParameters());
+        problems.addError(error.getResource(), error.getLocation(), error.getMessage(), error.getParameters());
         assertThat(problems.iterator().next(), is(error));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -113,7 +113,7 @@
     @Test
     public void shouldAddErrorByCodeAndMesssageAndResourceAndLocationAndParameters() {
         error = new Problem(Status.ERROR, 1, message, new Object[] {"error msg"}, resource, location, null);
-        problems.addError(error.getCode(), error.getMessage(), error.getResource(), error.getLocation(), error.getParameters());
+        problems.addError(error.getCode(), error.getResource(), error.getLocation(), error.getMessage(), error.getParameters());
         assertThat(problems.iterator().next(), is(error));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -124,9 +124,9 @@
         error = new Problem(Status.ERROR, Problem.DEFAULT_CODE, message, new Object[] {"error msg"}, resource, location,
                             throwable);
         problems.addError(error.getThrowable(),
-                          error.getMessage(),
                           error.getResource(),
                           error.getLocation(),
+                          error.getMessage(),
                           error.getParameters());
         assertThat(problems.iterator().next(), is(error));
         assertThat(problems.isEmpty(), is(false));
@@ -138,9 +138,9 @@
         error = new Problem(Status.ERROR, 1, message, new Object[] {"error msg"}, resource, location, throwable);
         problems.addError(error.getThrowable(),
                           error.getCode(),
-                          error.getMessage(),
                           error.getResource(),
                           error.getLocation(),
+                          error.getMessage(),
                           error.getParameters());
         assertThat(problems.iterator().next(), is(error));
         assertThat(problems.isEmpty(), is(false));
@@ -186,7 +186,7 @@
     public void shouldAddWarningByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
         warning = new Problem(Status.WARNING, Problem.DEFAULT_CODE, message, new Object[] {"warning msg"}, resource, location,
                               null);
-        problems.addWarning(warning.getMessage(), warning.getResource(), warning.getLocation(), warning.getParameters());
+        problems.addWarning(warning.getResource(), warning.getLocation(), warning.getMessage(), warning.getParameters());
         assertThat(problems.iterator().next(), is(warning));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -196,9 +196,9 @@
     public void shouldAddWarningByCodeAndMesssageAndResourceAndLocationAndParameters() {
         warning = new Problem(Status.WARNING, 1, message, new Object[] {"warning msg"}, resource, location, null);
         problems.addWarning(warning.getCode(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         assertThat(problems.iterator().next(), is(warning));
         assertThat(problems.isEmpty(), is(false));
@@ -210,9 +210,9 @@
         warning = new Problem(Status.WARNING, Problem.DEFAULT_CODE, message, new Object[] {"warning msg"}, resource, location,
                               throwable);
         problems.addWarning(warning.getThrowable(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         assertThat(problems.iterator().next(), is(warning));
         assertThat(problems.isEmpty(), is(false));
@@ -224,9 +224,9 @@
         warning = new Problem(Status.WARNING, 1, message, new Object[] {"warning msg"}, resource, location, throwable);
         problems.addWarning(warning.getThrowable(),
                             warning.getCode(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         assertThat(problems.iterator().next(), is(warning));
         assertThat(problems.isEmpty(), is(false));
@@ -271,7 +271,7 @@
     @Test
     public void shouldAddInfoByMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
         info = new Problem(Status.INFO, Problem.DEFAULT_CODE, message, new Object[] {"info msg"}, resource, location, null);
-        problems.addInfo(info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+        problems.addInfo(info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
         assertThat(problems.iterator().next(), is(info));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -280,7 +280,7 @@
     @Test
     public void shouldAddInfoByCodeAndMesssageAndResourceAndLocationAndParameters() {
         info = new Problem(Status.INFO, 1, message, new Object[] {"info msg"}, resource, location, null);
-        problems.addInfo(info.getCode(), info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+        problems.addInfo(info.getCode(), info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
         assertThat(problems.iterator().next(), is(info));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -289,7 +289,7 @@
     @Test
     public void shouldAddInfoByThrowableAndMessageAndResourceAndLocationAndParametersUsingDefaultCode() {
         info = new Problem(Status.INFO, Problem.DEFAULT_CODE, message, new Object[] {"info msg"}, resource, location, throwable);
-        problems.addInfo(info.getThrowable(), info.getMessage(), info.getResource(), info.getLocation(), info.getParameters());
+        problems.addInfo(info.getThrowable(), info.getResource(), info.getLocation(), info.getMessage(), info.getParameters());
         assertThat(problems.iterator().next(), is(info));
         assertThat(problems.isEmpty(), is(false));
         assertThat(problems.size(), is(1));
@@ -300,9 +300,9 @@
         info = new Problem(Status.INFO, 1, message, new Object[] {"info msg"}, resource, location, throwable);
         problems.addInfo(info.getThrowable(),
                          info.getCode(),
-                         info.getMessage(),
                          info.getResource(),
                          info.getLocation(),
+                         info.getMessage(),
                          info.getParameters());
         assertThat(problems.iterator().next(), is(info));
         assertThat(problems.isEmpty(), is(false));
@@ -318,9 +318,9 @@
         assertThat(problems.size(), is(0));
         problems.addWarning(warning.getThrowable(),
                             warning.getCode(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         assertThat(problems.hasErrors(), is(false));
         assertThat(problems.hasWarnings(), is(true));
@@ -329,9 +329,9 @@
         assertThat(problems.size(), is(1));
         problems.addError(error.getThrowable(),
                           error.getCode(),
-                          error.getMessage(),
                           error.getResource(),
                           error.getLocation(),
+                          error.getMessage(),
                           error.getParameters());
         assertThat(problems.hasErrors(), is(true));
         assertThat(problems.hasWarnings(), is(true));
@@ -340,9 +340,9 @@
         assertThat(problems.size(), is(2));
         problems.addInfo(info.getThrowable(),
                          info.getCode(),
-                         info.getMessage(),
                          info.getResource(),
                          info.getLocation(),
+                         info.getMessage(),
                          info.getParameters());
         assertThat(problems.hasErrors(), is(true));
         assertThat(problems.hasWarnings(), is(true));
@@ -351,27 +351,27 @@
         assertThat(problems.size(), is(3));
         problems.addWarning(warning.getThrowable(),
                             warning.getCode(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         problems.addError(error.getThrowable(),
                           error.getCode(),
-                          error.getMessage(),
                           error.getResource(),
                           error.getLocation(),
+                          error.getMessage(),
                           error.getParameters());
         problems.addWarning(warning.getThrowable(),
                             warning.getCode(),
-                            warning.getMessage(),
                             warning.getResource(),
                             warning.getLocation(),
+                            warning.getMessage(),
                             warning.getParameters());
         problems.addError(error.getThrowable(),
                           error.getCode(),
-                          error.getMessage(),
                           error.getResource(),
                           error.getLocation(),
+                          error.getMessage(),
                           error.getParameters());
         assertThat(problems.hasErrors(), is(true));
         assertThat(problems.hasWarnings(), is(true));

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/model/Visitors.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -23,6 +23,7 @@
  */
 package org.jboss.dna.graph.query.model;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -32,6 +33,7 @@
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.NamespaceRegistry;
 import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.ValueFactory;
 
 /**
  * A set of common visitors that can be reused or extended, and methods that provide easy construction and calling of visitors.
@@ -51,7 +53,7 @@
      */
     public static <StrategyVisitor extends Visitor> StrategyVisitor visitAll( Visitable visitable,
                                                                               StrategyVisitor strategyVisitor ) {
-        visitable.accept(new WalkAllVisitor(strategyVisitor));
+        if (visitable != null) visitable.accept(new WalkAllVisitor(strategyVisitor));
         return strategyVisitor;
     }
 
@@ -66,7 +68,7 @@
      */
     public static <GeneralVisitor extends Visitor> GeneralVisitor visit( Visitable visitable,
                                                                          GeneralVisitor visitor ) {
-        visitable.accept(visitor);
+        if (visitable != null) visitable.accept(visitor);
         return visitor;
     }
 

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/AddAccessNodes.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -32,8 +32,8 @@
 import org.jboss.dna.graph.query.plan.PlanNode.Type;
 
 /**
- * An {@link OptimizerRule optimizer rule} that inserts an ACCESS above each SOURCE node in a query plan. This rule is often the
- * first rule to run against a {@link CanonicalPlanner canonical plan} (see
+ * An {@link OptimizerRule optimizer rule} that inserts an ACCESS above each SOURCE leaf node in a query plan. This rule is often
+ * the first rule to run against a {@link CanonicalPlanner canonical plan} (see
  * {@link RuleBasedOptimizer#populateRuleStack(LinkedList, PlanHints)}.
  * <p>
  * Before:
@@ -72,7 +72,7 @@
                              LinkedList<OptimizerRule> ruleStack ) {
         // On each of the source nodes ...
         for (PlanNode source : plan.findAllAtOrBelow(Type.SOURCE)) {
-            // The source node should have no children ...
+            // The source node may have children if it is a view ...
             if (source.getChildCount() != 0) continue;
 
             // Create the ACCESS node, set the selectors, and insert above the source node ...

Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java	                        (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,192 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.optimize;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitable;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that copies SELECT nodes that apply to one side of a equi-join condition so that they
+ * also apply to the other side fo the equi-join condition.
+ */
+ at Immutable
+public class CopyCriteria implements OptimizerRule {
+
+    public static final CopyCriteria INSTANCE = new CopyCriteria();
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+     *      org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+     */
+    public PlanNode execute( QueryContext context,
+                             PlanNode plan,
+                             LinkedList<OptimizerRule> ruleStack ) {
+        Set<PlanNode> copiedSelectNodes = new HashSet<PlanNode>();
+
+        for (PlanNode join : plan.findAllAtOrBelow(Type.JOIN)) {
+            // Get the join condition ...
+            JoinCondition joinCondition = join.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+            if (joinCondition instanceof EquiJoinCondition) {
+                EquiJoinCondition equiJoinCondition = (EquiJoinCondition)joinCondition;
+                SelectorName selector1 = equiJoinCondition.getSelector1Name();
+                SelectorName selector2 = equiJoinCondition.getSelector2Name();
+                Name property1 = equiJoinCondition.getProperty1Name();
+                Name property2 = equiJoinCondition.getProperty2Name();
+
+                // Walk up the tree looking for SELECT nodes that apply to one of the sides ...
+                PlanNode node = join.getParent();
+                while (node != null) {
+                    if (!copiedSelectNodes.contains(node)) {
+                        PlanNode copy = copySelectNode(context, node, selector1, property1, selector2, property2);
+                        if (copy != null) {
+                            node.insertAsParent(copy);
+                            copiedSelectNodes.add(node);
+                            copiedSelectNodes.add(copy);
+                        } else {
+                            copy = copySelectNode(context, node, selector2, property2, selector1, property1);
+                            if (copy != null) {
+                                node.insertAsParent(copy);
+                                copiedSelectNodes.add(node);
+                                copiedSelectNodes.add(copy);
+                            }
+                        }
+                    }
+                    node = node.getParent();
+                }
+            }
+        }
+        return plan;
+    }
+
+    protected PlanNode copySelectNode( QueryContext context,
+                                       PlanNode selectNode,
+                                       SelectorName selectorName,
+                                       Name propertyName,
+                                       SelectorName copySelectorName,
+                                       Name copyPropertyName ) {
+        if (selectNode.isNot(Type.SELECT)) return null;
+        if (selectNode.getSelectors().size() != 1 || !selectNode.getSelectors().contains(selectorName)) return null;
+
+        Constraint constraint = selectNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+        Set<Column> columns = getColumnsReferencedBy(constraint, context.getExecutionContext());
+        if (columns.size() != 1) return null;
+        Column column = columns.iterator().next();
+        if (!column.getSelectorName().equals(selectorName)) return null;
+        if (!column.getPropertyName().equals(propertyName)) return null;
+
+        // We know that this constraint ONLY applies to the referenced selector and property,
+        // so we will duplicate this constraint ...
+
+        // Create the new node ...
+        PlanNode copy = new PlanNode(Type.SELECT, copySelectorName);
+
+        // Copy the constraint, but change the references to the copy selector and property ...
+        ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+        PlanUtil.ColumnMapping mappings = new PlanUtil.ColumnMapping(selectorName);
+        mappings.map(stringFactory.create(propertyName), new Column(copySelectorName, copyPropertyName,
+                                                                    stringFactory.create(copyPropertyName)));
+        Constraint newCriteria = PlanUtil.replaceReferences(context, constraint, mappings, copy);
+        copy.setProperty(Property.SELECT_CRITERIA, newCriteria);
+
+        return copy;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+    /**
+     * Get the set of Column objects that represent those columns referenced by the visitable object.
+     * 
+     * @param visitable the object to be visited
+     * @param context the context; may not be null
+     * @return the set of Column objects, with column names that always are the string-form of the
+     *         {@link Column#getPropertyName() property name}; never null
+     */
+    public static Set<Column> getColumnsReferencedBy( Visitable visitable,
+                                                      ExecutionContext context ) {
+        if (visitable == null) return Collections.emptySet();
+        final ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
+        final Set<Column> symbols = new HashSet<Column>();
+        // Walk the entire structure, so only supply a StrategyVisitor (that does no navigation) ...
+        Visitors.visitAll(visitable, new AbstractVisitor() {
+            protected void addColumnFor( SelectorName selectorName,
+                                         Name property ) {
+                symbols.add(new Column(selectorName, property, stringFactory.create(property)));
+            }
+
+            @Override
+            public void visit( Column column ) {
+                symbols.add(column);
+            }
+
+            @Override
+            public void visit( EquiJoinCondition joinCondition ) {
+                addColumnFor(joinCondition.getSelector1Name(), joinCondition.getProperty1Name());
+                addColumnFor(joinCondition.getSelector2Name(), joinCondition.getProperty2Name());
+            }
+
+            @Override
+            public void visit( PropertyExistence prop ) {
+                addColumnFor(prop.getSelectorName(), prop.getPropertyName());
+            }
+
+            @Override
+            public void visit( PropertyValue prop ) {
+                addColumnFor(prop.getSelectorName(), prop.getPropertyName());
+            }
+        });
+        return symbols;
+    }
+
+}


Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/CopyCriteria.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java	                        (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,115 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.optimize;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+
+/**
+ * This rule attempts to ensure the proper location of {@link Type#PROJECT} nodes. For example, every {@link Type#ACCESS} node
+ * needs to have a PROJECT node below it and above any other nodes (e.g., {@link Type#SELECT} or {@link Type#SOURCE} nodes). This
+ * rule ensures that the PROJECT exists, but it also attempts to reduce any unnecessary columns in existing PROJECT nodes.
+ */
+public class PushProjects implements OptimizerRule {
+
+    public static final PushProjects INSTANCE = new PushProjects();
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+     *      org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+     */
+    public PlanNode execute( QueryContext context,
+                             PlanNode plan,
+                             LinkedList<OptimizerRule> ruleStack ) {
+
+        // Find all of the ACCESS nodes to make sure there is a PROJECT ...
+        for (PlanNode access : plan.findAllAtOrBelow(Type.ACCESS)) {
+            // Find the first node that is below any LIMIT, SORT, or DUP_REMOVE ...
+            PlanNode parentOfProject = access;
+            while (parentOfProject.isOneOf(Type.LIMIT, Type.SORT, Type.DUP_REMOVE)) {
+                parentOfProject = parentOfProject.getParent();
+            }
+
+            // Is there already a PROJECT here ?
+            assert parentOfProject.getChildCount() == 1; // should only have one child ...
+            PlanNode child = parentOfProject.getFirstChild(); // should only have one child ...
+            if (child.is(Type.PROJECT)) continue;
+
+            // If the parent of the ACCESS node is a PROJECT, then we can simply move it to here ...
+            PlanNode accessParent = access.getParent();
+            if (accessParent != null && accessParent.is(Type.PROJECT)) {
+                PlanNode project = accessParent;
+                if (project == plan) {
+                    // The PROJECT node is the root, so the ACCESS node will be the root ...
+                    plan = access;
+                    access.removeFromParent();
+                } else {
+                    project.extractFromParent();
+                }
+                child.insertAsParent(project);
+                if (plan == access) return plan;
+                continue;
+            }
+
+            // There is no PROJECT, so find the columns that are required by the plan above this point ...
+            List<Column> requiredColumns = PlanUtil.findRequiredColumns(context, child);
+
+            // And insert the PROJECT ...
+            PlanNode projectNode = new PlanNode(Type.PROJECT);
+            projectNode.setProperty(Property.PROJECT_COLUMNS, requiredColumns);
+            projectNode.addSelectors(getSelectorsFor(requiredColumns));
+            child.insertAsParent(projectNode);
+        }
+        return plan;
+    }
+
+    protected Set<SelectorName> getSelectorsFor( List<Column> columns ) {
+        Set<SelectorName> selectors = new HashSet<SelectorName>();
+        for (Column column : columns) {
+            selectors.add(column.getSelectorName());
+        }
+        return selectors;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+}


Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushProjects.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/PushSelectCriteria.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -64,10 +64,10 @@
  * <pre>
  *          ...
  *           |
+ *        ACCESS
+ *           |
  *        PROJECT      with the list of columns being SELECTed
  *           |
- *        ACCESS
- *           |
  *        SELECT1
  *           |         One or more SELECT plan nodes that each have
  *        SELECT2      a single non-join constraint that are then all AND-ed
@@ -104,6 +104,9 @@
  *   ACCESS     ACCESS
  *    ('s1')    ('s2')
  *     |           |
+ *   PROJECT    PROJECT
+ *    ('s1')    ('s2')
+ *     |           |
  *   SOURCE     SOURCE
  *    ('s1')    ('s2')
  * </pre>
@@ -123,6 +126,9 @@
  *   ACCESS     ACCESS
  *   ('s1')     ('s2')
  *     |           |
+ *  PROJECT     PROJECT
+ *   ('s1')     ('s2')
+ *     |           |
  *  SELECT1     SELECT2
  *   ('s1')     ('s2')
  *     |           |
@@ -134,6 +140,9 @@
  * </pre>
  * 
  * </p>
+ * <p>
+ * Also, any SELECT that applies to one side of an equi-join will be applied to <i>both</i> sides of the JOIN.
+ * </p>
  */
 @Immutable
 public class PushSelectCriteria implements OptimizerRule {

Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java	                        (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,132 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.optimize;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ * An {@link OptimizerRule optimizer rule} that replaces any SOURCE nodes that happen to be {@link View views}. This rewriting
+ * changes all of the elements of the plan that reference the SOURCE and it's columns, including criteria, project nodes, etc.
+ */
+ at Immutable
+public class ReplaceViews implements OptimizerRule {
+
+    public static final ReplaceViews INSTANCE = new ReplaceViews();
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.graph.query.optimize.OptimizerRule#execute(org.jboss.dna.graph.query.QueryContext,
+     *      org.jboss.dna.graph.query.plan.PlanNode, java.util.LinkedList)
+     */
+    public PlanNode execute( QueryContext context,
+                             PlanNode plan,
+                             LinkedList<OptimizerRule> ruleStack ) {
+        // Prepare a cache of the view plans ...
+        Map<SelectorName, PlanNode> viewPlanCache = new HashMap<SelectorName, PlanNode>();
+        CanonicalPlanner planner = new CanonicalPlanner();
+
+        // Prepare the maps that will record the old-to-new mappings from the old view SOURCE nodes to the table SOURCEs ...
+
+        // For each of the SOURCE nodes ...
+        Schemata schemata = context.getSchemata();
+        Set<PlanNode> processedSources = new HashSet<PlanNode>();
+        boolean foundViews = false;
+        do {
+            foundViews = false;
+            for (PlanNode sourceNode : plan.findAllAtOrBelow(Type.SOURCE)) {
+                if (processedSources.contains(sourceNode)) continue;
+                processedSources.add(sourceNode);
+
+                // Resolve the node to find the definition in the schemata ...
+                SelectorName tableName = sourceNode.getProperty(Property.SOURCE_NAME, SelectorName.class);
+                Table table = schemata.getTable(tableName);
+                if (table instanceof View) {
+                    View view = (View)table;
+                    PlanNode viewPlan = viewPlanCache.get(tableName);
+                    if (viewPlan == null) {
+                        viewPlan = planner.createPlan(context, view.getDefinition());
+                        if (viewPlan != null) viewPlanCache.put(tableName, viewPlan);
+                    }
+                    if (viewPlan == null) continue; // there were likely errors when creating the plan
+                    viewPlan = viewPlan.clone();
+
+                    // Insert the view plan under the parent SOURCE node ...
+                    sourceNode.addLastChild(viewPlan);
+
+                    // Update the plan above this node to replace references to the view columns with references to the
+                    // tables/views used by the view ...
+                    PlanUtil.ColumnMapping viewMappings = PlanUtil.createMappingFor(view, viewPlan);
+                    PlanUtil.replaceViewReferences(context, viewPlan, viewMappings);
+
+                    if (viewPlan.is(Type.PROJECT)) {
+                        // The PROJECT from the plan may actually not be needed if there is another PROJECT above it ...
+                        PlanNode node = viewPlan.getParent();
+                        while (node != null) {
+                            if (node.is(Type.PROJECT) && viewPlan.getSelectors().containsAll(node.getSelectors())) {
+                                viewPlan.extractFromParent();
+                                break;
+                            }
+                            node = node.getParent();
+                        }
+                    }
+                    foundViews = true;
+                }
+            }
+        } while (foundViews);
+
+        if (foundViews) {
+            // We re-wrote at least one SOURCE, but the resulting plan tree for the view could actually reference
+            // other views. Therefore, re-run this rule ...
+            ruleStack.addFirst(this);
+        }
+        return plan;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+}


Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/ReplaceViews.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RewriteIdentityJoins.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -24,46 +24,17 @@
 package org.jboss.dna.graph.query.optimize;
 
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import net.jcip.annotations.Immutable;
 import org.jboss.dna.graph.GraphI18n;
 import org.jboss.dna.graph.property.ValueFactory;
 import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.model.And;
-import org.jboss.dna.graph.query.model.ChildNode;
-import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
-import org.jboss.dna.graph.query.model.Column;
-import org.jboss.dna.graph.query.model.Comparison;
-import org.jboss.dna.graph.query.model.Constraint;
-import org.jboss.dna.graph.query.model.DescendantNode;
-import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
-import org.jboss.dna.graph.query.model.DynamicOperand;
 import org.jboss.dna.graph.query.model.EquiJoinCondition;
-import org.jboss.dna.graph.query.model.FullTextSearch;
-import org.jboss.dna.graph.query.model.FullTextSearchScore;
 import org.jboss.dna.graph.query.model.JoinCondition;
-import org.jboss.dna.graph.query.model.Length;
-import org.jboss.dna.graph.query.model.LowerCase;
-import org.jboss.dna.graph.query.model.NodeDepth;
-import org.jboss.dna.graph.query.model.NodeLocalName;
-import org.jboss.dna.graph.query.model.NodeName;
-import org.jboss.dna.graph.query.model.NodePath;
-import org.jboss.dna.graph.query.model.Not;
-import org.jboss.dna.graph.query.model.Or;
-import org.jboss.dna.graph.query.model.Ordering;
-import org.jboss.dna.graph.query.model.PropertyExistence;
-import org.jboss.dna.graph.query.model.PropertyValue;
-import org.jboss.dna.graph.query.model.SameNode;
-import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
 import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.model.StaticOperand;
-import org.jboss.dna.graph.query.model.UpperCase;
 import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanUtil;
 import org.jboss.dna.graph.query.plan.PlanNode.Property;
 import org.jboss.dna.graph.query.plan.PlanNode.Type;
 import org.jboss.dna.graph.query.validate.Schemata;
@@ -170,7 +141,7 @@
             ruleStack.addFirst(this);
 
             // Now rewrite the various portions of the plan that make use of the now-removed selectors ...
-            replaceReferencesToRemovedSource(context, plan, rewrittenSelectors);
+            PlanUtil.replaceReferencesToRemovedSource(context, plan, rewrittenSelectors);
         } else {
             // There are no-untouched JOIN nodes, which means the sole JOIN node was rewritten as a single SOURCE node
             assert plan.findAllAtOrBelow(Type.JOIN).isEmpty();
@@ -206,255 +177,6 @@
         }
     }
 
-    protected void replaceReferencesToRemovedSource( QueryContext context,
-                                                     PlanNode planNode,
-                                                     Map<SelectorName, SelectorName> rewrittenSelectors ) {
-        switch (planNode.getType()) {
-            case PROJECT:
-                List<Column> columns = planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
-                for (int i = 0; i != columns.size(); ++i) {
-                    Column column = columns.get(i);
-                    SelectorName replacement = rewrittenSelectors.get(column.getSelectorName());
-                    if (replacement != null) {
-                        columns.set(i, new Column(replacement, column.getPropertyName(), column.getColumnName()));
-                    }
-                }
-                break;
-            case SELECT:
-                Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
-                Constraint newConstraint = replaceReferencesToRemovedSource(context, constraint, rewrittenSelectors);
-                if (constraint != newConstraint) {
-                    planNode.setProperty(Property.SELECT_CRITERIA, newConstraint);
-                }
-                break;
-            case SORT:
-                List<Object> orderBys = planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
-                if (orderBys != null && !orderBys.isEmpty()) {
-                    if (orderBys.get(0) instanceof SelectorName) {
-                        for (int i = 0; i != orderBys.size(); ++i) {
-                            SelectorName selectorName = (SelectorName)orderBys.get(i);
-                            SelectorName replacement = rewrittenSelectors.get(selectorName);
-                            if (replacement != null) {
-                                orderBys.set(i, replacement);
-                            }
-                        }
-                    } else {
-                        for (int i = 0; i != orderBys.size(); ++i) {
-                            Ordering ordering = (Ordering)orderBys.get(i);
-                            DynamicOperand operand = ordering.getOperand();
-                            orderBys.set(i, replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
-                        }
-                    }
-                }
-                break;
-            case JOIN:
-                // Update the join condition ...
-                JoinCondition joinCondition = planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
-                JoinCondition newCondition = replaceReferencesToRemovedSource(context, joinCondition, rewrittenSelectors);
-                if (joinCondition != newCondition) {
-                    planNode.setProperty(Property.JOIN_CONDITION, newCondition);
-                }
-
-                // Update the join criteria (if there are some) ...
-                List<Constraint> constraints = planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
-                if (constraints != null && !constraints.isEmpty()) {
-                    for (int i = 0; i != constraints.size(); ++i) {
-                        Constraint old = constraints.get(i);
-                        Constraint replacement = replaceReferencesToRemovedSource(context, old, rewrittenSelectors);
-                        if (replacement != old) {
-                            constraints.set(i, replacement);
-                        }
-                    }
-                }
-                break;
-            case GROUP:
-            case SET_OPERATION:
-            case DUP_REMOVE:
-            case LIMIT:
-            case NULL:
-            case SOURCE:
-            case ACCESS:
-                // None of these have to be changed ...
-                break;
-        }
-
-        // Update the selectors referenced by the node ...
-        Set<SelectorName> selectorsToAdd = null;
-        for (Iterator<SelectorName> iter = planNode.getSelectors().iterator(); iter.hasNext();) {
-            SelectorName replacement = rewrittenSelectors.get(iter.next());
-            if (replacement != null) {
-                iter.remove();
-                if (selectorsToAdd == null) selectorsToAdd = new HashSet<SelectorName>();
-                selectorsToAdd.add(replacement);
-            }
-        }
-        if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd);
-
-        // Now call recursively on the children ...
-        for (PlanNode child : planNode) {
-            replaceReferencesToRemovedSource(context, child, rewrittenSelectors);
-        }
-    }
-
-    protected DynamicOperand replaceReferencesToRemovedSource( QueryContext context,
-                                                               DynamicOperand operand,
-                                                               Map<SelectorName, SelectorName> rewrittenSelectors ) {
-        if (operand instanceof FullTextSearchScore) {
-            FullTextSearchScore score = (FullTextSearchScore)operand;
-            SelectorName replacement = rewrittenSelectors.get(score.getSelectorName());
-            if (replacement == null) return score;
-            return new FullTextSearchScore(replacement);
-        }
-        if (operand instanceof Length) {
-            Length operation = (Length)operand;
-            PropertyValue wrapped = operation.getPropertyValue();
-            SelectorName replacement = rewrittenSelectors.get(wrapped.getSelectorName());
-            if (replacement == null) return operand;
-            return new Length(new PropertyValue(replacement, wrapped.getPropertyName()));
-        }
-        if (operand instanceof LowerCase) {
-            LowerCase operation = (LowerCase)operand;
-            SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
-            if (replacement == null) return operand;
-            return new LowerCase(replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
-        }
-        if (operand instanceof UpperCase) {
-            UpperCase operation = (UpperCase)operand;
-            SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
-            if (replacement == null) return operand;
-            return new UpperCase(replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
-        }
-        if (operand instanceof NodeName) {
-            NodeName name = (NodeName)operand;
-            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
-            if (replacement == null) return name;
-            return new NodeName(replacement);
-        }
-        if (operand instanceof NodeLocalName) {
-            NodeLocalName name = (NodeLocalName)operand;
-            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
-            if (replacement == null) return name;
-            return new NodeLocalName(replacement);
-        }
-        if (operand instanceof PropertyValue) {
-            PropertyValue value = (PropertyValue)operand;
-            SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
-            if (replacement == null) return operand;
-            return new PropertyValue(replacement, value.getPropertyName());
-        }
-        if (operand instanceof NodeDepth) {
-            NodeDepth depth = (NodeDepth)operand;
-            SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
-            if (replacement == null) return operand;
-            return new NodeDepth(replacement);
-        }
-        if (operand instanceof NodePath) {
-            NodePath path = (NodePath)operand;
-            SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
-            if (replacement == null) return operand;
-            return new NodePath(replacement);
-        }
-        return operand;
-    }
-
-    protected Constraint replaceReferencesToRemovedSource( QueryContext context,
-                                                           Constraint constraint,
-                                                           Map<SelectorName, SelectorName> rewrittenSelectors ) {
-        if (constraint instanceof And) {
-            And and = (And)constraint;
-            Constraint left = replaceReferencesToRemovedSource(context, and.getLeft(), rewrittenSelectors);
-            Constraint right = replaceReferencesToRemovedSource(context, and.getRight(), rewrittenSelectors);
-            if (left == and.getLeft() && right == and.getRight()) return and;
-            return new And(left, right);
-        }
-        if (constraint instanceof Or) {
-            Or or = (Or)constraint;
-            Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(), rewrittenSelectors);
-            Constraint right = replaceReferencesToRemovedSource(context, or.getRight(), rewrittenSelectors);
-            if (left == or.getLeft() && right == or.getRight()) return or;
-            return new Or(left, right);
-        }
-        if (constraint instanceof Not) {
-            Not not = (Not)constraint;
-            Constraint wrapped = replaceReferencesToRemovedSource(context, not.getConstraint(), rewrittenSelectors);
-            if (wrapped == not.getConstraint()) return not;
-            return new Not(wrapped);
-        }
-        if (constraint instanceof SameNode) {
-            SameNode sameNode = (SameNode)constraint;
-            SelectorName replacement = rewrittenSelectors.get(sameNode.getSelectorName());
-            if (replacement == null) return sameNode;
-            return new SameNode(replacement, sameNode.getPath());
-        }
-        if (constraint instanceof ChildNode) {
-            ChildNode childNode = (ChildNode)constraint;
-            SelectorName replacement = rewrittenSelectors.get(childNode.getSelectorName());
-            if (replacement == null) return childNode;
-            return new ChildNode(replacement, childNode.getParentPath());
-        }
-        if (constraint instanceof DescendantNode) {
-            DescendantNode descendantNode = (DescendantNode)constraint;
-            SelectorName replacement = rewrittenSelectors.get(descendantNode.getSelectorName());
-            if (replacement == null) return descendantNode;
-            return new DescendantNode(replacement, descendantNode.getAncestorPath());
-        }
-        if (constraint instanceof PropertyExistence) {
-            PropertyExistence existence = (PropertyExistence)constraint;
-            SelectorName replacement = rewrittenSelectors.get(existence.getSelectorName());
-            if (replacement == null) return existence;
-            return new PropertyExistence(replacement, existence.getPropertyName());
-        }
-        if (constraint instanceof FullTextSearch) {
-            FullTextSearch search = (FullTextSearch)constraint;
-            SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
-            if (replacement == null) return search;
-            return new PropertyExistence(replacement, search.getPropertyName());
-        }
-        if (constraint instanceof Comparison) {
-            Comparison comparison = (Comparison)constraint;
-            DynamicOperand lhs = comparison.getOperand1();
-            StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
-            DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors);
-            if (lhs == newLhs) return comparison;
-            return new Comparison(newLhs, comparison.getOperator(), rhs);
-        }
-        return constraint;
-    }
-
-    protected JoinCondition replaceReferencesToRemovedSource( QueryContext context,
-                                                              JoinCondition joinCondition,
-                                                              Map<SelectorName, SelectorName> rewrittenSelectors ) {
-        if (joinCondition instanceof EquiJoinCondition) {
-            EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
-            SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
-            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
-            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
-            return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name());
-        }
-        if (joinCondition instanceof SameNodeJoinCondition) {
-            SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
-            SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
-            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
-            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
-            return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
-        }
-        if (joinCondition instanceof ChildNodeJoinCondition) {
-            ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
-            SelectorName childSelector = rewrittenSelectors.get(condition.getChildSelectorName());
-            SelectorName parentSelector = rewrittenSelectors.get(condition.getParentSelectorName());
-            if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
-            return new ChildNodeJoinCondition(parentSelector, childSelector);
-        }
-        if (joinCondition instanceof DescendantNodeJoinCondition) {
-            DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
-            SelectorName ancestor = rewrittenSelectors.get(condition.getAncestorSelectorName());
-            SelectorName descendant = rewrittenSelectors.get(condition.getDescendantSelectorName());
-            if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
-            return new ChildNodeJoinCondition(ancestor, descendant);
-        }
-        return joinCondition;
-    }
-
     /**
      * {@inheritDoc}
      * 

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizer.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -72,10 +72,15 @@
         if (hints.hasJoin) {
             ruleStack.addFirst(ChooseJoinAlgorithm.USE_ONLY_NESTED_JOIN_ALGORITHM);
         }
+        ruleStack.addFirst(PushProjects.INSTANCE);
         if (hints.hasCriteria) {
             ruleStack.addFirst(PushSelectCriteria.INSTANCE);
         }
         ruleStack.addFirst(AddAccessNodes.INSTANCE);
         ruleStack.addFirst(RightOuterToLeftOuterJoins.INSTANCE);
+        ruleStack.addFirst(CopyCriteria.INSTANCE);
+        if (hints.hasView) {
+            ruleStack.addFirst(ReplaceViews.INSTANCE);
+        }
     }
 }

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/parse/InvalidQueryException.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -26,7 +26,7 @@
 import org.jboss.dna.common.util.CheckArg;
 
 /**
- * An exception signalling that an XPath query is invalid (but typically well-formed)
+ * An exception signalling that a query is invalid (but typically well-formed)
  */
 public class InvalidQueryException extends RuntimeException {
 
@@ -34,38 +34,38 @@
      */
     private static final long serialVersionUID = 1L;
 
-    private final String xpath;
+    private final String query;
 
     /**
      * Create an exception with the invalid query.
      * 
-     * @param xpath the XPath query that is invalid
+     * @param query the query that is invalid
      */
-    public InvalidQueryException( String xpath ) {
+    public InvalidQueryException( String query ) {
         super();
-        this.xpath = xpath;
+        this.query = query;
     }
 
     /**
      * Create an exception with the invalid query and a message.
      * 
-     * @param xpath the XPath query that is invalid
+     * @param query the query that is invalid
      * @param message
      */
-    public InvalidQueryException( String xpath,
+    public InvalidQueryException( String query,
                                   String message ) {
         super(message);
-        CheckArg.isNotNull(xpath, "xpath");
-        this.xpath = xpath;
+        CheckArg.isNotNull(query, "query");
+        this.query = query;
     }
 
     /**
      * Get the XPath query that is invalid.
      * 
-     * @return the query
+     * @return the query; never null
      */
-    public String getXPath() {
-        return xpath;
+    public String getQuery() {
+        return query;
     }
 
 }

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/CanonicalPlanner.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -31,6 +31,7 @@
 import org.jboss.dna.graph.GraphI18n;
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.NameFactory;
+import org.jboss.dna.graph.property.ValueFactory;
 import org.jboss.dna.graph.query.QueryContext;
 import org.jboss.dna.graph.query.model.AllNodes;
 import org.jboss.dna.graph.query.model.And;
@@ -53,6 +54,7 @@
 import org.jboss.dna.graph.query.plan.PlanNode.Type;
 import org.jboss.dna.graph.query.validate.Schemata;
 import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
 
 /**
  * The planner that produces a canonical query plan given a {@link QueryCommand query command}.
@@ -85,7 +87,7 @@
  * from the top node of the plan.
  * </p>
  * <p>
- * This canonical plan, however, is optimized and rearranged so that it performs faster.
+ * This canonical plan, however, is later optimized and rearranged so that it performs faster.
  * </p>
  */
 public class CanonicalPlanner implements Planner {
@@ -190,14 +192,15 @@
             }
             // Validate the source name and set the available columns ...
             Table table = context.getSchemata().getTable(selector.getName());
-            if (table == null) {
-                context.getProblems().addError(GraphI18n.tableDoesNotExist,
-                                               selector.getName().getString(context.getExecutionContext()));
-            } else {
+            if (table != null) {
+                if (table instanceof View) context.getHints().hasView = true;
                 if (usedSelectors.put(selector.getAliasOrName(), table) != null) {
                     // There was already a table with this alias or name ...
                 }
                 node.setProperty(Property.SOURCE_COLUMNS, table.getColumns());
+            } else {
+                context.getProblems().addError(GraphI18n.tableDoesNotExist,
+                                               selector.getName().getString(context.getExecutionContext()));
             }
             return node;
         }
@@ -217,7 +220,8 @@
             // Handle each child
             Source[] clauses = new Source[] {join.getLeft(), join.getRight()};
             for (int i = 0; i < 2; i++) {
-                node.addLastChild(createPlanNode(context, clauses[i], usedSelectors));
+                PlanNode sourceNode = createPlanNode(context, clauses[i], usedSelectors);
+                node.addLastChild(sourceNode);
 
                 // Add selectors to the joinNode
                 for (PlanNode child : node.getChildren()) {
@@ -269,6 +273,7 @@
                 }
             });
 
+            criteriaNode.addFirstChild(plan);
             plan = criteriaNode;
         }
         return plan;
@@ -379,6 +384,7 @@
             }
         } else {
             // Add the selector used by each column ...
+            ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
             for (Column column : columns) {
                 SelectorName tableName = column.getSelectorName();
                 // Add the selector that is being used ...
@@ -391,9 +397,11 @@
                                                    tableName.getString(context.getExecutionContext()));
                 } else {
                     // Make sure that the column is in the table ...
-                    if (table.getColumn(column.getColumnName()) == null) {
+                    Name columnName = column.getPropertyName();
+                    String name = stringFactory.create(columnName);
+                    if (table.getColumn(name) == null) {
                         context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable,
-                                                       column.getColumnName(),
+                                                       name,
                                                        tableName.getString(context.getExecutionContext()));
                     }
                 }

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanHints.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -28,19 +28,19 @@
 @NotThreadSafe
 public final class PlanHints {
 
-    // This flag indicates that the plan has a criteria somewhere
+    /** This flag indicates that the plan has a criteria somewhere */
     public boolean hasCriteria = false;
 
-    // This flag indicates that the plan has a join somewhere
+    /** This flag indicates that the plan has a join somewhere */
     public boolean hasJoin = false;
 
-    // This flag indicates that the plan has a sort somewhere
+    /** This flag indicates that the plan has a sort somewhere */
     public boolean hasSort = false;
 
     // List of groups to make dependent
     // public List makeDepGroups = null;
 
-    // flag indicates that the plan has a union somewhere
+    /** flag indicates that the plan has a union somewhere */
     public boolean hasSetQuery = false;
 
     // flag indicating that the plan has a grouping node somewhere
@@ -55,6 +55,9 @@
 
     public boolean hasFullTextSearch = false;
 
+    /** Flag indicates that the plan has at least one view somewhere */
+    public boolean hasView = false;
+
     public PlanHints() {
     }
 
@@ -62,6 +65,7 @@
     public String toString() {
         StringBuilder sb = new StringBuilder("PlanHints {");
         sb.append(" hasCriteria=").append(hasCriteria);
+        sb.append(", hasView=").append(hasView);
         sb.append(", hasJoin=").append(hasJoin);
         sb.append(", hasSort=").append(hasSort);
         sb.append(", hasSetQuery=").append(hasSetQuery);

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanNode.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -36,6 +36,7 @@
 import java.util.Set;
 import net.jcip.annotations.NotThreadSafe;
 import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.ObjectUtil;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.query.model.Column;
 import org.jboss.dna.graph.query.model.Command;
@@ -54,7 +55,7 @@
  * A representation of a single node within a plan tree.
  */
 @NotThreadSafe
-public final class PlanNode implements Iterable<PlanNode>, Readable {
+public final class PlanNode implements Iterable<PlanNode>, Readable, Cloneable {
 
     /**
      * An enumeration dictating the type of plan tree nodes.
@@ -298,6 +299,70 @@
     }
 
     /**
+     * Return true if this node's type does not match the supplied type
+     * 
+     * @param type the type to compare
+     * @return true if this node's type is different than that supplied, or false if they are the same
+     */
+    public boolean isNot( Type type ) {
+        return this.type != type;
+    }
+
+    /**
+     * Return true if this node's type does not match any of the supplied types
+     * 
+     * @param first the type to compare
+     * @param rest the additional types to compare
+     * @return true if this node's type is different than all of those supplied, or false if matches one of the supplied types
+     */
+    public boolean isNotOneOf( Type first,
+                               Type... rest ) {
+        return isNotOneOf(EnumSet.of(first, rest));
+    }
+
+    /**
+     * Return true if this node's type does not match any of the supplied types
+     * 
+     * @param types the types to compare
+     * @return true if this node's type is different than all of those supplied, or false if matches one of the supplied types
+     */
+    public boolean isNotOneOf( Set<Type> types ) {
+        return !types.contains(type);
+    }
+
+    /**
+     * Return true if this node's type does match the supplied type
+     * 
+     * @param type the type to compare
+     * @return true if this node's type is the same as that supplied, or false if the types are different
+     */
+    public boolean is( Type type ) {
+        return this.type == type;
+    }
+
+    /**
+     * Return true if this node's type matches one of the supplied types
+     * 
+     * @param first the type to compare
+     * @param rest the additional types to compare
+     * @return true if this node's type is one of those supplied, or false otherwise
+     */
+    public boolean isOneOf( Type first,
+                            Type... rest ) {
+        return isOneOf(EnumSet.of(first, rest));
+    }
+
+    /**
+     * Return true if this node's type matches one of the supplied types
+     * 
+     * @param types the types to compare
+     * @return true if this node's type is one of those supplied, or false otherwise
+     */
+    public boolean isOneOf( Set<Type> types ) {
+        return types.contains(type);
+    }
+
+    /**
      * Get the parent of this node.
      * 
      * @return the parent node, or null if this node has no parent
@@ -357,6 +422,7 @@
      */
     public void insertAsParent( PlanNode newParent ) {
         if (newParent == null) return;
+        newParent.removeFromParent();
         if (this.parent != null) {
             this.parent.replaceChild(this, newParent);
         }
@@ -832,6 +898,34 @@
     }
 
     /**
+     * {@inheritDoc}
+     * <p>
+     * This class returns a new clone of the plan tree rooted at this node. However, the top node of the resulting plan tree (that
+     * is, the node returned from this method) has no parent.
+     * </p>
+     * 
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public PlanNode clone() {
+        return cloneWithoutNewParent();
+    }
+
+    protected PlanNode cloneWithoutNewParent() {
+        PlanNode result = new PlanNode(this.type, null, this.selectors);
+        if (this.nodeProperties != null && !this.nodeProperties.isEmpty()) {
+            result.nodeProperties = new HashMap<Property, Object>(this.nodeProperties);
+        }
+        // Clone the children ...
+        for (PlanNode child : children) {
+            PlanNode childClone = child.cloneWithoutNewParent();
+            // The child has no parent, so add the child to the new result ...
+            result.addLastChild(childClone);
+        }
+        return result;
+    }
+
+    /**
      * Determine whether the supplied plan is equivalent to this plan.
      * 
      * @param other the other plan to compare with this instance
@@ -840,9 +934,15 @@
     public boolean isSameAs( PlanNode other ) {
         if (other == null) return false;
         if (this.getType() != other.getType()) return false;
-        if (!this.nodeProperties.equals(other.nodeProperties)) return false;
+        if (!ObjectUtil.isEqualWithNulls(this.nodeProperties, other.nodeProperties)) return false;
         if (!this.getSelectors().equals(other.getSelectors())) return false;
-        return this.getChildren().equals(other.getChildren());
+        if (this.getChildCount() != other.getChildCount()) return false;
+        Iterator<PlanNode> thisChildren = this.getChildren().iterator();
+        Iterator<PlanNode> thatChildren = other.getChildren().iterator();
+        while (thisChildren.hasNext() && thatChildren.hasNext()) {
+            if (!thisChildren.next().isSameAs(thatChildren.next())) return false;
+        }
+        return true;
     }
 
     /**

Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java	                        (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,842 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.plan;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.And;
+import org.jboss.dna.graph.query.model.ChildNode;
+import org.jboss.dna.graph.query.model.ChildNodeJoinCondition;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Constraint;
+import org.jboss.dna.graph.query.model.DescendantNode;
+import org.jboss.dna.graph.query.model.DescendantNodeJoinCondition;
+import org.jboss.dna.graph.query.model.DynamicOperand;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.FullTextSearch;
+import org.jboss.dna.graph.query.model.FullTextSearchScore;
+import org.jboss.dna.graph.query.model.JoinCondition;
+import org.jboss.dna.graph.query.model.Length;
+import org.jboss.dna.graph.query.model.LowerCase;
+import org.jboss.dna.graph.query.model.NodeDepth;
+import org.jboss.dna.graph.query.model.NodeLocalName;
+import org.jboss.dna.graph.query.model.NodeName;
+import org.jboss.dna.graph.query.model.NodePath;
+import org.jboss.dna.graph.query.model.Not;
+import org.jboss.dna.graph.query.model.Or;
+import org.jboss.dna.graph.query.model.Ordering;
+import org.jboss.dna.graph.query.model.PropertyExistence;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SameNode;
+import org.jboss.dna.graph.query.model.SameNodeJoinCondition;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.StaticOperand;
+import org.jboss.dna.graph.query.model.UpperCase;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.model.Visitors.AbstractVisitor;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ * Utilities for working with {@link PlanNode}s.
+ */
+public class PlanUtil {
+
+    /**
+     * Collected the minimum set of columns from the supplied table that are required by or used within the plan at the supplied
+     * node or above. This method first looks to see if the supplied node is a {@link Type#PROJECT} node, and if so immediately
+     * returns that node's list of {@link Property#PROJECT_COLUMNS projected columns}. Otherwise, this method finds all of the
+     * {@link Type#SOURCE} nodes below the supplied plan node, and accumulates the set of sources for which the columns are to be
+     * found. The method then walks up the plan, looking for references to columns of the supplied table, starting with the
+     * supplied node and walking up until a {@link PlanNode.Type#PROJECT PROJECT} node is found or the top of the plan is reached.
+     * <p>
+     * The resulting column list will always contain at the front the {@link Property#PROJECT_COLUMNS columns} from the nearest
+     * PROJECT ancestor, followed by any columns required by criteria. This is done so that the processing of the nearest PROJECT
+     * ancestor node can simply use the sublist of each tuple.
+     * </p>
+     * 
+     * @param context the query context; may not be null
+     * @param planNode the plan node at which the required columns need to be found; may not be null
+     * @return the list of {@link Column} objects that are used in the plan; never null but possibly empty if no columns are
+     *         actually needed
+     */
+    public static List<Column> findRequiredColumns( QueryContext context,
+                                                    PlanNode planNode ) {
+        List<Column> columns = null;
+        PlanNode node = planNode;
+        PlanNode projectNode = null;
+        // First find the columns from the nearest PROJECT ancestor ...
+        do {
+            switch (node.getType()) {
+                case PROJECT:
+                    columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+                    projectNode = node;
+                    node = null;
+                    break;
+                default:
+                    node = node.getParent();
+                    break;
+            }
+        } while (node != null);
+
+        // If the supplied node is a PROJECT, just return the PROJECT node's column list ...
+        if (projectNode == planNode) return columns;
+
+        // Find the names of the selectors ...
+        Set<SelectorName> names = new HashSet<SelectorName>();
+        for (PlanNode source : planNode.findAllAtOrBelow(Type.SOURCE)) {
+            names.add(source.getProperty(Property.SOURCE_NAME, SelectorName.class));
+            names.add(source.getProperty(Property.SOURCE_ALIAS, SelectorName.class));
+        }
+
+        // Add the PROJECT columns first ...
+        RequiredColumnVisitor collectionVisitor = new RequiredColumnVisitor(context.getExecutionContext(), names);
+        if (columns != null) {
+            for (Column projectedColumn : columns) {
+                collectionVisitor.visit(projectedColumn);
+            }
+        }
+
+        // Now add the columns from the JOIN and SELECT ancestors ...
+        node = planNode;
+        do {
+            switch (node.getType()) {
+                case JOIN:
+                    Constraint criteria = node.getProperty(Property.JOIN_CONSTRAINTS, Constraint.class);
+                    JoinCondition joinCondition = node.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+                    Visitors.visitAll(criteria, collectionVisitor);
+                    Visitors.visitAll(joinCondition, collectionVisitor);
+                    break;
+                case SELECT:
+                    criteria = node.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+                    Visitors.visitAll(criteria, collectionVisitor);
+                    break;
+                case PROJECT:
+                    // Already handled above, but we can stop looking for columns ...
+                    return collectionVisitor.getRequiredColumns();
+                default:
+                    break;
+            }
+            node = node.getParent();
+        } while (node != null);
+        return collectionVisitor.getRequiredColumns();
+    }
+
+    protected static class RequiredColumnVisitor extends AbstractVisitor {
+        private final ExecutionContext context;
+        private final Set<SelectorName> names;
+        private final List<Column> columns = new LinkedList<Column>();
+        private final Set<Name> requiredColumnNames = new HashSet<Name>();
+
+        protected RequiredColumnVisitor( ExecutionContext context,
+                                         Set<SelectorName> names ) {
+            this.names = names;
+            this.context = context;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyExistence)
+         */
+        @Override
+        public void visit( PropertyExistence existence ) {
+            requireColumn(existence.getSelectorName(), existence.getPropertyName());
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.PropertyValue)
+         */
+        @Override
+        public void visit( PropertyValue value ) {
+            requireColumn(value.getSelectorName(), value.getPropertyName());
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.EquiJoinCondition)
+         */
+        @Override
+        public void visit( EquiJoinCondition condition ) {
+            requireColumn(condition.getSelector1Name(), condition.getProperty1Name());
+            requireColumn(condition.getSelector2Name(), condition.getProperty2Name());
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.graph.query.model.Visitors.AbstractVisitor#visit(org.jboss.dna.graph.query.model.Column)
+         */
+        @Override
+        public void visit( Column column ) {
+            requireColumn(column.getSelectorName(), column.getPropertyName());
+        }
+
+        protected void requireColumn( SelectorName selector,
+                                      Name propertyName ) {
+            if (names.contains(selector)) {
+                // The column is part of the table we're interested in ...
+                if (requiredColumnNames.add(propertyName)) {
+                    String alias = context.getValueFactories().getStringFactory().create(propertyName);
+                    columns.add(new Column(selector, propertyName, alias));
+                }
+            }
+        }
+
+        /**
+         * Get the columns that are required.
+         * 
+         * @return the columns; never null
+         */
+        public List<Column> getRequiredColumns() {
+            return columns;
+        }
+    }
+
+    public static void replaceReferencesToRemovedSource( QueryContext context,
+                                                         PlanNode planNode,
+                                                         Map<SelectorName, SelectorName> rewrittenSelectors ) {
+        switch (planNode.getType()) {
+            case PROJECT:
+                List<Column> columns = planNode.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+                for (int i = 0; i != columns.size(); ++i) {
+                    Column column = columns.get(i);
+                    SelectorName replacement = rewrittenSelectors.get(column.getSelectorName());
+                    if (replacement != null) {
+                        columns.set(i, new Column(replacement, column.getPropertyName(), column.getColumnName()));
+                    }
+                }
+                break;
+            case SELECT:
+                Constraint constraint = planNode.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+                Constraint newConstraint = replaceReferencesToRemovedSource(context, constraint, rewrittenSelectors);
+                if (constraint != newConstraint) {
+                    planNode.setProperty(Property.SELECT_CRITERIA, newConstraint);
+                }
+                break;
+            case SORT:
+                List<Object> orderBys = planNode.getPropertyAsList(Property.SORT_ORDER_BY, Object.class);
+                if (orderBys != null && !orderBys.isEmpty()) {
+                    if (orderBys.get(0) instanceof SelectorName) {
+                        for (int i = 0; i != orderBys.size(); ++i) {
+                            SelectorName selectorName = (SelectorName)orderBys.get(i);
+                            SelectorName replacement = rewrittenSelectors.get(selectorName);
+                            if (replacement != null) {
+                                orderBys.set(i, replacement);
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i != orderBys.size(); ++i) {
+                            Ordering ordering = (Ordering)orderBys.get(i);
+                            DynamicOperand operand = ordering.getOperand();
+                            orderBys.set(i, replaceReferencesToRemovedSource(context, operand, rewrittenSelectors));
+                        }
+                    }
+                }
+                break;
+            case JOIN:
+                // Update the join condition ...
+                JoinCondition joinCondition = planNode.getProperty(Property.JOIN_CONDITION, JoinCondition.class);
+                JoinCondition newCondition = replaceReferencesToRemovedSource(context, joinCondition, rewrittenSelectors);
+                if (joinCondition != newCondition) {
+                    planNode.setProperty(Property.JOIN_CONDITION, newCondition);
+                }
+
+                // Update the join criteria (if there are some) ...
+                List<Constraint> constraints = planNode.getPropertyAsList(Property.JOIN_CONSTRAINTS, Constraint.class);
+                if (constraints != null && !constraints.isEmpty()) {
+                    for (int i = 0; i != constraints.size(); ++i) {
+                        Constraint old = constraints.get(i);
+                        Constraint replacement = replaceReferencesToRemovedSource(context, old, rewrittenSelectors);
+                        if (replacement != old) {
+                            constraints.set(i, replacement);
+                        }
+                    }
+                }
+                break;
+            case GROUP:
+            case SET_OPERATION:
+            case DUP_REMOVE:
+            case LIMIT:
+            case NULL:
+            case SOURCE:
+            case ACCESS:
+                // None of these have to be changed ...
+                break;
+        }
+
+        // Update the selectors referenced by the node ...
+        Set<SelectorName> selectorsToAdd = null;
+        for (Iterator<SelectorName> iter = planNode.getSelectors().iterator(); iter.hasNext();) {
+            SelectorName replacement = rewrittenSelectors.get(iter.next());
+            if (replacement != null) {
+                iter.remove();
+                if (selectorsToAdd == null) selectorsToAdd = new HashSet<SelectorName>();
+                selectorsToAdd.add(replacement);
+            }
+        }
+        if (selectorsToAdd != null) planNode.getSelectors().addAll(selectorsToAdd);
+
+        // Now call recursively on the children ...
+        for (PlanNode child : planNode) {
+            replaceReferencesToRemovedSource(context, child, rewrittenSelectors);
+        }
+    }
+
+    public static DynamicOperand replaceReferencesToRemovedSource( QueryContext context,
+                                                                   DynamicOperand operand,
+                                                                   Map<SelectorName, SelectorName> rewrittenSelectors ) {
+        if (operand instanceof FullTextSearchScore) {
+            FullTextSearchScore score = (FullTextSearchScore)operand;
+            SelectorName replacement = rewrittenSelectors.get(score.getSelectorName());
+            if (replacement == null) return score;
+            return new FullTextSearchScore(replacement);
+        }
+        if (operand instanceof Length) {
+            Length operation = (Length)operand;
+            PropertyValue wrapped = operation.getPropertyValue();
+            SelectorName replacement = rewrittenSelectors.get(wrapped.getSelectorName());
+            if (replacement == null) return operand;
+            return new Length(new PropertyValue(replacement, wrapped.getPropertyName()));
+        }
+        if (operand instanceof LowerCase) {
+            LowerCase operation = (LowerCase)operand;
+            SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
+            if (replacement == null) return operand;
+            return new LowerCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors));
+        }
+        if (operand instanceof UpperCase) {
+            UpperCase operation = (UpperCase)operand;
+            SelectorName replacement = rewrittenSelectors.get(operation.getSelectorName());
+            if (replacement == null) return operand;
+            return new UpperCase(replaceReferencesToRemovedSource(context, operation.getOperand(), rewrittenSelectors));
+        }
+        if (operand instanceof NodeName) {
+            NodeName name = (NodeName)operand;
+            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+            if (replacement == null) return name;
+            return new NodeName(replacement);
+        }
+        if (operand instanceof NodeLocalName) {
+            NodeLocalName name = (NodeLocalName)operand;
+            SelectorName replacement = rewrittenSelectors.get(name.getSelectorName());
+            if (replacement == null) return name;
+            return new NodeLocalName(replacement);
+        }
+        if (operand instanceof PropertyValue) {
+            PropertyValue value = (PropertyValue)operand;
+            SelectorName replacement = rewrittenSelectors.get(value.getSelectorName());
+            if (replacement == null) return operand;
+            return new PropertyValue(replacement, value.getPropertyName());
+        }
+        if (operand instanceof NodeDepth) {
+            NodeDepth depth = (NodeDepth)operand;
+            SelectorName replacement = rewrittenSelectors.get(depth.getSelectorName());
+            if (replacement == null) return operand;
+            return new NodeDepth(replacement);
+        }
+        if (operand instanceof NodePath) {
+            NodePath path = (NodePath)operand;
+            SelectorName replacement = rewrittenSelectors.get(path.getSelectorName());
+            if (replacement == null) return operand;
+            return new NodePath(replacement);
+        }
+        return operand;
+    }
+
+    public static Constraint replaceReferencesToRemovedSource( QueryContext context,
+                                                               Constraint constraint,
+                                                               Map<SelectorName, SelectorName> rewrittenSelectors ) {
+        if (constraint instanceof And) {
+            And and = (And)constraint;
+            Constraint left = replaceReferencesToRemovedSource(context, and.getLeft(), rewrittenSelectors);
+            Constraint right = replaceReferencesToRemovedSource(context, and.getRight(), rewrittenSelectors);
+            if (left == and.getLeft() && right == and.getRight()) return and;
+            return new And(left, right);
+        }
+        if (constraint instanceof Or) {
+            Or or = (Or)constraint;
+            Constraint left = replaceReferencesToRemovedSource(context, or.getLeft(), rewrittenSelectors);
+            Constraint right = replaceReferencesToRemovedSource(context, or.getRight(), rewrittenSelectors);
+            if (left == or.getLeft() && right == or.getRight()) return or;
+            return new Or(left, right);
+        }
+        if (constraint instanceof Not) {
+            Not not = (Not)constraint;
+            Constraint wrapped = replaceReferencesToRemovedSource(context, not.getConstraint(), rewrittenSelectors);
+            if (wrapped == not.getConstraint()) return not;
+            return new Not(wrapped);
+        }
+        if (constraint instanceof SameNode) {
+            SameNode sameNode = (SameNode)constraint;
+            SelectorName replacement = rewrittenSelectors.get(sameNode.getSelectorName());
+            if (replacement == null) return sameNode;
+            return new SameNode(replacement, sameNode.getPath());
+        }
+        if (constraint instanceof ChildNode) {
+            ChildNode childNode = (ChildNode)constraint;
+            SelectorName replacement = rewrittenSelectors.get(childNode.getSelectorName());
+            if (replacement == null) return childNode;
+            return new ChildNode(replacement, childNode.getParentPath());
+        }
+        if (constraint instanceof DescendantNode) {
+            DescendantNode descendantNode = (DescendantNode)constraint;
+            SelectorName replacement = rewrittenSelectors.get(descendantNode.getSelectorName());
+            if (replacement == null) return descendantNode;
+            return new DescendantNode(replacement, descendantNode.getAncestorPath());
+        }
+        if (constraint instanceof PropertyExistence) {
+            PropertyExistence existence = (PropertyExistence)constraint;
+            SelectorName replacement = rewrittenSelectors.get(existence.getSelectorName());
+            if (replacement == null) return existence;
+            return new PropertyExistence(replacement, existence.getPropertyName());
+        }
+        if (constraint instanceof FullTextSearch) {
+            FullTextSearch search = (FullTextSearch)constraint;
+            SelectorName replacement = rewrittenSelectors.get(search.getSelectorName());
+            if (replacement == null) return search;
+            return new FullTextSearch(replacement, search.getPropertyName(), search.getFullTextSearchExpression());
+        }
+        if (constraint instanceof Comparison) {
+            Comparison comparison = (Comparison)constraint;
+            DynamicOperand lhs = comparison.getOperand1();
+            StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
+            DynamicOperand newLhs = replaceReferencesToRemovedSource(context, lhs, rewrittenSelectors);
+            if (lhs == newLhs) return comparison;
+            return new Comparison(newLhs, comparison.getOperator(), rhs);
+        }
+        return constraint;
+    }
+
+    public static JoinCondition replaceReferencesToRemovedSource( QueryContext context,
+                                                                  JoinCondition joinCondition,
+                                                                  Map<SelectorName, SelectorName> rewrittenSelectors ) {
+        if (joinCondition instanceof EquiJoinCondition) {
+            EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
+            SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
+            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
+            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+            return new EquiJoinCondition(replacement1, condition.getProperty1Name(), replacement2, condition.getProperty2Name());
+        }
+        if (joinCondition instanceof SameNodeJoinCondition) {
+            SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
+            SelectorName replacement1 = rewrittenSelectors.get(condition.getSelector1Name());
+            SelectorName replacement2 = rewrittenSelectors.get(condition.getSelector2Name());
+            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+            return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
+        }
+        if (joinCondition instanceof ChildNodeJoinCondition) {
+            ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
+            SelectorName childSelector = rewrittenSelectors.get(condition.getChildSelectorName());
+            SelectorName parentSelector = rewrittenSelectors.get(condition.getParentSelectorName());
+            if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
+            return new ChildNodeJoinCondition(parentSelector, childSelector);
+        }
+        if (joinCondition instanceof DescendantNodeJoinCondition) {
+            DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
+            SelectorName ancestor = rewrittenSelectors.get(condition.getAncestorSelectorName());
+            SelectorName descendant = rewrittenSelectors.get(condition.getDescendantSelectorName());
+            if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
+            return new ChildNodeJoinCondition(ancestor, descendant);
+        }
+        return joinCondition;
+    }
+
+    public static void replaceViewReferences( QueryContext context,
+                                              PlanNode topOfViewInPlan,
+                                              ColumnMapping mappings ) {
+        assert topOfViewInPlan != null;
+        SelectorName viewName = mappings.getOriginalName();
+
+        // Walk up the plan to change any references to the view columns into references to the source columns...
+        PlanNode node = topOfViewInPlan;
+        ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+        List<PlanNode> potentiallyRemovableSources = new LinkedList<PlanNode>();
+        do {
+            // Remove the view from the selectors ...
+            if (node.getSelectors().contains(viewName)) {
+                switch (node.getType()) {
+                    case PROJECT:
+                        // Adjust the columns ...
+                        node.getSelectors().remove(viewName);
+                        List<Column> columns = node.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+                        if (columns != null) {
+                            for (int i = 0; i != columns.size(); ++i) {
+                                Column column = columns.get(i);
+                                if (column.getSelectorName().equals(viewName)) {
+                                    // This column references the view ...
+                                    String columnName = stringFactory.create(column.getPropertyName());
+                                    String columnAlias = column.getColumnName();
+                                    // Find the source column that this view column corresponds to ...
+                                    Column sourceColumn = mappings.getMappedColumn(columnName);
+                                    if (sourceColumn != null) {
+                                        SelectorName sourceName = sourceColumn.getSelectorName();
+                                        // Replace the view column with one that uses the same alias but that references the
+                                        // source
+                                        // column ...
+                                        columns.set(i, new Column(sourceName, sourceColumn.getPropertyName(), columnAlias));
+                                        node.addSelector(sourceName);
+                                    } else {
+                                        node.addSelector(column.getSelectorName());
+                                    }
+                                } else {
+                                    node.addSelector(column.getSelectorName());
+                                }
+                            }
+                        }
+                        break;
+                    case SELECT:
+                        Constraint constraint = node.getProperty(Property.SELECT_CRITERIA, Constraint.class);
+                        Constraint newConstraint = replaceReferences(context, constraint, mappings, node);
+                        if (constraint != newConstraint) {
+                            node.getSelectors().clear();
+                            node.addSelectors(Visitors.getSelectorsReferencedBy(newConstraint));
+                            node.setProperty(Property.SELECT_CRITERIA, newConstraint);
+                        }
+                        break;
+                    case SET_OPERATION:
+                        break;
+                    case SOURCE:
+                        SelectorName sourceName = node.getProperty(Property.SOURCE_NAME, SelectorName.class);
+                        assert sourceName.equals(sourceName); // selector name already matches
+                        potentiallyRemovableSources.add(node);
+                        break;
+                    default:
+                        break;
+                }
+                if (!node.getSelectors().contains(viewName)) {
+                    // Used to reference view, so it needs to at least reference
+                }
+            }
+            // Move to the parent ...
+            node = node.getParent();
+        } while (node != null);
+
+        // Remove any source node that was the view but that is no longer used in any higher node ...
+        for (PlanNode sourceNode : potentiallyRemovableSources) {
+            boolean stillRequired = false;
+            node = sourceNode.getParent();
+            while (node != null) {
+                if (node.getSelectors().contains(viewName)) {
+                    stillRequired = true;
+                    break;
+                }
+                node = node.getParent();
+            }
+            if (!stillRequired) {
+                assert sourceNode.getParent() != null;
+                sourceNode.extractFromParent();
+            }
+        }
+    }
+
+    public static Constraint replaceReferences( QueryContext context,
+                                                Constraint constraint,
+                                                ColumnMapping mapping,
+                                                PlanNode node ) {
+        if (constraint instanceof And) {
+            And and = (And)constraint;
+            Constraint left = replaceReferences(context, and.getLeft(), mapping, node);
+            Constraint right = replaceReferences(context, and.getRight(), mapping, node);
+            if (left == and.getLeft() && right == and.getRight()) return and;
+            return new And(left, right);
+        }
+        if (constraint instanceof Or) {
+            Or or = (Or)constraint;
+            Constraint left = replaceReferences(context, or.getLeft(), mapping, node);
+            Constraint right = replaceReferences(context, or.getRight(), mapping, node);
+            if (left == or.getLeft() && right == or.getRight()) return or;
+            return new Or(left, right);
+        }
+        if (constraint instanceof Not) {
+            Not not = (Not)constraint;
+            Constraint wrapped = replaceReferences(context, not.getConstraint(), mapping, node);
+            return wrapped == not.getConstraint() ? not : new Not(wrapped);
+        }
+        if (constraint instanceof SameNode) {
+            SameNode sameNode = (SameNode)constraint;
+            if (!mapping.getOriginalName().equals(sameNode.getSelectorName())) return sameNode;
+            if (!mapping.isMappedToSingleSelector()) return sameNode;
+            SelectorName selector = mapping.getSingleMappedSelectorName();
+            node.addSelector(selector);
+            return new SameNode(selector, sameNode.getPath());
+        }
+        if (constraint instanceof ChildNode) {
+            ChildNode childNode = (ChildNode)constraint;
+            if (!mapping.getOriginalName().equals(childNode.getSelectorName())) return childNode;
+            if (!mapping.isMappedToSingleSelector()) return childNode;
+            SelectorName selector = mapping.getSingleMappedSelectorName();
+            node.addSelector(selector);
+            return new ChildNode(selector, childNode.getParentPath());
+        }
+        if (constraint instanceof DescendantNode) {
+            DescendantNode descendantNode = (DescendantNode)constraint;
+            if (!mapping.getOriginalName().equals(descendantNode.getSelectorName())) return descendantNode;
+            if (!mapping.isMappedToSingleSelector()) return descendantNode;
+            SelectorName selector = mapping.getSingleMappedSelectorName();
+            node.addSelector(selector);
+            return new DescendantNode(selector, descendantNode.getAncestorPath());
+        }
+        if (constraint instanceof PropertyExistence) {
+            PropertyExistence existence = (PropertyExistence)constraint;
+            if (!mapping.getOriginalName().equals(existence.getSelectorName())) return existence;
+            ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+            Column sourceColumn = mapping.getMappedColumn(stringFactory.create(existence.getPropertyName()));
+            if (sourceColumn == null) return existence;
+            node.addSelector(sourceColumn.getSelectorName());
+            return new PropertyExistence(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
+        }
+        if (constraint instanceof FullTextSearch) {
+            FullTextSearch search = (FullTextSearch)constraint;
+            if (!mapping.getOriginalName().equals(search.getSelectorName())) return search;
+            ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+            Column sourceColumn = mapping.getMappedColumn(stringFactory.create(search.getPropertyName()));
+            if (sourceColumn == null) return search;
+            node.addSelector(sourceColumn.getSelectorName());
+            return new FullTextSearch(sourceColumn.getSelectorName(), sourceColumn.getPropertyName(),
+                                      search.getFullTextSearchExpression());
+        }
+        if (constraint instanceof Comparison) {
+            Comparison comparison = (Comparison)constraint;
+            DynamicOperand lhs = comparison.getOperand1();
+            StaticOperand rhs = comparison.getOperand2(); // Current only a literal; therefore, no reference to selector
+            DynamicOperand newLhs = replaceViewReferences(context, lhs, mapping, node);
+            if (lhs == newLhs) return comparison;
+            return new Comparison(newLhs, comparison.getOperator(), rhs);
+        }
+        return constraint;
+    }
+
+    public static DynamicOperand replaceViewReferences( QueryContext context,
+                                                        DynamicOperand operand,
+                                                        ColumnMapping mapping,
+                                                        PlanNode node ) {
+        if (operand instanceof FullTextSearchScore) {
+            FullTextSearchScore score = (FullTextSearchScore)operand;
+            if (!mapping.getOriginalName().equals(score.getSelectorName())) return score;
+            if (!mapping.isMappedToSingleSelector()) return score;
+            return new FullTextSearchScore(mapping.getSingleMappedSelectorName());
+        }
+        if (operand instanceof Length) {
+            Length operation = (Length)operand;
+            return new Length((PropertyValue)replaceViewReferences(context, operation.getPropertyValue(), mapping, node));
+        }
+        if (operand instanceof LowerCase) {
+            LowerCase operation = (LowerCase)operand;
+            return new LowerCase(replaceViewReferences(context, operation.getOperand(), mapping, node));
+        }
+        if (operand instanceof UpperCase) {
+            UpperCase operation = (UpperCase)operand;
+            return new UpperCase(replaceViewReferences(context, operation.getOperand(), mapping, node));
+        }
+        if (operand instanceof NodeName) {
+            NodeName name = (NodeName)operand;
+            if (!mapping.getOriginalName().equals(name.getSelectorName())) return name;
+            if (!mapping.isMappedToSingleSelector()) return name;
+            node.addSelector(mapping.getSingleMappedSelectorName());
+            return new NodeName(mapping.getSingleMappedSelectorName());
+        }
+        if (operand instanceof NodeLocalName) {
+            NodeLocalName name = (NodeLocalName)operand;
+            if (!mapping.getOriginalName().equals(name.getSelectorName())) return name;
+            if (!mapping.isMappedToSingleSelector()) return name;
+            node.addSelector(mapping.getSingleMappedSelectorName());
+            return new NodeLocalName(mapping.getSingleMappedSelectorName());
+        }
+        if (operand instanceof PropertyValue) {
+            PropertyValue value = (PropertyValue)operand;
+            if (!mapping.getOriginalName().equals(value.getSelectorName())) return value;
+            ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+            Column sourceColumn = mapping.getMappedColumn(stringFactory.create(value.getPropertyName()));
+            if (sourceColumn == null) return value;
+            node.addSelector(sourceColumn.getSelectorName());
+            return new PropertyValue(sourceColumn.getSelectorName(), sourceColumn.getPropertyName());
+        }
+        if (operand instanceof NodeDepth) {
+            NodeDepth depth = (NodeDepth)operand;
+            if (!mapping.getOriginalName().equals(depth.getSelectorName())) return depth;
+            if (!mapping.isMappedToSingleSelector()) return depth;
+            node.addSelector(mapping.getSingleMappedSelectorName());
+            return new NodeDepth(mapping.getSingleMappedSelectorName());
+        }
+        if (operand instanceof NodePath) {
+            NodePath path = (NodePath)operand;
+            if (!mapping.getOriginalName().equals(path.getSelectorName())) return path;
+            if (!mapping.isMappedToSingleSelector()) return path;
+            node.addSelector(mapping.getSingleMappedSelectorName());
+            return new NodePath(mapping.getSingleMappedSelectorName());
+        }
+        return operand;
+    }
+
+    public static JoinCondition replaceReferences( QueryContext context,
+                                                   JoinCondition joinCondition,
+                                                   ColumnMapping mapping,
+                                                   PlanNode node ) {
+        if (joinCondition instanceof EquiJoinCondition) {
+            EquiJoinCondition condition = (EquiJoinCondition)joinCondition;
+            SelectorName replacement1 = condition.getSelector1Name();
+            SelectorName replacement2 = condition.getSelector2Name();
+            Name property1 = condition.getProperty1Name();
+            Name property2 = condition.getProperty2Name();
+            if (replacement1.equals(mapping.getOriginalName())) {
+                ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+                Column sourceColumn = mapping.getMappedColumn(stringFactory.create(property1));
+                if (sourceColumn != null) {
+                    replacement1 = sourceColumn.getSelectorName();
+                    property1 = sourceColumn.getPropertyName();
+                }
+            }
+            if (replacement2.equals(mapping.getOriginalName())) {
+                ValueFactory<String> stringFactory = context.getExecutionContext().getValueFactories().getStringFactory();
+                Column sourceColumn = mapping.getMappedColumn(stringFactory.create(property2));
+                if (sourceColumn != null) {
+                    replacement2 = sourceColumn.getSelectorName();
+                    property2 = sourceColumn.getPropertyName();
+                }
+            }
+            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+            node.addSelector(replacement1, replacement2);
+            return new EquiJoinCondition(replacement1, property1, replacement2, property2);
+        }
+        // All the remaining conditions can only be rewritten if there is a single source ...
+        if (!mapping.isMappedToSingleSelector()) return joinCondition;
+
+        // There is only a single source ...
+        SelectorName viewName = mapping.getOriginalName();
+        SelectorName sourceName = mapping.getSingleMappedSelectorName();
+
+        if (joinCondition instanceof SameNodeJoinCondition) {
+            SameNodeJoinCondition condition = (SameNodeJoinCondition)joinCondition;
+            SelectorName replacement1 = condition.getSelector1Name();
+            SelectorName replacement2 = condition.getSelector2Name();
+            if (replacement1.equals(viewName)) replacement1 = sourceName;
+            if (replacement2.equals(viewName)) replacement2 = sourceName;
+            if (replacement1 == condition.getSelector1Name() && replacement2 == condition.getSelector2Name()) return condition;
+            node.addSelector(replacement1, replacement2);
+            return new SameNodeJoinCondition(replacement1, replacement2, condition.getSelector2Path());
+        }
+        if (joinCondition instanceof ChildNodeJoinCondition) {
+            ChildNodeJoinCondition condition = (ChildNodeJoinCondition)joinCondition;
+            SelectorName childSelector = condition.getChildSelectorName();
+            SelectorName parentSelector = condition.getParentSelectorName();
+            if (childSelector.equals(viewName)) childSelector = sourceName;
+            if (parentSelector.equals(viewName)) parentSelector = sourceName;
+            if (childSelector == condition.getChildSelectorName() && parentSelector == condition.getParentSelectorName()) return condition;
+            node.addSelector(childSelector, parentSelector);
+            return new ChildNodeJoinCondition(parentSelector, childSelector);
+        }
+        if (joinCondition instanceof DescendantNodeJoinCondition) {
+            DescendantNodeJoinCondition condition = (DescendantNodeJoinCondition)joinCondition;
+            SelectorName ancestor = condition.getAncestorSelectorName();
+            SelectorName descendant = condition.getDescendantSelectorName();
+            if (ancestor.equals(viewName)) ancestor = sourceName;
+            if (descendant.equals(viewName)) descendant = sourceName;
+            if (ancestor == condition.getAncestorSelectorName() && descendant == condition.getDescendantSelectorName()) return condition;
+            node.addSelector(ancestor, descendant);
+            return new ChildNodeJoinCondition(ancestor, descendant);
+        }
+        return joinCondition;
+    }
+
+    public static ColumnMapping createMappingFor( View view,
+                                                  PlanNode viewPlan ) {
+        ColumnMapping mapping = new ColumnMapping(view.getName());
+
+        // Find the PROJECT node in the view plan ...
+        PlanNode project = viewPlan.findAtOrBelow(Type.PROJECT);
+        assert project != null;
+
+        // Get the Columns from the PROJECT in the plan node ...
+        List<Column> projectedColumns = project.getPropertyAsList(Property.PROJECT_COLUMNS, Column.class);
+
+        // Get the Schemata columns defined by the view ...
+        List<org.jboss.dna.graph.query.validate.Schemata.Column> viewColumns = view.getColumns();
+        assert viewColumns.size() == projectedColumns.size();
+
+        for (int i = 0; i != viewColumns.size(); ++i) {
+            Column projectedColunn = projectedColumns.get(i);
+            String viewColumnName = viewColumns.get(i).getName();
+            mapping.map(viewColumnName, projectedColunn);
+        }
+        return mapping;
+    }
+
+    /**
+     * Defines how the view columns are mapped (or resolved) into the columns from the source tables.
+     */
+    public static class ColumnMapping {
+        private final SelectorName originalName;
+        private final Map<String, Column> mappedColumnsByOriginalColumnName = new HashMap<String, Column>();
+        private final Set<SelectorName> mappedSelectorNames = new HashSet<SelectorName>();
+
+        public ColumnMapping( SelectorName originalName ) {
+            this.originalName = originalName;
+        }
+
+        public void map( String originalColumnName,
+                         Column projectedColumn ) {
+            mappedColumnsByOriginalColumnName.put(originalColumnName, projectedColumn);
+            mappedSelectorNames.add(projectedColumn.getSelectorName());
+        }
+
+        public SelectorName getOriginalName() {
+            return originalName;
+        }
+
+        public Column getMappedColumn( String viewColumnName ) {
+            return mappedColumnsByOriginalColumnName.get(viewColumnName);
+        }
+
+        public boolean isMappedToSingleSelector() {
+            return mappedSelectorNames.size() == 1;
+        }
+
+        /**
+         * @return tableNames
+         */
+        public Set<SelectorName> getMappedSelectorNames() {
+            return mappedSelectorNames;
+        }
+
+        public SelectorName getSingleMappedSelectorName() {
+            return isMappedToSingleSelector() ? mappedSelectorNames.iterator().next() : null;
+        }
+    }
+
+}


Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/plan/PlanUtil.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableSchemata.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -32,10 +32,23 @@
 import java.util.Set;
 import net.jcip.annotations.Immutable;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.text.ParsingException;
 import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.GraphI18n;
 import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.QueryCommand;
 import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.parse.InvalidQueryException;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
 
 /**
  * An immutable {@link Schemata} implementation.
@@ -46,10 +59,13 @@
     /**
      * Obtain a new instance for building Schemata objects.
      * 
+     * @param context the execution context that can be used when building the schema
      * @return the new builder; never null
+     * @throws IllegalArgumentException if the context is null
      */
-    public static Builder createBuilder() {
-        return new Builder();
+    public static Builder createBuilder( ExecutionContext context ) {
+        CheckArg.isNotNull(context, "context");
+        return new Builder(context);
     }
 
     /**
@@ -58,8 +74,14 @@
     @NotThreadSafe
     public static class Builder {
 
-        private Map<SelectorName, ImmutableTable> tables = new HashMap<SelectorName, ImmutableTable>();
+        private final ExecutionContext context;
+        private final Map<SelectorName, ImmutableTable> tables = new HashMap<SelectorName, ImmutableTable>();
+        private final Map<SelectorName, QueryCommand> viewDefinitions = new HashMap<SelectorName, QueryCommand>();
 
+        protected Builder( ExecutionContext context ) {
+            this.context = context;
+        }
+
         /**
          * Add a table with the supplied name and column names. Each column will be given a type of {@link PropertyType#STRING}.
          * The table will also overwrite any existing table definition with the same name.
@@ -254,6 +276,43 @@
         }
 
         /**
+         * Add a view with the supplied name and SQL string definition. The column names and types will be inferred from the
+         * source table(s) and views(s) used in the definition.
+         * 
+         * @param name the name of the new view
+         * @param definition the SQL definition of the view
+         * @return this builder, for convenience in method chaining; never null
+         * @throws IllegalArgumentException if the view name is null or empty or the definition is null
+         * @throws ParsingException if the supplied definition is cannot be parsed as a SQL query
+         */
+        public Builder addView( String name,
+                                String definition ) {
+            CheckArg.isNotEmpty(name, "name");
+            CheckArg.isNotEmpty(definition, "definition");
+            SqlQueryParser parser = new SqlQueryParser();
+            QueryCommand command = parser.parseQuery(definition, context);
+            this.viewDefinitions.put(new SelectorName(name), command);
+            return this;
+        }
+
+        /**
+         * Add a view with the supplied name and definition. The column names and types will be inferred from the source table(s)
+         * used in the definition.
+         * 
+         * @param name the name of the new view
+         * @param definition the definition of the view
+         * @return this builder, for convenience in method chaining; never null
+         * @throws IllegalArgumentException if the view name is null or empty or the definition is null
+         */
+        public Builder addView( String name,
+                                QueryCommand definition ) {
+            CheckArg.isNotEmpty(name, "name");
+            CheckArg.isNotNull(definition, "definition");
+            this.viewDefinitions.put(new SelectorName(name), definition);
+            return this;
+        }
+
+        /**
          * Add a column with the supplied name and type to the named table. Any existing column with that name will be replaced
          * with the new column. If the table does not yet exist, it will be added.
          * 
@@ -320,9 +379,70 @@
          * tables (with their columns) as they exist at the moment this method is called.
          * 
          * @return the new Schemata; never null
+         * @throws InvalidQueryException if any of the view definitions is invalid and cannot be resolved
          */
         public Schemata build() {
-            return new ImmutableSchemata(new HashMap<SelectorName, Table>(tables));
+            ImmutableSchemata schemata = new ImmutableSchemata(new HashMap<SelectorName, Table>(tables));
+
+            // Make a copy of the view definitions, and create the views ...
+            Map<SelectorName, QueryCommand> definitions = new HashMap<SelectorName, QueryCommand>(viewDefinitions);
+            ValueFactory<String> stringFactory = context.getValueFactories().getStringFactory();
+            boolean added = false;
+            do {
+                added = false;
+                Set<SelectorName> viewNames = new HashSet<SelectorName>(definitions.keySet());
+                for (SelectorName name : viewNames) {
+                    QueryCommand command = definitions.get(name);
+                    // Create the canonical plan for the definition ...
+                    QueryContext queryContext = new QueryContext(context, new PlanHints(), schemata);
+                    CanonicalPlanner planner = new CanonicalPlanner();
+                    PlanNode plan = planner.createPlan(queryContext, command);
+                    if (queryContext.getProblems().hasErrors()) continue;
+
+                    // Get the columns from the top-level PROJECT ...
+                    PlanNode project = plan.findAtOrBelow(Type.PROJECT);
+                    assert project != null;
+                    List<org.jboss.dna.graph.query.model.Column> columns = project.getPropertyAsList(Property.PROJECT_COLUMNS,
+                                                                                                     org.jboss.dna.graph.query.model.Column.class);
+                    assert !columns.isEmpty();
+
+                    // Go through all the columns and look up the types ...
+                    List<Column> viewColumns = new ArrayList<Column>(columns.size());
+                    for (org.jboss.dna.graph.query.model.Column column : columns) {
+                        // Find the table that the column came from ...
+                        Table source = schemata.getTable(column.getSelectorName());
+                        if (source == null) break;
+                        String viewColumnName = column.getColumnName();
+                        String sourceColumnName = stringFactory.create(column.getPropertyName()); // getColumnName() returns alias
+                        Column sourceColumn = source.getColumn(sourceColumnName);
+                        if (sourceColumn == null) {
+                            throw new InvalidQueryException(Visitors.readable(command),
+                                                            "The view references a non-existant column '"
+                                                            + column.getColumnName() + "' in '" + source.getName() + "'");
+                        }
+                        viewColumns.add(new ImmutableColumn(viewColumnName, sourceColumn.getPropertyType()));
+                    }
+                    if (viewColumns.size() != columns.size()) {
+                        // We weren't able to resolve all of the columns,
+                        // so maybe the columns were referencing yet-to-be-built views ...
+                        continue;
+                    }
+
+                    // If we could resolve the definition ...
+                    ImmutableView view = new ImmutableView(name, viewColumns, command);
+                    definitions.remove(name);
+                    schemata = schemata.with(view);
+                    added = true;
+                }
+            } while (added && !definitions.isEmpty());
+
+            if (!definitions.isEmpty()) {
+                QueryCommand command = definitions.values().iterator().next();
+                throw new InvalidQueryException(Visitors.readable(command), "The view definition cannot be resolved: "
+                                                                            + Visitors.readable(command));
+            }
+
+            return schemata;
         }
     }
 

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableTable.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -187,6 +187,14 @@
         return new ImmutableTable(getName(), newColumns);
     }
 
+    public ImmutableTable withColumns( Iterable<Column> columns ) {
+        List<Column> newColumns = new LinkedList<Column>(this.getColumns());
+        for (Column column : columns) {
+            newColumns.add(new ImmutableColumn(column.getName(), column.getPropertyType()));
+        }
+        return new ImmutableTable(getName(), newColumns);
+    }
+
     public ImmutableTable with( SelectorName name ) {
         return new ImmutableTable(name, columnsByName, columns, keys);
     }

Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java	                        (rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,107 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.validate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.model.Visitors;
+import org.jboss.dna.graph.query.validate.Schemata.Column;
+import org.jboss.dna.graph.query.validate.Schemata.Key;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+
+/**
+ * 
+ */
+ at Immutable
+class ImmutableView extends ImmutableTable implements View {
+
+    private final QueryCommand definition;
+
+    protected ImmutableView( SelectorName name,
+                             Iterable<Column> columns,
+                             QueryCommand definition ) {
+        super(name, columns);
+        this.definition = definition;
+    }
+
+    protected ImmutableView( SelectorName name,
+                             Iterable<Column> columns,
+                             QueryCommand definition,
+                             Iterable<Column>... keyColumns ) {
+        super(name, columns, keyColumns);
+        this.definition = definition;
+    }
+
+    protected ImmutableView( SelectorName name,
+                             Map<String, Column> columnsByName,
+                             List<Column> columns,
+                             QueryCommand definition,
+                             Set<Key> keys ) {
+        super(name, columnsByName, columns, keys);
+        this.definition = definition;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.graph.query.validate.Schemata.View#getDefinition()
+     */
+    public QueryCommand getDefinition() {
+        return definition;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(getName().getName());
+        sb.append('(');
+        boolean first = true;
+        for (Column column : getColumns()) {
+            if (first) first = false;
+            else sb.append(", ");
+            sb.append(column);
+        }
+        sb.append(") AS '");
+        sb.append(Visitors.readable(definition));
+        sb.append('\'');
+        if (!getKeys().isEmpty()) {
+            sb.append(" with keys ");
+            first = true;
+            for (Key key : getKeys()) {
+                if (first) first = false;
+                else sb.append(", ");
+                sb.append(key);
+            }
+        }
+        return sb.toString();
+    }
+}


Property changes on: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/ImmutableView.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/Schemata.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -28,6 +28,7 @@
 import java.util.Map;
 import java.util.Set;
 import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.QueryCommand;
 import org.jboss.dna.graph.query.model.SelectorName;
 
 /**
@@ -36,10 +37,14 @@
 public interface Schemata {
 
     /**
-     * Get the information for the table with the supplied name within this schema.
+     * Get the information for the table or view with the supplied name within this schema.
+     * <p>
+     * The resulting definition is immutable, though subsequent calls to this method with the same argument may result in a
+     * different definition.
+     * </p>
      * 
-     * @param name the table name; may not be null
-     * @return the table information, or null if there is no such table
+     * @param name the table or view name; may not be null
+     * @return the table or view information, or null if there is no such table
      */
     Table getTable( SelectorName name );
 
@@ -56,6 +61,9 @@
 
         /**
          * Get the information for a column with the supplied name within this table.
+         * <p>
+         * The resulting column definition is immutable.
+         * </p>
          * 
          * @param name the column name; may not be null
          * @return the column information, or null if there is no such column
@@ -65,21 +73,21 @@
         /**
          * Get the queryable columns in this table.
          * 
-         * @return the map of column objects by their name; never null
+         * @return the immutable map of immutable column objects by their name; never null
          */
         Map<String, Column> getColumnsByName();
 
         /**
          * Get the queryable columns in this table.
          * 
-         * @return the ordered column objects; never null
+         * @return the immutable, ordered list of immutable column objects; never null
          */
         List<Column> getColumns();
 
         /**
          * Get the collection of keys for this table.
          * 
-         * @return the immutable collection of keys; never null, but possibly empty
+         * @return the immutable collection of immutable keys; never null, but possibly empty
          */
         Collection<Key> getKeys();
 
@@ -101,6 +109,9 @@
 
         /**
          * Obtain this table's {@link #getKeys() key} that contains exactly those columns listed.
+         * <p>
+         * The resulting key definition is immutable.
+         * </p>
          * 
          * @param columns the columns for the key
          * @return the key that uses exactly the supplied columns, or null if there is no such key
@@ -109,6 +120,9 @@
 
         /**
          * Obtain this table's {@link #getKeys() key} that contains exactly those columns listed.
+         * <p>
+         * The resulting key definition is immutable.
+         * </p>
          * 
          * @param columns the columns for the key
          * @return the key that uses exactly the supplied columns, or null if there is no such key
@@ -117,6 +131,18 @@
     }
 
     /**
+     * Information about a view that is defined in terms of other views/tables.
+     */
+    public interface View extends Table {
+        /**
+         * Get the {@link QueryCommand query} that is the definition of the view.
+         * 
+         * @return the view definition; never null
+         */
+        QueryCommand getDefinition();
+    }
+
+    /**
      * Information about a queryable column.
      */
     public interface Column {

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/query/validate/package-info.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -25,8 +25,9 @@
  * This package provides the interfaces that define the tables and columns that can be queried.  Though the query framework does not prescribe 
  * the semantics of a table or column, it does provide
  * a {@link org.jboss.dna.graph.query.validate.Schemata} interface that provides access to the immutable
- * {@link org.jboss.dna.graph.query.validate.Schemata.Table} definitions (that then contain the
- * {@link org.jboss.dna.graph.query.validate.Schemata.Column} and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
+ * {@link org.jboss.dna.graph.query.validate.Schemata.Table} and {@link org.jboss.dna.graph.query.validate.Schemata.View}
+ * definitions (that then contain the {@link org.jboss.dna.graph.query.validate.Schemata.Column} 
+ * and {@link org.jboss.dna.graph.query.validate.Schemata.Key} definitions).
  * <p>
  * Although any Schemata implementaiton can be used, the query framework provides an {@link org.jboss.dna.graph.query.validate.ImmutableSchemata}
  * class with a {@link org.jboss.dna.graph.query.validate.ImmutableSchemata.Builder builder} with a fluent API

Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/AbstractQueryTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -27,6 +27,7 @@
 import static org.hamcrest.core.IsSame.sameInstance;
 import static org.junit.Assert.assertThat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -51,6 +52,14 @@
         }
     }
 
+    protected void assertSameChildren( PlanNode node,
+                                       PlanNode... children ) {
+        assertThat(node.getChildCount(), is(children.length));
+        for (int i = 0; i != node.getChildCount(); ++i) {
+            assertThat(node.getChild(i).isSameAs(children[i]), is(true));
+        }
+    }
+
     protected void assertSelectors( PlanNode node,
                                     String... selectors ) {
         Set<SelectorName> selectorSet = new HashSet<SelectorName>();
@@ -60,6 +69,19 @@
         assertThat("Selectors don't match", node.getSelectors(), is(selectorSet));
     }
 
+    protected void assertProperty( PlanNode node,
+                                   Property name,
+                                   Object value ) {
+        assertThat("Property value doesn't match", node.getProperty(name), is(value));
+    }
+
+    protected <T> void assertPropertyIsList( PlanNode node,
+                                             Property name,
+                                             Class<T> valueType,
+                                             T... values ) {
+        assertThat("Property value doesn't match", node.getPropertyAsList(name, valueType), is(Arrays.asList(values)));
+    }
+
     protected void assertSortOrderBy( PlanNode sortNode,
                                       String... selectors ) {
         List<SelectorName> expected = new ArrayList<SelectorName>(selectors.length);

Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java	                        (rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,165 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.optimize;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.AbstractQueryTest;
+import org.jboss.dna.graph.query.QueryContext;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.plan.PlanHints;
+import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
+import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
+import org.jboss.dna.graph.query.validate.Schemata;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class ReplaceViewsTest extends AbstractQueryTest {
+
+    private ReplaceViews rule;
+    private QueryContext context;
+    private Schemata schemata;
+    private ImmutableSchemata.Builder builder;
+
+    @Before
+    public void beforeEach() {
+        ExecutionContext execContext = new ExecutionContext();
+        rule = ReplaceViews.INSTANCE;
+        builder = ImmutableSchemata.createBuilder(execContext);
+        builder.addTable("t1", "c11", "c12", "c13");
+        builder.addTable("t2", "c21", "c22", "c23");
+        builder.addView("v1", "SELECT c11, c12 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+        builder.addView("v2", "SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+        schemata = builder.build();
+        context = new QueryContext(execContext, new PlanHints(), schemata);
+    }
+
+    /**
+     * Before:
+     * 
+     * <pre>
+     *          ...
+     *           |
+     *        PROJECT      with the list of columns being SELECTed
+     *           |
+     *        SELECT1
+     *           |         One or more SELECT plan nodes that each have
+     *        SELECT2      a single non-join constraint that are then all AND-ed
+     *           |         together
+     *        SELECT3
+     *           |
+     *        SOURCE       A SOURCE that uses the view
+     * </pre>
+     * 
+     * And after:
+     * 
+     * <pre>
+     *          ...
+     *           |
+     *        PROJECT      with the list of columns being SELECTed
+     *           |
+     *        SELECT1
+     *           |         One or more SELECT plan nodes that each have
+     *        SELECT2      a single non-join constraint that are then all AND-ed
+     *           |         together
+     *        SELECT3
+     *           |
+     *        SOURCE
+     *           |
+     *        PROJECT      with the list of columns in the SELECT of the view
+     *           |
+     *        SELECT1      One or more SELECT plan nodes that each have a single non-join constraint
+     *           |         that are then all AND-ed together (as defined by the view plan
+     *           |
+     *        SOURCE       A SOURCE that the view uses
+     * </pre>
+     */
+    @Test
+    public void shouldReplaceViewWithPlanForViewWithSingleTable() {
+        // Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
+        PlanNode project = new PlanNode(Type.PROJECT, selector("v1"));
+        PlanNode select1 = new PlanNode(Type.SELECT, project, selector("v1"));
+        PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("v1"));
+        PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("v1"));
+        PlanNode source = new PlanNode(Type.SOURCE, select3, selector("v1"));
+        source.setProperty(Property.SOURCE_NAME, selector("v1"));
+
+        // Create the equivalent plan nodes for what should be created ...
+        PlanNode viewProject = new PlanNode(Type.PROJECT, selector("t1"));
+        PlanNode viewSelect = new PlanNode(Type.SELECT, viewProject, selector("t1"));
+        PlanNode viewSource = new PlanNode(Type.SOURCE, viewSelect, selector("t1"));
+        viewProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+        viewSelect.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+                                                                        Operator.LESS_THAN, new Literal(3L)));
+        viewSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+        viewSource.setProperty(Property.SOURCE_COLUMNS, schemata.getTable(selector("t1")).getColumns());
+
+        // Execute the rule ...
+        PlanNode result = rule.execute(context, project, new LinkedList<OptimizerRule>());
+        System.out.println(result);
+        assertThat(result, is(sameInstance(project)));
+        assertChildren(project, select1);
+        assertChildren(select1, select2);
+        assertChildren(select2, select3);
+        assertChildren(select3, source);
+        assertSameChildren(source, viewSelect);
+        assertSameChildren(source.getFirstChild(), viewSource);
+        assertSameChildren(source.getFirstChild().getFirstChild());
+    }
+
+    protected List<Column> columns( Column... columns ) {
+        return Arrays.asList(columns);
+    }
+
+    protected Column column( String table,
+                             String columnName ) {
+        return new Column(new SelectorName(table), name(columnName), columnName);
+    }
+
+    protected Column column( String table,
+                             String columnName,
+                             String alias ) {
+        return new Column(new SelectorName(table), name(columnName), alias);
+    }
+
+    protected Name name( String name ) {
+        return context.getExecutionContext().getValueFactories().getNameFactory().create(name);
+    }
+}


Property changes on: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/ReplaceViewsTest.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/optimize/RuleBasedOptimizerTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -25,18 +25,33 @@
 
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import org.jboss.dna.common.collection.Problems;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.query.AbstractQueryTest;
 import org.jboss.dna.graph.query.QueryContext;
-import org.jboss.dna.graph.query.optimize.OptimizerRule;
-import org.jboss.dna.graph.query.optimize.RuleBasedOptimizer;
+import org.jboss.dna.graph.query.model.Column;
+import org.jboss.dna.graph.query.model.Comparison;
+import org.jboss.dna.graph.query.model.EquiJoinCondition;
+import org.jboss.dna.graph.query.model.JoinType;
+import org.jboss.dna.graph.query.model.Literal;
+import org.jboss.dna.graph.query.model.Operator;
+import org.jboss.dna.graph.query.model.PropertyValue;
+import org.jboss.dna.graph.query.model.QueryCommand;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.parse.SqlQueryParser;
+import org.jboss.dna.graph.query.plan.CanonicalPlanner;
+import org.jboss.dna.graph.query.plan.JoinAlgorithm;
 import org.jboss.dna.graph.query.plan.PlanHints;
 import org.jboss.dna.graph.query.plan.PlanNode;
+import org.jboss.dna.graph.query.plan.PlanNode.Property;
 import org.jboss.dna.graph.query.plan.PlanNode.Type;
+import org.jboss.dna.graph.query.validate.ImmutableSchemata;
 import org.jboss.dna.graph.query.validate.Schemata;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,7 +59,7 @@
 /**
  * 
  */
-public class RuleBasedOptimizerTest {
+public class RuleBasedOptimizerTest extends AbstractQueryTest {
 
     private RuleBasedOptimizer optimizer;
     private List<OptimizerRule> rules;
@@ -54,7 +69,15 @@
 
     @Before
     public void beforeEach() {
-        context = new QueryContext(new ExecutionContext(), new PlanHints(), mock(Schemata.class));
+        ExecutionContext execContext = new ExecutionContext();
+        ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(execContext);
+        builder.addTable("t1", "c11", "c12", "c13");
+        builder.addTable("t2", "c21", "c22", "c23");
+        builder.addView("v1", "SELECT c11, c12 AS c2 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+        builder.addView("v2", "SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+        Schemata schemata = builder.build();
+        context = new QueryContext(execContext, new PlanHints(), schemata);
+
         node = new PlanNode(Type.ACCESS);
 
         ruleExecutionOrder = new ArrayList<Integer>();
@@ -85,6 +108,10 @@
         };
     }
 
+    // ----------------------------------------------------------------------------------------------------------------
+    // Test the rule stack logic
+    // ----------------------------------------------------------------------------------------------------------------
+
     @Test
     public void shouldExecuteEachRuleInSequence() {
         optimizer.optimize(context, node);
@@ -111,4 +138,184 @@
         assertThat(ruleExecutionOrder.get(2), is(2));
         assertThat(ruleExecutionOrder.size(), is(3));
     }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Test the actual rules
+    // ----------------------------------------------------------------------------------------------------------------
+
+    @Test
+    public void shouldOptimizePlanForSimpleQueryWithSelectColumns() {
+        node = optimize("SELECT c11,c12 FROM t1");
+        // Create the expected plan ...
+        PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+        PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+        PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1"));
+        source.setProperty(Property.SOURCE_NAME, selector("t1"));
+        source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(expected), is(true));
+    }
+
+    @Test
+    public void shouldOptimizePlanForSimpleQueryWithSelectStar() {
+        node = optimize("SELECT * FROM t1");
+        // Create the expected plan ...
+        PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+        PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t1", "c13")));
+        PlanNode source = new PlanNode(Type.SOURCE, project, selector("t1"));
+        source.setProperty(Property.SOURCE_NAME, selector("t1"));
+        source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(expected), is(true));
+    }
+
+    @Test
+    public void shouldOptimizePlanForSimpleQueryWithPropertyValueCriteria() {
+        node = optimize("SELECT c11, c12 FROM t1 WHERE c13 < CAST('3' AS LONG)");
+        // Create the expected plan ...
+        PlanNode expected = new PlanNode(Type.ACCESS, selector("t1"));
+        PlanNode project = new PlanNode(Type.PROJECT, expected, selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+        PlanNode select = new PlanNode(Type.SELECT, project, selector("t1"));
+        select.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+                                                                    Operator.LESS_THAN, new Literal(3L)));
+        PlanNode source = new PlanNode(Type.SOURCE, select, selector("t1"));
+        source.setProperty(Property.SOURCE_NAME, selector("t1"));
+        source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(expected), is(true));
+    }
+
+    @Test
+    public void shouldOptimizePlanForEquiJoinQuery() {
+        node = optimize("SELECT t1.c11, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11 = t2.c21");
+
+        // Create the expected plan ...
+        PlanNode project = new PlanNode(Type.PROJECT, selector("t2"), selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12"), column("t2", "c23")));
+        PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"), selector("t1"));
+        join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+        join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+        join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("t1"), name("c11"), selector("t2"), name("c21")));
+
+        PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+        PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1"));
+        leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11"), column("t1", "c12")));
+        PlanNode leftSource = new PlanNode(Type.SOURCE, leftProject, selector("t1"));
+        leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+        leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+        PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("t2"));
+        PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("t2"));
+        rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2", "c23"), column("t2", "c21")));
+        PlanNode rightSource = new PlanNode(Type.SOURCE, rightProject, selector("t2"));
+        rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+        rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns());
+
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(project), is(true));
+    }
+
+    @Test
+    public void shouldOptimizePlanForQueryUsingView() {
+        node = optimize("SELECT v1.c11 AS c1 FROM v1 WHERE v1.c11 = 'x' AND v1.c2 = 'y'");
+
+        // Create the expected plan ...
+        PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
+        PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1")));
+        PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
+        select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c11")),
+                                                                     Operator.EQUAL_TO, new Literal('x')));
+        PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
+        select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c12")),
+                                                                     Operator.EQUAL_TO, new Literal('y')));
+        PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
+        select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c13")),
+                                                                     Operator.LESS_THAN, new Literal(3L)));
+        PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
+        source.setProperty(Property.SOURCE_NAME, selector("t1"));
+        source.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(access), is(true));
+    }
+
+    @Test
+    public void shouldOptimizePlanForQueryUsingViewContainingJoin() {
+        node = optimize("SELECT v2.c11 AS c1 FROM v2 WHERE v2.c11 = 'x' AND v2.c12 = 'y'");
+
+        // Create the expected plan ...
+        PlanNode project = new PlanNode(Type.PROJECT, selector("t1"));
+        project.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11", "c1")));
+        PlanNode join = new PlanNode(Type.JOIN, project, selector("t2"), selector("t1"));
+        join.setProperty(Property.JOIN_ALGORITHM, JoinAlgorithm.NESTED_LOOP);
+        join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
+        join.setProperty(Property.JOIN_CONDITION, new EquiJoinCondition(selector("t1"), name("c11"), selector("t2"), name("c21")));
+
+        PlanNode leftAccess = new PlanNode(Type.ACCESS, join, selector("t1"));
+        PlanNode leftProject = new PlanNode(Type.PROJECT, leftAccess, selector("t1"));
+        leftProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t1", "c11")));
+        PlanNode leftSelect1 = new PlanNode(Type.SELECT, leftProject, selector("t1"));
+        leftSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c11")),
+                                                                         Operator.EQUAL_TO, new Literal('x')));
+        PlanNode leftSelect2 = new PlanNode(Type.SELECT, leftSelect1, selector("t1"));
+        leftSelect2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), name("c12")),
+                                                                         Operator.EQUAL_TO, new Literal('y')));
+        PlanNode leftSource = new PlanNode(Type.SOURCE, leftSelect2, selector("t1"));
+        leftSource.setProperty(Property.SOURCE_NAME, selector("t1"));
+        leftSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t1")).getColumns());
+
+        PlanNode rightAccess = new PlanNode(Type.ACCESS, join, selector("t2"));
+        PlanNode rightProject = new PlanNode(Type.PROJECT, rightAccess, selector("t2"));
+        rightProject.setProperty(Property.PROJECT_COLUMNS, columns(column("t2", "c21")));
+        PlanNode rightSelect1 = new PlanNode(Type.SELECT, rightProject, selector("t2"));
+        rightSelect1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t2"), name("c21")),
+                                                                          Operator.EQUAL_TO, new Literal('x')));
+        PlanNode rightSource = new PlanNode(Type.SOURCE, rightSelect1, selector("t2"));
+        rightSource.setProperty(Property.SOURCE_NAME, selector("t2"));
+        rightSource.setProperty(Property.SOURCE_COLUMNS, context.getSchemata().getTable(selector("t2")).getColumns());
+
+        // Compare the expected and actual plan ...
+        assertThat(node.isSameAs(project), is(true));
+    }
+
+    // ----------------------------------------------------------------------------------------------------------------
+    // Utility methods ...
+    // ----------------------------------------------------------------------------------------------------------------
+
+    protected List<Column> columns( Column... columns ) {
+        return Arrays.asList(columns);
+    }
+
+    protected Column column( String table,
+                             String columnName ) {
+        return new Column(new SelectorName(table), name(columnName), columnName);
+    }
+
+    protected Column column( String table,
+                             String columnName,
+                             String alias ) {
+        return new Column(new SelectorName(table), name(columnName), alias);
+    }
+
+    protected Name name( String name ) {
+        return context.getExecutionContext().getValueFactories().getNameFactory().create(name);
+    }
+
+    protected PlanNode optimize( String sql ) {
+        QueryCommand query = new SqlQueryParser().parseQuery(sql, context.getExecutionContext());
+        Problems problems = context.getProblems();
+        assertThat("Problems parsing query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+        PlanNode plan = new CanonicalPlanner().createPlan(context, query);
+        assertThat("Problems planning query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+        PlanNode optimized = new RuleBasedOptimizer().optimize(context, plan);
+        assertThat("Problems optimizing query: " + sql + "\n" + problems, problems.hasErrors(), is(false));
+        System.out.println();
+        System.out.println(sql);
+        System.out.println(optimized);
+        return optimized;
+    }
 }

Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/plan/CanonicalPlannerTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -35,9 +35,6 @@
 import org.jboss.dna.graph.query.model.Column;
 import org.jboss.dna.graph.query.model.QueryCommand;
 import org.jboss.dna.graph.query.model.SelectorName;
-import org.jboss.dna.graph.query.plan.CanonicalPlanner;
-import org.jboss.dna.graph.query.plan.PlanHints;
-import org.jboss.dna.graph.query.plan.PlanNode;
 import org.jboss.dna.graph.query.plan.PlanNode.Property;
 import org.jboss.dna.graph.query.plan.PlanNode.Type;
 import org.jboss.dna.graph.query.validate.ImmutableSchemata;
@@ -68,7 +65,7 @@
         hints = new PlanHints();
         builder = new QueryBuilder(context);
         problems = new SimpleProblems();
-        schemataBuilder = ImmutableSchemata.createBuilder();
+        schemataBuilder = ImmutableSchemata.createBuilder(context);
     }
 
     protected SelectorName selector( String name ) {

Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java	2009-10-15 19:55:58 UTC (rev 1293)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/process/AbstractQueryResultsTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -69,7 +69,7 @@
 
     protected Schemata schemataFor( Columns columns,
                                     PropertyType... types ) {
-        ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder();
+        ImmutableSchemata.Builder builder = ImmutableSchemata.createBuilder(executionContext);
         for (String selectorName : columns.getSelectorNames()) {
             final SelectorName selector = selector(selectorName);
             int i = 0;

Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java	                        (rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java	2009-10-15 19:57:20 UTC (rev 1294)
@@ -0,0 +1,179 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.graph.query.validate;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.PropertyType;
+import org.jboss.dna.graph.query.model.SelectorName;
+import org.jboss.dna.graph.query.validate.Schemata.Table;
+import org.jboss.dna.graph.query.validate.Schemata.View;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * 
+ */
+public class ImmutableSchemataBuilderTest {
+
+    private ExecutionContext context;
+    private ImmutableSchemata.Builder builder;
+    private Schemata schemata;
+
+    @Before
+    public void beforeEach() {
+        context = new ExecutionContext();
+        builder = ImmutableSchemata.createBuilder(context);
+    }
+
+    @After
+    public void afterEach() {
+        context = null;
+        builder = null;
+        schemata = null;
+    }
+
+    @Test
+    public void shouldBuildSchemaForSingleTableWithDefaultTypeForColumns() {
+        builder.addTable("t1", "c1", "c2", "c3");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    @Test
+    public void shouldBuildSchemaForMultipleTablesWithDefaultTypeForColumns() {
+        builder.addTable("t1", "c11", "c12", "c13");
+        builder.addTable("t2", "c21", "c22", "c23");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("t2"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    @Test
+    public void shouldBuildSchemaContainingViewOfSingleTable() {
+        builder.addTable("t1", "c1", "c2", "c3");
+        builder.addView("t2", "SELECT * FROM t1 WHERE c1=3");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("t2"));
+        assertThat(table, is(instanceOf(View.class)));
+        assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    @Test
+    public void shouldBuildSchemaContainingViewUsingAliasesOfColumns() {
+        builder.addTable("t1", "c1", "c2", "c3");
+        builder.addView("t2", "SELECT c1 as v1, c2 FROM t1 WHERE c1=3");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c3").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("t2"));
+        assertThat(table, is(instanceOf(View.class)));
+        assertThat(table.getColumn("v1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c2").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    @Test
+    public void shouldBuildSchemaContainingViewOfMultiTableJoin() {
+        builder.addTable("t1", "c11", "c12", "c13");
+        builder.addTable("t2", "c21", "c22", "c23");
+        builder.addView("v1", "SELECT t1.c11 as x1, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11=t2.c21 WHERE t1.c11=3");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("t2"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+        schemata = builder.build();
+        table = schemata.getTable(selector("v1"));
+        assertThat(table, is(instanceOf(View.class)));
+        assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    @Test
+    public void shouldBuildSchemaContainingViewUsingView() {
+        builder.addTable("t1", "c11", "c12", "c13");
+        builder.addTable("t2", "c21", "c22", "c23");
+        builder.addView("v2", "SELECT x1, c12 FROM v1 WHERE x1=3");
+        builder.addView("v1", "SELECT t1.c11 as x1, t1.c12, t2.c23 FROM t1 JOIN t2 ON t1.c11=t2.c21 WHERE t1.c11=3");
+        schemata = builder.build();
+        Table table = schemata.getTable(selector("t1"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c11").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c13").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("t2"));
+        assertThat(table, is(notNullValue()));
+        assertThat(table.getColumn("c21").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c22").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+        schemata = builder.build();
+        table = schemata.getTable(selector("v1"));
+        assertThat(table, is(instanceOf(View.class)));
+        assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c23").getPropertyType(), is(PropertyType.STRING));
+        table = schemata.getTable(selector("v2"));
+        assertThat(table, is(instanceOf(View.class)));
+        assertThat(table.getColumn("x1").getPropertyType(), is(PropertyType.STRING));
+        assertThat(table.getColumn("c12").getPropertyType(), is(PropertyType.STRING));
+    }
+
+    protected SelectorName selector( String name ) {
+        return new SelectorName(name);
+    }
+
+}


Property changes on: trunk/dna-graph/src/test/java/org/jboss/dna/graph/query/validate/ImmutableSchemataBuilderTest.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF



More information about the dna-commits mailing list