You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ma...@apache.org on 2014/11/01 19:45:26 UTC

git commit: PHOENIX-1399 Support cross joins and comma-separated table list

Repository: phoenix
Updated Branches:
  refs/heads/master 4de425131 -> bfc92b045


PHOENIX-1399 Support cross joins and comma-separated table list


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/bfc92b04
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/bfc92b04
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/bfc92b04

Branch: refs/heads/master
Commit: bfc92b045fb27b6b3cdd4ffd9b8d523afd0e88dd
Parents: 4de4251
Author: maryannxue <ma...@apache.org>
Authored: Sat Nov 1 14:44:26 2014 -0400
Committer: maryannxue <ma...@apache.org>
Committed: Sat Nov 1 14:44:26 2014 -0400

----------------------------------------------------------------------
 .../org/apache/phoenix/end2end/HashJoinIT.java  |  70 +++++-
 phoenix-core/src/main/antlr3/PhoenixSQL.g       |  30 +--
 .../apache/phoenix/compile/DeleteCompiler.java  |   2 +-
 .../apache/phoenix/compile/FromCompiler.java    |  10 +-
 .../apache/phoenix/compile/JoinCompiler.java    | 231 ++++++++++---------
 .../apache/phoenix/compile/QueryCompiler.java   |   4 +-
 .../phoenix/compile/StatementNormalizer.java    |  12 +-
 .../phoenix/compile/SubqueryRewriter.java       |   6 +-
 .../phoenix/compile/SubselectRewriter.java      |   6 +-
 .../apache/phoenix/compile/UpsertCompiler.java  |   2 +-
 .../phoenix/exception/SQLExceptionCode.java     |   1 +
 .../apache/phoenix/jdbc/PhoenixStatement.java   |   4 +-
 .../apache/phoenix/optimize/QueryOptimizer.java |   4 +-
 .../org/apache/phoenix/parse/JoinPartNode.java  |  53 -----
 .../apache/phoenix/parse/ParseNodeFactory.java  |  21 +-
 .../apache/phoenix/parse/ParseNodeRewriter.java |  25 +-
 .../apache/phoenix/parse/SelectStatement.java   |  18 +-
 17 files changed, 239 insertions(+), 260 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
index 3850ac9..5190c18 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
@@ -41,7 +41,6 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
 import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
@@ -1810,13 +1809,22 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
     
     @Test
     public void testStarJoin() throws Exception {
-        String[] query = new String[2];
+        String[] query = new String[5];
         query[0] = "SELECT \"order_id\", c.name, i.name iname, quantity, o.date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o JOIN " 
             + JOIN_CUSTOMER_TABLE_FULL_NAME + " c ON o.\"customer_id\" = c.\"customer_id\" JOIN " 
             + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" ORDER BY \"order_id\"";
-        query[1] = "SELECT /*+ NO_STAR_JOIN*/ \"order_id\", c.name, i.name iname, quantity, o.date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o JOIN " 
+        query[1] = "SELECT \"order_id\", c.name, i.name iname, quantity, o.date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o, " 
+                + JOIN_CUSTOMER_TABLE_FULL_NAME + " c, " 
+                + JOIN_ITEM_TABLE_FULL_NAME + " i WHERE o.\"item_id\" = i.\"item_id\" AND o.\"customer_id\" = c.\"customer_id\" ORDER BY \"order_id\"";
+        query[2] = "SELECT /*+ NO_STAR_JOIN*/ \"order_id\", c.name, i.name iname, quantity, o.date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o JOIN " 
                 + JOIN_CUSTOMER_TABLE_FULL_NAME + " c ON o.\"customer_id\" = c.\"customer_id\" JOIN " 
                 + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" ORDER BY \"order_id\"";
+        query[3] = "SELECT /*+ NO_STAR_JOIN*/  \"order_id\", c.name, i.name iname, quantity, o.date FROM (" + JOIN_ORDER_TABLE_FULL_NAME + " o, " 
+                + JOIN_CUSTOMER_TABLE_FULL_NAME + " c), " 
+                + JOIN_ITEM_TABLE_FULL_NAME + " i WHERE o.\"item_id\" = i.\"item_id\" AND o.\"customer_id\" = c.\"customer_id\" ORDER BY \"order_id\"";
+        query[4] = "SELECT \"order_id\", c.name, i.name iname, quantity, o.date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o, (" 
+                + JOIN_CUSTOMER_TABLE_FULL_NAME + " c, " 
+                + JOIN_ITEM_TABLE_FULL_NAME + " i) WHERE o.\"item_id\" = i.\"item_id\" AND o.\"customer_id\" = c.\"customer_id\" ORDER BY \"order_id\"";
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         Connection conn = DriverManager.getConnection(getUrl(), props);
         try {
@@ -1860,8 +1868,10 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
 
                 assertFalse(rs.next());
                 
-                rs = conn.createStatement().executeQuery("EXPLAIN " + query[i]);
-                assertEquals(plans[11 + i], QueryUtil.getExplainPlan(rs));
+                if (i < 4) {
+                    rs = conn.createStatement().executeQuery("EXPLAIN " + query[i]);
+                    assertEquals(plans[11 + (i/2)], QueryUtil.getExplainPlan(rs));
+                }
             }
         } finally {
             conn.close();
@@ -3828,17 +3838,56 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
     }
 
     @Test
-    public void testUnsupportedJoinConditions() throws Exception {
-        String query = "SELECT * FROM " + JOIN_ITEM_TABLE_FULL_NAME + " item JOIN " + JOIN_SUPPLIER_TABLE_FULL_NAME + " supp ON (item.\"supplier_id\" || supp.\"supplier_id\") = ''";
+    public void testNonEquiJoin() throws Exception {
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         Connection conn = DriverManager.getConnection(getUrl(), props);
         try {
+            String query = "SELECT item.name, supp.name FROM " + JOIN_ITEM_TABLE_FULL_NAME + " item, " + JOIN_SUPPLIER_TABLE_FULL_NAME + " supp WHERE item.\"supplier_id\" > supp.\"supplier_id\"";
             PreparedStatement statement = conn.prepareStatement(query);
+            ResultSet rs = statement.executeQuery();
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T3");
+            assertEquals(rs.getString(2), "S1");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T4");
+            assertEquals(rs.getString(2), "S1");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T5");
+            assertEquals(rs.getString(2), "S1");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T5");
+            assertEquals(rs.getString(2), "S2");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T5");
+            assertEquals(rs.getString(2), "S3");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T5");
+            assertEquals(rs.getString(2), "S4");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getString(2), "S1");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getString(2), "S2");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getString(2), "S3");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getString(2), "S4");
+            assertTrue(rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getString(2), "S5");
+
+            assertFalse(rs.next());
+            
+            query = "SELECT item.name, supp.name FROM " + JOIN_ITEM_TABLE_FULL_NAME + " item JOIN " + JOIN_SUPPLIER_TABLE_FULL_NAME + " supp ON item.\"supplier_id\" > supp.\"supplier_id\"";
+            statement = conn.prepareStatement(query);
             try {
                 statement.executeQuery();
-                fail("Should have got SQLFeatureNotSupportedException.");
-            } catch (SQLFeatureNotSupportedException e) {
-                assertEquals("Does not support non-standard or non-equi join conditions.", e.getMessage());
+                fail("Should have got SQLException.");
+            } catch (SQLException e) {
+                assertEquals(SQLExceptionCode.AMBIGUOUS_JOIN_CONDITION.getErrorCode(), e.getErrorCode());
             }
         } finally {
             conn.close();
@@ -3847,3 +3896,4 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
 
 }
 
+

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/antlr3/PhoenixSQL.g
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index e2636fb..aa7fdc6 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -647,31 +647,25 @@ parseOrderByField returns [OrderByNode ret]
         { $ret = factory.orderBy(expr, nullsLast, isAscending); }
     ;
 
-parseFrom returns [List<TableNode> ret]
-@init{ret = new ArrayList<TableNode>(4); }
-    :   t=table_ref {$ret.add(t);} (COMMA s=table_ref { $ret.add(s); })*
+parseFrom returns [TableNode ret]
+    :   t=table_list {$ret = t;}
+    ;
+    
+table_list returns [TableNode ret]
+    :   t=table_ref {$ret = t;} (COMMA s=table_ref { $ret = factory.join(JoinTableNode.JoinType.Inner, ret, s, null, false); })*
     ;
 
 table_ref returns [TableNode ret]
-    : t=single_table_ref p=join_parts { $ret = factory.table(t, p); }
-    ;
+	:	l=table_factor { $ret = l; } (j=join_type JOIN r=table_factor ON e=expression { $ret = factory.join(j, ret, r, e, false); })*
+	;
 
-single_table_ref returns [TableNode ret]
-    :   n=bind_name ((AS)? alias=identifier)? { $ret = factory.bindTable(alias, factory.table(null,n)); } // TODO: review
-    |   t=from_table_name ((AS)? alias=identifier)? (LPAREN cdefs=dyn_column_defs RPAREN)? { $ret = factory.namedTable(alias,t,cdefs); }
+table_factor returns [TableNode ret]
+    :   LPAREN t=table_list RPAREN { $ret = t; }
+    |   n=bind_name ((AS)? alias=identifier)? { $ret = factory.bindTable(alias, factory.table(null,n)); } // TODO: review
+    |   f=from_table_name ((AS)? alias=identifier)? (LPAREN cdefs=dyn_column_defs RPAREN)? { $ret = factory.namedTable(alias,f,cdefs); }
     |   LPAREN SELECT s=hinted_select_node RPAREN ((AS)? alias=identifier)? { $ret = factory.derivedTable(alias, s); }
     ;
 
-join_parts returns [List<JoinPartNode> ret]
-@init{ret = new ArrayList<JoinPartNode>(4); }
-	:	(p=join_part { $ret.add(p); })*
-	;
-
-join_part returns [JoinPartNode ret]
-	:	j=join_type JOIN r=single_table_ref ON e=expression { $ret = factory.joinPart(j, e, r); }
-	|	j=join_type JOIN LPAREN r=table_ref RPAREN ON e=expression { $ret = factory.joinPart(j, e, r); }
-	;
-
 join_type returns [JoinTableNode.JoinType ret]
     :   INNER?   { $ret = JoinTableNode.JoinType.Inner; }
     |   LEFT OUTER?   { $ret = JoinTableNode.JoinType.Left; }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index 1331a2a..6638819 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -315,7 +315,7 @@ public class DeleteCompiler {
                     aliasedNodes.add(FACTORY.aliasedNode(null, FACTORY.column(null, '"' + column.getName().getString() + '"', null)));
                 }
                 select = FACTORY.select(
-                        Collections.singletonList(delete.getTable()), 
+                        delete.getTable(), 
                         hint, false, aliasedNodes, delete.getWhere(), 
                         Collections.<ParseNode>emptyList(), null, 
                         delete.getOrderBy(), delete.getLimit(),

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
index 1627f45..b9246c9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
@@ -155,14 +155,12 @@ public class FromCompiler {
      */
     public static ColumnResolver getResolverForQuery(SelectStatement statement, PhoenixConnection connection)
     		throws SQLException {
-    	List<TableNode> fromNodes = statement.getFrom();
-        if (!statement.isJoin() && fromNodes.get(0) instanceof NamedTableNode)
-            return new SingleTableColumnResolver(connection, (NamedTableNode) fromNodes.get(0), true, 1);
+    	TableNode fromNode = statement.getFrom();
+        if (fromNode instanceof NamedTableNode)
+            return new SingleTableColumnResolver(connection, (NamedTableNode) fromNode, true, 1);
         
         MultiTableColumnResolver visitor = new MultiTableColumnResolver(connection, 1);
-        for (TableNode node : fromNodes) {
-            node.accept(visitor);
-        }
+        fromNode.accept(visitor);
         return visitor;
     }
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
index 140146c..b519dc4 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -39,6 +40,7 @@ import org.apache.phoenix.execute.TupleProjector;
 import org.apache.phoenix.expression.AndExpression;
 import org.apache.phoenix.expression.CoerceExpression;
 import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.LiteralExpression;
 import org.apache.phoenix.expression.function.CountAggregateFunction;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.parse.AliasedNode;
@@ -109,15 +111,9 @@ public class JoinCompiler {
     }
     
     public static JoinTable compile(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver) throws SQLException {
-        JoinCompiler compiler = new JoinCompiler(statement, select, resolver);
-        
-        List<TableNode> from = select.getFrom();
-        if (from.size() > 1) {
-            throw new SQLFeatureNotSupportedException("Cross join not supported.");
-        }
-        
+        JoinCompiler compiler = new JoinCompiler(statement, select, resolver);        
         JoinTableConstructor constructor = compiler.new JoinTableConstructor();
-        Pair<Table, List<JoinSpec>> res = from.get(0).accept(constructor);
+        Pair<Table, List<JoinSpec>> res = select.getFrom().accept(constructor);
         JoinTable joinTable = res.getSecond() == null ? compiler.new JoinTable(res.getFirst()) : compiler.new JoinTable(res.getFirst(), res.getSecond());
         if (select.getWhere() != null) {
             joinTable.addFilter(select.getWhere());
@@ -218,8 +214,8 @@ public class JoinCompiler {
         private final List<Table> tables;
         private final List<TableRef> tableRefs;
         private final boolean allLeftJoin;
-        private final boolean hasRightJoin;
-        private final List<JoinTable> prefilterAcceptedTables;
+        private final boolean isPrefilterAccepted;
+        private final List<JoinSpec> prefilterAcceptedTables;
         
         private JoinTable(Table table) {
             this.table = table;
@@ -228,8 +224,8 @@ public class JoinCompiler {
             this.tables = Collections.<Table>singletonList(table);
             this.tableRefs = Collections.<TableRef>singletonList(table.getTableRef());
             this.allLeftJoin = false;
-            this.hasRightJoin = false;
-            this.prefilterAcceptedTables = Collections.<JoinTable>emptyList();
+            this.isPrefilterAccepted = true;
+            this.prefilterAcceptedTables = Collections.<JoinSpec>emptyList();
         }
         
         private JoinTable(Table table, List<JoinSpec> joinSpecs) {
@@ -241,10 +237,12 @@ public class JoinCompiler {
             this.tables.add(table);
             boolean allLeftJoin = true;
             int lastRightJoinIndex = -1;
+            boolean hasFullJoin = false;
             for (int i = 0; i < joinSpecs.size(); i++) {
                 JoinSpec joinSpec = joinSpecs.get(i);
                 this.tables.addAll(joinSpec.getJoinTable().getTables());
                 allLeftJoin = allLeftJoin && joinSpec.getType() == JoinType.Left;
+                hasFullJoin = hasFullJoin || joinSpec.getType() == JoinType.Full;
                 if (joinSpec.getType() == JoinType.Right) {
                     lastRightJoinIndex = i;
                 }
@@ -253,12 +251,12 @@ public class JoinCompiler {
                 this.tableRefs.add(t.getTableRef());
             }
             this.allLeftJoin = allLeftJoin;
-            this.hasRightJoin = lastRightJoinIndex > -1;
-            this.prefilterAcceptedTables = new ArrayList<JoinTable>();
+            this.isPrefilterAccepted = !hasFullJoin && lastRightJoinIndex == -1;
+            this.prefilterAcceptedTables = new ArrayList<JoinSpec>();
             for (int i = lastRightJoinIndex == -1 ? 0 : lastRightJoinIndex; i < joinSpecs.size(); i++) {
                 JoinSpec joinSpec = joinSpecs.get(i);
-                if (joinSpec.getType() != JoinType.Left && joinSpec.getType() != JoinType.Anti) {
-                    prefilterAcceptedTables.add(joinSpec.getJoinTable());
+                if (joinSpec.getType() != JoinType.Left && joinSpec.getType() != JoinType.Anti && joinSpec.getType() != JoinType.Full) {
+                    prefilterAcceptedTables.add(joinSpec);
                 }
             }
         }
@@ -303,7 +301,7 @@ public class JoinCompiler {
             
             WhereNodeVisitor visitor = new WhereNodeVisitor(origResolver, table,
                     postFilters, Collections.<TableRef>singletonList(table.getTableRef()), 
-                    hasRightJoin, prefilterAcceptedTables);
+                    isPrefilterAccepted, prefilterAcceptedTables);
             filter.accept(visitor);
         }
         
@@ -415,11 +413,11 @@ public class JoinCompiler {
            if (!postFilters.isEmpty())
                return true;
            
-           if (!hasRightJoin && table.hasFilters())
+           if (isPrefilterAccepted && table.hasFilters())
                return true;
            
-           for (JoinTable joinTable : prefilterAcceptedTables) {
-               if (joinTable.hasFilters())
+           for (JoinSpec joinSpec : prefilterAcceptedTables) {
+               if (joinSpec.getJoinTable().hasFilters())
                    return true;
            }
            
@@ -433,6 +431,7 @@ public class JoinCompiler {
         private final JoinTable joinTable;
         private final boolean singleValueOnly;
         private Set<TableRef> dependencies;
+        private OnNodeVisitor onNodeVisitor;
         
         private JoinSpec(JoinType type, ParseNode onNode, JoinTable joinTable, 
                 boolean singleValueOnly, ColumnResolver resolver) throws SQLException {
@@ -441,13 +440,16 @@ public class JoinCompiler {
             this.joinTable = joinTable;
             this.singleValueOnly = singleValueOnly;
             this.dependencies = new HashSet<TableRef>();
-            OnNodeVisitor visitor = new OnNodeVisitor(resolver, onConditions, dependencies, joinTable);
-            onNode.accept(visitor);
-            if (onConditions.isEmpty()) {
-                visitor.throwUnsupportedJoinConditionException();
+            this.onNodeVisitor = new OnNodeVisitor(resolver, onConditions, dependencies, joinTable);
+            if (onNode != null) {
+                onNode.accept(this.onNodeVisitor);
             }
         }
         
+        public void addOnCondition(ParseNode node) throws SQLException {
+            node.accept(onNodeVisitor);
+        }
+        
         public JoinType getType() {
             return type;
         }
@@ -469,6 +471,12 @@ public class JoinCompiler {
         }
         
         public Pair<List<Expression>, List<Expression>> compileJoinConditions(StatementContext context, ColumnResolver leftResolver, ColumnResolver rightResolver) throws SQLException {
+            if (onConditions.isEmpty()) {
+                return new Pair<List<Expression>, List<Expression>>(
+                        Collections.<Expression> singletonList(LiteralExpression.newConstant(1)), 
+                        Collections.<Expression> singletonList(LiteralExpression.newConstant(1)));
+            }
+            
             ColumnResolver resolver = context.getResolver();
             List<Pair<Expression, Expression>> compiled = new ArrayList<Pair<Expression, Expression>>(onConditions.size());
             context.setResolver(leftResolver);
@@ -679,8 +687,7 @@ public class JoinCompiler {
             if (isSubselect())
                 return SubselectRewriter.applyPostFilters(subselect, preFilters, tableNode.getAlias());
             
-            List<TableNode> from = Collections.<TableNode>singletonList(tableNode);
-            return NODE_FACTORY.select(from, select.getHint(), false, selectNodes, getPreFiltersCombined(), null, null, null, null, 0, false, select.hasSequence());
+            return NODE_FACTORY.select(tableNode, select.getHint(), false, selectNodes, getPreFiltersCombined(), null, null, null, null, 0, false, select.hasSequence());
         }
         
         public boolean hasFilters() {
@@ -804,17 +811,17 @@ public class JoinCompiler {
         private Table table;
         private List<ParseNode> postFilters;
         private List<TableRef> selfTableRefs;
-        private boolean hasRightJoin;
-        private List<JoinTable> prefilterAcceptedTables;
+        private boolean isPrefilterAccepted;
+        private List<JoinSpec> prefilterAcceptedTables;
         ColumnRefParseNodeVisitor columnRefVisitor;
         
         public WhereNodeVisitor(ColumnResolver resolver, Table table,
-                List<ParseNode> postFilters, List<TableRef> selfTableRefs, boolean hasRightJoin, 
-                List<JoinTable> prefilterAcceptedTables) {
+                List<ParseNode> postFilters, List<TableRef> selfTableRefs, boolean isPrefilterAccepted, 
+                List<JoinSpec> prefilterAcceptedTables) {
             this.table = table;
             this.postFilters = postFilters;
             this.selfTableRefs = selfTableRefs;
-            this.hasRightJoin = hasRightJoin;
+            this.isPrefilterAccepted = isPrefilterAccepted;
             this.prefilterAcceptedTables = prefilterAcceptedTables;
             this.columnRefVisitor = new ColumnRefParseNodeVisitor(resolver);
         }
@@ -833,7 +840,7 @@ public class JoinCompiler {
             switch (type) {
             case NONE:
             case SELF_ONLY:
-                if (!hasRightJoin) {
+                if (isPrefilterAccepted) {
                     table.addFilter(node);
                 } else {
                     postFilters.add(node);
@@ -841,9 +848,9 @@ public class JoinCompiler {
                 break;
             case FOREIGN_ONLY:
                 JoinTable matched = null;
-                for (JoinTable joinTable : prefilterAcceptedTables) {
-                    if (columnRefVisitor.getContentType(joinTable.getTableRefs()) == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
-                        matched = joinTable;
+                for (JoinSpec joinSpec : prefilterAcceptedTables) {
+                    if (columnRefVisitor.getContentType(joinSpec.getJoinTable().getTableRefs()) == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
+                        matched = joinSpec.getJoinTable();
                         break;
                     }
                 }
@@ -879,6 +886,29 @@ public class JoinCompiler {
         public Void visitLeave(AndParseNode node, List<Void> l) throws SQLException {
             return null;
         }
+        
+        @Override
+        public Void visitLeave(ComparisonParseNode node, List<Void> l) 
+                throws SQLException {
+            if (!(node instanceof EqualParseNode))
+                return leaveBooleanNode(node, l);
+            
+            ListIterator<JoinSpec> iter = prefilterAcceptedTables.listIterator(prefilterAcceptedTables.size());
+            while (iter.hasPrevious()) {
+                JoinSpec joinSpec = iter.previous();
+                if (joinSpec.getType() != JoinType.Inner || joinSpec.isSingleValueOnly()) {
+                    continue;
+                }
+                
+                try {
+                    joinSpec.addOnCondition(node);
+                    return null;
+                } catch (SQLException e) {
+                }
+            }
+            
+            return leaveBooleanNode(node, l);
+        }
     }
     
     private static class OnNodeVisitor extends BooleanParseNodeVisitor<Void> {
@@ -894,49 +924,47 @@ public class JoinCompiler {
             this.joinTable = joinTable;
             this.columnRefVisitor = new ColumnRefParseNodeVisitor(resolver);
         }
-        
         @Override
         protected boolean enterBooleanNode(ParseNode node) throws SQLException {
             return false;
         }
-        
+
         @Override
         protected Void leaveBooleanNode(ParseNode node,
                 List<Void> l) throws SQLException {
             columnRefVisitor.reset();
             node.accept(columnRefVisitor);
             ColumnRefParseNodeVisitor.ColumnRefType type = columnRefVisitor.getContentType(joinTable.getTableRefs());
-            if (type == ColumnRefParseNodeVisitor.ColumnRefType.NONE 
+            if (type == ColumnRefParseNodeVisitor.ColumnRefType.NONE
                     || type == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
                 joinTable.addFilter(node);
             } else {
-                throwUnsupportedJoinConditionException();
+                throwAmbiguousJoinConditionException();
             }
             return null;
         }
-        
+
         @Override
         protected boolean enterNonBooleanNode(ParseNode node) throws SQLException {
             return false;
         }
-        
+
         @Override
         protected Void leaveNonBooleanNode(ParseNode node, List<Void> l) throws SQLException {
             return null;
         }
-        
+
         @Override
         public boolean visitEnter(AndParseNode node) throws SQLException {
             return true;
         }
-
         @Override
         public Void visitLeave(AndParseNode node, List<Void> l) throws SQLException {
             return null;
         }
-        
+
         @Override
-        public Void visitLeave(ComparisonParseNode node, List<Void> l) 
+        public Void visitLeave(ComparisonParseNode node, List<Void> l)
                 throws SQLException {
             if (!(node instanceof EqualParseNode))
                 return leaveBooleanNode(node, l);
@@ -951,16 +979,16 @@ public class JoinCompiler {
             if ((lhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY || lhsType == ColumnRefParseNodeVisitor.ColumnRefType.NONE)
                     && (rhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY || rhsType == ColumnRefParseNodeVisitor.ColumnRefType.NONE)) {
                 joinTable.addFilter(node);
-            } else if (lhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY 
+            } else if (lhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY
                     && rhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
                 onConditions.add(node);
                 dependencies.addAll(lhsTableRefSet);
-            } else if (rhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY 
+            } else if (rhsType == ColumnRefParseNodeVisitor.ColumnRefType.FOREIGN_ONLY
                     && lhsType == ColumnRefParseNodeVisitor.ColumnRefType.SELF_ONLY) {
                 onConditions.add(NODE_FACTORY.equal(node.getRHS(), node.getLHS()));
                 dependencies.addAll(rhsTableRefSet);
             } else {
-                throwUnsupportedJoinConditionException();
+                throwAmbiguousJoinConditionException();
             }
             return null;
         }
@@ -972,10 +1000,9 @@ public class JoinCompiler {
          * 2) a boolean condition referencing to the self table only.
          * Otherwise, it can be ambiguous.
          */
-        public void throwUnsupportedJoinConditionException() 
-                throws SQLFeatureNotSupportedException {
-            throw new SQLFeatureNotSupportedException("Does not support non-standard or non-equi join conditions.");
-        }           
+        public void throwAmbiguousJoinConditionException() throws SQLException {
+            throw new SQLExceptionInfo.Builder(SQLExceptionCode.AMBIGUOUS_JOIN_CONDITION).build().buildException();
+        }
     }
 
     private static class ColumnRefParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
@@ -1050,7 +1077,7 @@ public class JoinCompiler {
         return !select.isJoin() 
                 && !select.isAggregate() 
                 && !select.isDistinct() 
-                && !(select.getFrom().get(0) instanceof DerivedTableNode)
+                && !(select.getFrom() instanceof DerivedTableNode)
                 && select.getLimit() == null;
     }
     
@@ -1168,64 +1195,61 @@ public class JoinCompiler {
         if (replacement.isEmpty()) 
             return select;
         
-        List<TableNode> from = select.getFrom();
-        List<TableNode> newFrom = Lists.newArrayListWithExpectedSize(from.size());
-        for (TableNode node : from) {
-            newFrom.add(node.accept(new TableNodeVisitor<TableNode>() {
-                private TableRef resolveTable(String alias, TableName name) throws SQLException {
-                    if (alias != null)
-                        return resolver.resolveTable(null, alias);
+        TableNode from = select.getFrom();
+        TableNode newFrom = from.accept(new TableNodeVisitor<TableNode>() {
+            private TableRef resolveTable(String alias, TableName name) throws SQLException {
+                if (alias != null)
+                    return resolver.resolveTable(null, alias);
 
-                    return resolver.resolveTable(name.getSchemaName(), name.getTableName());
-                }
+                return resolver.resolveTable(name.getSchemaName(), name.getTableName());
+            }
 
-                private TableName getReplacedTableName(TableRef tableRef) {
-                    String schemaName = tableRef.getTable().getSchemaName().getString();
-                    return TableName.create(schemaName.length() == 0 ? null : schemaName, tableRef.getTable().getTableName().getString());
-                }
+            private TableName getReplacedTableName(TableRef tableRef) {
+                String schemaName = tableRef.getTable().getSchemaName().getString();
+                return TableName.create(schemaName.length() == 0 ? null : schemaName, tableRef.getTable().getTableName().getString());
+            }
 
-                @Override
-                public TableNode visit(BindTableNode boundTableNode) throws SQLException {
-                    TableRef tableRef = resolveTable(boundTableNode.getAlias(), boundTableNode.getName());
-                    TableRef replaceRef = replacement.get(tableRef);
-                    if (replaceRef == null)
-                        return boundTableNode;
+            @Override
+            public TableNode visit(BindTableNode boundTableNode) throws SQLException {
+                TableRef tableRef = resolveTable(boundTableNode.getAlias(), boundTableNode.getName());
+                TableRef replaceRef = replacement.get(tableRef);
+                if (replaceRef == null)
+                    return boundTableNode;
 
-                    String alias = boundTableNode.getAlias();
-                    return NODE_FACTORY.bindTable(alias == null ? null : '"' + alias + '"', getReplacedTableName(replaceRef));
-                }
+                String alias = boundTableNode.getAlias();
+                return NODE_FACTORY.bindTable(alias == null ? null : '"' + alias + '"', getReplacedTableName(replaceRef));
+            }
 
-                @Override
-                public TableNode visit(JoinTableNode joinNode) throws SQLException {
-                    TableNode lhs = joinNode.getLHS();
-                    TableNode rhs = joinNode.getRHS();
-                    TableNode lhsReplace = lhs.accept(this);
-                    TableNode rhsReplace = rhs.accept(this);
-                    if (lhs == lhsReplace && rhs == rhsReplace)
-                        return joinNode;
+            @Override
+            public TableNode visit(JoinTableNode joinNode) throws SQLException {
+                TableNode lhs = joinNode.getLHS();
+                TableNode rhs = joinNode.getRHS();
+                TableNode lhsReplace = lhs.accept(this);
+                TableNode rhsReplace = rhs.accept(this);
+                if (lhs == lhsReplace && rhs == rhsReplace)
+                    return joinNode;
 
-                    return NODE_FACTORY.join(joinNode.getType(), lhsReplace, rhsReplace, joinNode.getOnNode(), joinNode.isSingleValueOnly());
-                }
+                return NODE_FACTORY.join(joinNode.getType(), lhsReplace, rhsReplace, joinNode.getOnNode(), joinNode.isSingleValueOnly());
+            }
 
-                @Override
-                public TableNode visit(NamedTableNode namedTableNode)
-                        throws SQLException {
-                    TableRef tableRef = resolveTable(namedTableNode.getAlias(), namedTableNode.getName());
-                    TableRef replaceRef = replacement.get(tableRef);
-                    if (replaceRef == null)
-                        return namedTableNode;
+            @Override
+            public TableNode visit(NamedTableNode namedTableNode)
+                    throws SQLException {
+                TableRef tableRef = resolveTable(namedTableNode.getAlias(), namedTableNode.getName());
+                TableRef replaceRef = replacement.get(tableRef);
+                if (replaceRef == null)
+                    return namedTableNode;
 
-                    String alias = namedTableNode.getAlias();
-                    return NODE_FACTORY.namedTable(alias == null ? null : '"' + alias + '"', getReplacedTableName(replaceRef), namedTableNode.getDynamicColumns());
-                }
+                String alias = namedTableNode.getAlias();
+                return NODE_FACTORY.namedTable(alias == null ? null : '"' + alias + '"', getReplacedTableName(replaceRef), namedTableNode.getDynamicColumns());
+            }
 
-                @Override
-                public TableNode visit(DerivedTableNode subselectNode)
-                        throws SQLException {
-                    return subselectNode;
-                }
-            }));
-        }
+            @Override
+            public TableNode visit(DerivedTableNode subselectNode)
+                    throws SQLException {
+                return subselectNode;
+            }
+        });
         
         return IndexStatementRewriter.translate(NODE_FACTORY.select(select, newFrom), resolver, replacement);        
     }
@@ -1249,9 +1273,8 @@ public class JoinCompiler {
             }
         }
         String tableAlias = tableRef.getTableAlias();
-        List<? extends TableNode> from = Collections.singletonList(NODE_FACTORY.namedTable(tableAlias == null ? null : '"' + tableAlias + '"', tName, dynamicCols));
+        TableNode from = NODE_FACTORY.namedTable(tableAlias == null ? null : '"' + tableAlias + '"', tName, dynamicCols);
 
-        // TODO: review, as it seems like we're potentially losing if the select statement is an aggregate (i.e. if it's an ungrouped aggregate for example)
         return NODE_FACTORY.select(from, hintNode, false, selectList, where, groupBy, null, orderBy, null, 0, groupBy != null, hasSequence);
     }
     

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
index 214330c..b9ca813 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/QueryCompiler.java
@@ -247,7 +247,7 @@ public class QueryCompiler {
         JoinSpec lastJoinSpec = joinSpecs.get(joinSpecs.size() - 1);
         JoinType type = lastJoinSpec.getType();
         if (type == JoinType.Full)
-            throw new SQLFeatureNotSupportedException("Full joins not supported.");
+            throw new SQLFeatureNotSupportedException(type + " joins not supported.");
         
         if (type == JoinType.Right || type == JoinType.Inner) {
             if (!lastJoinSpec.getJoinTable().getJoinSpecs().isEmpty())
@@ -294,7 +294,7 @@ public class QueryCompiler {
             if (rhs.getLimit() != null && !rhs.isAggregate() && !rhs.isDistinct() && rhs.getOrderBy().isEmpty()) {
                 limit = LimitCompiler.compile(context, rhs);
             }
-            HashJoinInfo joinInfo = new HashJoinInfo(projectedTable.getTable(), joinIds, new List[] {joinExpressions}, new JoinType[] {type == JoinType.Inner ? type : JoinType.Left}, new boolean[] {true}, new PTable[] {lhsProjTable.getTable()}, new int[] {fieldPosition}, postJoinFilterExpression, limit, forceProjection);
+            HashJoinInfo joinInfo = new HashJoinInfo(projectedTable.getTable(), joinIds, new List[] {joinExpressions}, new JoinType[] {type == JoinType.Right ? JoinType.Left : type}, new boolean[] {true}, new PTable[] {lhsProjTable.getTable()}, new int[] {fieldPosition}, postJoinFilterExpression, limit, forceProjection);
             Pair<Expression, Expression> keyRangeExpressions = new Pair<Expression, Expression>(null, null);
             getKeyExpressionCombinations(keyRangeExpressions, context, rhsTableRef, type, joinExpressions, hashExpressions);
             return HashJoinPlan.create(joinTable.getStatement(), rhsPlan, joinInfo, new HashSubPlan[] {new HashSubPlan(0, lhsPlan, hashExpressions, false, keyRangeExpressions.getFirst(), keyRangeExpressions.getSecond(), lhsJoin.hasFilters())});

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementNormalizer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementNormalizer.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementNormalizer.java
index 803f554..f6a6f7a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementNormalizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/StatementNormalizer.java
@@ -37,7 +37,6 @@ import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.ParseNodeRewriter;
 import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.TableName;
-import org.apache.phoenix.parse.TableNode;
 import org.apache.phoenix.parse.TableNodeVisitor;
 import org.apache.phoenix.parse.TableWildcardParseNode;
 import org.apache.phoenix.parse.WildcardParseNode;
@@ -88,13 +87,10 @@ public class StatementNormalizer extends ParseNodeRewriter {
                     if (selectNodes == normSelectNodes) {
                         normSelectNodes = Lists.newArrayList(selectNodes.subList(0, i));
                     }
-                    for (TableNode tNode : statement.getFrom()) {
-                        TableNameVisitor visitor = new TableNameVisitor();
-                        List<TableName> tableNames = tNode.accept(visitor);
-                        for (TableName tableName : tableNames) {
-                            TableWildcardParseNode node = NODE_FACTORY.tableWildcard(tableName);
-                            normSelectNodes.add(NODE_FACTORY.aliasedNode(null, node));
-                        }
+                    List<TableName> tableNames = statement.getFrom().accept(new TableNameVisitor());
+                    for (TableName tableName : tableNames) {
+                        TableWildcardParseNode node = NODE_FACTORY.tableWildcard(tableName);
+                        normSelectNodes.add(NODE_FACTORY.aliasedNode(null, node));
                     }
                 } else if (selectNodes != normSelectNodes) {
                     normSelectNodes.add(aliasedNode);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java
index 3e470ce..01aca00 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubqueryRewriter.java
@@ -83,13 +83,13 @@ public class SubqueryRewriter extends ParseNodeRewriter {
         if (normWhere == where)
             return select;
         
-        return NODE_FACTORY.select(select, Collections.singletonList(rewriter.tableNode), normWhere);
+        return NODE_FACTORY.select(select, rewriter.tableNode, normWhere);
     }
     
     protected SubqueryRewriter(SelectStatement select, ColumnResolver resolver, PhoenixConnection connection) {
         this.resolver = resolver;
         this.connection = connection;
-        this.tableNode = select.getFrom().get(0);
+        this.tableNode = select.getFrom();
         this.topNode = null;
     }
     
@@ -339,7 +339,7 @@ public class SubqueryRewriter extends ParseNodeRewriter {
                 groupbyNodes.set(i - 1, aliasedNode.getNode());
             }
             SelectStatement derivedTableStmt = NODE_FACTORY.select(subquery, subquery.isDistinct(), derivedTableSelect, where, derivedTableGroupBy, true);
-            subquery = NODE_FACTORY.select(Collections.singletonList(NODE_FACTORY.derivedTable(derivedTableAlias, derivedTableStmt)), subquery.getHint(), false, selectNodes, null, groupbyNodes, null, Collections.<OrderByNode> emptyList(), null, subquery.getBindCount(), true, false);
+            subquery = NODE_FACTORY.select(NODE_FACTORY.derivedTable(derivedTableAlias, derivedTableStmt), subquery.getHint(), false, selectNodes, null, groupbyNodes, null, Collections.<OrderByNode> emptyList(), null, subquery.getBindCount(), true, false);
         }
         
         ParseNode onNode = conditionExtractor.getJoinCondition();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
index 35ea900..d229478 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/SubselectRewriter.java
@@ -59,9 +59,9 @@ public class SubselectRewriter extends ParseNodeRewriter {
     }
     
     public static SelectStatement flatten(SelectStatement select, PhoenixConnection connection) throws SQLException {
-        List<TableNode> from = select.getFrom();
-        while (from.size() == 1 && from.get(0) instanceof DerivedTableNode) {
-            DerivedTableNode derivedTable = (DerivedTableNode) from.get(0);
+        TableNode from = select.getFrom();
+        while (from != null && from instanceof DerivedTableNode) {
+            DerivedTableNode derivedTable = (DerivedTableNode) from;
             SelectStatement subselect = derivedTable.getSelect();
             ColumnResolver resolver = FromCompiler.getResolverForQuery(subselect, connection);
             SubselectRewriter rewriter = new SubselectRewriter(resolver, subselect.getSelect(), derivedTable.getAlias());

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index 44f62da..0be40b8 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -381,7 +381,7 @@ public class UpsertCompiler {
                         selectResolver = FromCompiler.getResolverForQuery(transformedSelect, connection);
                         select = StatementNormalizer.normalize(transformedSelect, selectResolver);
                     }
-                    sameTable = select.getFrom().size() == 1
+                    sameTable = !select.isJoin()
                         && tableRefToBe.equals(selectResolver.getTables().get(0));
                     tableRefToBe = adjustTimestampToMinOfSameTable(tableRefToBe, selectResolver.getTables());
                     /* We can run the upsert in a coprocessor if:

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index bf13eec..5c6018d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -80,6 +80,7 @@ public enum SQLExceptionCode {
     VALUE_IN_LIST_NOT_CONSTANT(214, "22008", "Values in IN must evaluate to a constant."),
     SINGLE_ROW_SUBQUERY_RETURNS_MULTIPLE_ROWS(215, "22015", "Single-row sub-query returns more than one row."),
     SUBQUERY_RETURNS_DIFFERENT_NUMBER_OF_FIELDS(216, "22016", "Sub-query must return the same number of fields as the left-hand-side expression of 'IN'."),
+    AMBIGUOUS_JOIN_CONDITION(217, "22017", "Amibiguous or non-equi join condition specified. Consider using table list with where clause."),
     
     /**
      * Constraint Violation (errorcode 03, sqlstate 23)

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index f6b7736..c369be8 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -292,7 +292,7 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
     }
     
     private static class ExecutableSelectStatement extends SelectStatement implements CompilableStatement {
-        private ExecutableSelectStatement(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
+        private ExecutableSelectStatement(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
                 List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate, boolean hasSequence) {
             super(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate, hasSequence);
         }
@@ -796,7 +796,7 @@ public class PhoenixStatement implements Statement, SQLCloseable, org.apache.pho
 
     protected static class ExecutableNodeFactory extends ParseNodeFactory {
         @Override
-        public ExecutableSelectStatement select(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select,
+        public ExecutableSelectStatement select(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select,
                                                 ParseNode where, List<ParseNode> groupBy, ParseNode having,
                                                 List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate, boolean hasSequence) {
             return new ExecutableSelectStatement(from, hint, isDistinct, select, where, groupBy == null ? Collections.<ParseNode>emptyList() : groupBy,

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index f027ab3..8f6d026 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -214,9 +214,9 @@ public class QueryOptimizer {
         schemaName = schemaName.length() == 0 ? null :  '"' + schemaName + '"';
 
         String tableName = '"' + index.getTableName().getString() + '"';
-        List<? extends TableNode> tables = Collections.singletonList(FACTORY.namedTable(alias, FACTORY.table(schemaName, tableName)));
+        TableNode table = FACTORY.namedTable(alias, FACTORY.table(schemaName, tableName));
         try {
-            SelectStatement indexSelect = FACTORY.select(select, tables);
+            SelectStatement indexSelect = FACTORY.select(select, table);
             ColumnResolver resolver = FromCompiler.getResolverForQuery(indexSelect, statement.getConnection());
             // Check index state of now potentially updated index table to make sure it's active
             if (PIndexState.ACTIVE.equals(resolver.getTables().get(0).getTable().getIndexState())) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinPartNode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinPartNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinPartNode.java
deleted file mode 100644
index cdbaaea..0000000
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/JoinPartNode.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.phoenix.parse;
-
-import org.apache.phoenix.parse.JoinTableNode.JoinType;
-
-/**
- * 
- * Node representing the partial join clause in the FROM clause of SQL
- *
- * 
- * @since 0.1
- */
-class JoinPartNode {
-    
-    private final JoinType type;
-    private final ParseNode onNode;
-    private final TableNode table;
-    
-    JoinPartNode(JoinType type, ParseNode onNode, TableNode table) {
-        this.type = type;
-        this.onNode = onNode;
-        this.table = table;
-    }
-
-    JoinType getType() {
-        return type;
-    }
-    
-    ParseNode getOnNode() {
-        return onNode;
-    }
-    
-    TableNode getTable() {
-        return table;
-    }
-}
-

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index 6f8339e..cc0b455 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -415,17 +415,6 @@ public class ParseNodeFactory {
         return new IsNullParseNode(child, negate);
     }
 
-    public TableNode table(TableNode table, List<JoinPartNode> parts) {
-        for (JoinPartNode part : parts) {
-            table = new JoinTableNode(part.getType(), table, part.getTable(), part.getOnNode(), false);
-        }
-        return table;
-    }
-
-    JoinPartNode joinPart(JoinType type, ParseNode onNode, TableNode table) {
-        return new JoinPartNode(type, onNode, table);
-    }
-
     public JoinTableNode join(JoinType type, TableNode lhs, TableNode rhs, ParseNode on, boolean singleValueOnly) {
         return new JoinTableNode(type, lhs, rhs, on, singleValueOnly);
     }
@@ -586,7 +575,7 @@ public class ParseNodeFactory {
         return new OuterJoinParseNode(node);
     }
 
-    public SelectStatement select(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
+    public SelectStatement select(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
             List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate, boolean hasSequence) {
 
         return new SelectStatement(from, hint, isDistinct, select, where, groupBy == null ? Collections.<ParseNode>emptyList() : groupBy, having,
@@ -606,14 +595,14 @@ public class ParseNodeFactory {
                 statement.getOrderBy(), statement.getLimit(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence());
     }
 
-    public SelectStatement select(SelectStatement statement, List<? extends TableNode> tables) {
-        return select(tables, statement.getHint(), statement.isDistinct(), statement.getSelect(), statement.getWhere(), statement.getGroupBy(),
+    public SelectStatement select(SelectStatement statement, TableNode table) {
+        return select(table, statement.getHint(), statement.isDistinct(), statement.getSelect(), statement.getWhere(), statement.getGroupBy(),
                 statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getBindCount(), statement.isAggregate(),
                 statement.hasSequence());
     }
 
-    public SelectStatement select(SelectStatement statement, List<? extends TableNode> tables, ParseNode where) {
-        return select(tables, statement.getHint(), statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(),
+    public SelectStatement select(SelectStatement statement, TableNode table, ParseNode where) {
+        return select(table, statement.getHint(), statement.isDistinct(), statement.getSelect(), where, statement.getGroupBy(),
                 statement.getHaving(), statement.getOrderBy(), statement.getLimit(), statement.getBindCount(), statement.isAggregate(),
                 statement.hasSequence());
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
index 338a45b..809480f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
@@ -57,24 +57,8 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> {
      */
     public static SelectStatement rewrite(SelectStatement statement, ParseNodeRewriter rewriter) throws SQLException {
         Map<String,ParseNode> aliasMap = rewriter.getAliasMap();
-        List<TableNode> from = statement.getFrom();
-        List<TableNode> normFrom = from;
-        TableNodeRewriter tableNodeRewriter = new TableNodeRewriter(rewriter);
-        for (int i = 0; i < from.size(); i++) {
-            TableNode tableNode = from.get(i);
-            tableNodeRewriter.reset();
-            TableNode normTableNode = tableNode.accept(tableNodeRewriter);
-            if (normTableNode == tableNode) {
-                if (from != normFrom) {
-                    normFrom.add(tableNode);
-                }
-                continue;
-            }
-            if (from == normFrom) {
-                normFrom = Lists.newArrayList(from.subList(0, i));        		    
-            }
-            normFrom.add(normTableNode);
-        }
+        TableNode from = statement.getFrom();
+        TableNode normFrom = from.accept(new TableNodeRewriter(rewriter));
         ParseNode where = statement.getWhere();
         ParseNode normWhere = where;
         if (where != null) {
@@ -541,9 +525,6 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> {
 	        this.parseNodeRewriter = parseNodeRewriter;
 	    }
 
-	    public void reset() {
-	    }
-
         @Override
         public TableNode visit(BindTableNode boundTableNode) throws SQLException {
             return boundTableNode;
@@ -557,7 +538,7 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode> {
             TableNode normLhsNode = lhsNode.accept(this);
             TableNode normRhsNode = rhsNode.accept(this);
             parseNodeRewriter.reset();
-            ParseNode normOnNode = onNode.accept(parseNodeRewriter);
+            ParseNode normOnNode = onNode == null ? null : onNode.accept(parseNodeRewriter);
             if (lhsNode == normLhsNode && rhsNode == normRhsNode && onNode == normOnNode)
                 return joinNode;
 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/bfc92b04/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
index e7302dc..961846b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
@@ -36,14 +36,14 @@ import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunctionInfo;
 public class SelectStatement implements FilterableStatement {
     public static final SelectStatement SELECT_ONE =
             new SelectStatement(
-                    Collections.<TableNode>emptyList(), null, false, 
+                    null, null, false, 
                     Collections.<AliasedNode>singletonList(new AliasedNode(null, LiteralParseNode.ONE)),
                     null, Collections.<ParseNode>emptyList(),
                     null, Collections.<OrderByNode>emptyList(),
                     null, 0, false, false);
     public static final SelectStatement COUNT_ONE =
             new SelectStatement(
-                    Collections.<TableNode>emptyList(), null, false,
+                    null, null, false,
                     Collections.<AliasedNode>singletonList(
                     new AliasedNode(null, 
                         new AggregateFunctionParseNode(
@@ -80,7 +80,7 @@ public class SelectStatement implements FilterableStatement {
                 select.getOrderBy(), select.getLimit(), select.getBindCount(), select.isAggregate(), select.hasSequence());
     }
     
-    private final List<TableNode> fromTable;
+    private final TableNode fromTable;
     private final HintNode hint;
     private final boolean isDistinct;
     private final List<AliasedNode> select;
@@ -104,10 +104,10 @@ public class SelectStatement implements FilterableStatement {
         return count;
     }
     
-    protected SelectStatement(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select,
+    protected SelectStatement(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select,
             ParseNode where, List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit,
             int bindCount, boolean isAggregate, boolean hasSequence) {
-        this.fromTable = Collections.unmodifiableList(from);
+        this.fromTable = from;
         this.hint = hint == null ? HintNode.EMPTY_HINT_NODE : hint;
         this.isDistinct = isDistinct;
         this.select = Collections.unmodifiableList(select);
@@ -136,7 +136,7 @@ public class SelectStatement implements FilterableStatement {
         return bindCount;
     }
     
-    public List<TableNode> getFrom() {
+    public TableNode getFrom() {
         return fromTable;
     }
     
@@ -190,13 +190,13 @@ public class SelectStatement implements FilterableStatement {
     }
 
     public boolean isJoin() {
-        return fromTable.size() > 1 || (fromTable.size() > 0 && fromTable.get(0) instanceof JoinTableNode);
+        return fromTable != null && fromTable instanceof JoinTableNode;
     }
     
     public SelectStatement getInnerSelectStatement() {
-        if (fromTable.size() != 1 || !(fromTable.get(0) instanceof DerivedTableNode))
+        if (fromTable == null || !(fromTable instanceof DerivedTableNode))
             return null;
         
-        return ((DerivedTableNode) fromTable.get(0)).getSelect();
+        return ((DerivedTableNode) fromTable).getSelect();
     }
 }