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/04/01 02:34:50 UTC

[3/3] git commit: PHOENIX-71 Support sub-joins

PHOENIX-71 Support sub-joins


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

Branch: refs/heads/master
Commit: 12bd7aeddd4e25a754b76c3f05b00730c4304644
Parents: 0849b25
Author: maryannxue <ma...@apache.org>
Authored: Mon Mar 31 20:33:50 2014 -0400
Committer: maryannxue <ma...@apache.org>
Committed: Mon Mar 31 20:33:50 2014 -0400

----------------------------------------------------------------------
 .../org/apache/phoenix/end2end/HashJoinIT.java  |  258 +++-
 phoenix-core/src/main/antlr3/PhoenixSQL.g       |   23 +-
 .../apache/phoenix/compile/FromCompiler.java    |   17 +-
 .../apache/phoenix/compile/JoinCompiler.java    | 1425 +++++++++---------
 .../apache/phoenix/compile/QueryCompiler.java   |   99 +-
 .../phoenix/compile/StatementNormalizer.java    |   42 +-
 .../apache/phoenix/optimize/QueryOptimizer.java |    2 +-
 .../org/apache/phoenix/parse/BindTableNode.java |    4 +-
 .../apache/phoenix/parse/DerivedTableNode.java  |    4 +-
 .../org/apache/phoenix/parse/JoinPartNode.java  |   53 +
 .../org/apache/phoenix/parse/JoinTableNode.java |   35 +-
 .../apache/phoenix/parse/NamedTableNode.java    |    4 +-
 .../apache/phoenix/parse/ParseNodeFactory.java  |   16 +-
 .../apache/phoenix/parse/ParseNodeRewriter.java |   81 +-
 .../apache/phoenix/parse/SelectStatement.java   |    4 +
 .../org/apache/phoenix/parse/TableNode.java     |    2 +-
 .../apache/phoenix/parse/TableNodeVisitor.java  |   10 +-
 .../phoenix/compile/JoinQueryCompilerTest.java  |   70 +-
 18 files changed, 1192 insertions(+), 957 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/12bd7aed/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 248621b..5ccef2c 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
@@ -226,7 +226,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "    BUILD HASH TABLE 1\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_DISPLAY_NAME,
                 /*
-                 * testSelfJoin
+                 * testSelfJoin()
                  *     SELECT i2.item_id, i1.name FROM joinItemTable i1 
                  *     JOIN joinItemTable i2 ON i1.item_id = i2.item_id 
                  *     ORDER BY i1.item_id
@@ -237,7 +237,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" +
                 "            SERVER FILTER BY FIRST KEY ONLY",
                 /*
-                 * testSelfJoin
+                 * testSelfJoin()
                  *     SELECT i1.name, i2.name FROM joinItemTable i1 
                  *     JOIN joinItemTable i2 ON i1.item_id = i2.supplier_id 
                  *     ORDER BY i1.name, i2.name
@@ -249,12 +249,12 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "    BUILD HASH TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME,
                 /*
-                 * testStarJoin
-                 * SELECT order_id, c.name, i.name iname, quantity, o.date 
-                 * FROM joinOrderTable o 
-                 * JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
-                 * JOIN joinItemTable i ON o.item_id = i.item_id 
-                 * ORDER BY order_id
+                 * testStarJoin()
+                 *     SELECT order_id, c.name, i.name iname, quantity, o.date 
+                 *     FROM joinOrderTable o 
+                 *     JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
+                 *     JOIN joinItemTable i ON o.item_id = i.item_id 
+                 *     ORDER BY order_id
                  */
                 "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
                 "    PARALLEL EQUI-JOIN 2 HASH TABLES:\n" +
@@ -263,12 +263,12 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "    BUILD HASH TABLE 1\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME,
                 /*
-                 * testStarJoin
-                 * SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date 
-                 * FROM joinOrderTable o 
-                 * JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
-                 * JOIN joinItemTable i ON o.item_id = i.item_id 
-                 * ORDER BY order_id
+                 * testStarJoin()
+                 *     SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date 
+                 *     FROM joinOrderTable o 
+                 *     JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
+                 *     JOIN joinItemTable i ON o.item_id = i.item_id 
+                 *     ORDER BY order_id
                  */
                 "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" +
                 "    SERVER SORTED BY [O.order_id]\n" +
@@ -279,6 +279,33 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "            PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
                 "            BUILD HASH TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_CUSTOMER_TABLE_DISPLAY_NAME,
+                /*
+                 * testSubJoin()
+                 *     SELECT * FROM joinCustomerTable c 
+                 *     INNER JOIN (joinOrderTable o 
+                 *         INNER JOIN (joinSupplierTable s 
+                 *             RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id)
+                 *         ON o.item_id = i.item_id)
+                 *     ON c.customer_id = o.customer_id
+                 *     WHERE c.customer_id <= '0000000005' 
+                 *         AND order_id != '000000000000003' 
+                 *         AND i.name != 'T3' 
+                 *     ORDER BY c.customer_id, i.name
+                 */
+                "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_DISPLAY_NAME + " [*] - ['0000000005']\n" +
+                "    SERVER SORTED BY [C.customer_id, I.NAME]\n" +
+                "CLIENT MERGE SORT\n" +
+                "    PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "    BUILD HASH TABLE 0\n" +
+                "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
+                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "            BUILD HASH TABLE 0\n" +
+                "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" +
+                "                    SERVER FILTER BY NAME != 'T3'\n" +
+                "                    PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "                    BUILD HASH TABLE 0\n" +
+                "                        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SUPPLIER_TABLE_DISPLAY_NAME,
                 }});
         testCases.add(new String[][] {
                 {
@@ -404,7 +431,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "    BUILD HASH TABLE 1\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_supplier",
                 /*
-                 * testSelfJoin
+                 * testSelfJoin()
                  *     SELECT i2.item_id, i1.name FROM joinItemTable i1 
                  *     JOIN joinItemTable i2 ON i1.item_id = i2.item_id 
                  *     ORDER BY i1.item_id
@@ -415,7 +442,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
                 "            SERVER FILTER BY FIRST KEY ONLY",
                 /*
-                 * testSelfJoin
+                 * testSelfJoin()
                  *     SELECT i1.name, i2.name FROM joinItemTable i1 
                  *     JOIN joinItemTable i2 ON i1.item_id = i2.supplier_id 
                  *     ORDER BY i1.name, i2.name
@@ -428,12 +455,12 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "    BUILD HASH TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item",
                 /*
-                 * testStarJoin
-                 * SELECT order_id, c.name, i.name iname, quantity, o.date 
-                 * FROM joinOrderTable o 
-                 * JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
-                 * JOIN joinItemTable i ON o.item_id = i.item_id 
-                 * ORDER BY order_id
+                 * testStarJoin()
+                 *     SELECT order_id, c.name, i.name iname, quantity, o.date 
+                 *     FROM joinOrderTable o 
+                 *     JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
+                 *     JOIN joinItemTable i ON o.item_id = i.item_id 
+                 *     ORDER BY order_id
                  */
                 "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
                 "    PARALLEL EQUI-JOIN 2 HASH TABLES:\n" +
@@ -443,12 +470,12 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
                 "            SERVER FILTER BY FIRST KEY ONLY",
                 /*
-                 * testStarJoin
-                 * SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date 
-                 * FROM joinOrderTable o 
-                 * JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
-                 * JOIN joinItemTable i ON o.item_id = i.item_id 
-                 * ORDER BY order_id
+                 * testStarJoin()
+                 *     SELECT (*NO_STAR_JOIN*) order_id, c.name, i.name iname, quantity, o.date 
+                 *     FROM joinOrderTable o 
+                 *     JOIN joinCustomerTable c ON o.customer_id = c.customer_id 
+                 *     JOIN joinItemTable i ON o.item_id = i.item_id 
+                 *     ORDER BY order_id
                  */
                 "CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
                 "    SERVER FILTER BY FIRST KEY ONLY\n" +
@@ -460,6 +487,33 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "            PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
                 "            BUILD HASH TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_customer",
+                /*
+                 * testSubJoin()
+                 *     SELECT * FROM joinCustomerTable c 
+                 *     INNER JOIN (joinOrderTable o 
+                 *         INNER JOIN (joinSupplierTable s 
+                 *             RIGHT JOIN joinItemTable i ON i.supplier_id = s.supplier_id)
+                 *         ON o.item_id = i.item_id)
+                 *     ON c.customer_id = o.customer_id
+                 *     WHERE c.customer_id <= '0000000005' 
+                 *         AND order_id != '000000000000003' 
+                 *         AND i.name != 'T3' 
+                 *     ORDER BY c.customer_id, i.name
+                 */
+                "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + JOIN_CUSTOMER_TABLE_DISPLAY_NAME + " [*] - ['0000000005']\n" +
+                "    SERVER SORTED BY [C.customer_id, I.0:NAME]\n" +
+                "CLIENT MERGE SORT\n" +
+                "    PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "    BUILD HASH TABLE 0\n" +
+                "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
+                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "            BUILD HASH TABLE 0\n" +
+                "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
+                "                    SERVER FILTER BY NAME != 'T3'\n" +
+                "                    PARALLEL EQUI-JOIN 1 HASH TABLES:\n" +
+                "                    BUILD HASH TABLE 0\n" +
+                "                        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_supplier",
                 }});
         return testCases;
     }
@@ -1309,13 +1363,16 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
     
     @Test
     public void testLeftRightJoin() throws Exception {
-        String query = "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
-            + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" RIGHT JOIN "
-            + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\" ORDER BY \"order_id\", s.\"supplier_id\" DESC";
+        String query1 = "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
+                + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" RIGHT JOIN "
+                + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\" ORDER BY \"order_id\", s.\"supplier_id\" DESC";
+        String query2 = "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
+                + "(" + JOIN_ITEM_TABLE_FULL_NAME + " i RIGHT JOIN " + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\")" 
+                + " ON o.\"item_id\" = i.\"item_id\" ORDER BY \"order_id\", s.\"supplier_id\" DESC";
         Properties props = new Properties(TEST_PROPERTIES);
         Connection conn = DriverManager.getConnection(getUrl(), props);
         try {
-            PreparedStatement statement = conn.prepareStatement(query);
+            PreparedStatement statement = conn.prepareStatement(query1);
             ResultSet rs = statement.executeQuery();
             assertTrue (rs.next());
             assertNull(rs.getString(1));
@@ -1367,21 +1424,9 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
             assertNotNull(rs.getDate(5));
 
             assertFalse(rs.next());
-        } finally {
-            conn.close();
-        }
-    }
-    
-    @Test
-    public void testMultiLeftJoin() throws Exception {
-        String query = "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
-            + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" LEFT JOIN "
-            + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\"";
-        Properties props = new Properties(TEST_PROPERTIES);
-        Connection conn = DriverManager.getConnection(getUrl(), props);
-        try {
-            PreparedStatement statement = conn.prepareStatement(query);
-            ResultSet rs = statement.executeQuery();
+            
+            statement = conn.prepareStatement(query2);
+            rs = statement.executeQuery();
             assertTrue (rs.next());
             assertEquals(rs.getString(1), "000000000000001");
             assertEquals(rs.getString(2), "T1");
@@ -1420,6 +1465,59 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
     }
     
     @Test
+    public void testMultiLeftJoin() throws Exception {
+        String[] queries = {
+                "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
+                        + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" LEFT JOIN "
+                        + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\"",
+                "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
+                        + "(" + JOIN_ITEM_TABLE_FULL_NAME + " i LEFT JOIN " + JOIN_SUPPLIER_TABLE_FULL_NAME + " s ON i.\"supplier_id\" = s.\"supplier_id\") " 
+                        + "ON o.\"item_id\" = i.\"item_id\""};
+        Properties props = new Properties(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            for (String query : queries) {
+                PreparedStatement statement = conn.prepareStatement(query);
+                ResultSet rs = statement.executeQuery();
+                assertTrue (rs.next());
+                assertEquals(rs.getString(1), "000000000000001");
+                assertEquals(rs.getString(2), "T1");
+                assertEquals(rs.getString(3), "S1");
+                assertEquals(rs.getInt(4), 1000);
+                assertNotNull(rs.getDate(5));
+                assertTrue (rs.next());
+                assertEquals(rs.getString(1), "000000000000002");
+                assertEquals(rs.getString(2), "T6");
+                assertEquals(rs.getString(3), "S6");
+                assertEquals(rs.getInt(4), 2000);
+                assertNotNull(rs.getDate(5));
+                assertTrue (rs.next());
+                assertEquals(rs.getString(1), "000000000000003");
+                assertEquals(rs.getString(2), "T2");
+                assertEquals(rs.getString(3), "S1");
+                assertEquals(rs.getInt(4), 3000);
+                assertNotNull(rs.getDate(5));
+                assertTrue (rs.next());
+                assertEquals(rs.getString(1), "000000000000004");
+                assertEquals(rs.getString(2), "T6");
+                assertEquals(rs.getString(3), "S6");
+                assertEquals(rs.getInt(4), 4000);
+                assertNotNull(rs.getDate(5));
+                assertTrue (rs.next());
+                assertEquals(rs.getString(1), "000000000000005");
+                assertEquals(rs.getString(2), "T3");
+                assertEquals(rs.getString(3), "S2");
+                assertEquals(rs.getInt(4), 5000);
+                assertNotNull(rs.getDate(5));
+
+                assertFalse(rs.next());
+            }
+        } finally {
+            conn.close();
+        }
+    }
+    
+    @Test
     public void testMultiRightJoin() throws Exception {
         String query = "SELECT \"order_id\", i.name, s.name, quantity, date FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o RIGHT JOIN " 
             + JOIN_ITEM_TABLE_FULL_NAME + " i ON o.\"item_id\" = i.\"item_id\" RIGHT JOIN "
@@ -2306,6 +2404,72 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
         }
 
     }
+    
+    @Test
+    public void testSubJoin() throws Exception {
+        String query1 = "SELECT i.name, count(c.name), min(s.name), max(quantity) FROM " + JOIN_ORDER_TABLE_FULL_NAME + " o LEFT JOIN " 
+                + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME + " s RIGHT JOIN " + JOIN_ITEM_TABLE_FULL_NAME + " i ON i.\"supplier_id\" = s.\"supplier_id\")" 
+                + " ON o.\"item_id\" = i.\"item_id\" LEFT JOIN " 
+                + JOIN_CUSTOMER_TABLE_FULL_NAME + " c ON c.\"customer_id\" = o.\"customer_id\" GROUP BY i.name ORDER BY i.name";
+        String query2 = "SELECT c.name, o.\"order_id\", i.name, s.name FROM " + JOIN_CUSTOMER_TABLE_FULL_NAME + " c INNER JOIN " 
+                + "(" + JOIN_ORDER_TABLE_FULL_NAME + " o INNER JOIN " 
+                + "(" + JOIN_SUPPLIER_TABLE_FULL_NAME + " s RIGHT JOIN " + JOIN_ITEM_TABLE_FULL_NAME + " i ON i.\"supplier_id\" = s.\"supplier_id\")" 
+                + " ON o.\"item_id\" = i.\"item_id\") ON c.\"customer_id\" = o.\"customer_id\"" 
+                + " WHERE c.\"customer_id\" <= '0000000005' AND \"order_id\" != '000000000000003' AND i.name != 'T3' ORDER BY c.\"customer_id\", i.name";
+        Properties props = new Properties(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            PreparedStatement statement = conn.prepareStatement(query1);
+            ResultSet rs = statement.executeQuery();
+            assertTrue (rs.next());
+            assertEquals(rs.getString(1), "T1");
+            assertEquals(rs.getInt(2), 1);
+            assertEquals(rs.getString(3), "S1");
+            assertEquals(rs.getInt(4), 1000);
+            assertTrue (rs.next());
+            assertEquals(rs.getString(1), "T2");
+            assertEquals(rs.getInt(2), 1);
+            assertEquals(rs.getString(3), "S1");
+            assertEquals(rs.getInt(4), 3000);
+            assertTrue (rs.next());
+            assertEquals(rs.getString(1), "T3");
+            assertEquals(rs.getInt(2), 1);
+            assertEquals(rs.getString(3), "S2");
+            assertEquals(rs.getInt(4), 5000);
+            assertTrue (rs.next());
+            assertEquals(rs.getString(1), "T6");
+            assertEquals(rs.getInt(2), 2);
+            assertEquals(rs.getString(3), "S6");
+            assertEquals(rs.getInt(4), 4000);
+
+            assertFalse(rs.next());
+            
+            statement = conn.prepareStatement(query2);
+            rs = statement.executeQuery();
+            assertTrue(rs.next());
+            assertEquals(rs.getString("c.name"), "C3");
+            assertEquals(rs.getString("O.order_id"), "000000000000002");
+            assertEquals(rs.getString("i.name"), "T6");
+            assertEquals(rs.getString("s.name"), "S6");
+            assertTrue(rs.next());
+            assertEquals(rs.getString("c.name"), "C4");
+            assertEquals(rs.getString("O.order_id"), "000000000000001");
+            assertEquals(rs.getString("i.name"), "T1");
+            assertEquals(rs.getString("s.name"), "S1");
+            assertTrue(rs.next());
+            assertEquals(rs.getString("c.name"), "C4");
+            assertEquals(rs.getString("O.order_id"), "000000000000004");
+            assertEquals(rs.getString("i.name"), "T6");
+            assertEquals(rs.getString("s.name"), "S6");
+
+            assertFalse(rs.next());            
+            
+            rs = conn.createStatement().executeQuery("EXPLAIN " + query2);
+            assertEquals(plans[13], QueryUtil.getExplainPlan(rs));
+        } finally {
+            conn.close();
+        }
+    }
 
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/12bd7aed/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 6ccd03e..4f1d107 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -629,23 +629,28 @@ parseOrderByField returns [OrderByNode ret]
 
 parseFrom returns [List<TableNode> ret]
 @init{ret = new ArrayList<TableNode>(4); }
-    :   t=table_ref {$ret.add(t);} (s=sub_table_ref { $ret.add(s); })*
-    ;
-    
-sub_table_ref returns [TableNode ret]
-    :   COMMA t=table_ref { $ret = t; }
-    |   t=join_spec { $ret = t; }
+    :   t=table_ref {$ret.add(t);} (COMMA s=table_ref { $ret.add(s); })*
     ;
 
 table_ref returns [TableNode ret]
+    : t=single_table_ref p=join_parts { $ret = factory.table(t, p); }
+    ;
+
+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); }
     |   LPAREN SELECT s=hinted_select_node RPAREN ((AS)? alias=identifier)? { $ret = factory.derivedTable(alias, s); }
     ;
 
-join_spec returns [TableNode ret]
-    :   j=join_type JOIN t=table_ref ON e=expression { $ret = factory.join(j, e, t); }
-    ;
+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; }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/12bd7aed/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 f43ff4f..500ae5f 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
@@ -146,7 +146,7 @@ public class FromCompiler {
     public static ColumnResolver getResolverForQuery(SelectStatement statement, PhoenixConnection connection)
     		throws SQLException {
     	List<TableNode> fromNodes = statement.getFrom();
-        if (fromNodes.size() == 1)
+        if (!statement.isJoin())
             return new SingleTableColumnResolver(connection, (NamedTableNode)fromNodes.get(0), true);
 
         MultiTableColumnResolver visitor = new MultiTableColumnResolver(connection);
@@ -337,7 +337,7 @@ public class FromCompiler {
     }
     
     // TODO: unused, but should be used for joins - make private once used
-    public static class MultiTableColumnResolver extends BaseColumnResolver implements TableNodeVisitor {
+    public static class MultiTableColumnResolver extends BaseColumnResolver implements TableNodeVisitor<Void> {
         private final ListMultimap<String, TableRef> tableMap;
         private final List<TableRef> tables;
 
@@ -353,17 +353,19 @@ public class FromCompiler {
         }
 
         @Override
-        public void visit(BindTableNode boundTableNode) throws SQLException {
+        public Void visit(BindTableNode boundTableNode) throws SQLException {
             throw new SQLFeatureNotSupportedException();
         }
 
         @Override
-        public void visit(JoinTableNode joinNode) throws SQLException {
-            joinNode.getTable().accept(this);
+        public Void visit(JoinTableNode joinNode) throws SQLException {
+            joinNode.getLHS().accept(this);
+            joinNode.getRHS().accept(this);
+            return null;
         }
 
         @Override
-        public void visit(NamedTableNode tableNode) throws SQLException {
+        public Void visit(NamedTableNode tableNode) throws SQLException {
             String alias = tableNode.getAlias();
             TableRef tableRef = createTableRef(tableNode, true);
             PTable theTable = tableRef.getTable();
@@ -378,10 +380,11 @@ public class FromCompiler {
             	tableMap.put(name, tableRef);
             }
             tables.add(tableRef);
+            return null;
         }
 
         @Override
-        public void visit(DerivedTableNode subselectNode) throws SQLException {
+        public Void visit(DerivedTableNode subselectNode) throws SQLException {
             throw new SQLFeatureNotSupportedException();
         }