net.snowflake.client.jdbc.SnowflakeDriver Maven / Gradle / Ivy
/*
* Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
*/
package net.snowflake.client.jdbc;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.List;
import java.util.Properties;
import net.snowflake.client.config.ConnectionParameters;
import net.snowflake.client.config.SFConnectionConfigParser;
import net.snowflake.client.core.SecurityUtil;
import net.snowflake.client.core.SnowflakeJdbcInternalApi;
import net.snowflake.client.jdbc.telemetryOOB.TelemetryService;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;
import net.snowflake.common.core.ResourceBundleManager;
import net.snowflake.common.core.SqlState;
/**
* JDBC Driver implementation of Snowflake for production. To use this driver, specify the following
* URL: jdbc:snowflake://host:port
*
* Note: don't add logger to this class since logger init will potentially break driver class
* loading
*/
public class SnowflakeDriver implements Driver {
private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeDriver.class);
public static final String AUTO_CONNECTION_STRING_PREFIX = "jdbc:snowflake:auto";
static SnowflakeDriver INSTANCE;
public static final Properties EMPTY_PROPERTIES = new Properties();
public static String implementVersion = "3.19.1";
static int majorVersion = 0;
static int minorVersion = 0;
static long patchVersion = 0;
protected static boolean disableIncidents = false;
private static boolean disableArrowResultFormat = false;
private static String disableArrowResultFormatMessage;
private static final ResourceBundleManager versionResourceBundleManager =
ResourceBundleManager.getSingleton("net.snowflake.client.jdbc.version");
static {
try {
DriverManager.registerDriver(INSTANCE = new SnowflakeDriver());
} catch (SQLException ex) {
throw new IllegalStateException("Unable to register " + SnowflakeDriver.class.getName(), ex);
}
initializeArrowSupport();
/*
* Get the manifest properties here.
*/
initializeClientVersionFromManifest();
SecurityUtil.addBouncyCastleProvider();
// Telemetry OOB is disabled
TelemetryService.disableOOBTelemetry();
}
/** try to initialize Arrow support if fails, JDBC is going to use the legacy format */
private static void initializeArrowSupport() {
try {
// this is required to enable direct memory usage for Arrow buffers in Java
System.setProperty("io.netty.tryReflectionSetAccessible", "true");
} catch (Throwable t) {
// fail to enable required feature for Arrow
disableArrowResultFormat = true;
disableArrowResultFormatMessage = t.getLocalizedMessage();
}
if ("true"
.equals(SnowflakeUtil.systemGetProperty("snowflake.jdbc.enable.illegalAccessWarning"))) {
return;
}
disableIllegalReflectiveAccessWarning();
}
static void disableIllegalReflectiveAccessWarning() {
// The netty dependency of arrow will cause an illegal reflective access warning
// This function try to eliminate the warning by setting
// jdk.internal.module.IllegalAccessLogger's logger as null
// Disable this function and manually run arrow tests, e.g. testResultSetMetadata., then
// the warning can be found in output.
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
Method putObjectVolatile =
unsafeClass.getDeclaredMethod(
"putObjectVolatile", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Method staticFieldBase = unsafeClass.getDeclaredMethod("staticFieldBase", Field.class);
Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
Object loggerBase = staticFieldBase.invoke(unsafe, loggerField);
putObjectVolatile.invoke(unsafe, loggerBase, loggerOffset, null);
} catch (Throwable ex) {
// If failed to eliminate warnings, do nothing
}
}
private static void initializeClientVersionFromManifest() {
/*
* Get JDBC version numbers from static string in snowflake-jdbc
*/
try {
// parse implementation version major.minor.change
if (implementVersion != null) {
String[] versionBreakdown = implementVersion.split("\\.");
if (versionBreakdown.length == 3) {
majorVersion = Integer.parseInt(versionBreakdown[0]);
minorVersion = Integer.parseInt(versionBreakdown[1]);
patchVersion = Long.parseLong(versionBreakdown[2]);
} else {
throw new SnowflakeSQLLoggedException(
null,
ErrorCode.INTERNAL_ERROR.getMessageCode(),
SqlState.INTERNAL_ERROR,
/*session = */ "Invalid Snowflake JDBC Version: " + implementVersion);
}
} else {
throw new SnowflakeSQLException(
SqlState.INTERNAL_ERROR,
ErrorCode.INTERNAL_ERROR.getMessageCode(),
/*session = */ null,
"Snowflake JDBC Version is not set. "
+ "Ensure static version string was initialized.");
}
} catch (Throwable ex) {
}
}
/**
* For testing purposes only- used to compare that JDBC version in pom.xml matches static string
*
* @return String with version from pom.xml file
*/
static String getClientVersionStringFromManifest() {
return versionResourceBundleManager.getLocalizedMessage("version");
}
public static boolean isDisableArrowResultFormat() {
return disableArrowResultFormat;
}
public static String getDisableArrowResultFormatMessage() {
return disableArrowResultFormatMessage;
}
/**
* Utility method to verify if the standard or fips snowflake-jdbc driver is being used.
*
* @return
*/
public static String getImplementationTitle() {
Package pkg = Package.getPackage("net.snowflake.client.jdbc");
return pkg.getImplementationTitle();
}
/**
* Utility method to get the complete jar name with version.
*
* @return
*/
public static String getJdbcJarname() {
return String.format("%s-%s", getImplementationTitle(), implementVersion);
}
/**
* Checks whether a given url is in a valid format.
*
*
The current uri format is: jdbc:snowflake://[host[:port]]
*
*
jdbc:snowflake:// - run in embedded mode jdbc:snowflake://localhost - connect to localhost
* default port (8080)
*
*
jdbc:snowflake://localhost:8080- connect to localhost port 8080
*
* @param url url of the database including host and port
* @return true if the url is valid
*/
@Override
public boolean acceptsURL(String url) {
return SnowflakeConnectString.parse(url, EMPTY_PROPERTIES).isValid();
}
/**
* Connect method
*
* @param url jdbc url
* @param info addition info for passing database/schema names
* @return connection
* @throws SQLException if failed to create a snowflake connection
*/
@Override
public Connection connect(String url, Properties info) throws SQLException {
ConnectionParameters connectionParameters =
overrideByFileConnectionParametersIfAutoConfiguration(url, info);
if (connectionParameters.getUrl() == null) {
// expected return format per the JDBC spec for java.sql.Driver#connect()
throw new SnowflakeSQLException("Unable to connect to url of 'null'.");
}
if (!SnowflakeConnectString.hasSupportedPrefix(connectionParameters.getUrl())) {
return null; // expected return format per the JDBC spec for java.sql.Driver#connect()
}
SnowflakeConnectString conStr =
SnowflakeConnectString.parse(
connectionParameters.getUrl(), connectionParameters.getParams());
if (!conStr.isValid()) {
throw new SnowflakeSQLException("Connection string is invalid. Unable to parse.");
}
return new SnowflakeConnectionV1(
connectionParameters.getUrl(), connectionParameters.getParams());
}
private static ConnectionParameters overrideByFileConnectionParametersIfAutoConfiguration(
String url, Properties info) throws SnowflakeSQLException {
if (url != null && url.contains(AUTO_CONNECTION_STRING_PREFIX)) {
// Connect using connection configuration file
ConnectionParameters connectionParameters =
SFConnectionConfigParser.buildConnectionParameters();
if (connectionParameters == null) {
throw new SnowflakeSQLException(
"Unavailable connection configuration parameters expected for auto configuration using file");
}
return connectionParameters;
} else {
return new ConnectionParameters(url, info);
}
}
/**
* Connect method using connection configuration file
*
* @return connection
* @throws SQLException if failed to create a snowflake connection
*/
@SnowflakeJdbcInternalApi
public Connection connect() throws SQLException {
logger.debug("Execute internal method connect() without parameters");
return connect(AUTO_CONNECTION_STRING_PREFIX, null);
}
@Override
public int getMajorVersion() {
return majorVersion;
}
@Override
public int getMinorVersion() {
return minorVersion;
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
DriverPropertyInfo[] retVal;
if (url == null || url.isEmpty()) {
retVal = new DriverPropertyInfo[1];
retVal[0] = new DriverPropertyInfo("serverURL", null);
retVal[0].description =
"server URL in form of ://:/";
return retVal;
}
Connection con = new SnowflakeConnectionV1(url, info, true);
List missingProperties =
((SnowflakeConnectionV1) con).returnMissingProperties();
con.close();
retVal = new DriverPropertyInfo[missingProperties.size()];
retVal = missingProperties.toArray(retVal);
return retVal;
}
@Override
public boolean jdbcCompliant() {
return false;
}
@Override
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
public static boolean isDisableIncidents() {
return disableIncidents;
}
public static void setDisableIncidents(boolean throttleIncidents) {
SnowflakeDriver.disableIncidents = throttleIncidents;
}
public static final void main(String[] args) {
if (args.length > 0 && "--version".equals(args[0])) {
Package pkg = Package.getPackage("net.snowflake.client.jdbc");
if (pkg != null) {
System.out.println(pkg.getImplementationVersion());
}
}
}
}