From 14dffcc60794c2c9c560a10c8d206afa553de77c Mon Sep 17 00:00:00 2001 From: Hyoungjun Kim Date: Tue, 21 Apr 2015 02:51:50 +0900 Subject: [PATCH 1/6] TAJO-1552: NPE occurs when GreedyHeuristicJoinOrderAlgorithm.getCost() returns infinity. --- .../org/apache/tajo/QueryTestCaseBase.java | 76 ++++++- ...TestGreedyHeuristicJoinOrderAlgorithm.java | 204 ++++++++++++++++++ .../tajo/engine/query/TestJoinQuery.java | 4 +- .../GreedyHeuristicJoinOrderAlgorithm.java | 27 ++- 4 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java diff --git a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java index ede667e78f..bc0fb4bca6 100644 --- a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java +++ b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java @@ -18,6 +18,7 @@ package org.apache.tajo; +import com.google.common.base.Joiner; import com.google.protobuf.ServiceException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -70,10 +71,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static org.junit.Assert.*; @@ -441,7 +439,18 @@ public ResultSet executeJsonFile(String jsonFileName) throws Exception { * @param result Query result to be compared. */ public final void assertResultSet(ResultSet result) throws IOException { - assertResultSet("Result Verification", result, getMethodName() + ".result"); + assertResultSet(result, false); + } + + /** + * Assert the equivalence between the expected result and an actual query result. + * If it isn't it throws an AssertionError. + * + * @param result Query result to be compared. + * @param ignoreOrdering If true, don't concern ordering. + */ + public final void assertResultSet(ResultSet result, boolean ignoreOrdering) throws IOException { + assertResultSet("Result Verification", result, getMethodName() + ".result", ignoreOrdering); } /** @@ -452,7 +461,7 @@ public final void assertResultSet(ResultSet result) throws IOException { * @param resultFileName The file name containing the result to be compared */ public final void assertResultSet(ResultSet result, String resultFileName) throws IOException { - assertResultSet("Result Verification", result, resultFileName); + assertResultSet("Result Verification", result, resultFileName, false); } /** @@ -461,12 +470,13 @@ public final void assertResultSet(ResultSet result, String resultFileName) throw * * @param message message The message to printed if the assertion is failed. * @param result Query result to be compared. + * @param ignoreOrdering If true, don't concern ordering. */ - public final void assertResultSet(String message, ResultSet result, String resultFileName) throws IOException { - FileSystem fs = currentQueryPath.getFileSystem(testBase.getTestingCluster().getConfiguration()); + public final void assertResultSet(String message, ResultSet result, String resultFileName, + boolean ignoreOrdering) throws IOException { Path resultFile = getResultFile(resultFileName); try { - verifyResultText(message, result, resultFile); + verifyResultText(message, result, resultFile, ignoreOrdering); } catch (SQLException e) { throw new IOException(e); } @@ -584,10 +594,56 @@ public String resultSetToString(ResultSet resultSet) throws SQLException { return sb.toString(); } + private Map stringToMap(String str) throws SQLException { + Map resultMap = new HashMap(); + + for (String eachLine: str.split("\n")) { + if (resultMap.containsKey(eachLine)) { + resultMap.put(eachLine, resultMap.get(eachLine) + 1); + } else { + resultMap.put(eachLine, 1); + } + } + + return resultMap; + } + private void verifyResultText(String message, ResultSet res, Path resultFile) throws SQLException, IOException { + verifyResultText(message, res, resultFile, false); + } + + /** + * It compares ResultSet with the contents of given resultFile. + * @param message the message which is shown when query failed. + * @param res query result + * @param resultFile the file path which contains the expected data. + * @param ignoreLineOrder if true, the order of result is ignored. + * @throws SQLException + * @throws IOException + */ + private void verifyResultText(String message, ResultSet res, Path resultFile, boolean ignoreLineOrder) + throws SQLException, IOException { String actualResult = resultSetToString(res); String expectedResult = FileUtil.readTextFile(new File(resultFile.toUri())); - assertEquals(message, expectedResult.trim(), actualResult.trim()); + if (ignoreLineOrder) { + Map resultSetMap = stringToMap(actualResult); + for (String eachExpectedLine: expectedResult.split("\n")) { + if (!resultSetMap.containsKey(eachExpectedLine)) { + fail(message + ", no expected value: " + eachExpectedLine); + } else { + int remainCount = resultSetMap.get(eachExpectedLine); + remainCount--; + if (remainCount == 0) { + resultSetMap.remove(eachExpectedLine); + } + } + } + if (!resultSetMap.isEmpty()) { + fail(message + ", too many result: " + Joiner.on(",").join(resultSetMap.keySet())); + } + } else { + assertEquals(message, expectedResult.trim(), actualResult.trim()); + } } private Path getQueryFilePath(String fileName) throws IOException { diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java new file mode 100644 index 0000000000..481b14d04f --- /dev/null +++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java @@ -0,0 +1,204 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.tajo.engine.planner; + +import org.apache.tajo.LocalTajoTestingUtility; +import org.apache.tajo.TajoConstants; +import org.apache.tajo.TajoTestingCluster; +import org.apache.tajo.algebra.Expr; +import org.apache.tajo.catalog.*; +import org.apache.tajo.catalog.proto.CatalogProtos.StoreType; +import org.apache.tajo.catalog.statistics.TableStats; +import org.apache.tajo.common.TajoDataTypes.Type; +import org.apache.tajo.engine.function.FunctionLoader; +import org.apache.tajo.engine.parser.SQLAnalyzer; +import org.apache.tajo.engine.query.QueryContext; +import org.apache.tajo.plan.LogicalOptimizer; +import org.apache.tajo.plan.LogicalPlan; +import org.apache.tajo.plan.LogicalPlanner; +import org.apache.tajo.plan.logical.*; +import org.apache.tajo.plan.util.PlannerUtil; +import org.apache.tajo.util.CommonTestingUtil; +import org.apache.tajo.util.KeyValueSet; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME; +import static org.apache.tajo.TajoConstants.DEFAULT_TABLESPACE_NAME; +import static org.junit.Assert.*; + +public class TestGreedyHeuristicJoinOrderAlgorithm { + + private static TajoTestingCluster util; + private static CatalogService catalog; + private static SQLAnalyzer sqlAnalyzer; + private static LogicalPlanner planner; + private static LogicalOptimizer optimizer; + private static QueryContext defaultContext; + + @BeforeClass + public static void setUp() throws Exception { + util = new TajoTestingCluster(); + util.startCatalogCluster(); + catalog = util.getMiniCatalogCluster().getCatalog(); + catalog.createTablespace(DEFAULT_TABLESPACE_NAME, "hdfs://localhost:1234/warehouse"); + catalog.createDatabase(DEFAULT_DATABASE_NAME, DEFAULT_TABLESPACE_NAME); + for (FunctionDesc funcDesc : FunctionLoader.findLegacyFunctions()) { + catalog.createFunction(funcDesc); + } + + Schema schema = new Schema(); + schema.addColumn("name", Type.TEXT); + schema.addColumn("empid", Type.INT4); + schema.addColumn("deptname", Type.TEXT); + + Schema schema2 = new Schema(); + schema2.addColumn("deptname", Type.TEXT); + schema2.addColumn("manager", Type.TEXT); + + Schema schema3 = new Schema(); + schema3.addColumn("deptname", Type.TEXT); + schema3.addColumn("score", Type.INT4); + schema3.addColumn("phone", Type.INT4); + + TableMeta meta = CatalogUtil.newTableMeta(StoreType.CSV); + TableDesc people = new TableDesc( + CatalogUtil.buildFQName(TajoConstants.DEFAULT_DATABASE_NAME, "employee"), schema, meta, + CommonTestingUtil.getTestDir().toUri()); + catalog.createTable(people); + + TableDesc student = + new TableDesc( + CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "dept"), schema2, StoreType.CSV, new KeyValueSet(), + CommonTestingUtil.getTestDir().toUri()); + catalog.createTable(student); + + TableDesc score = + new TableDesc( + CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "score"), schema3, StoreType.CSV, new KeyValueSet(), + CommonTestingUtil.getTestDir().toUri()); + catalog.createTable(score); + + /////////////////////////////////////////////////////////////////////////// + // creating table for overflow in JoinOrderOptimizer. + Schema schema4 = new Schema(); + schema4.addColumn("deptname", Type.TEXT); + schema4.addColumn("manager", Type.TEXT); + //If table's store type is StoreType.SYSTEM, Planner doesn't update table stats. + TableMeta largeTableMeta = CatalogUtil.newTableMeta(StoreType.SYSTEM); + TableDesc largeDept = + new TableDesc( + CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "large_dept"), schema4, StoreType.CSV, new KeyValueSet(), + CommonTestingUtil.getTestDir().toUri()); + largeDept.setMeta(largeTableMeta); + TableStats largeTableStats = new TableStats(); + largeTableStats.setNumBytes(1024L * 1024L * 1024L * 1024L * 1024L); //1 PB +// largeTableStats.setNumBytes(1024L * 1024L); //1 PB + largeDept.setStats(largeTableStats); + catalog.createTable(largeDept); + /////////////////////////////////////////////////////////////////////////// + + sqlAnalyzer = new SQLAnalyzer(); + planner = new LogicalPlanner(catalog); + optimizer = new LogicalOptimizer(util.getConfiguration()); + + defaultContext = LocalTajoTestingUtility.createDummyContext(util.getConfiguration()); + } + + @AfterClass + public static void tearDown() throws Exception { + util.shutdownCatalogCluster(); + } + + @Test + public final void testFindBestJoinOrder() throws Exception { + String query = "select e.name, d.manager from employee as e, dept as d, score as s, large_dept as ld " + + "where e.deptName = d.deptName and d.deptname = s.deptname and s.deptname=ld.deptname"; + Expr expr = sqlAnalyzer.parse(query); + LogicalPlan newPlan = planner.createPlan(defaultContext, expr); + + //Not optimized plan has 3 join nodes + // [[employee-dept]-score]-large_dept + LogicalNode[] joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); + assertNotNull(joinNodes); + assertEquals(3, joinNodes.length); + assertJoinNode(joinNodes[0], "default.e", "default.d"); + assertJoinNode(joinNodes[1], null, "default.s"); + assertJoinNode(joinNodes[2], null, "default.ld"); + + optimizer.optimize(newPlan); + + //Optimized plan has 3 join nodes + // [employee-dept]-[score-large_dept] + joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); + assertNotNull(joinNodes); + assertEquals(3, joinNodes.length); + assertJoinNode(joinNodes[0], "default.e", "default.d"); + assertJoinNode(joinNodes[1], "default.s", "default.ld"); + assertJoinNode(joinNodes[2], null, null); + } + + @Test + public final void testCheckingInfinityJoinScore() throws Exception { + // Test for TAJO-1552 + String query = "select a.deptname from large_dept a, large_dept b, large_dept c, " + + "large_dept d, large_dept e, large_dept f "; + + Expr expr = sqlAnalyzer.parse(query); + LogicalPlan newPlan = planner.createPlan(defaultContext, expr); + LogicalNode[] joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); + assertNotNull(joinNodes); + assertEquals(5, joinNodes.length); + assertJoinNode(joinNodes[0], "default.a", "default.b"); + assertJoinNode(joinNodes[1], null, "default.c"); + assertJoinNode(joinNodes[2], null, "default.d"); + assertJoinNode(joinNodes[3], null, "default.e"); + assertJoinNode(joinNodes[4], null, "default.f"); + + optimizer.optimize(newPlan); + + joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); + assertNotNull(joinNodes); + assertEquals(5, joinNodes.length); + assertJoinNode(joinNodes[0], "default.e", "default.f"); + assertJoinNode(joinNodes[1], "default.a", "default.b"); + assertJoinNode(joinNodes[2], "default.c", "default.d"); + assertJoinNode(joinNodes[3], null, null); + assertJoinNode(joinNodes[4], null, null); + + } + + private void assertJoinNode(LogicalNode node, String left, String right) { + assertEquals(NodeType.JOIN, node.getType()); + JoinNode joinNode = (JoinNode)node; + + if (left != null) { + assertEquals(left, ((ScanNode)joinNode.getLeftChild()).getCanonicalName()); + } else { + assertEquals(NodeType.JOIN, joinNode.getLeftChild().getType()); + } + + if (right != null) { + assertEquals(right, ((ScanNode)joinNode.getRightChild()).getCanonicalName()); + } else { + assertEquals(NodeType.JOIN, joinNode.getRightChild().getType()); + } + } +} diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java index 1078943ce4..40aba166f4 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java @@ -214,7 +214,7 @@ public final void testJoinWithMultipleJoinQual2() throws Exception { @Test public final void testJoinWithMultipleJoinQual3() throws Exception { ResultSet res = executeQuery(); - assertResultSet(res); + assertResultSet(res, true); cleanupQuery(res); } @@ -598,7 +598,7 @@ join partsupp ps on (s.s_suppkey = ps.ps_suppkey) where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO'); */ ResultSet res = executeJsonQuery(); - assertResultSet(res); + assertResultSet(res, true); cleanupQuery(res); } diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java index e4d61226a1..97d84044c3 100644 --- a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java +++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java @@ -37,6 +37,7 @@ */ public class GreedyHeuristicJoinOrderAlgorithm implements JoinOrderAlgorithm { public static final double DEFAULT_SELECTION_FACTOR = 0.1; + public static final int DEFAULT_JOIN_SCALE_ADJUST_FACTOR = 10 * 1024 * 1024; // 10MB @Override public FoundJoinOrder findBestOrder(LogicalPlan plan, LogicalPlan.QueryBlock block, JoinGraph joinGraph, @@ -196,7 +197,7 @@ private JoinEdge getBestPair(LogicalPlan plan, JoinGraph graph, Set } double cost = getCost(foundJoin); - if (cost < minCost) { + if (cost < minCost || (cost == minCost && cost == Double.MAX_VALUE)) { minCost = cost; bestJoin = foundJoin; relatedJoinEdges = joinEdgePairs; @@ -313,10 +314,26 @@ public static double getCost(JoinEdge joinEdge) { // TODO - should consider join type // TODO - should statistic information obtained from query history filterFactor = filterFactor * Math.pow(DEFAULT_SELECTION_FACTOR, joinEdge.getJoinQual().length); - return getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()) * filterFactor; + return checkInfinity(getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()) * filterFactor); } else { // make cost bigger if cross join - return Math.pow(getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()), 2); + return checkInfinity(Math.pow(getCost(joinEdge.getLeftRelation()) * getCost(joinEdge.getRightRelation()), 2)); + } + } + + + /** + * If a calculated cost is a infinity value, return Long.MAX_VALUE + * @param cost + * @return + */ + private static double checkInfinity(double cost) { + if (cost == Double.POSITIVE_INFINITY) { + return Long.MAX_VALUE; + } else if (cost == Double.NEGATIVE_INFINITY) { + return Long.MIN_VALUE; + } else { + return cost; } } @@ -351,10 +368,10 @@ public static double getCost(LogicalNode node) { case SCAN: ScanNode scanNode = (ScanNode) node; if (scanNode.getTableDesc().getStats() != null) { - double cost = ((ScanNode)node).getTableDesc().getStats().getNumBytes(); + double cost = ((ScanNode)node).getTableDesc().getStats().getNumBytes() / DEFAULT_JOIN_SCALE_ADJUST_FACTOR; return cost; } else { - return Long.MAX_VALUE; + return Integer.MAX_VALUE; } case UNION: From ed2943de1af55683bd942e8f42dc20f85208aad8 Mon Sep 17 00:00:00 2001 From: Hyoungjun Kim Date: Tue, 21 Apr 2015 02:54:43 +0900 Subject: [PATCH 2/6] TAJO-1552: NPE occurs when GreedyHeuristicJoinOrderAlgorithm.getCost() returns infinity. --- .../tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java index 97d84044c3..541cd58fe9 100644 --- a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java +++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java @@ -197,7 +197,8 @@ private JoinEdge getBestPair(LogicalPlan plan, JoinGraph graph, Set } double cost = getCost(foundJoin); - if (cost < minCost || (cost == minCost && cost == Double.MAX_VALUE)) { + if (cost < minCost || + (cost == minCost && cost == Double.MAX_VALUE)) { minCost = cost; bestJoin = foundJoin; relatedJoinEdges = joinEdgePairs; From d674b6567f35ed3a97682197d31d4b883710f520 Mon Sep 17 00:00:00 2001 From: Hyoungjun Kim Date: Tue, 21 Apr 2015 03:26:52 +0900 Subject: [PATCH 3/6] TAJO-1552: NPE occurs when GreedyHeuristicJoinOrderAlgorithm.getCost() returns infinity. Rename TestGreedyHeuristicJoinOrderAlgorithm to TestJoinOrderAlgorithm. --- ...isticJoinOrderAlgorithm.java => TestJoinOrderAlgorithm.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tajo-core/src/test/java/org/apache/tajo/engine/planner/{TestGreedyHeuristicJoinOrderAlgorithm.java => TestJoinOrderAlgorithm.java} (99%) diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java similarity index 99% rename from tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java rename to tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java index 481b14d04f..accce4432e 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestGreedyHeuristicJoinOrderAlgorithm.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java @@ -44,7 +44,7 @@ import static org.apache.tajo.TajoConstants.DEFAULT_TABLESPACE_NAME; import static org.junit.Assert.*; -public class TestGreedyHeuristicJoinOrderAlgorithm { +public class TestJoinOrderAlgorithm { private static TajoTestingCluster util; private static CatalogService catalog; From 0b05727faecf82b8e8a0e77de848ef3edffa635c Mon Sep 17 00:00:00 2001 From: Hyoungjun Kim Date: Mon, 11 May 2015 23:37:34 +0900 Subject: [PATCH 4/6] TAJO-1552: NPE occurs when GreedyHeuristicJoinOrderAlgorithm.getCost() returns infinity. --- .../org/apache/tajo/QueryTestCaseBase.java | 80 +--- .../tajo/engine/query/TestJoinQuery.java | 7 +- .../TestJoinQuery/testJoinWithJson2.json | 379 +++++++++--------- .../testJoinWithMultipleJoinQual3.sql | 3 +- 4 files changed, 215 insertions(+), 254 deletions(-) diff --git a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java index 331528f205..68fe94ec58 100644 --- a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java +++ b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java @@ -18,7 +18,6 @@ package org.apache.tajo; -import com.google.common.base.Joiner; import com.google.protobuf.ServiceException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,7 +66,10 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import static org.junit.Assert.*; @@ -221,7 +223,7 @@ public static void tearDownClass() throws ServiceException { public void printTestName() { /* protect a travis stalled build */ System.out.println("Run: " + name.getMethodName() + - " Used memory: " + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) + " Used memory: " + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "MBytes"); } @@ -437,18 +439,7 @@ public ResultSet executeJsonFile(String jsonFileName) throws Exception { * @param result Query result to be compared. */ public final void assertResultSet(ResultSet result) throws IOException { - assertResultSet(result, false); - } - - /** - * Assert the equivalence between the expected result and an actual query result. - * If it isn't it throws an AssertionError. - * - * @param result Query result to be compared. - * @param ignoreOrdering If true, don't concern ordering. - */ - public final void assertResultSet(ResultSet result, boolean ignoreOrdering) throws IOException { - assertResultSet("Result Verification", result, getMethodName() + ".result", ignoreOrdering); + assertResultSet("Result Verification", result, getMethodName() + ".result"); } /** @@ -459,7 +450,7 @@ public final void assertResultSet(ResultSet result, boolean ignoreOrdering) thro * @param resultFileName The file name containing the result to be compared */ public final void assertResultSet(ResultSet result, String resultFileName) throws IOException { - assertResultSet("Result Verification", result, resultFileName, false); + assertResultSet("Result Verification", result, resultFileName); } /** @@ -468,13 +459,12 @@ public final void assertResultSet(ResultSet result, String resultFileName) throw * * @param message message The message to printed if the assertion is failed. * @param result Query result to be compared. - * @param ignoreOrdering If true, don't concern ordering. */ - public final void assertResultSet(String message, ResultSet result, String resultFileName, - boolean ignoreOrdering) throws IOException { + public final void assertResultSet(String message, ResultSet result, String resultFileName) throws IOException { + FileSystem fs = currentQueryPath.getFileSystem(testBase.getTestingCluster().getConfiguration()); Path resultFile = getResultFile(resultFileName); try { - verifyResultText(message, result, resultFile, ignoreOrdering); + verifyResultText(message, result, resultFile); } catch (SQLException e) { throw new IOException(e); } @@ -592,56 +582,10 @@ public String resultSetToString(ResultSet resultSet) throws SQLException { return sb.toString(); } - private Map stringToMap(String str) throws SQLException { - Map resultMap = new HashMap(); - - for (String eachLine: str.split("\n")) { - if (resultMap.containsKey(eachLine)) { - resultMap.put(eachLine, resultMap.get(eachLine) + 1); - } else { - resultMap.put(eachLine, 1); - } - } - - return resultMap; - } - private void verifyResultText(String message, ResultSet res, Path resultFile) throws SQLException, IOException { - verifyResultText(message, res, resultFile, false); - } - - /** - * It compares ResultSet with the contents of given resultFile. - * @param message the message which is shown when query failed. - * @param res query result - * @param resultFile the file path which contains the expected data. - * @param ignoreLineOrder if true, the order of result is ignored. - * @throws SQLException - * @throws IOException - */ - private void verifyResultText(String message, ResultSet res, Path resultFile, boolean ignoreLineOrder) - throws SQLException, IOException { String actualResult = resultSetToString(res); String expectedResult = FileUtil.readTextFile(new File(resultFile.toUri())); - if (ignoreLineOrder) { - Map resultSetMap = stringToMap(actualResult); - for (String eachExpectedLine: expectedResult.split("\n")) { - if (!resultSetMap.containsKey(eachExpectedLine)) { - fail(message + ", no expected value: " + eachExpectedLine); - } else { - int remainCount = resultSetMap.get(eachExpectedLine); - remainCount--; - if (remainCount == 0) { - resultSetMap.remove(eachExpectedLine); - } - } - } - if (!resultSetMap.isEmpty()) { - fail(message + ", too many result: " + Joiner.on(",").join(resultSetMap.keySet())); - } - } else { - assertEquals(message, expectedResult.trim(), actualResult.trim()); - } + assertEquals(message, expectedResult.trim(), actualResult.trim()); } private Path getQueryFilePath(String fileName) throws IOException { @@ -874,4 +818,4 @@ private List listFiles(FileSystem fs, Path path) throws Exception { } return result; } -} +} \ No newline at end of file diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java index 417ade8496..28b9884e40 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestJoinQuery.java @@ -215,7 +215,7 @@ public final void testJoinWithMultipleJoinQual2() throws Exception { @Test public final void testJoinWithMultipleJoinQual3() throws Exception { ResultSet res = executeQuery(); - assertResultSet(res, true); + assertResultSet(res); cleanupQuery(res); } @@ -596,10 +596,11 @@ join region r on (n.n_regionkey = r.r_regionkey) ) t join supplier s on (s.s_nationkey = t.n_nationkey) join partsupp ps on (s.s_suppkey = ps.ps_suppkey) - where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO'); + where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO') + order by t.n_nationkey; */ ResultSet res = executeJsonQuery(); - assertResultSet(res, true); + assertResultSet(res); cleanupQuery(res); } diff --git a/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithJson2.json b/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithJson2.json index 353636a7f8..e90a146838 100644 --- a/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithJson2.json +++ b/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithJson2.json @@ -7,213 +7,228 @@ ) t join supplier s on (s.s_nationkey = t.n_nationkey) join partsupp ps on (s.s_suppkey = ps.ps_suppkey) - where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO'); + where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO') + order by t.n_nationkey; */ { - "IsDistinct": false, - "Projections": [ - { - "Expr": { - "Qualifier": "t", - "ColumnName": "n_nationkey", - "OpType": "Column" - }, - "OpType": "Target" - }, - { - "Expr": { - "Qualifier": "t", - "ColumnName": "n_name", - "OpType": "Column" - }, - "OpType": "Target" + "IsDistinct": false, + "Projections": [ + { + "Expr": { + "Qualifier": "t", + "ColumnName": "n_nationkey", + "OpType": "Column" + }, + "OpType": "Target" + }, + { + "Expr": { + "Qualifier": "t", + "ColumnName": "n_name", + "OpType": "Column" + }, + "OpType": "Target" + }, + { + "Expr": { + "Qualifier": "t", + "ColumnName": "n_regionkey", + "OpType": "Column" + }, + "OpType": "Target" + }, + { + "Expr": { + "Qualifier": "t", + "ColumnName": "n_comment", + "OpType": "Column" + }, + "OpType": "Target" + }, + { + "Expr": { + "Qualifier": "ps", + "ColumnName": "ps_availqty", + "OpType": "Column" + }, + "OpType": "Target" + }, + { + "Expr": { + "Qualifier": "s", + "ColumnName": "s_suppkey", + "OpType": "Column" + }, + "OpType": "Target" + } + ], + "Expr": { + "SortSpecs": [ + { + "SortKey": { + "Qualifier": "t", + "ColumnName": "n_nationkey", + "OpType": "Column" }, - { - "Expr": { - "Qualifier": "t", - "ColumnName": "n_regionkey", - "OpType": "Column" - }, - "OpType": "Target" + "IsAsc": true, + "IsNullFirst": false + } + ], + "Expr": { + "SelectCondition": { + "IsNot": false, + "LeftExpr": { + "Qualifier": "t", + "ColumnName": "n_name", + "OpType": "Column" }, - { - "Expr": { - "Qualifier": "t", - "ColumnName": "n_comment", - "OpType": "Column" + "RightExpr": { + "Values": [ + { + "Value": "ARGENTINA", + "ValueType": "String", + "OpType": "Literal" }, - "OpType": "Target" - }, - { - "Expr": { - "Qualifier": "ps", - "ColumnName": "ps_availqty", - "OpType": "Column" + { + "Value": "ETHIOPIA", + "ValueType": "String", + "OpType": "Literal" }, - "OpType": "Target" + { + "Value": "MOROCCO", + "ValueType": "String", + "OpType": "Literal" + } + ], + "OpType": "ValueList" }, - { - "Expr": { + "OpType": "InPredicate" + }, + "Expr": { + "Relations": [ + { + "JoinType": "INNER", + "JoinCondition": { + "LeftExpr": { "Qualifier": "s", "ColumnName": "s_suppkey", "OpType": "Column" - }, - "OpType": "Target" - } - ], - "Expr": { - "SelectCondition": { - "IsNot": false, - "LeftExpr": { - "Qualifier": "t", - "ColumnName": "n_name", + }, + "RightExpr": { + "Qualifier": "ps", + "ColumnName": "ps_suppkey", "OpType": "Column" + }, + "OpType": "Equals" }, - "RightExpr": { - "Values": [ + "IsNatural": false, + "LeftExpr": { + "JoinType": "INNER", + "JoinCondition": { + "LeftExpr": { + "Qualifier": "s", + "ColumnName": "s_nationkey", + "OpType": "Column" + }, + "RightExpr": { + "Qualifier": "t", + "ColumnName": "n_nationkey", + "OpType": "Column" + }, + "OpType": "Equals" + }, + "IsNatural": false, + "LeftExpr": { + "SubPlan": { + "IsDistinct": false, + "Projections": [ { - "Value": "ARGENTINA", - "ValueType": "String", - "OpType": "Literal" + "Expr": { + "ColumnName": "n_nationkey", + "OpType": "Column" + }, + "OpType": "Target" }, { - "Value": "ETHIOPIA", - "ValueType": "String", - "OpType": "Literal" + "Expr": { + "ColumnName": "n_name", + "OpType": "Column" + }, + "OpType": "Target" }, { - "Value": "MOROCCO", - "ValueType": "String", - "OpType": "Literal" - } - ], - "OpType": "ValueList" - }, - "OpType": "InPredicate" - }, - "Expr": { - "Relations": [ - { - "JoinType": "INNER", - "JoinCondition": { - "LeftExpr": { - "Qualifier": "s", - "ColumnName": "s_suppkey", - "OpType": "Column" - }, - "RightExpr": { - "Qualifier": "ps", - "ColumnName": "ps_suppkey", - "OpType": "Column" - }, - "OpType": "Equals" + "Expr": { + "ColumnName": "n_regionkey", + "OpType": "Column" + }, + "OpType": "Target" }, - "IsNatural": false, - "LeftExpr": { + { + "Expr": { + "ColumnName": "n_comment", + "OpType": "Column" + }, + "OpType": "Target" + } + ], + "Expr": { + "Relations": [ + { "JoinType": "INNER", "JoinCondition": { - "LeftExpr": { - "Qualifier": "s", - "ColumnName": "s_nationkey", - "OpType": "Column" - }, - "RightExpr": { - "Qualifier": "t", - "ColumnName": "n_nationkey", - "OpType": "Column" - }, - "OpType": "Equals" + "LeftExpr": { + "Qualifier": "n", + "ColumnName": "n_regionkey", + "OpType": "Column" + }, + "RightExpr": { + "Qualifier": "r", + "ColumnName": "r_regionkey", + "OpType": "Column" + }, + "OpType": "Equals" }, "IsNatural": false, "LeftExpr": { - "SubPlan": { - "IsDistinct": false, - "Projections": [ - { - "Expr": { - "ColumnName": "n_nationkey", - "OpType": "Column" - }, - "OpType": "Target" - }, - { - "Expr": { - "ColumnName": "n_name", - "OpType": "Column" - }, - "OpType": "Target" - }, - { - "Expr": { - "ColumnName": "n_regionkey", - "OpType": "Column" - }, - "OpType": "Target" - }, - { - "Expr": { - "ColumnName": "n_comment", - "OpType": "Column" - }, - "OpType": "Target" - } - ], - "Expr": { - "Relations": [ - { - "JoinType": "INNER", - "JoinCondition": { - "LeftExpr": { - "Qualifier": "n", - "ColumnName": "n_regionkey", - "OpType": "Column" - }, - "RightExpr": { - "Qualifier": "r", - "ColumnName": "r_regionkey", - "OpType": "Column" - }, - "OpType": "Equals" - }, - "IsNatural": false, - "LeftExpr": { - "TableName": "nation", - "TableAlias": "n", - "OpType": "Relation" - }, - "RightExpr": { - "TableName": "region", - "TableAlias": "r", - "OpType": "Relation" - }, - "OpType": "Join" - } - ], - "OpType": "RelationList" - }, - "OpType": "Projection" - }, - "TableName": "t", - "OpType": "TablePrimaryTableSubQuery" + "TableName": "nation", + "TableAlias": "n", + "OpType": "Relation" }, "RightExpr": { - "TableName": "supplier", - "TableAlias": "s", - "OpType": "Relation" + "TableName": "region", + "TableAlias": "r", + "OpType": "Relation" }, "OpType": "Join" - }, - "RightExpr": { - "TableName": "partsupp", - "TableAlias": "ps", - "OpType": "Relation" - }, - "OpType": "Join" - } - ], - "OpType": "RelationList" - }, - "OpType": "Filter" + } + ], + "OpType": "RelationList" + }, + "OpType": "Projection" + }, + "TableName": "t", + "OpType": "TablePrimaryTableSubQuery" + }, + "RightExpr": { + "TableName": "supplier", + "TableAlias": "s", + "OpType": "Relation" + }, + "OpType": "Join" + }, + "RightExpr": { + "TableName": "partsupp", + "TableAlias": "ps", + "OpType": "Relation" + }, + "OpType": "Join" + } + ], + "OpType": "RelationList" + }, + "OpType": "Filter" }, - "OpType": "Projection" -} + "OpType": "Sort" + }, + "OpType": "Projection" +} \ No newline at end of file diff --git a/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithMultipleJoinQual3.sql b/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithMultipleJoinQual3.sql index b34e9de18d..393db68c2b 100644 --- a/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithMultipleJoinQual3.sql +++ b/tajo-core/src/test/resources/queries/TestJoinQuery/testJoinWithMultipleJoinQual3.sql @@ -6,4 +6,5 @@ from ( ) t join supplier s on (s.s_nationkey = t.n_nationkey) join partsupp ps on (s.s_suppkey = ps.ps_suppkey) -where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO'); +where t.n_name in ('ARGENTINA','ETHIOPIA', 'MOROCCO') +order by t.n_nationkey; \ No newline at end of file From 297f5d8e3f78f33136a1b482c93b0461a552a2a1 Mon Sep 17 00:00:00 2001 From: Jihoon Son Date: Mon, 27 Jul 2015 12:55:09 +0900 Subject: [PATCH 5/6] TAJO-1552 --- .../planner/TestJoinOrderAlgorithm.java | 70 +++++++------------ .../org/apache/tajo/plan/LogicalPlanner.java | 4 +- 2 files changed, 28 insertions(+), 46 deletions(-) diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java index f5bbcf286b..b1710b91b7 100644 --- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java +++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestJoinOrderAlgorithm.java @@ -18,6 +18,8 @@ package org.apache.tajo.engine.planner; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; import org.apache.tajo.LocalTajoTestingUtility; import org.apache.tajo.TajoConstants; import org.apache.tajo.TajoTestingCluster; @@ -102,17 +104,23 @@ public static void setUp() throws Exception { Schema schema4 = new Schema(); schema4.addColumn("deptname", Type.TEXT); schema4.addColumn("manager", Type.TEXT); - //If table's store type is StoreType.SYSTEM, Planner doesn't update table stats. - TableMeta largeTableMeta = CatalogUtil.newTableMeta("SYSTEM"); - TableDesc largeDept = - new TableDesc( - CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "large_dept"), schema4, "TEXT", new KeyValueSet(), - CommonTestingUtil.getTestDir().toUri()); - largeDept.setMeta(largeTableMeta); - TableStats largeTableStats = new TableStats(); - largeTableStats.setNumBytes(1024L * 1024L * 1024L * 1024L * 1024L); //1 PB - largeDept.setStats(largeTableStats); - catalog.createTable(largeDept); + // Set store type as FAKEFILE to prevent auto update of physical information in LogicalPlanner.updatePhysicalInfo() + TableMeta largeTableMeta = CatalogUtil.newTableMeta("FAKEFILE"); + TableDesc largeDept; + TableStats largeTableStats; + FileSystem fs = FileSystem.getLocal(util.getConfiguration()); + for (int i = 0; i < 6; i++) { + Path tablePath = new Path(CommonTestingUtil.getTestDir(), "" + (i+1)); + fs.create(tablePath); + largeDept = + new TableDesc( + CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "large_dept"+(i+1)), schema4, largeTableMeta, + tablePath.toUri()); + largeTableStats = new TableStats(); + largeTableStats.setNumBytes(1024L * 1024L * 1024L * 1024L * 1024L * (i+1)); //1 PB * i + largeDept.setStats(largeTableStats); + catalog.createTable(largeDept); + } /////////////////////////////////////////////////////////////////////////// sqlAnalyzer = new SQLAnalyzer(); @@ -127,39 +135,11 @@ public static void tearDown() throws Exception { util.shutdownCatalogCluster(); } - @Test - public final void testFindBestJoinOrder() throws Exception { - String query = "select e.name, d.manager from employee as e, dept as d, score as s, large_dept as ld " + - "where e.deptName = d.deptName and d.deptname = s.deptname and s.deptname=ld.deptname"; - Expr expr = sqlAnalyzer.parse(query); - LogicalPlan newPlan = planner.createPlan(defaultContext, expr); - - //Not optimized plan has 3 join nodes - // [[employee-dept]-score]-large_dept - LogicalNode[] joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); - assertNotNull(joinNodes); - assertEquals(3, joinNodes.length); - assertJoinNode(joinNodes[0], "default.e", "default.d"); - assertJoinNode(joinNodes[1], null, "default.s"); - assertJoinNode(joinNodes[2], null, "default.ld"); - - optimizer.optimize(newPlan); - - //Optimized plan has 3 join nodes - // [employee-dept]-[score-large_dept] - joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); - assertNotNull(joinNodes); - assertEquals(3, joinNodes.length); - assertJoinNode(joinNodes[0], "default.e", "default.d"); - assertJoinNode(joinNodes[1], "default.s", "default.ld"); - assertJoinNode(joinNodes[2], null, null); - } - @Test public final void testCheckingInfinityJoinScore() throws Exception { // Test for TAJO-1552 - String query = "select a.deptname from large_dept a, large_dept b, large_dept c, " + - "large_dept d, large_dept e, large_dept f "; + String query = "select a.deptname from large_dept1 a, large_dept2 b, large_dept3 c, " + + "large_dept4 d, large_dept5 e, large_dept6 f "; Expr expr = sqlAnalyzer.parse(query); LogicalPlan newPlan = planner.createPlan(defaultContext, expr); @@ -177,11 +157,11 @@ public final void testCheckingInfinityJoinScore() throws Exception { joinNodes = PlannerUtil.findAllNodes(newPlan. getRootBlock().getRoot() , NodeType.JOIN); assertNotNull(joinNodes); assertEquals(5, joinNodes.length); - assertJoinNode(joinNodes[0], "default.f", "default.d"); - assertJoinNode(joinNodes[1], "default.c", "default.b"); + assertJoinNode(joinNodes[0], "default.d", "default.c"); + assertJoinNode(joinNodes[1], "default.b", "default.a"); assertJoinNode(joinNodes[2], null, null); - assertJoinNode(joinNodes[2], "default.e", "default.a"); - assertJoinNode(joinNodes[3], null, null); + assertJoinNode(joinNodes[3], "default.f", "default.e"); + assertJoinNode(joinNodes[4], null, null); } diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java index 47ab9b1e27..7f5cf9a2a6 100644 --- a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java +++ b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java @@ -1357,7 +1357,9 @@ private static LinkedHashSet createFieldTargetsFromRelation(QueryBlock b private void updatePhysicalInfo(PlanContext planContext, TableDesc desc) { if (desc.getUri() != null && - desc.getMeta().getStoreType() != "SYSTEM" && PlannerUtil.isFileStorageType(desc.getMeta().getStoreType())) { + !desc.getMeta().getStoreType().equals("SYSTEM") && + !desc.getMeta().getStoreType().equals("FAKEFILE") && // FAKEFILE is used for test + PlannerUtil.isFileStorageType(desc.getMeta().getStoreType())) { try { Path path = new Path(desc.getUri()); FileSystem fs = path.getFileSystem(planContext.queryContext.getConf()); From 9c1c58d6dab2a83e24a820fb491e5596b0fa68d6 Mon Sep 17 00:00:00 2001 From: Jihoon Son Date: Mon, 27 Jul 2015 14:57:43 +0900 Subject: [PATCH 6/6] fix unexpected changes in join order --- .../plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java index 61f1c23463..403d0b8091 100644 --- a/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java +++ b/tajo-plan/src/main/java/org/apache/tajo/plan/joinorder/GreedyHeuristicJoinOrderAlgorithm.java @@ -41,7 +41,6 @@ public class GreedyHeuristicJoinOrderAlgorithm implements JoinOrderAlgorithm { public static final double DEFAULT_SELECTION_FACTOR = 0.1; - public static final int DEFAULT_JOIN_SCALE_ADJUST_FACTOR = 10 * 1024 * 1024; // 10MB @Override public FoundJoinOrder findBestOrder(LogicalPlan plan, LogicalPlan.QueryBlock block, JoinGraphContext graphContext) @@ -238,7 +237,7 @@ private JoinEdge getBestPair(JoinEdgeFinderContext context, JoinGraphContext gra } private static JoinEdge swapLeftAndRightIfNecessary(JoinEdge edge) { - if (PlannerUtil.isCommutativeJoinType(edge.getJoinType()) || edge.getJoinType() == JoinType.FULL_OUTER) { + if (PlannerUtil.isCommutativeJoinType(edge.getJoinType())) { double leftCost = getCost(edge.getLeftVertex()); double rightCost = getCost(edge.getRightVertex()); if (leftCost < rightCost) { @@ -414,7 +413,8 @@ public static double getCost(JoinVertex joinVertex) { } /** - * If a calculated cost is a infinity value, return Long.MAX_VALUE + * Return the MAX(MIN) value if the given cost is positive(negative) infinity. + * * @param cost * @return */ @@ -464,7 +464,7 @@ public static double getCost(LogicalNode node) { case SCAN: ScanNode scanNode = (ScanNode) node; if (scanNode.getTableDesc().getStats() != null) { - cost = ((ScanNode)node).getTableDesc().getStats().getNumBytes() / DEFAULT_JOIN_SCALE_ADJUST_FACTOR; + cost = ((ScanNode)node).getTableDesc().getStats().getNumBytes(); } else { cost = Integer.MAX_VALUE; }