/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.planner; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.voltdb.expressions.AbstractExpression; import org.voltdb.expressions.TupleValueExpression; import org.voltdb.plannodes.AbstractPlanNode; import org.voltdb.plannodes.AbstractScanPlanNode; import org.voltdb.plannodes.AggregatePlanNode; import org.voltdb.plannodes.IndexScanPlanNode; import org.voltdb.plannodes.NestLoopIndexPlanNode; import org.voltdb.plannodes.NestLoopPlanNode; import org.voltdb.plannodes.OrderByPlanNode; import org.voltdb.plannodes.SchemaColumn; import org.voltdb.plannodes.SeqScanPlanNode; import org.voltdb.types.ExpressionType; import org.voltdb.types.IndexLookupType; import org.voltdb.types.JoinType; import org.voltdb.types.PlanNodeType; public class TestPlansJoin extends PlannerTestCase { private static class JoinOp { private final String m_string; private final ExpressionType m_operator; private JoinOp(String string, ExpressionType operator) { m_string = string; m_operator = operator; } static JoinOp NOT_DISTINCT = new JoinOp(" IS NOT DISTINCT FROM ", ExpressionType.COMPARE_NOTDISTINCT); static JoinOp EQUAL = new JoinOp("=", ExpressionType.COMPARE_EQUAL); static JoinOp[] JOIN_OPS = new JoinOp[] {EQUAL, NOT_DISTINCT}; @Override public String toString() { return m_string; } ExpressionType toOperator() { return m_operator; } } public void testBasicInnerJoin() { String query; String pattern; AbstractPlanNode pn; // SELECT * with USING clause should contain only one column // for each column from the USING expression. query = "SELECT * FROM R1 JOIN R2 USING(C)"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); assertEquals(4, pn.getOutputSchema().getColumns().size()); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestBasicInnerJoin(joinOp); } query = "SELECT R2.C FROM R1 JOIN R2 USING(X)"; pattern = "user lacks privilege or object not found: X"; failToCompile(query, pattern); } private void perJoinOpTestBasicInnerJoin(JoinOp joinOp) { String query; String pattern; AbstractPlanNode pn; List<SchemaColumn> selectColumns; // SELECT * with ON clause should return all columns from all tables query = "SELECT * FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R1.A, R1.C, D FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R1.A, C, R1.D FROM R1 JOIN R2 USING(C)"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R1.A, R2.C, R1.D FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); assertEquals("R1", selectColumns.get(0).getTableName()); assertEquals("R2", selectColumns.get(1).getTableName()); // The output table for C can be either R1 or R2 because it's an INNER join query = "SELECT R1.A, C, R1.D FROM R1 JOIN R2 USING(C)"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); assertEquals("R1", selectColumns.get(0).getTableName()); String table = selectColumns.get(1).getTableName(); assertTrue("R2".equals(table) || "R1".equals(table)); table = selectColumns.get(2).getTableName(); assertEquals("R1", table); query = "SELECT R2.C FROM R1 JOIN R2 ON R1.X" + joinOp + "R2.X"; pattern = "user lacks privilege or object not found: R1.X"; failToCompile(query, pattern); query = "SELECT * FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C AND 1"; pattern = "data type of expression is not boolean"; failToCompile(query, pattern); query = "SELECT * FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C AND MOD(3,1)=1"; pattern = "Join with filters that do not depend on joined tables is not supported in VoltDB"; failToCompile(query, pattern); } public void testBasicThreeTableInnerJoin() { String query; String pattern; for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestBasicThreeTableInnerJoin(joinOp); } // Here C could be the C from USING(C), which would be R1.C or R2.C, or else // R3.C. Either is possible, and this is ambiguous. query = "SELECT C FROM R1 INNER JOIN R2 USING (C), R3 " + " WHERE R1.A = R3.A"; pattern = "Column \"C\" is ambiguous"; failToCompile(query, pattern); } private void perJoinOpTestBasicThreeTableInnerJoin(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; query = "SELECT * FROM R1 JOIN R2 ON R1.C" + joinOp + "R2.C JOIN R3 ON R3.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 7, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R1.C, R2.C R3.C FROM R1 INNER JOIN R2 ON R1.C" + joinOp + "R2.C INNER JOIN R3 ON R3.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT C FROM R1 INNER JOIN R2 USING (C) INNER JOIN R3 USING(C)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT C FROM R1 INNER JOIN R2 USING (C), R3_NOC WHERE R1.A" + joinOp + "R3_NOC.A"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, null, // weakened. soon, replace with: NESTLOOPINDEX, SEQSCAN? PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); if (joinOp == JoinOp.EQUAL) { // weaken test for now node = followAssertedLeftChain(node, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); } } public void testScanJoinConditions() { String query; AbstractPlanNode pn; AbstractPlanNode node; AbstractScanPlanNode scan; AbstractExpression predicate; query = "SELECT * FROM R1 WHERE R1.C = 0"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.SEQSCAN); scan = (AbstractScanPlanNode) node; predicate = scan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestScanJoinConditions(joinOp); } } private void perJoinOpTestScanJoinConditions(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; AbstractExpression predicate; boolean theOpIsOnTheLeft; query = "SELECT * FROM R1, R2 WHERE R1.A" + joinOp + "R2.A AND R1.C > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) node.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); query = "SELECT * FROM R1, R2 WHERE R1.A" + joinOp + "R2.A AND R1.C > R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); theOpIsOnTheLeft = (predicate != null) && (predicate.getLeft() != null) && predicate.getLeft().getExpressionType() == joinOp.toOperator(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, (theOpIsOnTheLeft ? joinOp.toOperator() : ExpressionType.COMPARE_LESSTHAN), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, (theOpIsOnTheLeft ? ExpressionType.COMPARE_LESSTHAN : joinOp.toOperator()), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); query = "SELECT * FROM R1 JOIN R2 ON R1.A" + joinOp + "R2.A WHERE R1.C > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); query = "SELECT * FROM R1 JOIN R2 ON R1.A" + joinOp + "R2.A WHERE R1.C > R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); theOpIsOnTheLeft = (predicate != null) && (predicate.getLeft() != null) && predicate.getLeft().getExpressionType() == joinOp.toOperator(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, (theOpIsOnTheLeft ? joinOp.toOperator() : ExpressionType.COMPARE_LESSTHAN), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, (theOpIsOnTheLeft ? ExpressionType.COMPARE_LESSTHAN : joinOp.toOperator()), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); query = "SELECT * FROM R1, R2, R3 WHERE R1.A" + joinOp + "R2.A AND R1.C" + joinOp + "R3.C AND R1.A > 0"; pn = compileToTopDownTree(query, 7, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); // Validate trivial child 1 before child 0 to free up local variable nlj. seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R3", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); nlj = (NestLoopPlanNode) nlj.getChild(0); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); query = "SELECT * FROM R1 JOIN R2 ON R1.A" + joinOp + "R2.A AND R1.C" + joinOp + "R2.C WHERE R1.A > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); query = "SELECT A, C FROM R1 JOIN R2 USING (A, C)"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); query = "SELECT A, C FROM R1 JOIN R2 USING (A, C) WHERE A > 0"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); query = "SELECT * FROM R1 JOIN R2 ON R1.A" + joinOp + "R2.A JOIN R3 ON R1.C" + joinOp + "R3.C WHERE R1.A > 0"; pn = compileToTopDownTree(query, 7, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); // Validate trivial child 1 before child 0 to free up local variable nlj. seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R3", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); nlj = (NestLoopPlanNode) nlj.getChild(0); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); } public void testDisplayColumnFromUsingCondition() { String query; List<AbstractPlanNode> lpn; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; AbstractExpression predicate; SeqScanPlanNode seqScan; SchemaColumn sc0; List<SchemaColumn> selectColumns; query = "SELECT max(A) FROM R1 JOIN R2 USING(A)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); for (SchemaColumn sc : selectColumns) { AbstractExpression e = sc.getExpression(); assertTrue(e instanceof TupleValueExpression); TupleValueExpression tve = (TupleValueExpression) e; assertNotSame(-1, tve.getColumnIndex()); } node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP); assertNotNull(AggregatePlanNode.getInlineAggregationNode(node)); query = "SELECT distinct(A) FROM R1 JOIN R2 USING(A)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); for (SchemaColumn sc : selectColumns) { AbstractExpression e = sc.getExpression(); assertTrue(e instanceof TupleValueExpression); TupleValueExpression tve = (TupleValueExpression) e; assertNotSame(-1, tve.getColumnIndex()); } query = "SELECT A FROM R1 JOIN R2 USING(A) ORDER BY A"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); for (SchemaColumn sc : selectColumns) { AbstractExpression e = sc.getExpression(); assertTrue(e instanceof TupleValueExpression); TupleValueExpression tve = (TupleValueExpression) e; assertNotSame(-1, tve.getColumnIndex()); } query = "SELECT * FROM P1 LABEL JOIN R2 USING(A) " + "WHERE A > 0 AND R2.C >= 5"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_GREATERTHANOREQUALTO, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); query = "SELECT * FROM P1 LABEL LEFT JOIN R2 USING(A) WHERE A > 0"; lpn = compileToFragments(query); node = followAssertedLeftChain(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertTrue(JoinType.LEFT == nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); query = "SELECT A FROM R2 LABEL RIGHT JOIN P1 AP1 USING(A) WHERE A > 0"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); pn = lpn.get(0); selectColumns = pn.getOutputSchema().getColumns(); assertEquals(1, selectColumns.size()); sc0 = selectColumns.get(0); assertEquals("AP1", sc0.getTableAlias()); assertEquals("P1", sc0.getTableName()); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); selectColumns = seqScan.getOutputSchema().getColumns(); assertEquals(1, selectColumns.size()); sc0 = selectColumns.get(0); assertEquals("AP1", sc0.getTableAlias()); assertEquals("P1", sc0.getTableName()); } public void testTransitiveValueEquivalenceConditions() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestTransitiveValueEquivalenceConditions(joinOp); } } private void perJoinOpTestTransitiveValueEquivalenceConditions(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; IndexScanPlanNode indexScan; AbstractExpression predicate; boolean theConstantIsOnTheLeft; // R1.A" + joinOp + "R2.A AND R2.A = 1 => R1.A = 1 AND R2.A = 1 query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.A" + joinOp + "R2.A AND R2.A = 1 "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getPreJoinPredicate(); theConstantIsOnTheLeft = (predicate != null) && (predicate.getLeft() != null) && (predicate.getLeft().getExpressionType() == ExpressionType.VALUE_CONSTANT); if (theConstantIsOnTheLeft) { assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_CONSTANT, ExpressionType.VALUE_TUPLE); } else { assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); } assertNull(nlj.getJoinPredicate()); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // Same test but now R2 is outer table R1.A " + // joinOp + "R2.A AND R2.A = 1 => R1.A = 1 AND R2.A = 1 query = "SELECT * FROM R2 LEFT JOIN R1 ON R1.A" + joinOp + "R2.A AND R2.A = 1 "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(nlj.getJoinPredicate()); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // R1.A" + joinOp + "R2.A AND R2.C = 1 => R1.A " + // joinOp + "R2.A AND R2.C = 1 query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.A" + joinOp + "R2.A AND R2.C = 1 "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); // R1.A" + joinOp + "R2.A AND ABS(R2.C) = 1 => R1.A " + // joinOp + "R2.A AND ABS(R2.C) = 1 query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.A" + joinOp + "R2.A AND ABS(R2.C) = 1 "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); // R1.A" + joinOp + "R3.A - NLIJ query = "SELECT * FROM R1 LEFT JOIN R3 ON R1.A" + joinOp + "R3.A"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, null); // PlanNodeType.NESTLOOPINDEX, // PlanNodeType.SEQSCAN); weakened for now if (joinOp == JoinOp.EQUAL) { // weaken test for now node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); } // R1.A" + joinOp + "R3.A AND R1.A = 4 => R3.A = 4 AND R1.A = 4 -- NLJ/IndexScan query = "SELECT * FROM R1 LEFT JOIN R3 ON R1.A" + joinOp + "R3.A AND R1.A = 4"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, null); // weakened for now node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(nlj.getJoinPredicate()); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); if (joinOp == JoinOp.EQUAL) { // weakened for now indexScan = (IndexScanPlanNode) nlj.getChild(1); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(indexScan.getPredicate()); assertEquals("R3", indexScan.getTargetTableName()); } // R1.A" + joinOp + "R3.A AND R3.A = 4 => R3.A = 4 AND R1.A = 4 -- NLJ/IndexScan query = "SELECT * FROM R1 LEFT JOIN R3 ON R1.A" + joinOp + "R3.A AND R3.A = 4"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getPreJoinPredicate(); theConstantIsOnTheLeft = (predicate != null) && (predicate.getLeft() != null) && (predicate.getLeft().getExpressionType() == ExpressionType.VALUE_CONSTANT); if (theConstantIsOnTheLeft) { assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_CONSTANT, ExpressionType.VALUE_TUPLE); } else { assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); } assertNull(nlj.getJoinPredicate()); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); // predicate = seqScan.getPredicate(); // assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, // ExpressionType.VALUE_TUPLE, // ExpressionType.VALUE_CONSTANT); indexScan = (IndexScanPlanNode) nlj.getChild(1); assertEquals("R3", indexScan.getTargetTableName()); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(indexScan.getPredicate()); } public void testFunctionJoinConditions() { String query; String pattern; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; AbstractExpression predicate; query = "SELECT * FROM R1, R2"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getJoinPredicate()); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestFunctionJoinConditions(joinOp); } // USING expression can have only comma separated list of column names query = "SELECT * FROM R1 JOIN R2 USING (ABS(A))"; pattern = "user lacks privilege or object not found: ABS"; failToCompile(query, pattern); } private void perJoinOpTestFunctionJoinConditions(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; AbstractExpression predicate; query = "SELECT * FROM R1 JOIN R2 ON ABS(R1.A) " + joinOp + " ABS(R2.A) "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.FUNCTION, ExpressionType.VALUE_TUPLE, ExpressionType.FUNCTION, ExpressionType.VALUE_TUPLE); query = "SELECT * FROM R1, R2 WHERE ABS(R1.A) " + joinOp + " ABS(R2.A) "; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn,PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.FUNCTION, ExpressionType.VALUE_TUPLE, ExpressionType.FUNCTION, ExpressionType.VALUE_TUPLE); } public void testIndexJoinConditions() { String query; AbstractPlanNode pn; IndexScanPlanNode indexScan; AbstractExpression predicate; //TODO: These are not even join queries. They should // probably be moved to some other test class. query = "SELECT * FROM R3 WHERE R3.A = 0"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) pn.getChild(0); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(indexScan.getPredicate()); query = "SELECT * FROM R3 WHERE R3.A > 0 AND R3.A < 5 AND R3.C = 4"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) pn.getChild(0); assertEquals(IndexLookupType.GT, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); for (JoinOp joinOp : JoinOp.JOIN_OPS) { if (joinOp != JoinOp.EQUAL) { // weaken test for now continue; } perJoinOpTestIndexJoinConditions(joinOp); } } private void perJoinOpTestIndexJoinConditions(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; NestLoopIndexPlanNode nlij; IndexScanPlanNode indexScan; AbstractExpression predicate; SeqScanPlanNode seqScan; query = "SELECT * FROM R3, R2 WHERE R3.A" + joinOp + "R2.A AND R3.C > 0 AND R2.C >= 5"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertNull(nlij.getJoinPredicate()); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlij.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHANOREQUALTO, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); query = "SELECT * FROM R3 JOIN R2 ON R3.A" + joinOp + "R2.A WHERE R3.C > 0 AND R2.C >= 5"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertNull(nlij.getJoinPredicate()); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlij.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHANOREQUALTO, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); query = "SELECT * FROM R3 JOIN R2 USING(A) WHERE R3.C > 0 AND R2.C >= 5"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertNull(nlij.getJoinPredicate()); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlij.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHANOREQUALTO, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); query = "SELECT * FROM R3 JOIN R2 ON R3.A" + joinOp + " R2.A JOIN R1 ON R2.A" + joinOp + "R1.A WHERE R3.C > 0 AND R2.C >= 5"; pn = compileToTopDownTree(query, 7, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); nlij = (NestLoopIndexPlanNode) nlj.getChild(0); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlij.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHANOREQUALTO, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); } public void testOpIndexInnerJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { if (joinOp != JoinOp.EQUAL) { // weaken test for now continue; } perJoinTestOpIndexInnerJoin(joinOp); } } private void perJoinTestOpIndexInnerJoin(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; IndexScanPlanNode indexScan; query = "SELECT * FROM R3 JOIN R1 ON R1.C" + joinOp + "R3.A"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); // Test ORDER BY optimization on indexed self-join, ordering by LHS query = "SELECT X.A FROM R5 X, R5 Y WHERE X.A" + joinOp + "Y.A ORDER BY X.A"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) node; assertEquals("X", indexScan.getTargetTableAlias()); // Test ORDER BY optimization on indexed self-join, ordering by RHS query = "SELECT X.A FROM R5 X, R5 Y WHERE X.A" + joinOp + "Y.A ORDER BY Y.A"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) node; assertEquals("Y", indexScan.getTargetTableAlias()); // Test safety guarding misapplication of ORDER BY optimization on indexed self-join, // when ordering by combination of LHS and RHS columns. // These MAY become valid optimization cases when ENG-4728 is done, // using transitive equality to determine that the ORDER BY clause can be re-expressed // as being based on only one of the two table scans. query = "SELECT X.A, X.C FROM R4 X, R4 Y WHERE X.A" + joinOp + "Y.A ORDER BY X.A, Y.C"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); query = "SELECT X.A FROM R4 X, R4 Y WHERE X.A" + joinOp + "Y.A ORDER BY Y.A, X.C"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); } public void testMultiColumnJoin() { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; NestLoopIndexPlanNode nlij; IndexScanPlanNode indexScan; AbstractExpression predicate; // Test multi column condition on non index columns query = "SELECT A, C FROM R2 JOIN R1 USING(A, C)"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); query = "SELECT A, C FROM R3 JOIN R2 USING(A, C)"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); // Test multi column condition on index columns query = "SELECT A FROM R2 JOIN R3 USING(A)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestMultiColumnJoin(joinOp); } } private void perJoinOpTestMultiColumnJoin(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; NestLoopIndexPlanNode nlij; IndexScanPlanNode indexScan; AbstractExpression predicate; query = "SELECT R1.A, R2.A FROM R2 JOIN R1 ON R1.A" + joinOp + "R2.A AND R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); if (joinOp != JoinOp.EQUAL) { // weaken test for now return; } query = "SELECT R3.A, R2.A FROM R2 JOIN R3 ON R3.A" + joinOp + "R2.A"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); query = "SELECT R3.A, R2.A FROM R3 JOIN R2 ON R3.A" + joinOp + "R2.A AND R3.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); } public void testDistributedInnerJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestDistributedInnerJoin(joinOp); } } private void perJoinOpTestDistributedInnerJoin(JoinOp joinOp) { String query; String pattern; List<AbstractPlanNode> lpn; // JOIN replicated and one distributed table query = "SELECT * FROM R1 JOIN P2 ON R1.C" + joinOp + "P2.A"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); if (joinOp == JoinOp.EQUAL) { assertTopDownTree(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); } // Join multiple distributed tables on the partitioned column query = "SELECT * FROM P1 JOIN P2 USING(A)"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); if (joinOp == JoinOp.EQUAL) { assertTopDownTree(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); } // Two Distributed tables join on non-partitioned column query = "SELECT * FROM P1 JOIN P2 ON P1.C" + joinOp + "P2.E"; pattern = "This query is not plannable. The planner cannot guarantee that all rows would be in a single partition."; failToCompile(query, pattern); // Two Distributed tables join on boolean constant query = "SELECT * FROM P1 JOIN P2 ON 1=1"; pattern = "This query is not plannable. The planner cannot guarantee that all rows would be in a single partition."; failToCompile(query, pattern); } public void testBasicOuterJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestBasicOuterJoin(joinOp); } } private void perJoinOpTestBasicOuterJoin(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; AbstractExpression predicate; // SELECT * with ON clause should return all columns from all tables query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C AND R1.A = 5"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); predicate = nlj.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R1", seqScan.getTargetTableName()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); } public void testRightOuterJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestRightOuterJoin(joinOp); } } private void perJoinOpTestRightOuterJoin(JoinOp joinOp) { String query; List<AbstractPlanNode> lpn; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; AbstractExpression predicate; // SELECT * FROM R1 RIGHT JOIN R2 ON R1.C " + // joinOp + "R2.C => SELECT * FROM R2 LEFT JOIN R1 ON R1.C" + joinOp + "R2.C query = "SELECT * FROM R1 RIGHT JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R2", seqScan.getTargetTableName()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R1", seqScan.getTargetTableName()); // Same but with distributed table query = "SELECT * FROM P1 RIGHT JOIN R2 ON P1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertReplicatedLeftJoinCoordinator(lpn, "R2"); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.SEQSCAN); seqScan = (SeqScanPlanNode) node; assertEquals("P1", seqScan.getTargetTableName()); } public void testSeqScanOuterJoinCondition() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestSeqScanOuterJoinCondition(joinOp); } } private void perJoinOpTestSeqScanOuterJoinCondition(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; AbstractExpression predicate; SeqScanPlanNode seqScan; IndexScanPlanNode indexScan; // R1.C" + joinOp + "R2.C Inner-Outer join Expr stays at the NLJ as Join predicate query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); // R1.C" + joinOp + "R2.C Inner-Outer join Expr stays at the NLJ as Join predicate // R1.A > 0 Outer Join Expr stays at the the NLJ as pre-join predicate // R2.A < 0 Inner Join Expr is pushed down to the inner SeqScan node query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C AND R1.A > 0 AND R2.A < 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; predicate = nlj.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // R1.C" + joinOp + "R2.C Inner-Outer join Expr stays at the NLJ as Join predicate // (R1.A > 0 OR R2.A < 0) Inner-Outer join Expr stays at the NLJ as Join predicate query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C AND (R1.A > 0 OR R2.A < 0)"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); boolean theOrIsOnTheLeft = (predicate != null) && (predicate.getLeft() != null) && (ExpressionType.CONJUNCTION_OR == predicate.getLeft().getExpressionType()); if (theOrIsOnTheLeft) { assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.CONJUNCTION_OR, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); } else { assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.CONJUNCTION_OR, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); } assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); // R1.C" + joinOp + "R2.C Inner-Outer join Expr stays at the NLJ as Join predicate // R1.A > 0 Outer Where Expr is pushed down to the outer SeqScan node // R2.A IS NULL Inner Where Expr stays at the the NLJ as post join (where) predicate // (R1.C > R2.C OR R2.C IS NULL) Inner-Outer Where stays at the the NLJ as post join (where) predicate query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE R1.A > 0 AND R2.A IS NULL AND (R1.C > R2.C OR R2.C IS NULL)"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = nlj.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.CONJUNCTION_OR, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); seqScan = (SeqScanPlanNode) nlj.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); // R3.A" + joinOp + "R2.A Inner-Outer index join Expr. NLJ predicate. // R3.A > 3 Index Outer where expr pushed down to IndexScanPlanNode // R3.C < 0 non-index Outer where expr pushed down to IndexScanPlanNode as a predicate query = "SELECT * FROM R3 LEFT JOIN R2 ON R3.A" + joinOp + "R2.A WHERE R3.A > 3 AND R3.C < 0"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.INDEXSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); indexScan = (IndexScanPlanNode) nlj.getChild(0); assertEquals(IndexLookupType.GT, indexScan.getLookupType()); assertNull(indexScan.getEndExpression()); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // R3.C" + joinOp + "R2.C Inner-Outer non-index join Expr. NLJ predicate. // R3.A > 3 Index null rejecting inner where expr pushed down to IndexScanPlanNode // NLJ is simplified to be INNER query = "SELECT * FROM R2 LEFT JOIN R3 ON R3.C" + joinOp + "R2.C WHERE R3.A > 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.INNER, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R2", seqScan.getTargetTableName()); assertNull(seqScan.getPredicate()); indexScan = (IndexScanPlanNode) nlj.getChild(1); assertEquals(IndexLookupType.GT, indexScan.getLookupType()); assertNull(indexScan.getEndExpression()); assertNull(indexScan.getPredicate()); if (joinOp != JoinOp.EQUAL) { // weaken test for now return; } query = "SELECT * FROM R2 LEFT JOIN R3 ON R3.A" + joinOp + "R2.C WHERE R3.A > 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); NestLoopIndexPlanNode nlij = (NestLoopIndexPlanNode) node; assertEquals(JoinType.INNER, nlij.getJoinType()); } public void testDistributedSeqScanOuterJoinCondition() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestDistributedSeqScanOuterJoinCondition(joinOp); } } private void perJoinOpTestDistributedSeqScanOuterJoinCondition(JoinOp joinOp) { // Distributed Outer table String query; String pattern; List<AbstractPlanNode> lpn; AbstractPlanNode pn; query = "SELECT * FROM P1 LEFT JOIN R2 ON P1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertEquals(2, lpn.size()); assertProjectingCoordinator(lpn); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // Distributed Inner table query = "SELECT * FROM R2 LEFT JOIN P1 ON P1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertEquals(2, lpn.size()); assertReplicatedLeftJoinCoordinator(lpn, "R2"); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.SEQSCAN); // Distributed Inner and Outer table joined on the partition column query = "SELECT * FROM P1 LEFT JOIN P4 ON P1.A" + joinOp + "P4.A"; lpn = compileToFragments(query); assertEquals(2, lpn.size()); assertTopDownTree(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // Distributed Inner and Outer table joined on the non-partition column query = "SELECT * FROM P1 LEFT JOIN P4 ON P1.A" + joinOp + "P4.E"; pattern = "This query is not plannable. The planner cannot guarantee that all rows would be in a single partition"; failToCompile(query, pattern); } public void testBasicIndexOuterJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestBasicIndexOuterJoin(joinOp); } } private void perJoinOpTestBasicIndexOuterJoin(JoinOp joinOp) { // R3 is indexed but it's the outer table and the join expression // must stay at the NLJ so the index can't be used String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopIndexPlanNode nlij; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; IndexScanPlanNode indexScan; AbstractExpression predicate; query = "SELECT * FROM R3 LEFT JOIN R2 ON R3.A" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R3", seqScan.getTargetTableName()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); // R3 is indexed but it's the outer table so index can't be used query = "SELECT * FROM R2 RIGHT JOIN R3 ON R3.A" + joinOp + "R2.C"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertEquals("R3", seqScan.getTargetTableName()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertEquals("R2", seqScan.getTargetTableName()); if (joinOp != JoinOp.EQUAL) { // weaken test for now return; } query = "SELECT * FROM R2 LEFT JOIN R3 ON R2.C" + joinOp + "R3.A"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(JoinType.LEFT, nlij.getJoinType()); seqScan = (SeqScanPlanNode) nlij.getChild(0); assertEquals("R2", seqScan.getTargetTableName()); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); assertEquals("R3", indexScan.getTargetTableName()); } public void testIndexOuterJoinConditions() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { if (joinOp != JoinOp.EQUAL) { // weaken test for now continue; } perJoinOpTestIndexOuterJoinConditions(joinOp); } } private void perJoinOpTestIndexOuterJoinConditions(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopIndexPlanNode nlij; NestLoopPlanNode nlj; IndexScanPlanNode indexScan; AbstractExpression predicate; SeqScanPlanNode seqScan; // R1.C" + joinOp + "R3.A Inner-Outer index join Expr. NLIJ/Inlined IndexScan // R3.C > 0 Inner Join Expr is pushed down to the inlined IndexScan node as a predicate // R2.A < 6 Outer Join Expr is a pre-join predicate for NLIJ query = "SELECT * FROM R2 LEFT JOIN R3 ON R3.A" + joinOp + "R2.A AND R3.C > 0 AND R2.A < 6"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(JoinType.LEFT, nlij.getJoinType()); predicate = nlij.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); assertNull(nlij.getJoinPredicate()); assertNull(nlij.getWherePredicate()); seqScan = (SeqScanPlanNode) node.getChild(0); assertNull(seqScan.getPredicate()); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // R1.C" + joinOp + "R3.A Inner-Outer non-index join Expr. NLJ/IndexScan // R3.A > 0 Inner index Join Expr is pushed down to the inner IndexScan node as an index // R3.C != 0 Non-index Inner Join Expression is pushed down to the inner IndexScan node as a predicate // R2.A < 6 Outer Join Expr is a pre-join predicate for NLJ query = "SELECT * FROM R2 LEFT JOIN R3 ON R3.C" + joinOp + "R2.A AND R3.A > 0 AND R3.C != 0 AND R2.A < 6"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(JoinType.LEFT, nlj.getJoinType()); predicate = nlj.getPreJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); indexScan = (IndexScanPlanNode) nlj.getChild(1); assertEquals(IndexLookupType.GT, indexScan.getLookupType()); assertNull(indexScan.getEndExpression()); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_NOTEQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // R2.A" + joinOp + "R3.A Inner-Outer index join Expr. NLIJ/Inlined IndexScan // R3.A IS NULL Inner where expr - part of the NLIJ where predicate // R2.A < 6 OR R3.C IS NULL Inner-Outer where expr - part of the NLIJ where predicate // R2.A > 3 Outer where expr - pushed down to the outer node query = "SELECT * FROM R2 LEFT JOIN R3 ON R3.A" + joinOp + "R2.A WHERE R3.A IS NULL AND R2.A > 3 AND (R2.A < 6 OR R3.C IS NULL)"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(nlij.getJoinType(), JoinType.LEFT); assertNull(nlij.getPreJoinPredicate()); assertNull(nlij.getJoinPredicate()); predicate = nlij.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.CONJUNCTION_OR, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); seqScan = (SeqScanPlanNode) nlij.getChild(0); predicate = seqScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); } public void testDistributedInnerOuterTable() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestDistributedInnerOuterTable(joinOp); } } private void perJoinOpTestDistributedInnerOuterTable(JoinOp joinOp) { // Distributed Outer table String query; String pattern; List<AbstractPlanNode> lpn; AbstractPlanNode pn; query = "SELECT * FROM P1 LEFT JOIN R2 ON P1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); assertEquals(2, lpn.size()); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // Distributed Inner table query = "SELECT * FROM R2 LEFT JOIN P1 ON P1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertReplicatedLeftJoinCoordinator(lpn, "R2"); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.SEQSCAN); // Distributed Inner and Outer table joined on the partition column query = "SELECT * FROM P1 LEFT JOIN P4 ON P1.A" + joinOp + "P4.A"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); assertEquals(2, lpn.size()); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // Distributed Inner and Outer table joined on the non-partition column query = "SELECT * FROM P1 LEFT JOIN P4 ON P1.A" + joinOp + "P4.E"; pattern = "This query is not plannable. The planner cannot guarantee that all rows would be in a single partition"; failToCompile(query, pattern); } public void testDistributedIndexJoinConditions() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { if (joinOp != JoinOp.EQUAL) { // weaken test for now continue; } perJoinOpDistributedIndexJoinConditions(joinOp); } } private void perJoinOpDistributedIndexJoinConditions(JoinOp joinOp) { // Distributed outer table, replicated inner -NLIJ/inlined IndexScan String query; List<AbstractPlanNode> lpn; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; NestLoopIndexPlanNode nlij; IndexScanPlanNode indexScan; AbstractExpression predicate; // query = "SELECT * FROM P1 LEFT JOIN R3 ON P1.C" + // joinOp + "R3.A"; // lpn = compileToFragments(query); // assertEquals(2, lpn.size()); // pn = lpn.get(1).getChild(0); // assertTrue(node instanceof NestLoopIndexPlanNode); // assertEquals(1, pn.getChildCount()); // assertTrue(n.getChild(0) instanceof SeqScanPlanNode); // Distributed inner and replicated outer tables -NLJ/IndexScan query = "SELECT * FROM R3 LEFT JOIN P2 ON R3.A" + joinOp + "P2.A AND P2.A < 0 AND P2.E > 3 WHERE P2.A IS NULL"; lpn = compileToFragments(query); assertEquals(2, lpn.size()); //*enable to debug*/printExplainPlan(lpn); assertReplicatedLeftJoinCoordinator(lpn, "R3"); pn = lpn.get(0); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = nlj.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); pn = lpn.get(1); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) node; assertEquals(IndexLookupType.LT, indexScan.getLookupType()); assertNull(indexScan.getEndExpression()); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT, ExpressionType.OPERATOR_NOT, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); // Distributed inner and outer tables -NLIJ/inlined IndexScan query = "SELECT * FROM P2 RIGHT JOIN P3 ON P3.A" + joinOp + "P2.A AND P2.A < 0 WHERE P2.A IS NULL"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); pn = lpn.get(1); assertTopDownTree(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(JoinType.LEFT, nlij.getJoinType()); assertNull(nlij.getPreJoinPredicate()); assertNull(nlij.getJoinPredicate()); predicate = nlij.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = indexScan.getPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_LESSTHAN, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); } public void testNonSupportedJoin() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestNonSupportedJoin(joinOp); } } private void perJoinOpTestNonSupportedJoin(JoinOp joinOp) { String query; String pattern; // JOIN with parentheses (HSQL limitation) query = "SELECT R2.C FROM (R1 JOIN R2 ON R1.C" + joinOp + "R2.C) JOIN R3 ON R1.C" + joinOp + "R3.C"; pattern = "user lacks privilege or object not found: R1.C"; failToCompile(query, pattern); // JOIN with join hierarchy (HSQL limitation) query = "SELECT * FROM R1 JOIN R2 JOIN R3 ON R1.C" + joinOp + "R2.C ON R1.C" + joinOp + "R3.C"; pattern = "unexpected token"; failToCompile(query, pattern); } public void testOuterJoinSimplification() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestOuterJoinSimplification(joinOp); } } private void perJoinOpTestOuterJoinSimplification(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; NestLoopPlanNode nlj; SeqScanPlanNode seqScan; IndexScanPlanNode indexScan; AbstractExpression predicate; NestLoopIndexPlanNode nlij; query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE R2.C IS NOT NULL"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE R2.C > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 RIGHT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE R1.C > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R3 ON R1.C" + joinOp + "R3.C WHERE R3.A > 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R3 ON R1.C" + joinOp + "R3.A WHERE R3.A > 0"; if (joinOp == JoinOp.EQUAL) { // weaken test for now pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(nlij.getJoinType(), JoinType.INNER); } query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R2.C) < 10"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 RIGHT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R1.C) < 10"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R1.C) < 10"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.LEFT); query = "SELECT * FROM R1 RIGHT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R2.C) < 10"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.LEFT); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R2.C) < 10 AND R1.C = 3"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R2.C) < 10 OR R2.C IS NOT NULL"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R1.C) < 10 AND R1.C > 3"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.LEFT); query = "SELECT * FROM R1 LEFT JOIN R2 ON R1.C" + joinOp + "R2.C WHERE ABS(R1.C) < 10 OR R2.C IS NOT NULL"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.LEFT); // Test with seqscan with different filers. query = "SELECT R2.A, R1.* FROM R1 LEFT OUTER JOIN R2 ON R2.A" + joinOp + "R1.A WHERE R2.A > 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); //* enable for debug */ System.out.println(pn.toExplainPlanString()); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.INNER); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(nlj.getWherePredicate()); query = "SELECT R2.A, R1.* FROM R1 LEFT OUTER JOIN R2 ON R2.A" + joinOp + "R1.A WHERE R2.A IS NULL"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP); nlj = (NestLoopPlanNode) node; assertEquals(nlj.getJoinType(), JoinType.LEFT); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = nlj.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); seqScan = (SeqScanPlanNode) nlj.getChild(0); assertNull(seqScan.getPredicate()); seqScan = (SeqScanPlanNode) nlj.getChild(1); assertNull(seqScan.getPredicate()); if (joinOp != JoinOp.EQUAL) { // weaken test for now return; } query = "SELECT b.A, a.* FROM R1 a LEFT OUTER JOIN R4 b ON b.A" + joinOp + "a.A AND b.C " + joinOp + " a.C AND a.D " + joinOp + " b.D WHERE b.A IS NULL"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); //* enable for debug */ System.out.println(pn.toExplainPlanString()); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(nlij.getJoinType(), JoinType.LEFT); assertNull(nlij.getPreJoinPredicate()); assertNull(nlij.getJoinPredicate()); predicate = nlij.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, ExpressionType.CONJUNCTION_AND, ExpressionType.CONJUNCTION_AND, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); query = "SELECT b.A, a.* FROM R1 a LEFT OUTER JOIN R4 b ON b.A" + joinOp + "a.A AND b.C " + joinOp + " a.C AND a.D " + joinOp + " b.D WHERE b.B + b.A IS NULL"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(nlij.getJoinType(), JoinType.LEFT); assertNull(nlij.getPreJoinPredicate()); assertNull(nlij.getJoinPredicate()); predicate = nlij.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.OPERATOR_PLUS, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); query = "SELECT a.* FROM R1 a LEFT OUTER JOIN R5 b ON b.A" + joinOp + "a.A WHERE b.A IS NULL"; pn = compileToTopDownTree(query, 3, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; assertEquals(nlij.getJoinType(), JoinType.LEFT); assertNull(nlij.getPreJoinPredicate()); assertNull(nlij.getJoinPredicate()); predicate = nlij.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE); } public void testMoreThan5TableJoins() { for (JoinOp joinOp : JoinOp.JOIN_OPS) { if (joinOp != JoinOp.EQUAL) { // weaken test for now continue; } perJoinOpTestMoreThan5TableJoins(joinOp); } } private void perJoinOpTestMoreThan5TableJoins(JoinOp joinOp) { String query; List<AbstractPlanNode> lpn; // INNER JOIN with >5 tables. query = "SELECT R1.C FROM R3, R2, P1, P2, P3, R1 WHERE R3.A" + joinOp + "R2.A AND R2.A" + joinOp + "P1.A AND P1.A" + joinOp + "P2.A AND P3.A" + joinOp + "P2.A AND R1.C" + joinOp + "R2.C"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); assertTopDownTree(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOPINDEX, PlanNodeType.NESTLOOPINDEX, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // OUTER JOIN with >5 tables. query = "SELECT R1.C FROM R3, R2, P1, P2, P3 LEFT OUTER JOIN R1 ON R1.C" + joinOp + "R2.C WHERE R3.A" + joinOp + "R2.A AND R2.A" + joinOp + "P1.A AND P1.A" + joinOp + "P2.A AND P3.A" + joinOp + "P2.A"; lpn = compileToFragments(query); assertProjectingCoordinator(lpn); assertTopDownTree(lpn.get(1), PlanNodeType.SEND, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOPINDEX, PlanNodeType.NESTLOOPINDEX, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); } public void testAmbigousIdentifierInSelectList() throws Exception { String query; String pattern; // Since A is in the using list, lr.a and rr.a are the same. // This is not ambiguous. The two aliases reference the same column. query = "SELECT R1.A, A FROM R1 WHERE A > 0;"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.SEQSCAN); query = "SELECT lr.a FROM r1 lr, r1 rr ORDER BY a;"; compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT lr.a alias, lr.a, a, lr.a+1 aliasexp, lr.a+1, a+1 " + "FROM r1 lr ORDER BY a;"; compileToTopDownTree(query, 6, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.SEQSCAN); query = "SELECT lr.a a, a FROM r1 lr JOIN r1 rr using (a) ORDER BY a;"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT lr.a a, rr.a FROM r1 lr JOIN r1 rr using (a) ORDER BY a;"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // This is not actually an ambiguous query. This is actually ok. query = "SELECT * FROM R2 WHERE A IN (SELECT A FROM R1);"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.SEQSCAN); query = "SELECT R3.C, C FROM R1 INNER JOIN R2 USING(C) " + " INNER JOIN R3 USING(C);"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // This one is ok too. There are several common columns in R2, R1. // But they are fully qualified as R1.A, R2.A and so forth when * // is expanded. query = "SELECT * FROM R2, R1"; compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R1.C FROM R1 INNER JOIN R2 USING (C), R3"; compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R2.C FROM R1 INNER JOIN R2 USING (C), R3"; compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT R3.C, R1.C FROM R1 INNER JOIN R2 USING(C), R3;"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); query = "SELECT C, C FROM R1 GROUP BY C ORDER BY C;"; compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.ORDERBY, PlanNodeType.PROJECTION, PlanNodeType.SEQSCAN); query = "SELECT lr.a a, rr.a a FROM r1 lr, r2 rr ORDER BY a;"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: LR.A, RR.A"; failToCompile(query, pattern); // Simple ambiguous column reference. query = "SELECT A, C FROM R1, R2;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R2"; failToCompile(query, pattern); // Ambiguous reference in an arithmetic expression. query = "SELECT A + C FROM R1, R2;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R2"; failToCompile(query, pattern); query = "SELECT sqrt(A) FROM R1, R2;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R2"; failToCompile(query, pattern); // Ambiguous reference in a WHERE clause. query = "SELECT NOTC FROM R1, R3_NOC WHERE A > 100;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC WHERE A > sqrt(NOTC);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC WHERE sqrt(A) > sqrt(NOTC);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1 JOIN R3_NOC ON sqrt(A) > sqrt(NOTC);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); // Ambiguous reference to an unconstrained column in a join. That is, // C is in both R1 and R3, R1 and R3 are joined together, but not on C. // Note that we test above for a similar case, with three joined tables. query = "SELECT C FROM R1 INNER JOIN R3 USING(A);"; pattern = "Column \"C\" is ambiguous. It's in tables: R1, R3"; failToCompile(query, pattern); query = "SELECT C FROM R1 INNER JOIN R3 using(C), R2;"; pattern = "Column \"C\" is ambiguous. It's in tables: USING(C), R2"; failToCompile(query, pattern); // Ambiguous references in GROUP BY expressions. query = "SELECT NOTC FROM R1, R3_NOC GROUP BY A;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC GROUP BY sqrt(A);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT sqrt(R1.A) FROM R1, R3_NOC GROUP BY R1.A having count(A) + 2 * sum(A) > 2;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); // Ambiguous references in subqueries. query = "SELECT ALPHA FROM (SELECT SQRT(A) AS ALPHA FROM R1) AS S1, (SELECT SQRT(C) AS ALPHA FROM R1) AS S2;"; pattern = "Column \"ALPHA\" is ambiguous. It's in tables: S1, S2"; failToCompile(query, pattern); query = "SELECT ALPHA FROM (SELECT SQRT(A), SQRT(C) FROM R1, R3) AS S1, (SELECT SQRT(C) AS ALPHA FROM R1) AS S2;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3"; failToCompile(query, pattern); query = "select R3.C, C from R1 inner join R2 using(C) inner join R3 on C=R3.A;"; pattern = "Column \"C\" is ambiguous. It's in tables: USING(C), R3"; failToCompile(query, pattern); // Ambiguous columns in an ORDER BY expression. query = "SELECT LR.A, RR.A FROM R1 LR, R1 RR ORDER BY A;"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: LR.A, RR.A."; failToCompile(query, pattern); // Note that LT.A and RT.A are not considered here. query = "SELECT LT.A AS LA, RT.A AS RA FROM R1 AS LT, R1 AS RT ORDER BY A;"; pattern = "Column \"A\" is ambiguous. It's in tables: LT, RT"; failToCompile(query, pattern); // Two columns in the SELECT list with the same name. This complicates // checking for ORDER BY aliases. query = "SELECT LT.A AS LA, RT.A AS LA FROM R1 AS LT, R1 AS RT ORDER BY LA;"; pattern = "The name \"LA\" in an order by expression is ambiguous. It's in columns: LA(0), LA(1)"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC ORDER BY A;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC ORDER BY sqrt(A);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); // Ambiguous columns in an ORDER BY expression. query = "SELECT LR.A, RR.A FROM R1 LR, R1 RR ORDER BY A;"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: LR.A, RR.A"; failToCompile(query, pattern); // Note that LT.A and RT.A are not considered here. query = "SELECT LT.A AS LA, RT.A AS RA FROM R1 AS LT, R1 AS RT ORDER BY A;"; pattern = "Column \"A\" is ambiguous. It's in tables: LT, RT"; failToCompile(query, pattern); query = "SELECT LT.A, RT.A FROM R1 AS LT, R1 AS RT ORDER BY A"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: LT.A, RT.A"; failToCompile(query, pattern); // Two columns in the SELECT list with the same name. This complicates // checking for ORDER BY aliases. query = "SELECT (R1.A + 1) A, A FROM R1 ORDER BY A"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: A(0), R1.A."; failToCompile(query, pattern); query = "SELECT LT.A AS LA, RT.A AS LA FROM R1 AS LT, R1 AS RT ORDER BY LA;"; pattern = "The name \"LA\" in an order by expression is ambiguous. It's in columns: LA(0), LA(1)"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC ORDER BY A;"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); query = "SELECT NOTC FROM R1, R3_NOC ORDER BY sqrt(A);"; pattern = "Column \"A\" is ambiguous. It's in tables: R1, R3_NOC"; failToCompile(query, pattern); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestAmbigousIdentifierInSelectList(joinOp); } } private void perJoinOpTestAmbigousIdentifierInSelectList(JoinOp joinOp) throws Exception { String query; String pattern; // R1 JOIN R2 ON R1.A" + joinOp + "R2.A is not R1 JOIN R2 using(A). query = "SELECT lr.a a, rr.a a FROM r1 lr JOIN r1 rr ON lr.a " + joinOp + "rr.a ORDER BY a;"; pattern = "The name \"A\" in an order by expression is ambiguous. It's in columns: LR.A, RR.A"; failToCompile(query, pattern); query = "SELECT R1.C FROM R1 INNER JOIN R2 USING (C), R3 WHERE R1.A" + joinOp + "R3.A"; compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, null, PlanNodeType.SEQSCAN); query = "SELECT C FROM R1 INNER JOIN R2 using(C), R3 WHERE R1.A" + joinOp + "R3.A;"; pattern = "Column \"C\" is ambiguous. It's in tables: USING(C), R3"; failToCompile(query, pattern); } public void testUsingColumns() { String query; AbstractPlanNode pn; OrderByPlanNode orderBy; NestLoopPlanNode nlj; AggregatePlanNode aggr; List<SchemaColumn> selectColumns; SchemaColumn col; AbstractExpression colExp; AbstractExpression predicate; // Test USING column query = "SELECT MAX(R1.A), C FROM R1 FULL JOIN R2 USING (C) " + "WHERE C > 0 GROUP BY C ORDER BY C"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.ORDERBY, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); // ORDER BY column orderBy = (OrderByPlanNode) pn.getChild(0); List<AbstractExpression> s = orderBy.getSortExpressions(); assertEquals(1, s.size()); assertEquals(ExpressionType.VALUE_TUPLE, s.get(0).getExpressionType()); // WHERE nlj = (NestLoopPlanNode) orderBy.getChild(0); assertNull(nlj.getPreJoinPredicate()); predicate = nlj.getJoinPredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_EQUAL, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); predicate = nlj.getWherePredicate(); assertExprTopDownTree(predicate, ExpressionType.COMPARE_GREATERTHAN, ExpressionType.OPERATOR_CASE_WHEN, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE, ExpressionType.OPERATOR_ALTERNATIVE, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_CONSTANT); // GROUP BY aggr = (AggregatePlanNode) nlj.getInlinePlanNode(PlanNodeType.HASHAGGREGATE); assertNotNull(aggr); List<AbstractExpression> g = aggr.getGroupByExpressions(); assertEquals(1, g.size()); assertExprTopDownTree(g.get(0), ExpressionType.OPERATOR_CASE_WHEN, ExpressionType.OPERATOR_IS_NULL, ExpressionType.VALUE_TUPLE, ExpressionType.OPERATOR_ALTERNATIVE, ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); // Test three table full join query = "SELECT C FROM R1 FULL JOIN R2 USING (C) FULL JOIN R3 USING (C)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); col = selectColumns.get(0); assertEquals("C", col.getColumnAlias()); colExp = col.getExpression(); assertEquals(ExpressionType.VALUE_TUPLE, colExp.getExpressionType()); // Test three table INNER join. USING C column should be resolved query = "SELECT C FROM R1 JOIN R2 USING (C) JOIN R3 USING (C)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); assertEquals(1, selectColumns.size()); col = selectColumns.get(0); assertEquals("C", col.getColumnAlias()); colExp = col.getExpression(); assertEquals(ExpressionType.VALUE_TUPLE, colExp.getExpressionType()); // Test two table LEFT join. USING C column should be resolved query = "SELECT C FROM R1 LEFT JOIN R2 USING (C)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); assertEquals(1, selectColumns.size()); col = selectColumns.get(0); assertEquals("C", col.getColumnAlias()); colExp = col.getExpression(); assertEquals(ExpressionType.VALUE_TUPLE, colExp.getExpressionType()); // Test two table RIGHT join. USING C column should be resolved query = "SELECT C FROM R1 RIGHT JOIN R2 USING (C)"; pn = compileToTopDownTree(query, 1, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); selectColumns = pn.getOutputSchema().getColumns(); assertEquals(1, selectColumns.size()); col = selectColumns.get(0); assertEquals("C", col.getColumnAlias()); colExp = col.getExpression(); assertEquals(ExpressionType.VALUE_TUPLE, colExp.getExpressionType()); } public void testJoinOrders() { String query; AbstractPlanNode pn; AbstractPlanNode node; AbstractScanPlanNode sn; // R1 is an outer node - has one filter query = "SELECT * FROM R2 JOIN R1 USING (C) WHERE R1.A > 0"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN); sn = (AbstractScanPlanNode) node; assertEquals("R1", sn.getTargetTableName()); // R2 is an outer node - R2.A = 3 filter is discounter more than R1.A > 0 query = "SELECT * FROM R1 JOIN R2 USING (C) WHERE R1.A > 0 AND R2.A = 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN); sn = (AbstractScanPlanNode) node; assertEquals("R2", sn.getTargetTableName()); // R2 is an outer node - R2.A = 3 filter is discounter more than two non-EQ filters query = "SELECT * FROM R1 JOIN R2 USING (C) WHERE R1.A > 0 AND R1.A < 3 AND R2.A = 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN); sn = (AbstractScanPlanNode) node; assertEquals("R2", sn.getTargetTableName()); // R1 is an outer node - EQ + non-EQ overweight EQ query = "SELECT * FROM R1 JOIN R2 USING (C) WHERE R1.A = 0 AND R1.D < 3 AND R2.A = 3"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOP, PlanNodeType.SEQSCAN); sn = (AbstractScanPlanNode) node; assertEquals("R1", sn.getTargetTableName()); for (JoinOp joinOp : JoinOp.JOIN_OPS) { perJoinOpTestJoinOrders(joinOp); } } private void perJoinOpTestJoinOrders(JoinOp joinOp) { String query; AbstractPlanNode pn; AbstractPlanNode node; SeqScanPlanNode seqScan; NestLoopIndexPlanNode nlij; IndexScanPlanNode indexScan; AbstractExpression predicate; if (joinOp != JoinOp.EQUAL) { // weaken test for now return; } // Index Join (R3.A) still has a lower cost compare to a Loop Join // despite the R3.C = 0 equality filter on the inner node query = "SELECT * FROM R1 JOIN R3 ON R3.A" + joinOp + "R1.A WHERE R3.C = 0"; pn = compileToTopDownTree(query, 5, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); seqScan = (SeqScanPlanNode) node; assertEquals("R1", seqScan.getTargetTableName()); // R3.A is an INDEX. Both children are IndexScans. With everything being equal, // the Left table (L) has fewer filters and should be an inner node query = "SELECT L.A, R.A FROM R3 L JOIN R3 R ON L.A" + joinOp + "R.A WHERE R.A > 3 AND R.C = 3 AND L.A > 2 ;"; pn = compileToTopDownTree(query, 2, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.INDEXSCAN); indexScan = (IndexScanPlanNode) node; assertEquals("R", indexScan.getTargetTableAlias()); // NLIJ with inline inner IndexScan over R2 using its partial index is a winner // over the NLJ with R2 on the outer side query = "SELECT * FROM R3 JOIN R2 ON R3.C" + joinOp + "R2.C WHERE R2.C > 100;"; pn = compileToTopDownTree(query, 4, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX, PlanNodeType.SEQSCAN); node = followAssertedLeftChain(pn, PlanNodeType.SEND, PlanNodeType.PROJECTION, PlanNodeType.NESTLOOPINDEX); nlij = (NestLoopIndexPlanNode) node; indexScan = nlij.getInlineIndexScan(); assertEquals(IndexLookupType.EQ, indexScan.getLookupType()); predicate = indexScan.getEndExpression(); assertExprTopDownTree(predicate, joinOp.toOperator(), ExpressionType.VALUE_TUPLE, ExpressionType.VALUE_TUPLE); assertNull(indexScan.getPredicate()); assertEquals("PARTIAL_IND2", indexScan.getTargetIndexName()); seqScan = (SeqScanPlanNode) nlij.getChild(0); assertEquals("R3", seqScan.getTargetTableName()); } public void testExplainHighlights() { // These tests of critical aspects of join-related @Explain output were // migrated from the regression suite where they really did not belong. // They MAY be somewhat redundant with other less stringly tests in this // suite, but they do have the advantage of covering some key aspects of // explain string generation in an informal easily-maintained way that // does not get bogged down in the precise explain string syntax. String query; String explained; for (JoinOp joinOp : JoinOp.JOIN_OPS) { int notDistinctCount = joinOp == JoinOp.NOT_DISTINCT ? 1 : 0; query = "SELECT P1.A, P1.C, P3.A, P3.F " + "FROM P1 FULL JOIN P3 ON P1.A" + joinOp + "P3.A AND P1.A = ? AND P3.F = 1 " + "ORDER BY P1.A, P1.C, P3.A, P3.F"; explained = buildExplainPlan(compileToFragments(query)); assertTrue(explained.contains("NESTLOOP INDEX FULL JOIN")); assertEquals(notDistinctCount, StringUtils.countMatches(explained, "NOT DISTINCT")); query = "SELECT R1.A, R1.C, R3.A, R3.C " + "FROM R1 FULL JOIN R3 ON R3.A" + joinOp + "R1.A AND R3.A < 2 " + "ORDER BY R1.A, R1.D, R3.A, R3.C"; explained = buildExplainPlan(compileToFragments(query)); //* enable to debug */ System.out.println("DEBUG: " + explained); assertTrue(explained.contains("NESTLOOP INDEX FULL JOIN")); assertEquals(notDistinctCount, StringUtils.countMatches(explained, "NOT DISTINCT")); query = "SELECT LHS.A, LHS.C, RHS.A, RHS.C " + "FROM R3 LHS FULL JOIN R3 RHS ON LHS.A" + joinOp + "RHS.A AND LHS.A < 2 " + "ORDER BY 1, 2, 3, 4"; explained = buildExplainPlan(compileToFragments(query)); //* enable to debug */ System.out.println("DEBUG: " + explained); assertTrue(explained.contains("NESTLOOP INDEX FULL JOIN")); assertEquals(notDistinctCount, StringUtils.countMatches(explained, "NOT DISTINCT")); query = "SELECT * " + "FROM R1 FULL JOIN R2 " + "ON R1.A" + joinOp + "R2.A RIGHT JOIN P2 " + "ON P2.A" + joinOp + "R1.A " + "ORDER BY P2.A"; explained = buildExplainPlan(compileToFragments(query)); //* enable to debug */ System.out.println("DEBUG: " + explained); // Account for how IS NOT DISTINCT FROM does not reject all nulls. if (joinOp == JoinOp.EQUAL) { // weaken test for now assertFalse(explained.contains("FULL")); assertEquals(2, StringUtils.countMatches(explained, "LEFT")); } else { assertEquals(1, StringUtils.countMatches(explained, "FULL")); assertEquals(1, StringUtils.countMatches(explained, "LEFT")); } query = "SELECT * " + "FROM R1 FULL JOIN R2 " + "ON R1.A" + joinOp + "R2.A LEFT JOIN P2 " + "ON P2.A" + joinOp + "R2.A " + "ORDER BY P2.A"; explained = buildExplainPlan(compileToFragments(query)); assertTrue(explained.contains("FULL")); query = "SELECT * " + "FROM R1 RIGHT JOIN R2 " + "ON R1.A" + joinOp + "R2.A FULL JOIN P2 " + "ON R1.A" + joinOp + "P2.A " + "ORDER BY P2.A"; explained = buildExplainPlan(compileToFragments(query)); assertTrue(explained.contains("LEFT")); query = "SELECT * " + "FROM R1 FULL JOIN R2 " + "ON R1.A" + joinOp + "R2.A FULL JOIN P2 " + "ON R1.A" + joinOp + "P2.A " + "ORDER BY P2.A"; explained = buildExplainPlan(compileToFragments(query)); assertEquals(2, StringUtils.countMatches(explained, "FULL")); query = "SELECT MAX(R1.C), A " + "FROM R1 FULL JOIN R2 USING (A) " + "WHERE A > 0 GROUP BY A ORDER BY A"; explained = buildExplainPlan(compileToFragments(query)); assertEquals(1, StringUtils.countMatches(explained, "FULL")); query = "SELECT A " + "FROM R1 FULL JOIN R2 USING (A) " + "FULL JOIN R3 USING(A) " + "WHERE A > 0 ORDER BY A"; explained = buildExplainPlan(compileToFragments(query)); assertEquals(2, StringUtils.countMatches(explained, "FULL")); query = "SELECT L.A " + "FROM R3 L FULL JOIN R3 R " + "ON L.C" + joinOp + "R.C " + "ORDER BY A"; explained = buildExplainPlan(compileToFragments(query)); assertEquals(1, StringUtils.countMatches(explained, "FULL")); assertEquals(1, StringUtils.countMatches(explained, "SORT")); query = "SELECT L.A, SUM(L.C) " + "FROM R3 L FULL JOIN R3 R " + "ON L.C" + joinOp + "R.C " + "GROUP BY L.A ORDER BY 1"; explained = buildExplainPlan(compileToFragments(query)); assertEquals(1, StringUtils.countMatches(explained, "FULL")); assertEquals(1, StringUtils.countMatches(explained, "SORT")); assertEquals(1, StringUtils.countMatches(explained, "Serial AGGREGATION")); } } @Override protected void setUp() throws Exception { setupSchema(TestJoinOrder.class.getResource("testplans-join-ddl.sql"), "testplansjoin", false); } }