Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
ImpalaJdbcClient.java
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.cloudera.impala.testutil;
16 
17 import java.sql.Connection;
18 import java.sql.DriverManager;
19 import java.sql.ResultSet;
20 import java.sql.ResultSetMetaData;
21 import java.sql.SQLException;
22 import java.sql.Statement;
23 import java.text.DecimalFormat;
24 import java.util.ArrayList;
25 
26 import org.apache.commons.cli.BasicParser;
27 import org.apache.commons.cli.CommandLine;
28 import org.apache.commons.cli.Options;
29 import org.apache.commons.cli.ParseException;
30 import org.apache.log4j.ConsoleAppender;
31 import org.apache.log4j.Logger;
32 import org.apache.log4j.PatternLayout;
33 
34 import com.google.common.base.Joiner;
35 import com.google.common.base.Preconditions;
36 import com.google.common.collect.Lists;
37 
42 public class ImpalaJdbcClient {
43  private static final Logger LOG = Logger.getLogger(ImpalaJdbcClient.class);
44 
45  // Note: The older Hive Server JDBC driver (Hive .9 and earlier) is named similarly:
46  // "org.apache.hadoop.hive.jdbc.HiveDriver". However, Impala currently only supports
47  // the Hive Server 2 JDBC driver (Hive .10 and later).
48  private final static String HIVE_SERVER2_DRIVER_NAME =
49  "org.apache.hive.jdbc.HiveDriver";
50 
51  // Hive uses simple SASL by default. The auth configuration 'none' (both for the client
52  // and the server) correspond to using simple SASL.
53  private final static String SASL_AUTH_SPEC = ";auth=none";
54 
55  // As of Hive 0.11 'noSasl' is case sensitive. See HIVE-4232 for more details.
56  private final static String NOSASL_AUTH_SPEC = ";auth=noSasl";
57 
58  // The default connection string connects to localhost at the default hs2_port without
59  // Sasl.
60  private final static String DEFAULT_CONNECTION_STRING =
61  "jdbc:hive2://localhost:21050/default";
62 
63  private final String driverName_;
64  private final String connString_;
65  private Connection conn_;
66  private Statement stmt_;
67 
68  private ImpalaJdbcClient(String driverName, String connString) {
69  this.driverName_ = driverName;
70  this.connString_ = connString;
71  }
72 
73  private void validateConnection() throws SQLException {
74  if (conn_ == null) {
75  throw new RuntimeException("Connection not initialized.");
76  } else if (conn_.isClosed()) {
77  throw new RuntimeException("Connection not open.");
78  }
79  Preconditions.checkNotNull(stmt_);
80 
81  // Re-open if the statement if it has been closed.
82  if (stmt_.isClosed()) {
83  stmt_ = conn_.createStatement();
84  }
85  }
86 
87  public void connect() throws ClassNotFoundException, SQLException {
88  LOG.info("Using JDBC Driver Name: " + driverName_);
89  LOG.info("Connecting to: " + connString_);
90 
91  // Make sure the driver can be found, throws a ClassNotFoundException if
92  // it is not available.
93  Class.forName(driverName_);
94  conn_ = DriverManager.getConnection(connString_, "", "");
95  stmt_ = conn_.createStatement();
96  }
97 
98  /*
99  * Closes the internal Statement and Connection objects. If they are already closed
100  * this is a no-op.
101  */
102  public void close() throws SQLException {
103  if (stmt_ != null) {
104  stmt_.close();
105  }
106 
107  if (conn_ != null) {
108  conn_.close();
109  }
110  }
111 
112  /*
113  * Executes the given query and returns the ResultSet. Will re-open the Statement
114  * if needed.
115  */
116  public ResultSet execQuery(String query) throws SQLException {
118  LOG.info("Executing: " + query);
119  return stmt_.executeQuery(query);
120  }
121 
122  public void changeDatabase(String db_name) throws SQLException {
124  LOG.info("Using: " + db_name);
125  stmt_.execute("use " + db_name);
126  }
127 
128  public Connection getConnection() {
129  return conn_;
130  }
131 
132  public Statement getStatement() {
133  return stmt_;
134  }
135 
137  return new ImpalaJdbcClient(
139  }
140 
141  public static ImpalaJdbcClient createClientUsingHiveJdbcDriver(String connString) {
142  return new ImpalaJdbcClient(HIVE_SERVER2_DRIVER_NAME, connString);
143  }
144 
148  private static class ClientExecOptions {
149  private final String connStr;
150  private final String query;
151 
152  public ClientExecOptions(String connStr, String query) {
153  this.connStr = connStr;
154  this.query = query;
155  }
156 
157  public String getQuery() {
158  return query;
159  }
160 
161  public String getConnStr() {
162  return connStr;
163  }
164  }
165 
169  private static ClientExecOptions parseOptions(String [] args) throws ParseException {
170  Options options = new Options();
171  options.addOption("i", true, "host:port of target machine impalad is listening on");
172  options.addOption("c", true,
173  "Full connection string to use. Overrides host/port value");
174  options.addOption("t", true, "SASL/NOSASL, whether to use SASL transport or not");
175  options.addOption("q", true, "Query String");
176  options.addOption("help", false, "Help");
177 
178  BasicParser optionParser = new BasicParser();
179  CommandLine cmdArgs = optionParser.parse(options, args);
180 
181  String transportOption = cmdArgs.getOptionValue("t");
182  if (transportOption == null) {
183  LOG.error("Must specify '-t' option, whether to use SASL transport or not.");
184  LOG.error("Using the wrong type of transport will cause the program to hang.");
185  LOG.error("Usage: " + options.toString());
186  System.exit(1);
187  }
188  if (!transportOption.equalsIgnoreCase("SASL") &&
189  !transportOption.equalsIgnoreCase("NOSASL")) {
190  LOG.error("Invalid argument " + transportOption + " to '-t' option.");
191  LOG.error("Usage: " + options.toString());
192  System.exit(1);
193  }
194  boolean useSasl = transportOption.equalsIgnoreCase("SASL");
195 
196  String connStr = cmdArgs.getOptionValue("c", null);
197 
198  // If the user didn't specify a custom connection string, build a connection
199  // string using HiveServer 2 JDBC driver and no security.
200  if (connStr == null) {
201  String hostPort = cmdArgs.getOptionValue("i", "localhost:21050");
202  connStr = "jdbc:hive2://" + hostPort + "/";
203  }
204  // Append appropriate auth option to connection string.
205  if (useSasl) {
206  connStr = connStr + SASL_AUTH_SPEC;
207  } else {
208  connStr = connStr + NOSASL_AUTH_SPEC;
209  }
210 
211  String query = cmdArgs.getOptionValue("q");
212  if (query == null) {
213  LOG.error("Must specify a query to execute.");
214  LOG.error("Usage: " + options.toString());
215  System.exit(1);
216  }
217 
218  return new ClientExecOptions(connStr, query);
219  }
220 
221  private static String formatColumnValue(String colVal, String columnType)
222  throws NumberFormatException {
223  columnType = columnType.toLowerCase();
224  if (colVal == null) {
225  return columnType.equals("string") ? "'NULL'" : "NULL";
226  }
227 
228  if (columnType.equals("string")) {
229  return "'" + colVal + "'";
230  } else if (columnType.equals("float") || columnType.equals("double")) {
231  // Fixup formatting of float/double values to match the expected test
232  // results
233  DecimalFormat df = new DecimalFormat("#.##################################");
234  double doubleVal = Double.parseDouble(colVal);
235  return df.format(doubleVal);
236  }
237  return colVal;
238  }
239 
245  private static void execQuery(ImpalaJdbcClient client, String queryString)
246  throws SQLException, NumberFormatException {
247 
248  String[] queries = queryString.trim().split(";");
249  for (String query: queries) {
250  query = query.trim();
251  if (query.indexOf(" ") > -1) {
252  if (query.substring(0, query.indexOf(" ")).equalsIgnoreCase("use")) {
253  String[] split_query = query.split(" ");
254  String db_name = split_query[split_query.length - 1];
255  client.changeDatabase(db_name);
256  client.getStatement().close();
257  continue;
258  }
259  }
260  long startTime = System.currentTimeMillis();
261  ResultSet res = client.execQuery(query);
262  ResultSetMetaData meta = res.getMetaData();
263  ArrayList<String> arrayList = Lists.newArrayList();
264 
265  // This token (and the [END] token) are used to help parsing the result output
266  // for test verification purposes.
267  LOG.info("----[START]----");
268  int rowCount = 0;
269  while (res.next()) {
270  arrayList.clear();
271  for (int i = 1; i <= meta.getColumnCount(); ++i) {
272  // Format the value based on the column type
273  String colVal = formatColumnValue(res.getString(i), meta.getColumnTypeName(i));
274  arrayList.add(colVal);
275  }
276  LOG.info(Joiner.on(",").join(arrayList));
277  ++rowCount;
278  }
279  LOG.info("----[END]----");
280  long endTime = System.currentTimeMillis();
281  float seconds = (endTime - startTime) / 1000F;
282  LOG.info("Returned " + rowCount + " row(s) in " + seconds + "s");
283 
284  // TODO: To work around a JDBC driver issue (CDH-10035), make sure the Statement
285  // is closed after every query.
286  client.getStatement().close();
287  }
288  }
289 
294  public static void main(String[] args) throws SQLException, ClassNotFoundException,
295  ParseException {
296  // Remove all prefixes from the logging output to make it easier to parse and disable
297  // the root logger from spewing anything. This is done to make it easier to parse
298  // the output.
299  PatternLayout layout = new PatternLayout("%m%n");
300  ConsoleAppender consoleAppender = new ConsoleAppender(layout);
301  LOG.addAppender(consoleAppender);
302  LOG.setAdditivity(false);
303 
304  ClientExecOptions execOptions = parseOptions(args);
305 
306  ImpalaJdbcClient client =
307  ImpalaJdbcClient.createClientUsingHiveJdbcDriver(execOptions.getConnStr());
308 
309  try {
310  client.connect();
311  execQuery(client, execOptions.getQuery());
312  } finally {
313  client.close();
314  }
315  }
316 }
static void execQuery(ImpalaJdbcClient client, String queryString)
static String formatColumnValue(String colVal, String columnType)
static ImpalaJdbcClient createClientUsingHiveJdbcDriver(String connString)
static ClientExecOptions parseOptions(String[] args)
ImpalaJdbcClient(String driverName, String connString)
static ImpalaJdbcClient createClientUsingHiveJdbcDriver()