[280892] ODA Dynamic Query Result Specification Enhancements - refine query spec
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/Messages.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/Messages.java
index 16c438d..693992a 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/Messages.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/Messages.java
@@ -62,11 +62,15 @@
     public static String querySpec_CUSTOM_FILTER_LESS_THAN_MIN_ARGS;
     public static String querySpec_EXTENSION_ID_NOT_FOUND;
     public static String querySpec_INVALID_AGGR_EXPR;
+    public static String querySpec_INVALID_AGGR_HIDE_COLUMN;
     public static String querySpec_INVALID_ARG;
     public static String querySpec_INVALID_CLASS_TYPE_ATTRIBUTE;
     public static String querySpec_INVALID_EXT_POINT_ATTR_VALUE;
     public static String querySpec_INVALID_EXT_POINT_ELEMENT;
     public static String querySpec_INVALID_FILTER_EXPR;
+    public static String querySpec_INVALID_RESULT_PROJ;
+    public static String querySpec_INVALID_SORT_KEY;
+    public static String querySpec_INVALID_SORT_SPEC;
     public static String querySpec_MAX_ONE_NEGATING_EXPR;
     public static String querySpec_MISSING_COMPOSITE_MIN_CHILDREN;
     public static String querySpec_MISSING_EXPR_VARIABLE;
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/messages.properties b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/messages.properties
index 48807f9..f89998c 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/messages.properties
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/nls/messages.properties
@@ -62,11 +62,15 @@
 querySpec_CUSTOM_FILTER_LESS_THAN_MIN_ARGS=The custom filter expression ({0}) has {1} arguments, but requires a minimum of {2} arguments.
 querySpec_EXTENSION_ID_NOT_FOUND=No {0} extension found with the id ({1}).
 querySpec_INVALID_AGGR_EXPR=Invalid aggregate expression.
+querySpec_INVALID_AGGR_HIDE_COLUMN=Invalid aggregate projection on a hidden column {0}.
 querySpec_INVALID_ARG=Invalid argument: {0}.
 querySpec_INVALID_CLASS_TYPE_ATTRIBUTE=Invalid class type ({0}).  The {1} attribute must be an instance of {2}.
 querySpec_INVALID_EXT_POINT_ATTR_VALUE=The {0} extension ({1}) has an invalid value ({2}) for the {3} attribute.
 querySpec_INVALID_EXT_POINT_ELEMENT=The {0} extension ({1}) is invalid.  See the schema definition for required content.
 querySpec_INVALID_FILTER_EXPR=Invalid filter expression.
+querySpec_INVALID_RESULT_PROJ=Invalid result projection.
+querySpec_INVALID_SORT_KEY=Invalid sort key.
+querySpec_INVALID_SORT_SPEC=Invalid sort specification.
 querySpec_MAX_ONE_NEGATING_EXPR=Cannot add more than one negating expression to a {0}.
 querySpec_MISSING_COMPOSITE_MIN_CHILDREN=The composite {0} must contain at least {1} child expressions.
 querySpec_MISSING_EXPR_VARIABLE=The filter expression ({0}) is missing an associated variable.
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/IValidator.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/IValidator.java
index f1ede59..1d31ae4 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/IValidator.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/IValidator.java
@@ -17,6 +17,7 @@
 import org.eclipse.datatools.connectivity.oda.OdaException;
 import org.eclipse.datatools.connectivity.oda.spec.result.AggregateExpression;
 import org.eclipse.datatools.connectivity.oda.spec.result.FilterExpression;
+import org.eclipse.datatools.connectivity.oda.spec.result.SortSpecification;
 
 /**
  * <strong>EXPERIMENTAL</strong>.
@@ -85,5 +86,15 @@
      */
     public void validateSyntax( AggregateExpression aggrExpr, ValidationContext context )
         throws OdaException;
+    
+    /**
+     * Validates the specified sort specification in the specified context.
+     * @param sortSpec  a {@link SortSpecification} to validate
+     * @param context      context for validation; may be null which would limit the scope of validation
+     * @throws OdaException if validation failed. The cause is defined 
+     *          by the class implementing this method.
+     */
+    public void validate( SortSpecification sortSpec, ValidationContext context ) 
+        throws OdaException;
 
 }
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/AggregateExpression.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/AggregateExpression.java
index 748d921..9e4b917 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/AggregateExpression.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/AggregateExpression.java
@@ -108,34 +108,12 @@
     /**
      * Gets the alias of this aggregate expression instance.  The alias may be used to reference
      * an instance.
-     * @return  the alias, or the combined aliases of its input source variables if no alias is specified
-     * @return 
+     * @return  the alias; may be null
      */
     public String getAlias()
     {
-        if( m_alias == null )
-        {
-            StringBuffer alias = new StringBuffer( getQualifiedId() );
-            alias.append( ALIAS_SEPARATOR );
-            alias.append( getVariableAliases() );
-            m_alias = alias.toString();
-        }
         return m_alias;
     }
-
-    protected String getVariableAliases()
-    {
-        // iterate thru each input variable and append its alias
-        StringBuffer combinedAlias = new StringBuffer();
-        Iterator<ExpressionVariable> iter = getVariables().iterator();
-        while( iter.hasNext() )
-        {
-            if( combinedAlias.length() > 0 )
-                combinedAlias.append( ALIAS_SEPARATOR );
-            combinedAlias.append( iter.next().getAlias() );
-        }
-        return combinedAlias.toString();
-    }
     
     /**
      * Specifies the alias.
@@ -221,5 +199,31 @@
      */
     public abstract void validateSyntax( ValidationContext context ) 
         throws OdaException;
-    
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString()
+    {
+        StringBuffer buffer = new StringBuffer( getQualifiedId() );
+        buffer.append( ALIAS_SEPARATOR );
+        buffer.append( getVariableAliases() );
+        return buffer.toString();
+    }
+
+    protected String getVariableAliases()
+    {
+        // iterate thru each input variable and append its alias
+        StringBuffer combinedAlias = new StringBuffer();
+        Iterator<ExpressionVariable> iter = getVariables().iterator();
+        while( iter.hasNext() )
+        {
+            if( combinedAlias.length() > 0 )
+                combinedAlias.append( ALIAS_SEPARATOR );
+            combinedAlias.append( iter.next().getAlias() );
+        }
+        return combinedAlias.toString();
+    }
+   
 }
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/ResultProjection.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/ResultProjection.java
index 1a98ab2..1c49c7d 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/ResultProjection.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/ResultProjection.java
@@ -52,7 +52,8 @@
      * Each set is grouped by the unique values of all the other result set column(s) 
      * that do not have an aggregate expression projected.
      * <br>A projected tabular result set returns one row for each group. 
-     * @param resultColumn   the projected column identifier in the result set
+     * @param resultColumn   the column identifier in the projected result set targeted for the 
+     *          output of the specified aggregate expression
      * @param aggregate an {@link AggregateExpression} whose output value is projected on the 
      *                  specified result set column
      * @throws OdaException
@@ -62,7 +63,7 @@
         validateColumnIdentifier( resultColumn );
         if( getHiddenResultColumns().contains( resultColumn ) )
             throw ValidatorUtil.newAggregateException( 
-                    Messages.bind( "Cannot project aggregation on a hidden column {0}.", resultColumn ), 
+                    Messages.bind( Messages.querySpec_INVALID_AGGR_HIDE_COLUMN, resultColumn ), 
                     aggregate );
         
         getAggregatedColumns().put( resultColumn, aggregate );
@@ -108,26 +109,6 @@
     }
     
     /**
-     * Specifies the aggregate projection on a new result set column.
-     * The appended result column can be referenced in the result set by the specified aggregate's alias.
-     * @param aggregate an {@link AggregateExpression} whose output value is projected on the 
-     *                  new result set column
-     * @throws OdaException
-     * @see #setProjection(ColumnIdentifier, AggregateExpression)
-     */
-    public void addResultColumn( AggregateExpression aggregate ) throws OdaException
-    {
-        // add a new column with the aggregate's first input source variable, if available
-        ColumnIdentifier newColumn = new ColumnIdentifier( aggregate.getAlias() );
-        ExpressionVariable columnExprVariable = aggregate.getVariables().isEmpty() ? 
-                                                null : aggregate.getVariables().get( 0 );
-        getAddedResultColumns().put( newColumn, columnExprVariable );
-        
-        // project the aggregate expression onto the new column
-        setProjection( newColumn, aggregate );
-    }
-    
-    /**
      * Returns a map of result columns that are to be dynamically added to the query result set.
      * Each result column in the map is associated with an input variable 
      * that resolves to the column value.
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/SortSpecification.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/SortSpecification.java
index b84fbcd..0c6ff02 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/SortSpecification.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/result/SortSpecification.java
@@ -18,7 +18,9 @@
 import java.util.List;
 
 import org.eclipse.datatools.connectivity.oda.IDataSetMetaData;
+import org.eclipse.datatools.connectivity.oda.OdaException;
 import org.eclipse.datatools.connectivity.oda.nls.Messages;
+import org.eclipse.datatools.connectivity.oda.spec.ValidationContext;
 
 /**
  * <strong>EXPERIMENTAL</strong>.
@@ -231,7 +233,7 @@
 	/**
 	 * Returns the result set column identifier of the sort key 
      * at the specified position.
-	 * @param pos       position of the sort key (1-based).
+	 * @param pos       sequence position of the sort key (1-based).
      * @return          the name of the result set column for the specified sort key.
      * @throws          IndexOutOfBoundsException if <code>pos</code> is out of range 
      *                  (pos < 1 || pos > getSortKeyCount()).
@@ -244,7 +246,7 @@
 	
 	/**
 	 * Returns the sort direction of the sort key at the specified position.
-	 * @param pos		position of the sort key (1-based)
+	 * @param pos		sequence position of the sort key (1-based)
 	 * @return			constant value of the sort direction for the specified sort key
 	 * @throws 			IndexOutOfBoundsException if <code>pos</code> is out of range 
 	 * 					(pos < 1 || pos > getSortKeyCount()).
@@ -256,7 +258,7 @@
 
 	/**
      * Returns the null ordering of the sort key at the specified position.
-     * @param pos       position of the sort key (1-based)
+     * @param pos       sequence position of the sort key (1-based)
      * @return          constant value of the null ordering type for the specified sort key
      * @throws          IndexOutOfBoundsException if <code>pos</code> is out of range 
      *                  (pos < 1 || pos > getSortKeyCount()).
@@ -284,21 +286,13 @@
 	}
 	
 	/**
-	 * Returns an array of all column identifiers for the sort keys of a 
-	 * <code>sortModeSingleOrder</code> <code>SortSpecification</code> object.
-	 * @return	an array of all column identifiers for the sort keys of a 
-	 * 			<code>sortModeSingleOrder</code> <code>SortSpecification</code> 
-	 * 			object; an empty array if no sort keys are associated 
-	 * 			with this <code>SortSpecification</code>.
-	 * @throws IllegalStateException	if this <code>SortSpecification</code>'s sort 
-	 * 									mode is not <code>sortModeSingleOrder</code>.
+	 * Returns an array of all column identifiers for the sort keys.
+	 * @return	an array of all column identifiers for the sort keys;
+	 *          may be an empty array if no sort keys are associated 
+	 * 			with this <code>SortSpecification</code>
 	 */
 	public ColumnIdentifier[] getSortColumns()
 	{
-		if( getSortMode() != IDataSetMetaData.sortModeSingleOrder )
-			throw new IllegalStateException( 
-					Messages.sortSpec_ONLY_IN_SINGLE_ORDER_MODE );
-		
 		int size = getSortKeyCountImpl();
 		ColumnIdentifier[] sortColumns = new ColumnIdentifier[ size ];
 		
@@ -407,6 +401,20 @@
 		}
 	}
 
+    /**
+     * Validates this expression in the specified context. 
+     * @param context   context for validation; may be null which would limit the scope of validation
+     * @throws OdaException if validation failed. The concrete reason is 
+     *          defined by the subclass implementing this method.
+     */
+    public void validate( ValidationContext context ) 
+        throws OdaException
+    {
+        // pass this to custom validator, if exists, for overall validation
+        if( context != null && context.getValidator() != null )
+            context.getValidator().validate( this, context );
+    }
+
 	/*
 	 * A simple private helper class that stores the state of 
 	 * each sort key.
diff --git a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/util/ValidatorUtil.java b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/util/ValidatorUtil.java
index 2a999ab..af5afff 100644
--- a/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/util/ValidatorUtil.java
+++ b/plugins/org.eclipse.datatools.connectivity.oda/src/org/eclipse/datatools/connectivity/oda/spec/util/ValidatorUtil.java
@@ -25,6 +25,8 @@
 import org.eclipse.datatools.connectivity.oda.spec.result.AggregateExpression;
 import org.eclipse.datatools.connectivity.oda.spec.result.CustomAggregate;
 import org.eclipse.datatools.connectivity.oda.spec.result.FilterExpression;
+import org.eclipse.datatools.connectivity.oda.spec.result.ResultProjection;
+import org.eclipse.datatools.connectivity.oda.spec.result.SortSpecification;
 import org.eclipse.datatools.connectivity.oda.spec.result.filter.AtomicExpression;
 import org.eclipse.datatools.connectivity.oda.spec.result.filter.CompositeExpression;
 import org.eclipse.datatools.connectivity.oda.spec.result.filter.CustomExpression;
@@ -368,7 +370,7 @@
     }
     
     /**
-     * Indicates whether the specified FilterExpression is identified as one of the cause(s)
+     * Indicates whether the specified FilterExpression is one of the cause(s)
      * in the specified OdaException chain.
      * @param filterExpr    a filter expression whose processing might have caused
      *          an OdaException
@@ -400,7 +402,7 @@
     /**
      * Creates and returns a top-level OdaException to indicate that the 
      * specified aggregate expression is the cause of the specified driverEx exception.
-     * @param invalidAggrExpr a top-level aggregate expression that is invalid
+     * @param invalidAggrExpr the invalid AggregateExpression to set as the cause
      * @param driverEx  optional detail OdaException thrown by an ODA driver that has detected 
      *              the invalid state; may be null
      * @return  an OdaException chain with the specified invalid aggregate expression
@@ -436,7 +438,7 @@
     }
     
     /**
-     * Indicates whether the specified aggregate expression is identified as one of the cause(s)
+     * Indicates whether the specified aggregate expression is one of the cause(s)
      * in the specified OdaException chain.
      * @param aggrExpr    an aggregate expression whose processing might have caused an OdaException
      * @param rootEx    the root of an OdaException chain caught while processing 
@@ -463,6 +465,208 @@
 
         return false;
     }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified result projection is the cause of the specified driverEx exception.
+     * @param resultProj  the invalid ResultProjection to set as the cause
+     * @param driverEx    optional detail OdaException thrown by an ODA driver that has detected 
+     *              the invalid state; may be null
+     * @return  an OdaException chain with the specified invalid result projection
+     *          identified as the cause
+     * @see {@link #isInvalidResultProjection(ResultProjection, OdaException)}
+     */
+    public static OdaException newResultProjectionException( ResultProjection resultProj, OdaException driverEx )
+    {
+        return newResultProjectionException( Messages.querySpec_INVALID_RESULT_PROJ, resultProj, driverEx );
+    }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified result projection is the cause of the specified driverEx exception.
+     * @param message   custom exception message
+     * @param resultProj  the invalid ResultProjection to set as the cause
+     * @return  an OdaException with the specified message and invalid result projection
+     *          identified as the cause
+     * @see {@link #isInvalidResultProjection(ResultProjection, OdaException)}
+     */
+    public static OdaException newResultProjectionException( String message, ResultProjection resultProj )
+    {
+        return newResultProjectionException( message, resultProj, null );
+    }
+    
+    private static OdaException newResultProjectionException( String message, ResultProjection resultProj, 
+            OdaException chainedEx )
+    {
+        // if this result projection is already identified as a cause in the caught exception,
+        // proceed to use it as is; otherwise, add this expr as the root cause 
+        if( chainedEx != null && isInvalidResultProjection( resultProj, chainedEx ) )
+            return chainedEx;
+        return newOdaException( message, getInstanceId( resultProj ), chainedEx );
+    }
+    
+    /**
+     * Indicates whether the specified result projection is one of the cause(s)
+     * in the specified OdaException chain.
+     * @param resultProj  a result projection whose processing might have caused an OdaException
+     * @param rootEx    the root of an OdaException chain caught while processing the result projection
+     * @return  true if the specified result projection is one of the cause(s) in the OdaException chain;
+     *          false otherwise
+     */
+    public static boolean isInvalidResultProjection( ResultProjection resultProj, OdaException rootEx )
+    {
+        if( resultProj == null )
+            return true;
+
+        String resultProjId = getInstanceId( resultProj );
+        OdaException currentEx = rootEx;
+        while( currentEx != null )
+        {
+            Throwable cause = currentEx.getCause();
+            if( cause instanceof IllegalArgumentException && 
+                    resultProjId.equals( cause.getMessage() ) )
+                return true;
+            
+            currentEx = currentEx.getNextException();
+        }
+
+        return false;
+    }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified sort key is the cause of the specified driverEx exception.
+     * @param sortKeySequenceOrder the sequence ordering position of a sort key that is invalid
+     * @param driverEx  optional detail OdaException thrown by an ODA driver that has detected 
+     *              the invalid state; may be null
+     * @return  an OdaException chain with the specified invalid sort key
+     *          identified as the cause
+     * @see {@link #isInvalidSortKey(int, OdaException)}
+     */
+    public static OdaException newSortKeyException( int sortKeySequenceOrder, OdaException driverEx )
+    {
+        return newSortKeyException( Messages.querySpec_INVALID_SORT_KEY, sortKeySequenceOrder, driverEx );
+    }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified sort key is the cause of the specified driverEx exception.
+     * @param message   custom exception message
+     * @param sortKeySequenceOrder the sequence ordering position of a sort key that is invalid
+     * @return  an OdaException with the specified message and invalid sort key
+     *          identified as the cause
+     * @see {@link #isInvalidSortKey(int, OdaException)}
+     */
+    public static OdaException newSortKeyException( String message, int sortKeySequenceOrder )
+    {
+        return newSortKeyException( message, sortKeySequenceOrder, null );
+    }
+    
+    private static OdaException newSortKeyException( String message, int sortKeySequenceOrder, 
+            OdaException chainedEx )
+    {
+        // if this sort key is already identified as a cause in the caught exception,
+        // proceed to use it as is; otherwise, add this expr as the root cause 
+        if( chainedEx != null && isInvalidSortKey( sortKeySequenceOrder, chainedEx ) )
+            return chainedEx;
+        return newOdaException( message, String.valueOf( sortKeySequenceOrder ), chainedEx );
+    }
+    
+    /**
+     * Indicates whether the specified sort key is one of the cause(s)
+     * in the specified OdaException chain.
+     * @param sortKeySequenceOrder  the sequence ordering position of a sort key
+     *                  whose processing might have caused an OdaException
+     * @param rootEx    the root of an OdaException chain caught while processing the sort key
+     * @return  true if the specified sort key is one of the cause(s) in the OdaException chain;
+     *          false otherwise
+     */
+    public static boolean isInvalidSortKey( int sortKeySequenceOrder, OdaException rootEx )
+    {
+        if( sortKeySequenceOrder == 0 )
+            return true;
+
+        String aggrExprId = String.valueOf( sortKeySequenceOrder );
+        OdaException currentEx = rootEx;
+        while( currentEx != null )
+        {
+            Throwable cause = currentEx.getCause();
+            if( cause instanceof IllegalArgumentException && 
+                    aggrExprId.equals( cause.getMessage() ) )
+                return true;
+            
+            currentEx = currentEx.getNextException();
+        }
+
+        return false;
+    }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified sort specification is the cause of the specified driverEx exception.
+     * @param sortSpec  the invalid SortSpecification to set as the cause
+     * @param driverEx    optional detail OdaException thrown by an ODA driver that has detected 
+     *              the invalid state; may be null
+     * @return  an OdaException chain with the specified invalid sort key
+     *          identified as the cause
+     * @see {@link #isInvalidSortSpec(SortSpecification, OdaException)}
+     */
+    public static OdaException newSortSpecException( SortSpecification sortSpec, OdaException driverEx )
+    {
+        return newSortSpecException( Messages.querySpec_INVALID_SORT_SPEC, sortSpec, driverEx );
+    }
+    
+    /**
+     * Creates and returns a top-level OdaException to indicate that the 
+     * specified sort specification is the cause of the specified driverEx exception.
+     * @param message   custom exception message
+     * @param sortSpec  the invalid SortSpecification to set as the cause
+     * @return  an OdaException with the specified message and invalid sort key
+     *          identified as the cause
+     * @see {@link #isInvalidSortSpec(SortSpecification, OdaException)}
+     */
+    public static OdaException newSortSpecException( String message, SortSpecification sortSpec )
+    {
+        return newSortSpecException( message, sortSpec, null );
+    }
+    
+    private static OdaException newSortSpecException( String message, SortSpecification sortSpec, 
+            OdaException chainedEx )
+    {
+        // if this sort spec is already identified as a cause in the caught exception,
+        // proceed to use it as is; otherwise, add this expr as the root cause 
+        if( chainedEx != null && isInvalidSortSpec( sortSpec, chainedEx ) )
+            return chainedEx;
+        return newOdaException( message, getInstanceId( sortSpec ), chainedEx );
+    }
+    
+    /**
+     * Indicates whether the specified sort specification is one of the cause(s)
+     * in the specified OdaException chain.
+     * @param sortSpec  a sort specification whose processing might have caused an OdaException
+     * @param rootEx    the root of an OdaException chain caught while processing the sort specification
+     * @return  true if the specified sort specification is one of the cause(s) in the OdaException chain;
+     *          false otherwise
+     */
+    public static boolean isInvalidSortSpec( SortSpecification sortSpec, OdaException rootEx )
+    {
+        if( sortSpec == null )
+            return true;
+
+        String sortSpecId = getInstanceId( sortSpec );
+        OdaException currentEx = rootEx;
+        while( currentEx != null )
+        {
+            Throwable cause = currentEx.getCause();
+            if( cause instanceof IllegalArgumentException && 
+                    sortSpecId.equals( cause.getMessage() ) )
+                return true;
+            
+            currentEx = currentEx.getNextException();
+        }
+
+        return false;
+    }
 
     private static String getInstanceId( FilterExpression filterExpr )
     {
@@ -475,7 +679,21 @@
     {
         if( aggrExpr == null )
             return null;
-        return aggrExpr.getAlias() + AT_SYMBOL + Integer.toHexString( aggrExpr.hashCode() );
+        return aggrExpr.getName() + AT_SYMBOL + Integer.toHexString( aggrExpr.hashCode() );
+    }
+    
+    private static String getInstanceId( ResultProjection resultProj )
+    {
+        if( resultProj == null )
+            return null;
+        return resultProj.getClass().getSimpleName() + AT_SYMBOL + Integer.toHexString( resultProj.hashCode() );
+    }
+    
+    private static String getInstanceId( SortSpecification sortSpec )
+    {
+        if( sortSpec == null )
+            return null;
+        return sortSpec + AT_SYMBOL + Integer.toHexString( sortSpec.hashCode() );
     }
     
 }
diff --git a/releng/org.eclipse.datatools.connectivity.releng/maps/connectivity-plugins.map b/releng/org.eclipse.datatools.connectivity.releng/maps/connectivity-plugins.map
index 2b23bd0..92a52be 100644
--- a/releng/org.eclipse.datatools.connectivity.releng/maps/connectivity-plugins.map
+++ b/releng/org.eclipse.datatools.connectivity.releng/maps/connectivity-plugins.map
@@ -5,7 +5,7 @@
 plugin@org.eclipse.datatools.connectivity.sqm.core=v200906020553,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.sqm.core

 plugin@org.eclipse.datatools.connectivity.sqm.core.ui=v200906020553,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.sqm.core.ui

 plugin@org.eclipse.datatools.connectivity.sqm.server.ui=v200906020553,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.sqm.server.ui

-plugin@org.eclipse.datatools.connectivity.oda=v200907251248,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.oda

+plugin@org.eclipse.datatools.connectivity.oda=v200907311357,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.oda

 plugin@org.eclipse.datatools.connectivity.oda.template.ui=v200906020553,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.oda.template.ui

 plugin@org.eclipse.datatools.connectivity.oda.profile=v200906020553,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.oda.profile

 plugin@org.eclipse.datatools.connectivity.oda.flatfile=v200907061455,:pserver:anonymous@dev.eclipse.org:/cvsroot/datatools,,org.eclipse.datatools.connectivity/plugins/org.eclipse.datatools.connectivity.oda.flatfile

diff --git a/tests/org.eclipse.datatools.connectivity.oda.consumer.testdriver/src/org/eclipse/datatools/connectivity/oda/consumer/testdriver/spec/impl/ExpressionTester.java b/tests/org.eclipse.datatools.connectivity.oda.consumer.testdriver/src/org/eclipse/datatools/connectivity/oda/consumer/testdriver/spec/impl/ExpressionTester.java
index f466fea..b648992 100644
--- a/tests/org.eclipse.datatools.connectivity.oda.consumer.testdriver/src/org/eclipse/datatools/connectivity/oda/consumer/testdriver/spec/impl/ExpressionTester.java
+++ b/tests/org.eclipse.datatools.connectivity.oda.consumer.testdriver/src/org/eclipse/datatools/connectivity/oda/consumer/testdriver/spec/impl/ExpressionTester.java
@@ -101,7 +101,6 @@
         // TODO 
         // validate a custom expression is supported 
         // vaildate ExpressionVariable is of type RESULT_COLUMN
-                
     }
 
     /* (non-Javadoc)
@@ -111,7 +110,6 @@
             throws OdaException
     {
         // TODO Auto-generated method stub
-        
     }
 
     /* (non-Javadoc)
@@ -121,7 +119,15 @@
             ValidationContext context ) throws OdaException
     {
         // TODO Auto-generated method stub
-        
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.datatools.connectivity.oda.spec.IValidator#validate(org.eclipse.datatools.connectivity.oda.spec.result.SortSpecification, org.eclipse.datatools.connectivity.oda.spec.ValidationContext)
+     */
+    public void validate( SortSpecification sortSpec, ValidationContext context )
+            throws OdaException
+    {
+        // TODO Auto-generated method stub  
     }
 
     /* (non-Javadoc)