package edu.brown.optimizer; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.junit.Test; import org.voltdb.catalog.Column; import org.voltdb.catalog.Procedure; import org.voltdb.catalog.Statement; import org.voltdb.catalog.Table; import org.voltdb.planner.PlanColumn; import org.voltdb.planner.PlannerContext; import org.voltdb.plannodes.AbstractJoinPlanNode; import org.voltdb.plannodes.AbstractPlanNode; import org.voltdb.plannodes.AbstractScanPlanNode; import org.voltdb.plannodes.AggregatePlanNode; import org.voltdb.plannodes.LimitPlanNode; import org.voltdb.plannodes.OrderByPlanNode; import org.voltdb.plannodes.ProjectionPlanNode; import org.voltdb.plannodes.SeqScanPlanNode; import org.voltdb.types.PlanNodeType; import edu.brown.benchmark.AbstractProjectBuilder; import edu.brown.catalog.CatalogUtil; import edu.brown.expressions.ExpressionUtil; import edu.brown.plannodes.PlanNodeTreeWalker; import edu.brown.plannodes.PlanNodeUtil; import edu.brown.utils.CollectionUtil; /** * @author pavlo */ public class TestPlanOptimizer extends BasePlanOptimizerTestCase { final Set<String> DEBUG = new HashSet<String>(); { // DEBUG.add("DistinctAggregate"); // DEBUG.add("MultipleAggregates"); // DEBUG.add("JoinProjection"); DEBUG.add("LimitNoWhere"); // DEBUG.add("LimitOrderBy"); } AbstractProjectBuilder pb = new PlanOptimizerTestProjectBuilder("planopt") { { this.addStmtProcedure("MultipleAggregates", "SELECT C_B_ID, SUM(C_VALUE0), SUM(C_VALUE1), " + " AVG(C_VALUE0), AVG(C_VALUE1) " + "FROM TABLEC GROUP BY C_B_ID"); this.addStmtProcedure("DistinctAggregate", "SELECT COUNT(DISTINCT(TABLEB.B_ID)) AS DISTINCTNUMBER " + "FROM TABLEA, TABLEB " + "WHERE TABLEA.A_ID = TABLEB.B_A_ID AND TABLEA.A_ID = ? AND TABLEB.B_ID < ?"); this.addStmtProcedure("DistinctCount", "SELECT COUNT(DISTINCT(TABLEB.B_A_ID)) FROM TABLEB"); this.addStmtProcedure("MaxGroupPassThrough", "SELECT B_ID, Max(TABLEB.B_A_ID) FROM TABLEB GROUP BY B_ID"); this.addStmtProcedure("MaxMultiGroupBy", "SELECT MAX(TABLEC.C_ID) FROM TABLEC GROUP BY TABLEC.C_B_A_ID, TABLEC.C_VALUE0"); this.addStmtProcedure("Max", "SELECT MAX(TABLEB.B_A_ID) FROM TABLEB"); this.addStmtProcedure("Min", "SELECT MIN(TABLEB.B_A_ID) FROM TABLEB"); this.addStmtProcedure("AggregateCount", "SELECT COUNT(TABLEB.B_A_ID) AS cnt, B_VALUE0 FROM TABLEB GROUP BY B_VALUE0"); this.addStmtProcedure("LimitNoWhere", "SELECT * FROM TABLEA LIMIT 1"); this.addStmtProcedure("Limit", "SELECT * FROM TABLEA WHERE TABLEA.A_ID > ? AND TABLEA.A_ID <= ? AND TABLEA.A_VALUE0 != ? LIMIT 15"); this.addStmtProcedure("LimitJoin", "SELECT TABLEA.A_ID,TABLEB.B_ID FROM TABLEA, TABLEB WHERE TABLEA.A_ID > ? AND TABLEA.A_ID = TABLEB.B_A_ID LIMIT 15"); this.addStmtProcedure("ThreeWayJoin", "SELECT TABLEA.A_VALUE0, TABLEB.B_VALUE0, ((TABLEC.C_VALUE0 + TABLEC.C_VALUE1) / TABLEB.B_A_ID) AS blah " + "FROM TABLEA, TABLEB, TABLEC " + "WHERE TABLEA.A_ID = TABLEB.B_A_ID AND TABLEA.A_ID = TABLEC.C_B_A_ID AND TABLEA.A_VALUE3 = ? " + " AND TABLEC.C_B_A_ID = ? AND TABLEC.C_VALUE0 != ? AND TABLEC.C_VALUE1 != ?"); this.addStmtProcedure("SingleProjection", "SELECT TABLEA.A_VALUE0 FROM TABLEA WHERE TABLEA.A_ID = ?"); this.addStmtProcedure("NonPartitioningProjection", "SELECT TABLEA.A_ID FROM TABLEA WHERE TABLEA.A_VALUE0 = ?"); this.addStmtProcedure("JoinProjection", "SELECT TABLEA.A_ID, TABLEA.A_VALUE0, TABLEA.A_VALUE1, TABLEA.A_VALUE2, TABLEA.A_VALUE3, TABLEA.A_VALUE4 " + "FROM TABLEA, TABLEB " + "WHERE TABLEA.A_ID = ? AND TABLEA.A_ID = TABLEB.B_A_ID"); this.addStmtProcedure("AggregateColumnAddition", "SELECT AVG(TABLEC.C_VALUE0), C_B_A_ID " + " FROM TABLEC WHERE TABLEC.C_ID = ? GROUP BY C_B_A_ID"); this.addStmtProcedure("OrderBy", "SELECT TABLEC.C_B_A_ID FROM TABLEC ORDER BY TABLEC.C_B_A_ID DESC, TABLEC.C_VALUE0 ASC"); this.addStmtProcedure("LimitOrderBy", "SELECT C_ID FROM TABLEC ORDER BY C_B_A_ID LIMIT 1000"); this.addStmtProcedure("SingleSelect", "SELECT A_ID FROM TABLEA"); this.addStmtProcedure("TwoTableJoin", "SELECT B_ID, B_A_ID, B_VALUE0, C_ID, C_VALUE0 " + " FROM TABLEB, TABLEC " + " WHERE B_A_ID = ? AND B_ID = ? " + " AND B_A_ID = C_B_A_ID AND B_ID = C_B_ID " + " ORDER BY B_VALUE1 ASC LIMIT 25"); } }; @Override protected void setUp() throws Exception { super.setUp(pb); } private void check(Statement catalog_stmt) throws Exception { // Grab the root node of the multi-partition query plan tree for this Statement for (boolean dtxn : new boolean[]{ true, false }) { AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, dtxn); assertNotNull(root); if (DEBUG.contains(catalog_stmt.getParent().getName())) System.err.println(PlanNodeUtil.debug(root)); BasePlanOptimizerTestCase.validate(root); } // FOR } /** * testMultipleAggregates */ @Test public void testMultipleAggregates() throws Exception { Procedure catalog_proc = this.getProcedure("MultipleAggregates"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testExtractReferencedColumns */ @Test public void testExtractReferencedColumns() throws Exception { Procedure catalog_proc = this.getProcedure("DistinctCount"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, true); assertNotNull(root); Collection<SeqScanPlanNode> scan_nodes = PlanNodeUtil.getPlanNodes(root, SeqScanPlanNode.class); SeqScanPlanNode scan_node = CollectionUtil.first(scan_nodes); assertNotNull(scan_node); PlanOptimizerState state = new PlanOptimizerState(catalog_db, PlannerContext.singleton()); Collection<PlanColumn> referenced = PlanOptimizerUtil.extractReferencedColumns(state, scan_node); assertNotNull(referenced); // Make sure all of the columns that we get back have a matching column in // the table scanned in the PlanNode Table catalog_tbl = this.getTable(scan_node.getTargetTableName()); // System.err.println(referenced); for (PlanColumn pc : referenced) { assertNotNull(pc); Collection<Column> columns = ExpressionUtil.getReferencedColumns(catalog_db, pc.getExpression()); assertEquals(pc.toString(), 1, columns.size()); Column catalog_col = CollectionUtil.first(columns); assertNotNull(pc.toString(), catalog_col); assertEquals(pc.toString(), catalog_tbl, catalog_col.getParent()); } // FOR } /** * testDistinctAggregate */ @Test public void testDistinctAggregate() throws Exception { Procedure catalog_proc = this.getProcedure("DistinctAggregate"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testDistinctCount */ @Test public void testDistinctCount() throws Exception { Procedure catalog_proc = this.getProcedure("DistinctCount"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testMaxGroupPassThrough */ @Test public void testMaxGroupPassThrough() throws Exception { Procedure catalog_proc = this.getProcedure("MaxGroupPassThrough"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testMaxMultiGroupBy */ @Test public void testMaxMultiGroupBy() throws Exception { Procedure catalog_proc = this.getProcedure("MaxMultiGroupBy"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testMax */ @Test public void testMax() throws Exception { Procedure catalog_proc = this.getProcedure("Max"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testMin */ @Test public void testMin() throws Exception { Procedure catalog_proc = this.getProcedure("Min"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testAggregateCount */ @Test public void testAggregateCount() throws Exception { Procedure catalog_proc = this.getProcedure("AggregateCount"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); assertNotNull(root); // We should have two AggregatePlanNodes. // Make sure that they have the same GroupByColumns Collection<AggregatePlanNode> agg_nodes = PlanNodeUtil.getPlanNodes(root, AggregatePlanNode.class); assertEquals(2, agg_nodes.size()); AggregatePlanNode agg0 = CollectionUtil.get(agg_nodes, 0); assertNotNull(agg0); AggregatePlanNode agg1 = CollectionUtil.get(agg_nodes, 1); assertNotNull(agg1); assertNotSame(agg0, agg1); // System.err.println(PlanNodeUtil.debug(root)); assertEquals(agg0.getAggregateOutputColumns(), agg1.getAggregateOutputColumns()); assertEquals(agg0.getGroupByColumnNames(), agg1.getGroupByColumnNames()); // assertEquals(agg0.getGroupByColumnGuids(), agg1.getGroupByColumnGuids()); } /** * testLimitNoWhere */ @Test public void testLimitNoWhere() throws Exception { Procedure catalog_proc = this.getProcedure("LimitNoWhere"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); //validateNodeColumnOffsets(root); assertNotNull(root); // We should have two LIMIT nodes: // (1) One that is inline on the SeqScan that executes at each partition // (2) One that executes at the base partition Collection<LimitPlanNode> limit_nodes = PlanNodeUtil.getPlanNodes(root, LimitPlanNode.class); assertEquals(1, limit_nodes.size()); Collection<SeqScanPlanNode> scan_nodes = PlanNodeUtil.getPlanNodes(root, SeqScanPlanNode.class); assertEquals(1, scan_nodes.size()); LimitPlanNode inline_node = CollectionUtil.first(scan_nodes).getInlinePlanNode(PlanNodeType.LIMIT); assertNotNull(inline_node); // Get the Limit nodes output columns and make sure their valid for (LimitPlanNode limit_node : limit_nodes) { assertNotNull(limit_node); for (int column_guid : limit_node.getOutputColumnGUIDs()) { PlanColumn column = PlannerContext.singleton().get(column_guid); // System.err.println(String.format("[%02d] %s", column_guid, // column)); // System.err.println("=================="); // System.err.println(PlannerContext.singleton().debug()); assertNotNull("Invalid PlanColumn [guid=" + column_guid + "]", column); assertEquals(column_guid, column.guid()); } // FOR } // FOR } /** * testLimit */ @Test public void testLimit() throws Exception { Procedure catalog_proc = this.getProcedure("Limit"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this // Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); //validateNodeColumnOffsets(root); assertNotNull(root); // First check that our single scan node has an limit node Collection<LimitPlanNode> limit_nodes = PlanNodeUtil.getPlanNodes(root, LimitPlanNode.class); assertEquals(1, limit_nodes.size()); // Get the Limit nodes output columns and make sure their valid LimitPlanNode limit_node = CollectionUtil.first(limit_nodes); assertNotNull(limit_node); for (int column_guid : limit_node.getOutputColumnGUIDs()) { PlanColumn column = PlannerContext.singleton().get(column_guid); // System.err.println(String.format("[%02d] %s", column_guid, // column)); // System.err.println("=================="); // System.err.println(PlannerContext.singleton().debug()); assertNotNull("Invalid PlanColumn [guid=" + column_guid + "]", column); assertEquals(column_guid, column.guid()); } // FOR // System.err.println(PlanNodeUtil.debug(root)); } /** * testLimitJoin */ @Test public void testLimitJoin() throws Exception { Procedure catalog_proc = this.getProcedure("LimitJoin"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); assertNotNull(root); // First check that our single scan node has an limit node Collection<LimitPlanNode> limit_nodes = PlanNodeUtil.getPlanNodes(root, LimitPlanNode.class); assertEquals(1, limit_nodes.size()); // Get the Limit nodes output columns and make sure their valid LimitPlanNode limit_node = CollectionUtil.first(limit_nodes); assertNotNull(limit_node); for (int column_guid : limit_node.getOutputColumnGUIDs()) { PlanColumn column = PlannerContext.singleton().get(column_guid); // System.err.println(String.format("[%02d] %s", column_guid, // column)); // System.err.println("=================="); // System.err.println(PlannerContext.singleton().debug()); assertNotNull("Invalid PlanColumn [guid=" + column_guid + "]", column); assertEquals(column_guid, column.guid()); } // FOR // System.err.println(PlanNodeUtil.debug(root)); } /** * testThreeWayJoin */ @Test public void testThreeWayJoin() throws Exception { Procedure catalog_proc = this.getProcedure("ThreeWayJoin"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testSingleProjection */ @Test public void testSingleProjection() throws Exception { Procedure catalog_proc = this.getProcedure("SingleProjection"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this // Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); assertNotNull(root); // First check that our single scan node has an inline Projection Collection<AbstractScanPlanNode> scan_nodes = PlanNodeUtil.getPlanNodes(root, AbstractScanPlanNode.class); assertEquals(1, scan_nodes.size()); AbstractScanPlanNode scan_node = CollectionUtil.first(scan_nodes); assertNotNull(scan_node); assertEquals(1, scan_node.getInlinePlanNodes().size()); // Get the Projection and make sure it has valid output columns ProjectionPlanNode inline_proj = (ProjectionPlanNode) scan_node.getInlinePlanNodes().get(PlanNodeType.PROJECTION); assertNotNull(inline_proj); for (int column_guid : inline_proj.getOutputColumnGUIDs()) { PlanColumn column = PlannerContext.singleton().get(column_guid); // System.err.println(String.format("[%02d] %s", column_guid, // column)); // System.err.println("=================="); // System.err.println(PlannerContext.singleton().debug()); assertNotNull("Invalid PlanColumn [guid=" + column_guid + "]", column); assertEquals(column_guid, column.guid()); } // FOR // Now check to make sure there are no other Projections in the tree Collection<ProjectionPlanNode> proj_nodes = PlanNodeUtil.getPlanNodes(root, ProjectionPlanNode.class); assertEquals(0, proj_nodes.size()); } /** * testNonPartitioningProjection */ @Test public void testNonPartitioningProjection() throws Exception { Procedure catalog_proc = this.getProcedure("NonPartitioningProjection"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testJoinProjection */ @Test public void testJoinProjection() throws Exception { Procedure catalog_proc = this.getProcedure("JoinProjection"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); assertNotNull(root); //validateNodeColumnOffsets(root); // Grab the single-partition root node and make sure that they have the same // output columns in their topmost send node AbstractPlanNode spRoot = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, true); assertNotNull(spRoot); assertEquals(root.getOutputColumnGUIDCount(), spRoot.getOutputColumnGUIDCount()); PlannerContext context = PlannerContext.singleton(); assertNotNull(context); for (int i = 0, cnt = root.getOutputColumnGUIDCount(); i < cnt; i++) { Integer guid0 = root.getOutputColumnGUID(i); PlanColumn col0 = context.get(guid0); assertNotNull(col0); Integer guid1 = spRoot.getOutputColumnGUID(i); PlanColumn col1 = context.get(guid1); assertNotNull(col1); assertTrue(col0.equals(col1, false, true)); } // FOR new PlanNodeTreeWalker() { @Override protected void callback(AbstractPlanNode element) { // System.out.println("element plannodetype: " + // element.getPlanNodeType() + " depth: " + this.getDepth()); } }.traverse(root); // // System.err.println("+++++++++++++++++++++++++++++++++++++++++++++++++++"); // // System.err.println(PlanNodeUtil.debug(root)); // // // // System.err.println("+++++++++++++++++++++++++++++++++++++++++++++++++++"); // // // // System.err.println("# of Fragments: " + // catalog_stmt.getMs_fragments().size()); // // for (PlanFragment pf : catalog_stmt.getMs_fragments()) { // // System.err.println(pf.getName() + "\n" + // PlanNodeUtil.debug(QueryPlanUtil.deserializePlanFragment(pf))); // } // At the very bottom of our tree should be a scan. Grab that and then // check to see that it has an inline ProjectionPlanNode. We will then // look to see whether // all of the columns // we need to join are included. Note that we don't care which table is // scanned first, as we can // dynamically figure things out for ourselves Collection<AbstractScanPlanNode> scan_nodes = PlanNodeUtil.getPlanNodes(root, AbstractScanPlanNode.class); assertEquals(1, scan_nodes.size()); AbstractScanPlanNode scan_node = CollectionUtil.first(scan_nodes); assertNotNull(scan_node); Table catalog_tbl = this.getTable(scan_node.getTargetTableName()); assertEquals(1, scan_node.getInlinePlanNodes().size()); ProjectionPlanNode inline_proj = (ProjectionPlanNode) scan_node.getInlinePlanNodes().get(PlanNodeType.PROJECTION); assertNotNull(inline_proj); // Validate output columns for (int column_guid : inline_proj.getOutputColumnGUIDs()) { PlanColumn column = PlannerContext.singleton().get(column_guid); assertNotNull("Missing PlanColumn [guid=" + column_guid + "]", column); assertEquals(column_guid, column.guid()); // Check that only columns from the scanned table are there String table_name = column.originTableName(); assertNotNull(table_name); String column_name = column.originColumnName(); assertNotNull(column_name); assertEquals(table_name + "." + column_name, catalog_tbl.getName(), table_name); assertNotNull(table_name + "." + column_name, catalog_tbl.getColumns().get(column_name)); } // FOR Collection<Column> proj_columns = null; proj_columns = PlanNodeUtil.getOutputColumnsForPlanNode(catalog_db, inline_proj); assertFalse(proj_columns.isEmpty()); // Now find the join and get all of the columns from the first scanned // table in the join operation Collection<AbstractJoinPlanNode> join_nodes = PlanNodeUtil.getPlanNodes(root, AbstractJoinPlanNode.class); assertNotNull(join_nodes); assertEquals(1, join_nodes.size()); AbstractJoinPlanNode join_node = CollectionUtil.first(join_nodes); assertNotNull(join_node); // Remove the columns from the second table Collection<Column> join_columns = CatalogUtil.getReferencedColumnsForPlanNode(catalog_db, join_node); assertNotNull(join_columns); assertFalse(join_columns.isEmpty()); // System.err.println(CatalogUtil.debug(join_columns)); Iterator<Column> it = join_columns.iterator(); while (it.hasNext()) { Column catalog_col = it.next(); if (catalog_col.getParent().equals(catalog_tbl) == false) { it.remove(); } } // WHILE assertFalse(join_columns.isEmpty()); // System.err.println("COLUMNS: " + CatalogUtil.debug(join_columns)); // Ok so now we have the list of columns that are filtered out in the // inline projection and the list of // columns that are used in the join from the first table. So we need to // make sure that // every table that is in the join is in the projection for (Column catalog_col : join_columns) { assert (proj_columns.contains(catalog_col)) : "Missing: " + CatalogUtil.getDisplayName(catalog_col); } // FOR // Lastly, we need to look at the root SEND node and get its output // columns, and make sure that they are also included in the bottom projection Collection<Column> send_columns = PlanNodeUtil.getOutputColumnsForPlanNode(catalog_db, root); assertFalse(send_columns.isEmpty()); for (Column catalog_col : send_columns) { assert(proj_columns.contains(catalog_col)) : "Missing: " + CatalogUtil.getDisplayName(catalog_col); } // FOR } /** * testAggregateColumnAddition */ @Test public void testAggregateColumnAddition() throws Exception { Procedure catalog_proc = this.getProcedure("AggregateColumnAddition"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testAggregateOrderBy */ @Test public void testAggregateOrderBy() throws Exception { Procedure catalog_proc = this.getProcedure("OrderBy"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testLimitOrderBy */ @Test public void testLimitOrderBy() throws Exception { Procedure catalog_proc = this.getProcedure("LimitOrderBy"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); // Grab the root node of the multi-partition query plan tree for this // Statement AbstractPlanNode root = PlanNodeUtil.getRootPlanNodeForStatement(catalog_stmt, false); assertNotNull(root); // We should have two LIMITs and two ORDER BYs Class<?> planClasses[] = { LimitPlanNode.class, OrderByPlanNode.class }; for (Class<?> c : planClasses) { @SuppressWarnings("unchecked") Collection<AbstractPlanNode> nodes = PlanNodeUtil.getPlanNodes(root, (Class<AbstractPlanNode>)c); assertNotNull(nodes); assertEquals(2, nodes.size()); // Make sure each one only has one child! for (AbstractPlanNode node : nodes) { assertEquals(PlanNodeUtil.debug(node), 1, node.getChildPlanNodeCount()); } // FOR } // FOR } /** * testSingleSelect */ @Test public void testSingleSelect() throws Exception { Procedure catalog_proc = this.getProcedure("SingleSelect"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } /** * testTwoTableJoin */ @Test public void testTwoTableJoin() throws Exception { Procedure catalog_proc = this.getProcedure("TwoTableJoin"); Statement catalog_stmt = this.getStatement(catalog_proc, "sql"); this.check(catalog_stmt); } }