/**
 * 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.hive.jdbc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.metastore.ObjectStore;
import org.apache.hadoop.hive.ql.exec.FunctionRegistry;
import org.apache.hive.common.util.ReflectionUtil;
import org.apache.hive.jdbc.miniHS2.MiniHS2;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.NucleusContext;
import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
import org.datanucleus.AbstractNucleusContext;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestJdbcWithMiniHS2 {
  private static MiniHS2 miniHS2 = null;
  private static String dataFileDir;
  private static Path kvDataFilePath;
  private static final String tmpDir = System.getProperty("test.tmp.dir");
  private static final String testDbName = "testjdbcminihs2";
  private static final String defaultDbName = "default";
  private static final String tableName = "testjdbcminihs2tbl";
  private static final String tableComment = "Simple table";
  private static Connection conDefault = null;
  private static Connection conTestDb = null;
  private static String testUdfClassName =
      "org.apache.hadoop.hive.contrib.udf.example.UDFExampleAdd";

  @BeforeClass
  public static void setupBeforeClass() throws Exception {
    MiniHS2.cleanupLocalDir();
    HiveConf conf = new HiveConf();
    dataFileDir = conf.get("test.data.files").replace('\\', '/').replace("c:", "");
    kvDataFilePath = new Path(dataFileDir, "kv1.txt");
    try {
      startMiniHS2(conf);
    } catch (Exception e) {
     System.out.println("Unable to start MiniHS2: " + e);
     throw e;
    }
    // Open default connections which will be used throughout the tests
    try {
      openDefaultConnections();
    } catch (Exception e) {
      System.out.println("Unable to open default connections to MiniHS2: " + e);
      throw e;
    }
    Statement stmt = conDefault.createStatement();
    stmt.execute("drop database if exists " + testDbName + " cascade");
    stmt.execute("create database " + testDbName);
    stmt.close();

    try {
      openTestConnections();
    } catch (Exception e) {
      System.out.println("Unable to open default connections to MiniHS2: " + e);
      throw e;
    }

    // tables in test db
    createTestTables(conTestDb, testDbName);
  }

  private static Connection getConnection() throws Exception {
    return getConnection(miniHS2.getJdbcURL(), System.getProperty("user.name"), "bar");
  }

  private static Connection getConnection(String dbName) throws Exception {
    return getConnection(miniHS2.getJdbcURL(dbName), System.getProperty("user.name"), "bar");
  }

  private static Connection getConnection(String jdbcURL, String user, String pwd)
      throws SQLException {
    Connection conn = DriverManager.getConnection(jdbcURL, user, pwd);
    assertNotNull(conn);
    return conn;
  }

  private static void createTestTables(Connection conn, String dbName) throws SQLException {
    Statement stmt = conn.createStatement();
    Path dataFilePath = new Path(dataFileDir, "kv1.txt");
    // We've already dropped testDbName in constructor & we also drop it in tearDownAfterClass
    String prefix = dbName + ".";
    String tableName = prefix + TestJdbcWithMiniHS2.tableName;

    // create a table
    stmt.execute("create table " + tableName
        + " (int_col int comment 'the int column', value string) comment '" + tableComment + "'");
    // load data
    stmt.execute("load data local inpath '" + dataFilePath.toString() + "' into table " + tableName);

    stmt.close();
  }

  @AfterClass
  public static void tearDownAfterClass() throws Exception {
    // drop test db and its tables and views
    Statement stmt = conDefault.createStatement();
    stmt.execute("set hive.support.concurrency = false");
    stmt.execute("drop database if exists " + testDbName + " cascade");
    stmt.close();
    if (conTestDb != null) {
      conTestDb.close();
    }
    if (conDefault != null) {
      conDefault.close();
    }
    stopMiniHS2();
    cleanupMiniHS2();
  }

  private static void restoreMiniHS2AndConnections()  throws Exception {
    if (conTestDb != null) {
      try {
        conTestDb.close();
      } catch (SQLException e) {
        // Do nothing
      }
    }
    if (conDefault != null) {
      try {
        conDefault.close();
      } catch (SQLException e) {
        // Do nothing
      }
    }
    stopMiniHS2();
    HiveConf conf = new HiveConf();
    startMiniHS2(conf);
    openDefaultConnections();
    openTestConnections();
  }

  private static void startMiniHS2(HiveConf conf) throws Exception {
    conf.setBoolVar(ConfVars.HIVE_SUPPORT_CONCURRENCY, false);
    conf.setBoolVar(ConfVars.HIVE_SERVER2_LOGGING_OPERATION_ENABLED, false);
    miniHS2 = new MiniHS2.Builder().withConf(conf).cleanupLocalDirOnStartup(false).build();
    Map<String, String> confOverlay = new HashMap<String, String>();
    miniHS2.start(confOverlay);
  }

  private static void stopMiniHS2() {
    if ((miniHS2 != null) && (miniHS2.isStarted())) {
      miniHS2.stop();
    }
  }

  private static void cleanupMiniHS2() throws IOException {
    if (miniHS2 != null) {
      miniHS2.cleanup();
    }
    MiniHS2.cleanupLocalDir();
  }

  private static void openDefaultConnections() throws Exception {
    conDefault = getConnection();
  }

  private static void openTestConnections() throws Exception {
    conTestDb = getConnection(testDbName);
  }

  @Test
  public void testConnection() throws Exception {
    Statement stmt = conTestDb.createStatement();
    ResultSet res = stmt.executeQuery("select * from " + tableName + " limit 5");
    assertTrue(res.next());
    res.close();
    stmt.close();
  }

  @Test
  public void testParallelCompilation() throws Exception {
    Statement stmt = conTestDb.createStatement();
    stmt.execute("set hive.driver.parallel.compilation=true");
    stmt.execute("set hive.server2.async.exec.async.compile=true");
    stmt.close();
    startConcurrencyTest(conTestDb, tableName, 10);
    Connection conn = getConnection(testDbName);
    startConcurrencyTest(conn, tableName, 10);
    conn.close();
  }

  @Test
  public void testParallelCompilation2() throws Exception {
    Statement stmt = conTestDb.createStatement();
    stmt.execute("set hive.driver.parallel.compilation=false");
    stmt.execute("set hive.server2.async.exec.async.compile=true");
    stmt.close();
    startConcurrencyTest(conTestDb, tableName, 10);
    Connection conn = getConnection(testDbName);
    startConcurrencyTest(conn, tableName, 10);
    conn.close();
  }

  @Test
  public void testConcurrentStatements() throws Exception {
    startConcurrencyTest(conTestDb, tableName, 50);
  }

  private static void startConcurrencyTest(Connection conn, String tableName, int numTasks) {
    // Start concurrent testing
    int POOL_SIZE = 100;
    int TASK_COUNT = numTasks;

    SynchronousQueue<Runnable> executorQueue = new SynchronousQueue<Runnable>();
    ExecutorService workers =
        new ThreadPoolExecutor(1, POOL_SIZE, 20, TimeUnit.SECONDS, executorQueue);
    List<Future<Boolean>> list = new ArrayList<Future<Boolean>>();
    int i = 0;
    while (i < TASK_COUNT) {
      try {
        Future<Boolean> future = workers.submit(new JDBCTask(conn, i, tableName));
        list.add(future);
        i++;
      } catch (RejectedExecutionException ree) {
        try {
          TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
        }
      }
    }

    for (Future<Boolean> future : list) {
      try {
        Boolean result = future.get(30, TimeUnit.SECONDS);
        assertTrue(result);
      } catch (ExecutionException ee) {
        fail("Concurrent Statement failed: " + ee.getCause());
      } catch (TimeoutException te) {
        System.out.println("Task was timeout after 30 second: " + te);
      } catch (CancellationException ce) {
        System.out.println("Task was interrupted: " + ce);
      } catch (InterruptedException ie) {
        System.out.println("Thread was interrupted: " + ie);
      }
    }
    workers.shutdown();
  }

  static class JDBCTask implements Callable<Boolean> {
    private String showsql = "show tables";
    private String querysql;
    private int seq = 0;
    Connection con = null;
    Statement stmt = null;
    ResultSet res = null;

    JDBCTask(Connection con, int seq, String tblName) {
      this.con = con;
      this.seq = seq;
      querysql = "SELECT count(value) FROM " + tblName;
    }

    public Boolean call() throws SQLException {
      int mod = seq%10;
      try {
        if (mod < 2) {
          String name = con.getMetaData().getDatabaseProductName();
        } else if (mod < 5) {
          stmt = con.createStatement();
          res = stmt.executeQuery(querysql);
          while (res.next()) {
            res.getInt(1);
          }
        } else if (mod < 7) {
          res = con.getMetaData().getSchemas();
          if (res.next()) {
            res.getString(1);
          }
        } else {
          stmt = con.createStatement();
          res = stmt.executeQuery(showsql);
          if (res.next()) {
            res.getString(1);
          }
        }
        return new Boolean(true);
      } finally {
        try {
          if (res != null) {
            res.close();
            res = null;
          }
          if (stmt != null) {
            stmt.close();
            stmt = null;
          }
        } catch (SQLException sqle1) {
        }
      }
    }
  }

  /**   This test is to connect to any database without using the command "Use <<DB>>"
   *  1) connect to default database.
   *  2) Create a new DB test_default.
   *  3) Connect to test_default database.
   *  4) Connect and create table under test_default_test.
   *  5) Connect and display all tables.
   *  6) Connect to default database and shouldn't find table test_default_test.
   *  7) Connect and drop test_default_test.
   *  8) drop test_default database.
   */

  @Test
  public void testURIDatabaseName() throws Exception{

    String  jdbcUri  = miniHS2.getJdbcURL().substring(0, miniHS2.getJdbcURL().indexOf("default"));

    Connection conn= getConnection(jdbcUri+"default", System.getProperty("user.name"),"bar");
    String dbName="test_connection_non_default_db";
    String tableInNonDefaultSchema="table_in_non_default_schema";
    Statement stmt = conn.createStatement();
    stmt.execute("create database  if not exists "+dbName);
    stmt.close();
    conn.close();

    conn = getConnection(jdbcUri+dbName,System.getProperty("user.name"),"bar");
    stmt = conn .createStatement();
    boolean expected = stmt.execute(" create table "+tableInNonDefaultSchema +" (x int)");
    stmt.close();
    conn .close();

    conn  = getConnection(jdbcUri+dbName,System.getProperty("user.name"),"bar");
    stmt = conn .createStatement();
    ResultSet res = stmt.executeQuery("show tables");
    boolean testTableExists = false;
    while (res.next()) {
      assertNotNull("table name is null in result set", res.getString(1));
      if (tableInNonDefaultSchema.equalsIgnoreCase(res.getString(1))) {
        testTableExists = true;
      }
    }
    assertTrue("table name  "+tableInNonDefaultSchema
        + "   found in SHOW TABLES result set", testTableExists);
    stmt.close();
    conn .close();

    conn  = getConnection(jdbcUri+"default",System.getProperty("user.name"),"bar");
    stmt = conn .createStatement();
    res = stmt.executeQuery("show tables");
    testTableExists = false;
    while (res.next()) {
      assertNotNull("table name is null in result set", res.getString(1));
      if (tableInNonDefaultSchema.equalsIgnoreCase(res.getString(1))) {
        testTableExists = true;
      }
    }

    assertFalse("table name "+tableInNonDefaultSchema
        + "  NOT  found in SHOW TABLES result set", testTableExists);
    stmt.close();
    conn .close();

    conn  = getConnection(jdbcUri+dbName,System.getProperty("user.name"),"bar");
    stmt = conn .createStatement();
    stmt.execute("set hive.support.concurrency = false");
    res = stmt.executeQuery("show tables");

    stmt.execute(" drop table if exists table_in_non_default_schema");
    expected = stmt.execute("DROP DATABASE "+ dbName);
    stmt.close();
    conn.close();

    conn  = getConnection(jdbcUri+"default",System.getProperty("user.name"),"bar");
    stmt = conn .createStatement();
    res = stmt.executeQuery("show tables");
    testTableExists = false;
    while (res.next()) {
      assertNotNull("table name is null in result set", res.getString(1));
      if (tableInNonDefaultSchema.equalsIgnoreCase(res.getString(1))) {
        testTableExists = true;
      }
    }

    // test URI with no dbName
    conn  = getConnection(jdbcUri, System.getProperty("user.name"),"bar");
    verifyCurrentDB("default", conn);
    conn.close();

    conn  = getConnection(jdbcUri + ";", System.getProperty("user.name"),"bar");
    verifyCurrentDB("default", conn);
    conn.close();

    conn  = getConnection(jdbcUri + ";/foo=bar;foo1=bar1", System.getProperty("user.name"),"bar");
    verifyCurrentDB("default", conn);
    conn.close();
  }

  /**
   * verify that the current db is the one expected. first create table as <db>.tab and then
   * describe that table to check if <db> is the current database
   * @param expectedDbName
   * @param hs2Conn
   * @throws Exception
   */
  private void verifyCurrentDB(String expectedDbName, Connection hs2Conn) throws Exception {
    String verifyTab = "miniHS2DbVerificationTable";
    Statement stmt = hs2Conn.createStatement();
    stmt.execute("DROP TABLE IF EXISTS " + expectedDbName + "." + verifyTab);
    stmt.execute("CREATE TABLE " + expectedDbName + "." + verifyTab + "(id INT)");
    stmt.execute("DESCRIBE " + verifyTab);
    stmt.execute("DROP TABLE IF EXISTS " + expectedDbName + "." + verifyTab);
    stmt.close();
  }

  @Test
  public void testConnectionSchemaAPIs() throws Exception {
    /**
     * get/set Schema are new in JDK7 and not available in java.sql.Connection in JDK6. Hence the
     * test uses HiveConnection object to call these methods so that test will run with older JDKs
     */
    HiveConnection hiveConn = (HiveConnection) conDefault;
    assertEquals(defaultDbName, hiveConn.getSchema());
    Statement stmt = conDefault.createStatement();

    stmt.execute("USE " + testDbName);
    assertEquals(testDbName, hiveConn.getSchema());

    stmt.execute("USE " + defaultDbName);
    assertEquals(defaultDbName, hiveConn.getSchema());

    hiveConn.setSchema(defaultDbName);
    assertEquals(defaultDbName, hiveConn.getSchema());
    hiveConn.setSchema(defaultDbName);
    assertEquals(defaultDbName, hiveConn.getSchema());

    assertTrue(hiveConn.getCatalog().isEmpty());
    hiveConn.setCatalog("foo");
    assertTrue(hiveConn.getCatalog().isEmpty());
  }

  /**
   * This method tests whether while creating a new connection, the config
   * variables specified in the JDBC URI are properly set for the connection.
   * This is a test for HiveConnection#configureConnection.
   *
   * @throws Exception
   */
  @Test
  public void testNewConnectionConfiguration() throws Exception {
    // Set some conf parameters
    String hiveConf =
        "hive.cli.print.header=true;hive.server2.async.exec.shutdown.timeout=20;"
            + "hive.server2.async.exec.threads=30;hive.server2.thrift.max.worker.threads=15";
    // Set some conf vars
    String hiveVar = "stab=salesTable;icol=customerID";
    String jdbcUri = miniHS2.getJdbcURL() + "?" + hiveConf + "#" + hiveVar;
    // Open a new connection with these conf & vars
    Connection con1 = DriverManager.getConnection(jdbcUri);
    // Execute "set" command and retrieve values for the conf & vars specified above
    // Assert values retrieved
    Statement stmt = con1.createStatement();
    // Verify that the property has been properly set while creating the
    // connection above
    verifyConfProperty(stmt, "hive.cli.print.header", "true");
    verifyConfProperty(stmt, "hive.server2.async.exec.shutdown.timeout", "20");
    verifyConfProperty(stmt, "hive.server2.async.exec.threads", "30");
    verifyConfProperty(stmt, "hive.server2.thrift.max.worker.threads", "15");
    verifyConfProperty(stmt, "stab", "salesTable");
    verifyConfProperty(stmt, "icol", "customerID");
    stmt.close();
    con1.close();
  }

  private void verifyConfProperty(Statement stmt, String property,
      String expectedValue) throws Exception {
    ResultSet res = stmt.executeQuery("set " + property);
    while (res.next()) {
      String resultValues[] = res.getString(1).split("=");
      assertEquals(resultValues[1], expectedValue);
    }
  }

  @Test
  public void testMetadataQueriesWithSerializeThriftInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    setSerializeInTasksInConf(stmt);
    ResultSet rs = stmt.executeQuery("show tables");
    assertTrue(rs.next());
    stmt.execute("describe " + tableName);
    stmt.execute("explain select * from " + tableName);
    // Note: by closing stmt object, we are also reverting any session specific config changes.
    stmt.close();
  }

  @Test
  public void testSelectThriftSerializeInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    setSerializeInTasksInConf(stmt);
    stmt.execute("set hive.compute.query.using.stats=false");
    stmt.execute("drop table if exists testSelectThriftOrders");
    stmt.execute("drop table if exists testSelectThriftCustomers");
    stmt.execute("create table testSelectThriftOrders (orderid int, orderdate string, customerid int)");
    stmt.execute("create table testSelectThriftCustomers (customerid int, customername string, customercountry string)");
    stmt.execute("insert into testSelectThriftOrders values (1, '2015-09-09', 123), "
        + "(2, '2015-10-10', 246), (3, '2015-11-11', 356)");
    stmt.execute("insert into testSelectThriftCustomers values (123, 'David', 'America'), "
        + "(246, 'John', 'Canada'), (356, 'Mary', 'CostaRica')");
    ResultSet countOrders = stmt.executeQuery("select count(*) from testSelectThriftOrders");
    while (countOrders.next()) {
      assertEquals(3, countOrders.getInt(1));
    }
    ResultSet maxOrders =
        stmt.executeQuery("select max(customerid) from testSelectThriftCustomers");
    while (maxOrders.next()) {
      assertEquals(356, maxOrders.getInt(1));
    }
    stmt.execute("drop table testSelectThriftOrders");
    stmt.execute("drop table testSelectThriftCustomers");
    stmt.close();
  }

  @Test
  public void testJoinThriftSerializeInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    setSerializeInTasksInConf(stmt);
    stmt.execute("drop table if exists testThriftJoinOrders");
    stmt.execute("drop table if exists testThriftJoinCustomers");
    stmt.execute("create table testThriftJoinOrders (orderid int, orderdate string, customerid int)");
    stmt.execute("create table testThriftJoinCustomers (customerid int, customername string, customercountry string)");
    stmt.execute("insert into testThriftJoinOrders values (1, '2015-09-09', 123), (2, '2015-10-10', 246), "
        + "(3, '2015-11-11', 356)");
    stmt.execute("insert into testThriftJoinCustomers values (123, 'David', 'America'), "
        + "(246, 'John', 'Canada'), (356, 'Mary', 'CostaRica')");
    ResultSet joinResultSet =
        stmt.executeQuery("select testThriftJoinOrders.orderid, testThriftJoinCustomers.customername "
            + "from testThriftJoinOrders inner join testThriftJoinCustomers where "
            + "testThriftJoinOrders.customerid=testThriftJoinCustomers.customerid");
    Map<Integer, String> expectedResult = new HashMap<Integer, String>();
    expectedResult.put(1, "David");
    expectedResult.put(2, "John");
    expectedResult.put(3, "Mary");
    for (int i = 1; i < 4; i++) {
      assertTrue(joinResultSet.next());
      assertEquals(joinResultSet.getString(2), expectedResult.get(i));
    }
    stmt.execute("drop table testThriftJoinOrders");
    stmt.execute("drop table testThriftJoinCustomers");
    stmt.close();
  }

  @Test
  public void testEmptyResultsetThriftSerializeInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    setSerializeInTasksInConf(stmt);
    stmt.execute("drop table if exists testThriftSerializeShow1");
    stmt.execute("drop table if exists testThriftSerializeShow2");
    stmt.execute("create table testThriftSerializeShow1 (a int)");
    stmt.execute("create table testThriftSerializeShow2 (b int)");
    stmt.execute("insert into testThriftSerializeShow1 values (1)");
    stmt.execute("insert into testThriftSerializeShow2 values (2)");
    ResultSet rs = stmt.executeQuery("select * from testThriftSerializeShow1 inner join "
        + "testThriftSerializeShow2 where testThriftSerializeShow1.a=testThriftSerializeShow2.b");
    assertTrue(!rs.next());
    stmt.execute("drop table testThriftSerializeShow1");
    stmt.execute("drop table testThriftSerializeShow2");
    stmt.close();
  }

  @Test
  public void testFloatCast2DoubleThriftSerializeInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    setSerializeInTasksInConf(stmt);
    stmt.execute("drop table if exists testThriftSerializeShow1");
    stmt.execute("drop table if exists testThriftSerializeShow2");
    stmt.execute("create table testThriftSerializeShow1 (a float)");
    stmt.execute("create table testThriftSerializeShow2 (b double)");
    stmt.execute("insert into testThriftSerializeShow1 values (1.1), (2.2), (3.3)");
    stmt.execute("insert into testThriftSerializeShow2 values (2.2), (3.3), (4.4)");
    ResultSet rs =
        stmt.executeQuery("select * from testThriftSerializeShow1 inner join "
            + "testThriftSerializeShow2 where testThriftSerializeShow1.a=testThriftSerializeShow2.b");
    assertTrue(!rs.next());
    stmt.execute("drop table testThriftSerializeShow1");
    stmt.execute("drop table testThriftSerializeShow2");
    stmt.close();
  }

  @Test
  public void testEnableThriftSerializeInTasks() throws Exception {
    Statement stmt = conTestDb.createStatement();
    stmt.execute("drop table if exists testThriftSerializeShow1");
    stmt.execute("drop table if exists testThriftSerializeShow2");
    stmt.execute("create table testThriftSerializeShow1 (a int)");
    stmt.execute("create table testThriftSerializeShow2 (b int)");
    stmt.execute("insert into testThriftSerializeShow1 values (1)");
    stmt.execute("insert into testThriftSerializeShow2 values (2)");
    ResultSet rs =
        stmt.executeQuery("select * from testThriftSerializeShow1 inner join "
            + "testThriftSerializeShow2 where testThriftSerializeShow1.a=testThriftSerializeShow2.b");
    assertTrue(!rs.next());

    unsetSerializeInTasksInConf(stmt);
    rs =
        stmt.executeQuery("select * from testThriftSerializeShow1 inner join "
            + "testThriftSerializeShow2 where testThriftSerializeShow1.a=testThriftSerializeShow2.b");
    assertTrue(!rs.next());

    setSerializeInTasksInConf(stmt);
    rs =
        stmt.executeQuery("select * from testThriftSerializeShow1 inner join "
            + "testThriftSerializeShow2 where testThriftSerializeShow1.a=testThriftSerializeShow2.b");
    assertTrue(!rs.next());

    stmt.execute("drop table testThriftSerializeShow1");
    stmt.execute("drop table testThriftSerializeShow2");
    stmt.close();
  }

  private void setSerializeInTasksInConf(Statement stmt) throws SQLException {
    stmt.execute("set hive.server2.thrift.resultset.serialize.in.tasks=true");
    stmt.execute("set hive.server2.thrift.resultset.max.fetch.size=1000");
  }

  private void unsetSerializeInTasksInConf(Statement stmt) throws SQLException {
    stmt.execute("set hive.server2.thrift.resultset.serialize.in.tasks=false");
    stmt.execute("set hive.server2.thrift.resultset.max.fetch.size");
  }

  /**
   * Tests the creation of the 3 scratch dirs: hdfs, local, downloaded resources (which is also local).
   * 1. Test with doAs=false: open a new JDBC session and verify the presence of directories/permissions
   * 2. Test with doAs=true: open a new JDBC session and verify the presence of directories/permissions
   * @throws Exception
   */
  @Test
  public void testSessionScratchDirs() throws Exception {
    // Stop HiveServer2
    stopMiniHS2();
    HiveConf conf = new HiveConf();
    String userName;
    Path scratchDirPath;
    // Set a custom prefix for hdfs scratch dir path
    conf.set("hive.exec.scratchdir", tmpDir + "/hs2");
    // Set a scratch dir permission
    String fsPermissionStr = "700";
    conf.set("hive.scratch.dir.permission", fsPermissionStr);
    // Start an instance of HiveServer2 which uses miniMR
    startMiniHS2(conf);
    // 1. Test with doAs=false
    String sessionConf="hive.server2.enable.doAs=false";
    userName = System.getProperty("user.name");
    Connection conn = getConnection(miniHS2.getJdbcURL(testDbName, sessionConf), userName, "password");
    // FS
    FileSystem fs = miniHS2.getLocalFS();
    FsPermission expectedFSPermission = new FsPermission(HiveConf.getVar(conf,
        HiveConf.ConfVars.SCRATCHDIRPERMISSION));

    // Verify scratch dir paths and permission
    // HDFS scratch dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.SCRATCHDIR) + "/" + userName);
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, false);

    // Local scratch dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.LOCALSCRATCHDIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, true);

    // Downloaded resources dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.DOWNLOADED_RESOURCES_DIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, true);
    conn.close();

    // 2. Test with doAs=true
    sessionConf="hive.server2.enable.doAs=true";
    // Test for user "neo"
    userName = "neo";
    conn = getConnection(miniHS2.getJdbcURL(testDbName, sessionConf), userName, "the-one");

    // Verify scratch dir paths and permission
    // HDFS scratch dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.SCRATCHDIR) + "/" + userName);
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, false);

    // Local scratch dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.LOCALSCRATCHDIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, true);

    // Downloaded resources dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.DOWNLOADED_RESOURCES_DIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, true);
    conn.close();

    // Restore original state
    restoreMiniHS2AndConnections();
  }


  /**
   * Test UDF whitelist
   * - verify default value
   * - verify udf allowed with default whitelist
   * - verify udf allowed with specific whitelist
   * - verify udf disallowed when not in whitelist
   * @throws Exception
   */
  @Test
  public void testUdfWhiteBlackList() throws Exception {
    HiveConf testConf = new HiveConf();
    assertTrue(testConf.getVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_WHITELIST).isEmpty());
    // verify that udf in default whitelist can be executed
    Statement stmt = conDefault.createStatement();
    stmt.executeQuery("SELECT substr('foobar', 4) ");
    stmt.close();

    // setup whitelist
    stopMiniHS2();
    Set<String> funcNames = FunctionRegistry.getFunctionNames();
    funcNames.remove("reflect");
    String funcNameStr = "";
    for (String funcName : funcNames) {
      funcNameStr += "," + funcName;
    }
    funcNameStr = funcNameStr.substring(1); // remove ',' at begining
    testConf.setVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_WHITELIST, funcNameStr);
    startMiniHS2(testConf);
    Connection conn =
        getConnection(miniHS2.getJdbcURL(testDbName), System.getProperty("user.name"), "bar");
    stmt = conn.createStatement();
    // verify that udf in whitelist can be executed
    stmt.executeQuery("SELECT substr('foobar', 3) ");
    // verify that udf not in whitelist fails
    try {
      stmt.executeQuery("SELECT reflect('java.lang.String', 'valueOf', 1) ");
      fail("reflect() udf invocation should fail");
    } catch (SQLException e) {
      // expected
    }
    conn.close();

    // Restore original state
    restoreMiniHS2AndConnections();
  }

  /** Test UDF blacklist
   *   - verify default value
   *   - verify udfs allowed with default blacklist
   *   - verify udf disallowed when in blacklist
   * @throws Exception
   */
  @Test
  public void testUdfBlackList() throws Exception {
    HiveConf testConf = new HiveConf();
    assertTrue(testConf.getVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_BLACKLIST).isEmpty());
    Statement stmt = conDefault.createStatement();
    // verify that udf in default whitelist can be executed
    stmt.executeQuery("SELECT substr('foobar', 4) ");

    stopMiniHS2();
    testConf.setVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_BLACKLIST, "reflect");
    startMiniHS2(testConf);
    Connection conn =
        getConnection(miniHS2.getJdbcURL(testDbName), System.getProperty("user.name"), "bar");
    stmt = conn.createStatement();

    try {
      stmt.executeQuery("SELECT reflect('java.lang.String', 'valueOf', 1) ");
      fail("reflect() udf invocation should fail");
    } catch (SQLException e) {
      // expected
    }
    conn.close();
    // Restore original state
    restoreMiniHS2AndConnections();
  }

  /** Test UDF blacklist overrides whitelist
   * @throws Exception
   */
  @Test
  public void testUdfBlackListOverride() throws Exception {
    stopMiniHS2();
    // setup whitelist
    HiveConf testConf = new HiveConf();

    Set<String> funcNames = FunctionRegistry.getFunctionNames();
    String funcNameStr = "";
    for (String funcName : funcNames) {
      funcNameStr += "," + funcName;
    }
    funcNameStr = funcNameStr.substring(1); // remove ',' at begining
    testConf.setVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_WHITELIST, funcNameStr);
    testConf.setVar(ConfVars.HIVE_SERVER2_BUILTIN_UDF_BLACKLIST, "reflect");
    startMiniHS2(testConf);
    Connection conn =
        getConnection(miniHS2.getJdbcURL(testDbName), System.getProperty("user.name"), "bar");
    Statement stmt = conn.createStatement();

    // verify that udf in black list fails even though it's included in whitelist
    try {
      stmt.executeQuery("SELECT reflect('java.lang.String', 'valueOf', 1) ");
      fail("reflect() udf invocation should fail");
    } catch (SQLException e) {
      // expected
    }
    conn.close();
    // Restore original state
    restoreMiniHS2AndConnections();
  }

  /**
   * Tests the creation of the root hdfs scratch dir, which should be writable by all.
   *
   * @throws Exception
   */
  @Test
  public void testRootScratchDir() throws Exception {
    // Stop HiveServer2
    stopMiniHS2();
    HiveConf conf = new HiveConf();
    String userName;
    Path scratchDirPath;
    conf.set("hive.exec.scratchdir", tmpDir + "/hs2");
    // Start an instance of HiveServer2 which uses miniMR
    startMiniHS2(conf);
    userName = System.getProperty("user.name");
    Connection conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    // FS
    FileSystem fs = miniHS2.getLocalFS();
    FsPermission expectedFSPermission = new FsPermission((short)00733);
    // Verify scratch dir paths and permission
    // HDFS scratch dir
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.SCRATCHDIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, false);
    conn.close();

    // Test with multi-level scratch dir path
    // Stop HiveServer2
    stopMiniHS2();
    conf.set("hive.exec.scratchdir", tmpDir + "/level1/level2/level3");
    startMiniHS2(conf);
    conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    scratchDirPath = new Path(HiveConf.getVar(conf, HiveConf.ConfVars.SCRATCHDIR));
    verifyScratchDir(conf, fs, scratchDirPath, expectedFSPermission, userName, false);
    conn.close();

    // Restore original state
    restoreMiniHS2AndConnections();
  }

  private void verifyScratchDir(HiveConf conf, FileSystem fs, Path scratchDirPath,
      FsPermission expectedFSPermission, String userName, boolean isLocal) throws Exception {
    String dirType = isLocal ? "Local" : "DFS";
    assertTrue("The expected " + dirType + " scratch dir does not exist for the user: " +
        userName, fs.exists(scratchDirPath));
    if (fs.exists(scratchDirPath) && !isLocal) {
      assertEquals("DFS scratch dir permissions don't match", expectedFSPermission,
          fs.getFileStatus(scratchDirPath).getPermission());
    }
  }

  /**
   * Test for http header size
   * @throws Exception
   */
  @Test
  public void testHttpHeaderSize() throws Exception {
    // Stop HiveServer2
    stopMiniHS2();
    HiveConf conf = new HiveConf();
    conf.set("hive.server2.transport.mode", "http");
    conf.setInt("hive.server2.thrift.http.request.header.size", 1024);
    conf.setInt("hive.server2.thrift.http.response.header.size", 1024);
    startMiniHS2(conf);

    // Username is added to the request header
    String userName = StringUtils.leftPad("*", 100);
    Connection conn = null;
    // This should go fine, since header should be less than the configured header size
    try {
      conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    } catch (Exception e) {
      fail("Not expecting exception: " + e);
    } finally {
      if (conn != null) {
        conn.close();
      }
    }

    // This should fail with given HTTP response code 413 in error message, since header is more
    // than the configured the header size
    userName = StringUtils.leftPad("*", 2000);
    try {
      conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    } catch (Exception e) {
      assertTrue("Header exception thrown", e != null);
      assertTrue(e.getMessage().contains("HTTP Response code: 413"));
    } finally {
      if (conn != null) {
        conn.close();
      }
    }

    // Stop HiveServer2 to increase header size
    stopMiniHS2();
    conf.setInt("hive.server2.thrift.http.request.header.size", 3000);
    conf.setInt("hive.server2.thrift.http.response.header.size", 3000);
    startMiniHS2(conf);

    // This should now go fine, since we increased the configured header size
    try {
      conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    } catch (Exception e) {
      fail("Not expecting exception: " + e);
    } finally {
      if (conn != null) {
        conn.close();
      }
    }

    // Restore original state
    restoreMiniHS2AndConnections();
  }

  /**
   * Test for jdbc driver retry on NoHttpResponseException
   * @throws Exception
   */
  @Test
  public void testHttpRetryOnServerIdleTimeout() throws Exception {
    // Stop HiveServer2
    stopMiniHS2();
    HiveConf conf = new HiveConf();
    conf.set("hive.server2.transport.mode", "http");
    // Set server's idle timeout to a very low value
    conf.set("hive.server2.thrift.http.max.idle.time", "5");
    startMiniHS2(conf);
    String userName = System.getProperty("user.name");
    Connection conn = getConnection(miniHS2.getJdbcURL(testDbName), userName, "password");
    Statement stmt = conn.createStatement();
    stmt.execute("select from_unixtime(unix_timestamp())");
    // Sleep for longer than server's idletimeout and execute a query
    TimeUnit.SECONDS.sleep(10);
    try {
      stmt.execute("select from_unixtime(unix_timestamp())");
    } catch (Exception e) {
      fail("Not expecting exception: " + e);
    } finally {
      if (conn != null) {
        conn.close();
      }
    }
    // Restore original state
    restoreMiniHS2AndConnections();
  }

  /**
   * Tests that DataNucleus' NucleusContext.classLoaderResolverMap clears cached class objects
   * (& hence doesn't leak classloaders) on closing any session
   *
   * @throws Exception
   */
  @Test
  public void testAddJarDataNucleusUnCaching() throws Exception {
    Path jarFilePath = getHiveContribJarPath();
    // We need a new connection object as we'll check the cache size after connection close
    Connection conn =
        getConnection(miniHS2.getJdbcURL(testDbName), System.getProperty("user.name"), "password");
    Statement stmt = conn.createStatement();
    int mapSizeAfterClose;
    // Add the jar file
    stmt.execute("ADD JAR " + jarFilePath.toString());
    // Create a temporary function using the jar
    stmt.execute("CREATE TEMPORARY FUNCTION add_func AS '" + testUdfClassName + "'");
    ResultSet res = stmt.executeQuery("DESCRIBE FUNCTION add_func");
    checkForNotExist(res);
    // Execute the UDF
    stmt.execute("SELECT add_func(int_col, 1) from " + tableName + " limit 1");
    // Close the connection
    conn.close();
    mapSizeAfterClose = getNucleusClassLoaderResolverMapSize();
    System.out.println("classLoaderResolverMap size after connection close: " + mapSizeAfterClose);
    // Cache size should be 0 now
    Assert.assertTrue("Failed; NucleusContext classLoaderResolverMap size: " + mapSizeAfterClose,
        mapSizeAfterClose == 0);
  }

  @SuppressWarnings("unchecked")
  private int getNucleusClassLoaderResolverMapSize() {
    Field classLoaderResolverMap;
    Field pmf;
    JDOPersistenceManagerFactory jdoPmf = null;
    NucleusContext nc = null;
    Map<String, ClassLoaderResolver> cMap;
    try {
      pmf = ObjectStore.class.getDeclaredField("pmf");
      if (pmf != null) {
        pmf.setAccessible(true);
        jdoPmf = (JDOPersistenceManagerFactory) pmf.get(null);
        if (jdoPmf != null) {
          nc = jdoPmf.getNucleusContext();
        }
      }
    } catch (Exception e) {
      System.out.println(e);
    }
    if (nc != null) {
      try {
        classLoaderResolverMap = AbstractNucleusContext.class.getDeclaredField("classLoaderResolverMap");
        if (classLoaderResolverMap != null) {
          classLoaderResolverMap.setAccessible(true);
          cMap = (Map<String, ClassLoaderResolver>) classLoaderResolverMap.get(nc);
          if (cMap != null) {
            return cMap.size();
          }
        }
      } catch (Exception e) {
        System.out.println(e);
      }
    }
    return -1;
  }

  /**
   * Tests ADD JAR uses Hives ReflectionUtil.CONSTRUCTOR_CACHE
   *
   * @throws Exception
   */
  @Test
  public void testAddJarConstructorUnCaching() throws Exception {
    // This test assumes the hive-contrib JAR has been built as part of the Hive build.
    // Also dependent on the UDFExampleAdd class within that JAR.
    setReflectionUtilCache();
    Path jarFilePath = getHiveContribJarPath();
    long cacheBeforeAddJar, cacheAfterClose;
    // Force the cache clear so we know its empty
    invalidateReflectionUtlCache();
    cacheBeforeAddJar = getReflectionUtilCacheSize();
    System.out.println("CONSTRUCTOR_CACHE size before add jar: " + cacheBeforeAddJar);
    System.out.println("CONSTRUCTOR_CACHE as map before add jar:" + getReflectionUtilCache().asMap());
    Assert.assertTrue("FAILED: CONSTRUCTOR_CACHE size before add jar: " + cacheBeforeAddJar,
            cacheBeforeAddJar == 0);

    // Add the jar file
    Statement stmt = conTestDb.createStatement();
    stmt.execute("ADD JAR " + jarFilePath.toString());
    // Create a temporary function using the jar
    stmt.execute("CREATE TEMPORARY FUNCTION add_func AS '" + testUdfClassName + "'");
    // Execute the UDF
    ResultSet res = stmt.executeQuery("SELECT add_func(int_col, 1) from " + tableName + " limit 1");
    assertTrue(res.next());
    TimeUnit.SECONDS.sleep(7);
    // Have to force a cleanup of all expired entries here because its possible that the
    // expired entries will still be counted in Cache.size().
    // Taken from:
    // http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/cache/CacheBuilder.html
    cleanUpReflectionUtlCache();
    cacheAfterClose = getReflectionUtilCacheSize();
    System.out.println("CONSTRUCTOR_CACHE size after connection close: " + cacheAfterClose);
    Assert.assertTrue("FAILED: CONSTRUCTOR_CACHE size after connection close: " + cacheAfterClose,
            cacheAfterClose == 0);
    stmt.execute("DROP TEMPORARY FUNCTION IF EXISTS add_func");
    stmt.close();
  }

  private void setReflectionUtilCache() {
    Field constructorCacheField;
    Cache<Class<?>, Constructor<?>> tmp;
    try {
      constructorCacheField = ReflectionUtil.class.getDeclaredField("CONSTRUCTOR_CACHE");
      if (constructorCacheField != null) {
        constructorCacheField.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(constructorCacheField, constructorCacheField.getModifiers()
            & ~Modifier.FINAL);
        tmp =
            CacheBuilder.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).concurrencyLevel(64)
                .weakKeys().weakValues().build();
        constructorCacheField.set(tmp.getClass(), tmp);
      }
    } catch (Exception e) {
      System.out.println("Error when setting the CONSTRUCTOR_CACHE to expire: " + e);
    }
  }

  private Cache getReflectionUtilCache() {
    Field constructorCacheField;
    try {
      constructorCacheField = ReflectionUtil.class.getDeclaredField("CONSTRUCTOR_CACHE");
      if (constructorCacheField != null) {
        constructorCacheField.setAccessible(true);
        return (Cache) constructorCacheField.get(null);
      }
    } catch (Exception e) {
      System.out.println("Error when getting the CONSTRUCTOR_CACHE var: " + e);
    }
    return null;
  }

  private void invalidateReflectionUtlCache() {
    try {
      Cache constructorCache = getReflectionUtilCache();
      if (constructorCache != null) {
        constructorCache.invalidateAll();
      }
    } catch (Exception e) {
      System.out.println("Error when trying to invalidate the cache: " + e);
    }
  }

  private void cleanUpReflectionUtlCache() {
    try {
      Cache constructorCache = getReflectionUtilCache();
      if (constructorCache != null) {
        constructorCache.cleanUp();
      }
    } catch (Exception e) {
      System.out.println("Error when trying to cleanUp the cache: " + e);
    }
  }

  private long getReflectionUtilCacheSize() {
    try {
      Cache constructorCache = getReflectionUtilCache();
      if (constructorCache != null) {
        return constructorCache.size();
      }
    } catch (Exception e) {
      System.out.println(e);
    }
    return -1;
  }

  @Test
  public void testPermFunc() throws Exception {
    // This test assumes the hive-contrib JAR has been built as part of the Hive build.
    // Also dependent on the UDFExampleAdd class within that JAR.
    Path jarFilePath = getHiveContribJarPath();
    Statement stmt = conTestDb.createStatement();
    ResultSet res;
    // Add the jar file
    stmt.execute("ADD JAR " + jarFilePath.toString());

    // Register function
    String queryStr =
        "CREATE FUNCTION example_add AS '" + testUdfClassName + "' USING JAR '" + jarFilePath + "'";
    stmt.execute(queryStr);

    // Call describe
    res = stmt.executeQuery("DESCRIBE FUNCTION " + testDbName + ".example_add");
    checkForNotExist(res);

    // Use UDF in query
    res = stmt.executeQuery("SELECT example_add(1, 2) FROM " + tableName + " LIMIT 1");
    assertTrue("query has results", res.next());
    assertEquals(3, res.getInt(1));
    assertFalse("no more results", res.next());

    // A new connection should be able to call describe/use function without issue
    Connection conn2 = getConnection(testDbName);
    Statement stmt2 = conn2.createStatement();
    stmt2.execute("USE " + testDbName);
    res = stmt2.executeQuery("DESCRIBE FUNCTION " + testDbName + ".example_add");
    checkForNotExist(res);

    res =
        stmt2.executeQuery("SELECT " + testDbName + ".example_add(1, 1) FROM " + tableName
            + " LIMIT 1");
    assertTrue("query has results", res.next());
    assertEquals(2, res.getInt(1));
    assertFalse("no more results", res.next());
    conn2.close();
    stmt.execute("DROP FUNCTION IF EXISTS " + testDbName + ".example_add");
    stmt.close();
  }

  private Path getHiveContribJarPath() {
    String mvnRepo = System.getProperty("maven.local.repository");
    String hiveVersion = System.getProperty("hive.version");
    String jarFileName = "hive-contrib-" + hiveVersion + ".jar";
    String[] pathParts = {
        "org", "apache", "hive",
        "hive-contrib", hiveVersion, jarFileName
    };

    // Create path to hive-contrib JAR on local filesystem
    Path jarFilePath = new Path(mvnRepo);
    for (String pathPart : pathParts) {
      jarFilePath = new Path(jarFilePath, pathPart);
    }
    return jarFilePath;
  }

  @Test
  public void testTempTable() throws Exception {
    // Create temp table with current connection
    String tempTableName = "tmp1";
    Statement stmt = conTestDb.createStatement();
    stmt.execute("CREATE TEMPORARY TABLE " + tempTableName + " (key string, value string)");
    stmt.execute("load data local inpath '" + kvDataFilePath.toString() + "' into table "
        + tempTableName);

    String resultVal = "val_238";
    String queryStr = "SELECT * FROM " + tempTableName + " where value = '" + resultVal + "'";

    ResultSet res = stmt.executeQuery(queryStr);
    assertTrue(res.next());
    assertEquals(resultVal, res.getString(2));
    res.close();
    stmt.close();

    // Test getTables()
    DatabaseMetaData md = conTestDb.getMetaData();
    assertTrue(md.getConnection() == conTestDb);

    ResultSet rs = md.getTables(null, null, tempTableName, null);
    boolean foundTable = false;
    while (rs.next()) {
      String tableName = rs.getString(3);
      if (tableName.equalsIgnoreCase(tempTableName)) {
        assertFalse("Table not found yet", foundTable);
        foundTable = true;
      }
    }
    assertTrue("Found temp table", foundTable);

    // Test getTables() with no table name pattern
    rs = md.getTables(null, null, null, null);
    foundTable = false;
    while (rs.next()) {
      String tableName = rs.getString(3);
      if (tableName.equalsIgnoreCase(tempTableName)) {
        assertFalse("Table not found yet", foundTable);
        foundTable = true;
      }
    }
    assertTrue("Found temp table", foundTable);

    // Test getColumns()
    rs = md.getColumns(null, null, tempTableName, null);
    assertTrue("First row", rs.next());
    assertTrue(rs.getString(3).equalsIgnoreCase(tempTableName));
    assertTrue(rs.getString(4).equalsIgnoreCase("key"));
    assertEquals(Types.VARCHAR, rs.getInt(5));

    assertTrue("Second row", rs.next());
    assertTrue(rs.getString(3).equalsIgnoreCase(tempTableName));
    assertTrue(rs.getString(4).equalsIgnoreCase("value"));
    assertEquals(Types.VARCHAR, rs.getInt(5));

    // A second connection should not be able to see the table
    Connection conn2 =
        DriverManager.getConnection(miniHS2.getJdbcURL(testDbName),
            System.getProperty("user.name"), "bar");
    Statement stmt2 = conn2.createStatement();
    stmt2.execute("USE " + testDbName);
    boolean gotException = false;
    try {
      res = stmt2.executeQuery(queryStr);
    } catch (SQLException err) {
      // This is expected to fail.
      assertTrue("Expecting table not found error, instead got: " + err,
          err.getMessage().contains("Table not found"));
      gotException = true;
    }
    assertTrue("Exception while querying non-existing temp table", gotException);
    conn2.close();
  }

  private void checkForNotExist(ResultSet res) throws Exception {
    int numRows = 0;
    while (res.next()) {
      numRows++;
      String strVal = res.getString(1);
      assertEquals("Should not find 'not exist'", -1, strVal.toLowerCase().indexOf("not exist"));
    }
    assertTrue("Rows returned from describe function", numRows > 0);
  }

  @Test
  public void testReplDumpResultSet() throws Exception {
    String tid =
        TestJdbcWithMiniHS2.class.getCanonicalName().toLowerCase().replace('.', '_') + "_"
            + System.currentTimeMillis();
    String testPathName = System.getProperty("test.warehouse.dir", "/tmp") + Path.SEPARATOR + tid;
    Path testPath = new Path(testPathName);
    FileSystem fs = testPath.getFileSystem(new HiveConf());
    Statement stmt = conDefault.createStatement();
    try {
      stmt.execute("set hive.repl.rootdir = " + testPathName);
      ResultSet rs = stmt.executeQuery("repl dump " + testDbName);
      ResultSetMetaData rsMeta = rs.getMetaData();
      assertEquals(2, rsMeta.getColumnCount());
      int numRows = 0;
      while (rs.next()) {
        numRows++;
        URI uri = new URI(rs.getString(1));
        int notificationId = rs.getInt(2);
        assertNotNull(uri);
        assertEquals(testPath.toUri().getScheme(), uri.getScheme());
        assertEquals(testPath.toUri().getAuthority(), uri.getAuthority());
        // In test setup, we append '/next' to hive.repl.rootdir and use that as the dump location
        assertEquals(testPath.toUri().getPath() + "/next", uri.getPath());
        assertNotNull(notificationId);
      }
      assertEquals(1, numRows);
    } finally {
      // Clean up
      fs.delete(testPath, true);
    }
  }

  @Test
  public void testFetchSize() throws Exception {
    // Test setting fetch size below max
    Connection fsConn = getConnection(miniHS2.getJdbcURL("default", "fetchSize=50", ""),
      System.getProperty("user.name"), "bar");
    Statement stmt = fsConn.createStatement();
    stmt.execute("set hive.server2.thrift.resultset.serialize.in.tasks=true");
    int fetchSize = stmt.getFetchSize();
    assertEquals(50, fetchSize);
    stmt.close();
    fsConn.close();
    // Test setting fetch size above max
    fsConn = getConnection(
      miniHS2.getJdbcURL(
        "default",
        "fetchSize=" + (miniHS2.getHiveConf().getIntVar(
          HiveConf.ConfVars.HIVE_SERVER2_THRIFT_RESULTSET_MAX_FETCH_SIZE) + 1),
        ""),
      System.getProperty("user.name"), "bar");
    stmt = fsConn.createStatement();
    stmt.execute("set hive.server2.thrift.resultset.serialize.in.tasks=true");
    fetchSize = stmt.getFetchSize();
    assertEquals(
      miniHS2.getHiveConf().getIntVar(
        HiveConf.ConfVars.HIVE_SERVER2_THRIFT_RESULTSET_MAX_FETCH_SIZE),
      fetchSize);
    stmt.close();
    fsConn.close();
  }
}
