| /* |
| * 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.hadoop.hive.ql; |
| |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.hadoop.hive.conf.HiveConf; |
| import org.apache.hadoop.hive.conf.HiveConf.ConfVars; |
| import org.apache.hadoop.hive.metastore.HMSHandler; |
| import org.apache.hadoop.hive.metastore.conf.MetastoreConf; |
| import org.apache.hive.jdbc.miniHS2.MiniHS2; |
| import org.apache.hive.service.cli.HiveSQLException; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| /** |
| * This unit test is for testing HIVE-13884 with more complex queries and |
| * hive.metastore.limit.partition.request enabled. |
| * It covers cases when the query predicates can be pushed down and the |
| * number of partitions can be retrieved via directSQL. |
| * It also covers cases when the number of partitions cannot be retrieved |
| * via directSQL, so it falls back to ORM. |
| */ |
| public class TestMetaStoreLimitPartitionRequest { |
| |
| private static final String DB_NAME = "max_partition_test_db"; |
| private static final String TABLE_NAME = "max_partition_test_table"; |
| private static int PARTITION_REQUEST_LIMIT = 4; |
| private static MiniHS2 miniHS2 = null; |
| private static HiveConf conf; |
| private Connection hs2Conn = null; |
| private Statement stmt; |
| |
| @BeforeClass |
| public static void beforeTest() throws Exception { |
| Class.forName(MiniHS2.getJdbcDriverName()); |
| conf = new HiveConf(); |
| DriverManager.setLoginTimeout(0); |
| |
| conf.setBoolVar(HiveConf.ConfVars.HIVE_SUPPORT_CONCURRENCY, false); |
| conf.setIntVar(HiveConf.ConfVars.METASTORE_LIMIT_PARTITION_REQUEST, PARTITION_REQUEST_LIMIT); |
| conf.setBoolVar(HiveConf.ConfVars.METASTORE_INTEGER_JDO_PUSHDOWN, true); |
| conf.setBoolVar(HiveConf.ConfVars.METASTORE_TRY_DIRECT_SQL, true); |
| conf.setBoolVar(HiveConf.ConfVars.DYNAMICPARTITIONING, true); |
| conf.setBoolVar(HiveConf.ConfVars.HIVE_CBO_ENABLED, false); |
| |
| miniHS2 = new MiniHS2.Builder().withConf(conf).build(); |
| Map<String, String> overlayProps = new HashMap<String, String>(); |
| miniHS2.start(overlayProps); |
| createDb(); |
| } |
| |
| private static void createDb() throws Exception { |
| Connection conn = |
| DriverManager.getConnection(miniHS2.getJdbcURL(), System.getProperty("user.name"), "bar"); |
| Statement stmt2 = conn.createStatement(); |
| stmt2.execute("DROP DATABASE IF EXISTS " + DB_NAME + " CASCADE"); |
| stmt2.execute("CREATE DATABASE " + DB_NAME); |
| stmt2.close(); |
| conn.close(); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL(DB_NAME), |
| System.getProperty("user.name"), "bar"); |
| stmt = hs2Conn.createStatement(); |
| stmt.execute("USE " + DB_NAME); |
| createTable(); |
| } |
| |
| private void createTable() throws Exception { |
| String tmpTableName = TABLE_NAME + "_tmp"; |
| stmt.execute("CREATE TABLE " + tmpTableName |
| + " (id string, value string, num string, ds date) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n' STORED AS TEXTFILE"); |
| stmt.execute("INSERT OVERWRITE TABLE " + tmpTableName |
| + " VALUES ('1', 'value1', '25', '2008-04-09'), ('2', 'value2', '30', '2008-04-09'), " |
| + "('3', 'value3', '35', '2008-04-09'), ('4', 'value4', '40', '2008-04-09'), " |
| + "('5', 'value5', '25', '2008-05-09'), ('6', 'value6', '30', '2008-05-09'), " |
| + "('7', 'value7', '35', '2008-05-09'), ('8', 'value8', '40', '2008-05-09'), " |
| + "('9', 'value9', '25', '2009-04-09'), ('10', 'value10', '30', '2009-04-09'), " |
| + "('11', 'value11', '35', '2009-04-09'), ('12', 'value12', '40', '2009-04-09')"); |
| |
| stmt.execute("CREATE TABLE " + TABLE_NAME + " (id string, value string) PARTITIONED BY (num string, ds date)"); |
| stmt.execute("INSERT OVERWRITE TABLE " + TABLE_NAME + " PARTITION (num, ds) SELECT id, value, num, ds FROM " + tmpTableName); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| String tmpTableName = TABLE_NAME + "_tmp"; |
| stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME); |
| stmt.execute("DROP TABLE IF EXISTS " + tmpTableName); |
| stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME + "_num_tmp"); |
| |
| if (hs2Conn != null) { |
| hs2Conn.close(); |
| } |
| } |
| |
| @AfterClass |
| public static void afterTest() throws Exception { |
| if (miniHS2 != null && miniHS2.isStarted()) { |
| miniHS2.stop(); |
| } |
| } |
| |
| /* Tests with queries which can be pushed down and executed with directSQL */ |
| |
| @Test |
| public void testSimpleQueryWithDirectSql() throws Exception { |
| String queryString = "select value from %s where num='25' and ds='2008-04-09'"; |
| executeQuery(queryString, "value1"); |
| } |
| |
| @Test |
| public void testMoreComplexQueryWithDirectSql() throws Exception { |
| String queryString = "select value from %s where (ds between '2009-01-01' and '2009-12-31' and num='25') or (ds between '2008-01-01' and '2008-12-31' and num='30')"; |
| executeQuery(queryString, "value2", "value6", "value9"); |
| } |
| |
| /* |
| * Tests with queries which can be pushed down and executed with directSQL, but the number of |
| * partitions which should be fetched is bigger than the maximum set by the |
| * hive.metastore.limit.partition.request parameter. |
| */ |
| |
| @Test |
| public void testSimpleQueryWithDirectSqlTooManyPartitions() throws Exception { |
| String queryString = "select value from %s where ds>'2008-04-20'"; |
| executeQueryExceedPartitionLimit(queryString, 8); |
| } |
| |
| @Test |
| public void testMoreComplexQueryWithDirectSqlTooManyPartitions() throws Exception { |
| String queryString = "select value from %s where num='25' or (num='30' and ds between '2008-01-01' and '2008-12-31')"; |
| executeQueryExceedPartitionLimit(queryString, 5); |
| } |
| |
| /* |
| * Tests with queries which cannot be executed with directSQL, because of type mismatch. The type |
| * of the num column is string, but the parameters used in the where clause are numbers. After |
| * falling back to ORM, the number of partitions can be fetched by the |
| * ObjectStore.getNumPartitionsViaOrmFilter method. |
| */ |
| |
| @Test |
| public void testQueryWithFallbackToORM1() throws Exception { |
| String queryString = "select value from %s where num!=25 and num!=35 and num!=40"; |
| executeQuery(queryString, "value2", "value6", "value10"); |
| } |
| |
| @Test |
| public void testQueryWithFallbackToORMTooManyPartitions1() throws Exception { |
| String queryString = "select value from %s where num=30 or num=25"; |
| executeQueryExceedPartitionLimit(queryString, 6); |
| } |
| |
| /* |
| * Tests with queries which cannot be executed with directSQL, because of type mismatch. The type |
| * of the num column is string, but the parameters used in the where clause are numbers. After |
| * falling back to ORM the number of partitions cannot be fetched by the |
| * ObjectStore.getNumPartitionsViaOrmFilter method. They are fetched by the |
| * ObjectStore.getPartitionNamesPrunedByExprNoTxn method. |
| */ |
| |
| @Test |
| public void testQueryWithFallbackToORM2() throws Exception { |
| String queryString = "select value from %s where num!=25 and ds='2008-04-09'"; |
| executeQuery(queryString, "value2", "value3", "value4"); |
| } |
| |
| @Test |
| public void testQueryWithFallbackToORM3() throws Exception { |
| String queryString = "select value from %s where num between 26 and 31"; |
| executeQuery(queryString, "value2", "value6", "value10"); |
| } |
| |
| @Test |
| public void testQueryWithFallbackToORMTooManyPartitions2() throws Exception { |
| String queryString = "select value from %s where num!=25 and (ds='2008-04-09' or ds='2008-05-09')"; |
| executeQueryExceedPartitionLimit(queryString, 6); |
| } |
| |
| @Test |
| public void testQueryWithFallbackToORMTooManyPartitions3() throws Exception { |
| String queryString = "select value from %s where num>=30"; |
| executeQueryExceedPartitionLimit(queryString, 9); |
| } |
| |
| @Test |
| public void testQueryWithFallbackToORMTooManyPartitions4() throws Exception { |
| String queryString = "select value from %s where num between 20 and 50"; |
| executeQueryExceedPartitionLimit(queryString, 12); |
| } |
| |
| /* |
| * Tests with queries which cannot be executed with directSQL, because the contain like or in. |
| * After falling back to ORM the number of partitions cannot be fetched by the |
| * ObjectStore.getNumPartitionsViaOrmFilter method. They are fetched by the |
| * ObjectStore.getPartitionNamesPrunedByExprNoTxn method. |
| */ |
| |
| @Test |
| public void testQueryWithInWithFallbackToORM() throws Exception { |
| setupNumTmpTable(); |
| String queryString = "select value from %s a where ds='2008-04-09' and a.num in (select value from " + TABLE_NAME + "_num_tmp)"; |
| executeQuery(queryString, "value1", "value2"); |
| } |
| |
| @Test |
| public void testQueryWithInWithFallbackToORMTooManyPartitions() throws Exception { |
| setupNumTmpTable(); |
| String queryString = "select value from %s a where a.num in (select value from " + TABLE_NAME + "_num_tmp)"; |
| executeQueryExceedPartitionLimit(queryString, 12); |
| } |
| |
| @Test |
| public void testQueryWithInWithFallbackToORMTooManyPartitions2() throws Exception { |
| setupNumTmpTable(); |
| String queryString = "select value from %s a where a.num in (select value from " + TABLE_NAME + "_num_tmp where value='25')"; |
| executeQueryExceedPartitionLimit(queryString, 12); |
| } |
| |
| @Test |
| public void testQueryWithLikeWithFallbackToORMTooManyPartitions() throws Exception { |
| String queryString = "select value from %s where num like '3%%'"; |
| executeQueryExceedPartitionLimit(queryString, 6); |
| } |
| |
| private void setupNumTmpTable() throws SQLException { |
| stmt.execute("CREATE TABLE " + TABLE_NAME + "_num_tmp (value string)"); |
| stmt.execute("INSERT INTO " + TABLE_NAME + "_num_tmp VALUES ('25')"); |
| stmt.execute("INSERT INTO " + TABLE_NAME + "_num_tmp VALUES ('30')"); |
| } |
| |
| private void executeQuery(String query, String... expectedValues) throws SQLException { |
| String queryStr = String.format(query, TABLE_NAME); |
| ResultSet result = stmt.executeQuery(queryStr); |
| assertTrue(result != null); |
| Set<String> expectedValueSet = new HashSet<>(Arrays.asList(expectedValues)); |
| Set<String> resultValues = getResultValues(result); |
| String errorMsg = getWrongResultErrorMsg(queryStr, expectedValueSet.toString(), resultValues.toString()); |
| assertTrue(errorMsg, resultValues.equals(expectedValueSet)); |
| } |
| |
| private Set<String> getResultValues(ResultSet result) throws SQLException { |
| Set<String> resultValues = new HashSet<>(); |
| while(result.next()) { |
| resultValues.add(result.getString(1)); |
| } |
| return resultValues; |
| } |
| |
| private void executeQueryExceedPartitionLimit(String query, int expectedPartitionNumber) throws Exception { |
| try { |
| String queryStr = String.format(query, TABLE_NAME); |
| stmt.executeQuery(queryStr); |
| fail("The query should have failed, because the number of requested partitions are bigger than " |
| + PARTITION_REQUEST_LIMIT); |
| } catch (HiveSQLException e) { |
| String exceedLimitMsg = String.format(HMSHandler.PARTITION_NUMBER_EXCEED_LIMIT_MSG, expectedPartitionNumber, |
| TABLE_NAME, PARTITION_REQUEST_LIMIT, MetastoreConf.ConfVars.LIMIT_PARTITION_REQUEST.toString()); |
| assertTrue(getWrongExceptionMessage(exceedLimitMsg, e.getMessage()), |
| e.getMessage().contains(exceedLimitMsg.toString())); |
| } |
| } |
| |
| private String getWrongResultErrorMsg(String query, String expectedValues, String resultValues) { |
| StringBuilder errorMsg = new StringBuilder(); |
| errorMsg.append("The query '"); |
| errorMsg.append(query); |
| errorMsg.append("' returned wrong values. It returned the values "); |
| errorMsg.append(resultValues); |
| errorMsg.append(" instead of the expected "); |
| errorMsg.append(expectedValues); |
| return errorMsg.toString(); |
| } |
| |
| private String getWrongExceptionMessage(String exceedLimitMsg, String exceptionMessage) { |
| StringBuilder errorMsg = new StringBuilder(); |
| errorMsg.append("The message of the exception doesn't contain the expected '"); |
| errorMsg.append(exceedLimitMsg.toString()); |
| errorMsg.append("'. It is: "); |
| errorMsg.append(exceptionMessage); |
| return errorMsg.toString(); |
| } |
| |
| } |