@Override
  public String[] getColumnNamesForProcedure(String procedureName) {
    List<String> ret = new ArrayList<String>();
    try {
      DatabaseMetaData metaData = this.getConnection().getMetaData();
      ResultSet results = metaData.getProcedureColumns(null, null, procedureName, null);
      if (null == results) {
        LOG.debug("Get Procedure Columns returns null");
        return null;
      }

      try {
        while (results.next()) {
          if (results.getInt("COLUMN_TYPE") != DatabaseMetaData.procedureColumnReturn) {
            String name = results.getString("COLUMN_NAME");
            ret.add(name);
          }
        }
        String[] result = ret.toArray(new String[ret.size()]);
        LOG.debug("getColumnsNamesForProcedure returns " + StringUtils.join(ret, ","));
        return result;
      } finally {
        results.close();
        getConnection().commit();
      }
    } catch (SQLException e) {
      LoggingUtils.logAll(LOG, "Error reading procedure metadata: ", e);
      throw new RuntimeException("Can't fetch column names for procedure.", e);
    }
  }
  @Override
  public String[] getColumnNames(String tableName) {
    Connection c = null;
    Statement s = null;
    ResultSet rs = null;
    List<String> columns = new ArrayList<String>();
    String listColumnsQuery = getListColumnsQuery(tableName);
    try {
      c = getConnection();
      s = c.createStatement();
      rs = s.executeQuery(listColumnsQuery);
      while (rs.next()) {
        columns.add(rs.getString(1));
      }
      c.commit();
    } catch (SQLException sqle) {
      try {
        if (c != null) {
          c.rollback();
        }
      } catch (SQLException ce) {
        LoggingUtils.logAll(LOG, "Failed to rollback transaction", ce);
      }
      LoggingUtils.logAll(LOG, "Failed to list columns from query: " + listColumnsQuery, sqle);
      throw new RuntimeException(sqle);
    } finally {
      if (rs != null) {
        try {
          rs.close();
        } catch (SQLException re) {
          LoggingUtils.logAll(LOG, "Failed to close resultset", re);
        }
      }
      if (s != null) {
        try {
          s.close();
        } catch (SQLException se) {
          LoggingUtils.logAll(LOG, "Failed to close statement", se);
        }
      }
    }

    return filterSpecifiedColumnNames(columns.toArray(new String[columns.size()]));
  }
  @Override
  public String[] listTables() {
    Connection c = null;
    Statement s = null;
    ResultSet rs = null;
    List<String> tables = new ArrayList<String>();
    try {
      c = getConnection();
      s = c.createStatement();
      rs = s.executeQuery(getListTablesQuery());
      while (rs.next()) {
        tables.add(rs.getString(1));
      }
      c.commit();
    } catch (SQLException sqle) {
      try {
        if (c != null) {
          c.rollback();
        }
      } catch (SQLException ce) {
        LoggingUtils.logAll(LOG, "Failed to rollback transaction", ce);
      }
      LoggingUtils.logAll(LOG, "Failed to list tables", sqle);
      throw new RuntimeException(sqle);
    } finally {
      if (rs != null) {
        try {
          rs.close();
        } catch (SQLException re) {
          LoggingUtils.logAll(LOG, "Failed to close resultset", re);
        }
      }
      if (s != null) {
        try {
          s.close();
        } catch (SQLException se) {
          LoggingUtils.logAll(LOG, "Failed to close statement", se);
        }
      }
    }

    return tables.toArray(new String[tables.size()]);
  }
  @Override
  public Map<String, String> getColumnTypeNamesForProcedure(String procedureName) {
    Map<String, String> ret = new TreeMap<String, String>();
    try {
      DatabaseMetaData metaData = this.getConnection().getMetaData();
      ResultSet results = metaData.getProcedureColumns(null, null, procedureName, null);
      if (null == results) {
        LOG.debug("getColumnTypesForProcedure returns null");
        return null;
      }

      try {
        while (results.next()) {
          if (results.getInt("COLUMN_TYPE") != DatabaseMetaData.procedureColumnReturn) {
            // we don't care if we get several rows for the
            // same ORDINAL_POSITION (e.g. like H2 gives us)
            // as we'll just overwrite the entry in the map:
            ret.put(results.getString("COLUMN_NAME"), results.getString("TYPE_NAME"));
          }
        }

        LOG.debug("Columns returned = " + StringUtils.join(ret.keySet(), ","));
        LOG.debug("Type names returned = " + StringUtils.join(ret.values(), ","));

        return ret.isEmpty() ? null : ret;
      } finally {
        if (results != null) {
          results.close();
        }
        getConnection().commit();
      }
    } catch (SQLException sqlException) {
      LoggingUtils.logAll(
          LOG, "Error reading primary key metadata: " + sqlException.toString(), sqlException);
      return null;
    }
  }
  @Override
  public void execAndPrint(String s) {
    // Override default execAndPrint() with a special version that forces
    // use of fully-buffered ResultSets (MySQLManager uses streaming ResultSets
    // in the default execute() method; but the execAndPrint() method needs to
    // issue overlapped queries for metadata.)

    ResultSet results = null;
    try {
      // Explicitly setting fetchSize to zero disables streaming.
      results = super.execute(s, 0);
    } catch (SQLException sqlE) {
      LoggingUtils.logAll(LOG, "Error executing statement: ", sqlE);
      release();
      return;
    }

    PrintWriter pw = new PrintWriter(System.out, true);
    try {
      formatAndPrintResultSet(results, pw);
    } finally {
      pw.close();
    }
  }
  @Override
  public String getPrimaryKey(String tableName) {
    Connection c = null;
    Statement s = null;
    ResultSet rs = null;
    List<String> columns = new ArrayList<String>();
    try {
      c = getConnection();
      s = c.createStatement();

      String primaryKeyQuery = getPrimaryKeyQuery(tableName);
      LOG.debug(
          "Retrieving primary key for table '" + tableName + "' with query " + primaryKeyQuery);
      rs = s.executeQuery(primaryKeyQuery);
      while (rs.next()) {
        columns.add(rs.getString(1));
      }
      c.commit();
    } catch (SQLException sqle) {
      try {
        if (c != null) {
          c.rollback();
        }
      } catch (SQLException ce) {
        LoggingUtils.logAll(LOG, "Failed to rollback transaction", ce);
      }
      LoggingUtils.logAll(LOG, "Failed to list primary key", sqle);
      throw new RuntimeException(sqle);
    } finally {
      if (rs != null) {
        try {
          rs.close();
        } catch (SQLException re) {
          LoggingUtils.logAll(LOG, "Failed to close resultset", re);
        }
      }
      if (s != null) {
        try {
          s.close();
        } catch (SQLException se) {
          LoggingUtils.logAll(LOG, "Failed to close statement", se);
        }
      }
    }

    if (columns.size() == 0) {
      // Table has no primary key
      return null;
    }

    if (columns.size() > 1) {
      // The primary key is multi-column primary key. Warn the user.
      LOG.warn(
          "The table "
              + tableName
              + " "
              + "contains a multi-column primary key. Sqoop will default to "
              + "the column "
              + columns.get(0)
              + " only for this job.");
    }

    return columns.get(0);
  }