/**
* 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.lens.client.jdbc;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.UriBuilder;
import org.apache.lens.client.LensClientConfig;
import org.apache.lens.client.LensConnectionParams;
/**
* The Class JDBCUtils.
*/
public final class JDBCUtils {
private JDBCUtils() {
}
/** Property key for the Database name. */
static final String DB_PROPERTY_KEY = "DBNAME";
/**
* Property key for host on which lens server is running.
*/
static final String HOST_PROPERTY_KEY = "HOST";
/**
* Property key for port on which lens server is running.
*/
static final String PORT_PROPERTY_KEY = "PORT";
/** The Constant URL_PREFIX. */
public static final String URL_PREFIX = "jdbc:lens://";
/** The Constant URI_JDBC_PREFIX. */
private static final String URI_JDBC_PREFIX = "jdbc:";
/** The Constant KEY_VALUE_REGEX. */
private static final String KEY_VALUE_REGEX = "([^;]*)=([^;]*)[;]?";
/**
* Parses the JDBC Connection URL.
* <p></p>
* The URL format is specified as following :
* <ul>
* <li>hostname which contains hosts on which lens server is hosted parsed from authority section</li>
* <li>port number if specificed in authority part.</li>
* <li>db name from path</li>
* <li>session settings from path, separated by ;</li>
* <li>configuration settings separated by ; from query setting</li>
* <li>variables setting separated by ; from fragment</li>
* </ul>
* <p>
* Examples :-
* </p>
* <code>
* jdbc:lens://hostname:port/dbname;[optional key value pair of session settings]?
* [optional configuration settings for connection]#[optional variables to be used in query]
* </code>
*
* @param uri to be used to connect to lens server
* @return final list of connection parameters
* @throws IllegalArgumentException if URI provided is malformed
*/
public static LensConnectionParams parseUrl(String uri) throws IllegalArgumentException {
LensConnectionParams params = new LensConnectionParams();
if (!uri.startsWith(URL_PREFIX)) {
throw new IllegalArgumentException("Bad URL format");
}
if (uri.equalsIgnoreCase(URL_PREFIX)) {
return params;
}
URI jdbcUri = URI.create(uri.substring(URI_JDBC_PREFIX.length()));
Pattern pattern = Pattern.compile(KEY_VALUE_REGEX);
// dbname and session settings
String sessVars = jdbcUri.getPath();
if ((sessVars != null) && !sessVars.isEmpty()) {
String dbName = "";
// removing leading '/' returned by getPath()
sessVars = sessVars.substring(1);
if (!sessVars.contains(";")) {
// only dbname is provided
dbName = sessVars;
} else {
// we have dbname followed by session parameters
dbName = sessVars.substring(0, sessVars.indexOf(';'));
sessVars = sessVars.substring(sessVars.indexOf(';') + 1);
if (sessVars != null) {
Matcher sessMatcher = pattern.matcher(sessVars);
while (sessMatcher.find()) {
params.getSessionVars().put(sessMatcher.group(1), sessMatcher.group(2));
}
}
}
if (!dbName.isEmpty()) {
params.setDbName(dbName);
}
}
// parse hive conf settings
String confStr = jdbcUri.getQuery();
if (confStr != null) {
Matcher confMatcher = pattern.matcher(confStr);
while (confMatcher.find()) {
params.getLensConfs().put(confMatcher.group(1), confMatcher.group(2));
}
}
// parse hive var settings
String varStr = jdbcUri.getFragment();
if (varStr != null) {
Matcher varMatcher = pattern.matcher(varStr);
while (varMatcher.find()) {
params.getLensVars().put(varMatcher.group(1), varMatcher.group(2));
}
}
UriBuilder baseUriBuilder = UriBuilder.fromUri(LensClientConfig.DEFAULT_SERVER_BASE_URL);
if (jdbcUri.getHost() != null) {
baseUriBuilder.host(jdbcUri.getHost());
}
if (jdbcUri.getPort() != -1) {
baseUriBuilder.port(jdbcUri.getPort());
}
params.setBaseUrl(baseUriBuilder.build().toString());
return params;
}
/** The manifest attributes. */
private static Attributes manifestAttributes = null;
/**
* Load manifest attributes.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
private static synchronized void loadManifestAttributes() throws IOException {
if (manifestAttributes != null) {
return;
}
Class<?> clazz = JDBCUtils.class;
String classContainer = clazz.getProtectionDomain().getCodeSource().getLocation().toString();
URL manifestUrl = new URL("jar:" + classContainer + "!/META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(manifestUrl.openStream());
manifestAttributes = manifest.getMainAttributes();
}
/**
* Fetch manifest attribute.
*
* @param attributeName the attribute name
* @return the string
* @throws IOException Signals that an I/O exception has occurred.
*/
private static String fetchManifestAttribute(Attributes.Name attributeName) throws IOException {
loadManifestAttributes();
return manifestAttributes.getValue(attributeName);
}
/**
* Gets the version.
*
* @param tokenPosition the token position
* @return the version
*/
static int getVersion(int tokenPosition) {
int version = -1;
try {
String fullVersion = fetchManifestAttribute(Attributes.Name.IMPLEMENTATION_VERSION);
String[] tokens = fullVersion.split("\\."); //$NON-NLS-1$
if (tokens != null && tokens.length > 0 && tokens[tokenPosition] != null) {
version = Integer.parseInt(tokens[tokenPosition]);
}
} catch (Exception e) {
version = -1;
}
return version;
}
/**
* Parses the url for property info.
*
* @param url the url
* @param info the info
* @return the properties
* @throws SQLException the SQL exception
*/
static Properties parseUrlForPropertyInfo(String url, Properties info) throws SQLException {
Properties urlProperties = (info != null) ? new Properties(info) : new Properties();
if (url == null || !url.startsWith(URL_PREFIX)) {
throw new SQLException("Invalid connection url :" + url);
}
LensConnectionParams params = parseUrl(url);
// urlProperties.put(HOST_PROPERTY_KEY, params.getHost());
// urlProperties.put(PORT_PROPERTY_KEY, params.getPort());
urlProperties.put(DB_PROPERTY_KEY, params.getDbName());
return urlProperties;
}
/**
* Gets the SQL type.
*
* @param type the type
* @return the SQL type
* @throws SQLException the SQL exception
*/
public static int getSQLType(String type) throws SQLException {
if ("string".equalsIgnoreCase(type)) {
return Types.VARCHAR;
} else if ("varchar".equalsIgnoreCase(type)) {
return Types.VARCHAR;
} else if ("char".equalsIgnoreCase(type)) {
return Types.CHAR;
} else if ("float".equalsIgnoreCase(type)) {
return Types.FLOAT;
} else if ("double".equalsIgnoreCase(type)) {
return Types.DOUBLE;
} else if ("boolean".equalsIgnoreCase(type)) {
return Types.BOOLEAN;
} else if ("tinyint".equalsIgnoreCase(type)) {
return Types.TINYINT;
} else if ("smallint".equalsIgnoreCase(type)) {
return Types.SMALLINT;
} else if ("int".equalsIgnoreCase(type)) {
return Types.INTEGER;
} else if ("bigint".equalsIgnoreCase(type)) {
return Types.BIGINT;
} else if ("date".equalsIgnoreCase(type)) {
return Types.DATE;
} else if ("timestamp".equalsIgnoreCase(type)) {
return Types.TIMESTAMP;
} else if ("decimal".equalsIgnoreCase(type)) {
return Types.DECIMAL;
} else if ("binary".equalsIgnoreCase(type)) {
return Types.BINARY;
} else if ("map".equalsIgnoreCase(type)) {
return Types.JAVA_OBJECT;
} else if ("array".equalsIgnoreCase(type)) {
return Types.ARRAY;
} else if ("struct".equalsIgnoreCase(type)) {
return Types.STRUCT;
}
throw new SQLException("Unrecognized column type: " + type);
}
/**
* Column display size.
*
* @param columnType the column type
* @return the int
* @throws SQLException the SQL exception
*/
static int columnDisplaySize(int columnType) throws SQLException {
// according to hiveTypeToSqlType possible options are:
switch (columnType) {
case Types.BOOLEAN:
return columnPrecision(columnType);
case Types.CHAR:
case Types.VARCHAR:
return columnPrecision(columnType);
case Types.BINARY:
return Integer.MAX_VALUE; // hive has no max limit for binary
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
return columnPrecision(columnType) + 1; // allow +/-
case Types.DATE:
return 10;
case Types.TIMESTAMP:
return columnPrecision(columnType);
// see http://download.oracle.com/javase/6/docs/api/constant-values.html#java.lang.Float.MAX_EXPONENT
case Types.FLOAT:
return 24; // e.g. -(17#).e-###
// see http://download.oracle.com/javase/6/docs/api/constant-values.html#java.lang.Double.MAX_EXPONENT
case Types.DOUBLE:
return 25; // e.g. -(17#).e-####
case Types.DECIMAL:
return columnPrecision(columnType) + 2; // '-' sign and '.'
case Types.JAVA_OBJECT:
case Types.ARRAY:
case Types.STRUCT:
return Integer.MAX_VALUE;
default:
throw new SQLException("Invalid column type: " + columnType);
}
}
/**
* Column precision.
*
* @param columnType the column type
* @return the int
* @throws SQLException the SQL exception
*/
static int columnPrecision(int columnType) throws SQLException {
// according to hiveTypeToSqlType possible options are:
switch (columnType) {
case Types.BOOLEAN:
return 1;
case Types.CHAR:
case Types.VARCHAR:
return Integer.MAX_VALUE; // hive has no max limit for strings
case Types.BINARY:
return Integer.MAX_VALUE; // hive has no max limit for binary
case Types.TINYINT:
return 3;
case Types.SMALLINT:
return 5;
case Types.INTEGER:
return 10;
case Types.BIGINT:
return 19;
case Types.FLOAT:
return 7;
case Types.DOUBLE:
return 15;
case Types.DATE:
return 10;
case Types.TIMESTAMP:
return 29;
case Types.DECIMAL:
return 5;
case Types.JAVA_OBJECT:
case Types.ARRAY:
case Types.STRUCT:
return Integer.MAX_VALUE;
default:
throw new SQLException("Invalid column type: " + columnType);
}
}
/**
* Column scale.
*
* @param columnType the column type
* @return the int
* @throws SQLException the SQL exception
*/
static int columnScale(int columnType) throws SQLException {
// according to hiveTypeToSqlType possible options are:
switch (columnType) {
case Types.BOOLEAN:
case Types.CHAR:
case Types.VARCHAR:
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
case Types.DATE:
case Types.BINARY:
return 0;
case Types.FLOAT:
return 7;
case Types.DOUBLE:
return 15;
case Types.TIMESTAMP:
return 9;
case Types.DECIMAL:
return 5;
case Types.JAVA_OBJECT:
case Types.ARRAY:
case Types.STRUCT:
return 0;
default:
throw new SQLException("Invalid column type: " + columnType);
}
}
/**
* Column class name.
*
* @param columnType the column type
* @return the string
* @throws SQLException the SQL exception
*/
static String columnClassName(int columnType) throws SQLException {
switch (columnType) {
case Types.BOOLEAN:
return Boolean.class.getName();
case Types.CHAR:
case Types.VARCHAR:
return String.class.getName();
case Types.TINYINT:
return Byte.class.getName();
case Types.SMALLINT:
return Short.class.getName();
case Types.INTEGER:
return Integer.class.getName();
case Types.BIGINT:
return Long.class.getName();
case Types.DATE:
return Date.class.getName();
case Types.FLOAT:
return Float.class.getName();
case Types.DOUBLE:
return Double.class.getName();
case Types.TIMESTAMP:
return Timestamp.class.getName();
case Types.DECIMAL:
return BigInteger.class.getName();
case Types.BINARY:
return byte[].class.getName();
case Types.JAVA_OBJECT:
case Types.ARRAY:
case Types.STRUCT:
return String.class.getName();
default:
throw new SQLException("Invalid column type: " + columnType);
}
}
}