public List<ColumnStatistics> getPartitionStats(
      final String catName, final String dbName, final String tableName, final List<String> partNames,
      List<String> colNames, String engine, boolean enableBitVector, boolean enableKll) throws MetaException {
    if (colNames.isEmpty() || partNames.isEmpty()) {
      return Collections.emptyList();
    }
    final boolean doTrace = LOG.isDebugEnabled();
    final String queryText0 = "select \"PARTITIONS\".\"PART_NAME\", " + getStatsList(enableBitVector, enableKll)
        + " from " + PART_COL_STATS
        + " inner join " + PARTITIONS + " on " + PART_COL_STATS + ".\"PART_ID\" = " + PARTITIONS + ".\"PART_ID\""
        + " inner join " + TBLS + " on " + PARTITIONS + ".\"TBL_ID\" = " + TBLS + ".\"TBL_ID\""
        + " inner join " + DBS + " on " + TBLS + ".\"DB_ID\" = " + DBS + ".\"DB_ID\""
        + " where " + DBS + ".\"CTLG_NAME\" = ? and " + DBS + ".\"NAME\" = ? and " + TBLS + ".\"TBL_NAME\" = ? "
        + " and " + PART_COL_STATS + ".\"COLUMN_NAME\" in (%1$s)"
        + " and " + PARTITIONS + ".\"PART_NAME\" in (%2$s)"
        + " and " + PART_COL_STATS + ".\"ENGINE\" = ? "
        + " order by " + PARTITIONS +  ".\"PART_NAME\"";
    Batchable<String, Object[]> b = new Batchable<String, Object[]>() {
      @Override
      public List<Object[]> run(final List<String> inputColNames) throws MetaException {
        Batchable<String, Object[]> b2 = new Batchable<String, Object[]>() {
          @Override
          public List<Object[]> run(List<String> inputPartNames) throws MetaException {
            String queryText = String.format(queryText0,
                makeParams(inputColNames.size()), makeParams(inputPartNames.size()));
            long start = doTrace ? System.nanoTime() : 0;
            Query query = pm.newQuery("javax.jdo.query.SQL", queryText);
            try {
              Object qResult = executeWithArray(query, prepareParams(
                  catName, dbName, tableName, inputPartNames, inputColNames, engine), queryText);
              MetastoreDirectSqlUtils.timingTrace(doTrace, queryText0, start, (doTrace ? System.nanoTime() : 0));
              if (qResult == null) {
                return Collections.emptyList();
              }
              return MetastoreDirectSqlUtils.ensureList(qResult);
            } finally {
              addQueryAfterUse(query);
            }
          }
        };
        try {
          return Batchable.runBatched(batchSize, partNames, b2);
        } finally {
          addQueryAfterUse(b2);
        }
      }
    };

    List<ColumnStatistics> result = new ArrayList<ColumnStatistics>(partNames.size());
    String lastPartName = null;
    int from = 0;
    try {
      List<Object[]> list = Batchable.runBatched(batchSize, colNames, b);
      for (int i = 0; i <= list.size(); ++i) {
        boolean isLast = i == list.size();
        String partName = isLast ? null : (String) list.get(i)[0];
        if (!isLast && partName.equals(lastPartName)) {
          continue;
        } else if (from != i) {
          ColumnStatisticsDesc csd =
              new ColumnStatisticsDesc(false, dbName, tableName);
          csd.setCatName(catName);
          csd.setPartName(lastPartName);
          result.add(makeColumnStats(list.subList(from, i), csd, 1, engine));
        }
        lastPartName = partName;
        from = i;
        Deadline.checkTimeout();
      }
    } finally {
      b.closeAllQueries();
    }
    return result;
  }
Should be called with the list short enough to not trip up Oracle/etc.
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/MetaStoreDirectSql.java