/* 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.voltdb.plannodes.AbstractPlanNode; import org.voltdb.plannodes.AggregatePlanNode; import org.voltdb.plannodes.IndexCountPlanNode; import org.voltdb.plannodes.IndexScanPlanNode; import org.voltdb.plannodes.TableCountPlanNode; import org.voltdb.types.PlanNodeType; // Revised for ENG-6131 public class TestPlansCount extends PlannerTestCase { @Override protected void setUp() throws Exception { setupSchema(getClass().getResource("testplans-count-ddl.sql"), "testcount", false); } @Override protected void tearDown() throws Exception { super.tearDown(); } // DOES NOT support the cases down below right now // This is treated as new TABLE COUNT plan for replicated table public void testCountStar000() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); pn = compileToFragments("SELECT count(1) from T1"); p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); } // This is treated as new TABLE COUNT plan for partitioned table public void testCountStar001() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from P1"); AbstractPlanNode p = pn.get(0).getChild(0); // AGGREGATE_SUM assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof TableCountPlanNode); pn = compileToFragments("SELECT count(1) from P1"); p = pn.get(0).getChild(0); // AGGREGATE_SUM assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof TableCountPlanNode); } public void testCountStar002() { List<AbstractPlanNode> pn = compileToFragments("SELECT POINTS, count(*) from T1 Group by POINTS"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertNotNull(p.getInlinePlanNode(PlanNodeType.AGGREGATE)); pn = compileToFragments("SELECT POINTS, count(1) from T1 Group by POINTS"); p = pn.get(0).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertNotNull(p.getInlinePlanNode(PlanNodeType.AGGREGATE)); } // Test subquery temp table count public void testCountStar003() { List<AbstractPlanNode> pn = compileToFragments("select count(*) from (SELECT count(*) from T1) Temp"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); p = p.getChild(0); assertTrue(p instanceof TableCountPlanNode); pn = compileToFragments("select count(1) from (SELECT count(*) from T1) Temp"); p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); p = p.getChild(0); assertTrue(p instanceof TableCountPlanNode); } public void testCountStar004() { List<AbstractPlanNode> pn = compileToFragments("select count(*) from (SELECT count(*) from P1) Temp"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); p = p.getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof TableCountPlanNode); pn = compileToFragments("select count(1) from (SELECT count(1) from P1) Temp"); p = pn.get(0).getChild(0); assertTrue(p instanceof TableCountPlanNode); p = p.getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof TableCountPlanNode); } // This is generated as an IndexScan which can't be converted into an index count, // rather than as the same table count as "SELECT count(*) from T1". // The meaningless "order by" here fools the planner. // We should fix this later by generally ignoring the "order by" clause on non-grouped aggregate queries. public void testCountStar01() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 ORDER BY POINTS ASC"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T1 ORDER BY POINTS ASC"); checkIndexCounter(pn, false); } public void testCountStar02() { List<AbstractPlanNode> pn = compileToFragments("SELECT P1.ID, T3.T3_ID from P1, T3 where P1.ID >= T3.T3_ID order by P1.ID, T3.T3_ID limit 10"); checkIndexCounter(pn, false); } public void testCountStar04() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS = ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS = ?"); checkIndexCounter(pn, true); } public void testCountStar05() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS < ? ORDER BY ID DESC"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS < ? ORDER BY ID DESC"); checkIndexCounter(pn, true); } public void testCountStar06() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS >= 3 AND AGE = ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS >= 3 AND AGE = ?"); checkIndexCounter(pn, false); } public void testCountStar07() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND AGE = 3 AND POINTS < ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND AGE = 3 AND POINTS < ?"); checkIndexCounter(pn, false); } public void testCountStar08() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME >'XIN' AND POINTS = ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME >'XIN' AND POINTS = ?"); checkIndexCounter(pn, false); } public void testCountStar10() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND POINTS > ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND POINTS > ?"); checkIndexCounter(pn, true); } // Down below are cases that we can replace public void testCountStar11() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS < 4"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS < 4"); checkIndexCounter(pn, true); } public void testCountStar12() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS < ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS < ?"); checkIndexCounter(pn, true); } public void testCountStar13() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS >= 3"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS >= 3"); checkIndexCounter(pn, true); } public void testCountStar14() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS > 3 AND POINTS <= 6"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS > 3 AND POINTS <= 6"); checkIndexCounter(pn, true); } public void testCountStar15() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS > ? AND POINTS < ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS > ? AND POINTS < ?"); checkIndexCounter(pn, true); } public void testCountStar16() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND POINTS >= ? AND POINTS <= ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND POINTS >= ? AND POINTS <= ?"); checkIndexCounter(pn, true); } public void testCountStar17() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND POINTS < ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND POINTS < ?"); checkIndexCounter(pn, true); } public void testCountStar18() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN'"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN'"); checkIndexCounter(pn, false); } // Planner bug with Constant value overflow public void testCountStar19() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND POINTS >= 3 AND POINTS <= 600000000000000000000000000"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue((p instanceof IndexCountPlanNode) == false); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND POINTS >= 3 AND POINTS <= 600000000000000000000000000"); p = pn.get(0).getChild(0); assertTrue((p instanceof IndexCountPlanNode) == false); } // test with group by with Replicated table public void testCountStar20() { List<AbstractPlanNode> pn = compileToFragments("SELECT AGE, count(*) from T2 WHERE USERNAME ='XIN' AND POINTS < 1 Group by AGE"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertTrue(p.getInlinePlanNode(PlanNodeType.AGGREGATE) != null || p.getInlinePlanNode(PlanNodeType.HASHAGGREGATE) != null); pn = compileToFragments("SELECT AGE, count(1) from T2 WHERE USERNAME ='XIN' AND POINTS < 1 Group by AGE"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); p = pn.get(0).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertTrue(p.getInlinePlanNode(PlanNodeType.AGGREGATE) != null || p.getInlinePlanNode(PlanNodeType.HASHAGGREGATE) != null); } // test with group by with Partitioned table public void testCountStar21() { List<AbstractPlanNode> pn = compileToFragments("SELECT RATIO, count(*) from P1 WHERE NUM < 1 Group by RATIO"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertTrue(p.getInlinePlanNode(PlanNodeType.AGGREGATE) != null || p.getInlinePlanNode(PlanNodeType.HASHAGGREGATE) != null); pn = compileToFragments("SELECT RATIO, count(1) from P1 WHERE NUM < 1 Group by RATIO"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexScanPlanNode); assertTrue(p.getInlinePlanNode(PlanNodeType.AGGREGATE) != null || p.getInlinePlanNode(PlanNodeType.HASHAGGREGATE) != null); } // Test counting index feature with partitioned table public void testCountStar22() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from P1 WHERE NUM < ?"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexCountPlanNode); pn = compileToFragments("SELECT count(1) from P1 WHERE NUM < ?"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexCountPlanNode); } public void testCountStar23() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T1 WHERE POINTS < 4 ORDER BY POINTS DESC"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T1 WHERE POINTS < 4 ORDER BY POINTS DESC"); checkIndexCounter(pn, true); } public void testCountStar24() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE ID = 1 AND USERNAME > 'JOHN' "); checkIndexCounter(pn, false); pn = compileToFragments("SELECT count(1) from T2 WHERE ID = 1 AND USERNAME > 'JOHN' "); checkIndexCounter(pn, false); } public void testCountStar25() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T2 WHERE USERNAME ='XIN' AND POINTS >= ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T2 WHERE USERNAME ='XIN' AND POINTS >= ?"); checkIndexCounter(pn, true); } // test with FLOAT type public void testCountStar26() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from P1 WHERE NUM = 1 AND RATIO >= ?"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexCountPlanNode); pn = compileToFragments("SELECT count(1) from P1 WHERE NUM = 1 AND RATIO >= ?"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof IndexCountPlanNode); } // should not replace public void testCountStar27() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T2 WHERE USERNAME >= 'XIN' AND POINTS = ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT COUNT(1) FROM T2 WHERE USERNAME >= 'XIN' AND POINTS = ?"); checkIndexCounter(pn, false); } public void testCountStar28() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T1 WHERE AGE = 18 AND ID + POINTS > ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT COUNT(1) FROM T1 WHERE AGE = 18 AND ID + POINTS > ?"); checkIndexCounter(pn, true); } public void testCountStar29() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T1 WHERE AGE / 2 = 10 AND ABS(AGE) > ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT COUNT(1) FROM T1 WHERE AGE / 2 = 10 AND ABS(AGE) > ?"); checkIndexCounter(pn, true); } // should not replace public void testCountStar30() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T1 WHERE AGE / 2 <= ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT COUNT(1) FROM T1 WHERE AGE / 2 <= ?"); checkIndexCounter(pn, false); } // Padding maximum value case. public void testCountStar31() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(*) from T3 WHERE T3_NUM1 =1 AND T3_NUM2 >= ?"); checkIndexCounter(pn, true); pn = compileToFragments("SELECT count(1) from T3 WHERE T3_NUM1 = 1 AND T3_NUM2 >= ?"); checkIndexCounter(pn, true); } // Not padding maximum value case. public void testCountStar32() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T2 WHERE ID = 1 AND USERNAME > ?"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT COUNT(1) FROM T2 WHERE ID = 1 AND USERNAME > ?"); checkIndexCounter(pn, false); } private void checkCompareNotDistinctFlags(AbstractPlanNode apn, Boolean... expectedFlags) { List<Boolean> compareNotDistinctFlags = null; if (apn instanceof IndexScanPlanNode) { compareNotDistinctFlags = ((IndexScanPlanNode)apn).getCompareNotDistinctFlags(); } else if (apn instanceof IndexCountPlanNode) { compareNotDistinctFlags = ((IndexCountPlanNode)apn).getCompareNotDistinctFlags(); } assertNotNull(compareNotDistinctFlags); assertEquals(compareNotDistinctFlags.size(), expectedFlags.length); for (int i = 0; i < compareNotDistinctFlags.size(); i++) { assertEquals(expectedFlags[i], compareNotDistinctFlags.get(i)); } } // Index count with "IS NOT DISTINCT FROM" public void testCountStar33() { List<AbstractPlanNode> pn = compileToFragments("SELECT COUNT(*) FROM T_ENG_11096 WHERE a = 1 AND b IS NOT DISTINCT FROM CAST(NULL AS INT) AND c > 1"); checkIndexCounter(pn, true); checkCompareNotDistinctFlags(pn.get(0).getChild(0), false, true, false); pn = compileToFragments("SELECT COUNT(1) FROM T_ENG_11096 WHERE a = 1 AND b IS NOT DISTINCT FROM CAST(NULL AS INT) AND c > 1"); checkIndexCounter(pn, true); checkCompareNotDistinctFlags(pn.get(0).getChild(0), false, true, false); pn = compileToFragments("SELECT COUNT(*) FROM T_ENG_11096 WHERE a = 1 AND b IS NOT DISTINCT FROM CAST(NULL AS INT)"); for ( AbstractPlanNode nd : pn) System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); AbstractPlanNode p = pn.get(0).getChild(0); checkCompareNotDistinctFlags(p, false, true); assertNotNull(p.getInlinePlanNode(PlanNodeType.AGGREGATE)); } // Testing the count(<non-nullable column>) on a partitioned table. public void testCountColumn01() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(NUM) from P1 WHERE ID=1 AND NUM >= ?"); checkIndexCounter(pn, true); } // Testing the count( distinct <non-nullable column>) on a partitioned table. public void testCountColumn02() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(distinct NUM) from P1 WHERE ID=1 AND NUM >= ?"); checkIndexCounter(pn, false); } // Testing the count(<nullable column>) on a partitioned table. public void testCountColumn03() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(RATIO) from P1 WHERE ID=1 AND RATIO >= ?"); checkIndexCounter(pn, false); } // Testing the count( distinct <nullable column>) on a partitioned table. public void testCountColumn04() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(distinct RATIO) from P1 WHERE ID=1 AND RATIO >= ?"); checkIndexCounter(pn, false); } // Testing the count(<non-nullable column>) on a replicated table. public void testCountColumn05() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(POINTS) from T1 WHERE ID=1 AND POINTS >= ?"); checkIndexCounter(pn, true); } // Testing the count( distinct <non-nullable column>) on a replicated table. public void testCountColumn06() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(distinct POINTS) from T1 WHERE ID=1 AND POINTS >= ?"); checkIndexCounter(pn, false); } // Testing the count(<nullable column>) on a replicated table. public void testCountColumn07() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(AGE) from T1 WHERE ID=1 AND AGE >= ?"); checkIndexCounter(pn, false); } // Testing the count( distinct <nullable column>) on a replicated table. public void testCountColumn08() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(distinct AGE) from T1 WHERE ID=1 AND AGE >= ?"); checkIndexCounter(pn, false); } public void testTableCount() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(ID) from P1"); AbstractPlanNode p = pn.get(0).getChild(0); assertTrue(p instanceof AggregatePlanNode); p = pn.get(1).getChild(0); assertTrue(p instanceof TableCountPlanNode); } public void testCountSubquery() { List<AbstractPlanNode> pn = compileToFragments("SELECT count(distinct RATIO) from (SELECT * FROM P1) a"); checkIndexCounter(pn, false); pn = compileToFragments("SELECT * from T1, (SELECT count(age) FROM T1) as subq"); checkIndexCounter(pn, false); } /** * Check Whether or not the original plan is replaced with CountingIndexPlanNode. * * @param pn * The generated plan * @param isReplaceable * Whether or not the original plan is replaced with CountingIndexPlanNode */ private void checkIndexCounter(List<AbstractPlanNode> pn, boolean isReplaceable) { assertTrue(pn.size() > 0); for ( AbstractPlanNode nd : pn) { System.out.println("PlanNode Explain string:\n" + nd.toExplainPlanString()); } AbstractPlanNode p = pn.get(0).getChild(0); if (isReplaceable) assertTrue(p instanceof IndexCountPlanNode); else assertTrue((p instanceof IndexCountPlanNode) == false); } }