net.starschema.clouddb.jdbc.BQConnection Maven / Gradle / Ivy
Show all versions of bqjdbc Show documentation
/**
* Copyright (c) 2015, STARSCHEMA LTD. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
*
*
1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
*
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.starschema.clouddb.jdbc;
import com.google.api.client.http.HttpTransport;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.model.DatasetReference;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.GeneralSecurityException;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The connection class which builds the connection between BigQuery and the Driver
*
* @author Gunics Balázs, Horváth Attila
*/
public class BQConnection implements Connection {
// We permit using either a "." or ":" as the delimiter between the dataset and project ids.
private static final String PROJECT_DELIMITERS = ":.";
// The following regex uses a lookahead to match the last occurrence of a project delimiter.
private static final String LAST_PROJECT_DELIMITER_REGEX =
"[" + PROJECT_DELIMITERS + "](?=[^" + PROJECT_DELIMITERS + "]*$)";
/** Variable to store auto commit mode */
private boolean autoCommitEnabled = false;
/** Instance log4j.Logger */
Logger logger;
/** The bigquery client to access the service. */
private Bigquery bigquery = null;
/** The default dataset id to configure on queries processed by this connection. */
private String dataset = null;
/**
* The default dataset project id to configure on queries processed by this connection.
*
*
We follow the same naming convention as the corresponding system variable @@dataset_project_id.
*/
private String datasetProjectId = null;
/** The ProjectId to use for billing on queries processed by this connection. */
private final String projectId;
/** Boolean to determine if the Connection is closed */
private boolean isclosed = false;
private Long maxBillingBytes;
private Integer timeoutMs;
private final Map labels;
private final boolean useQueryCache;
private final Set runningStatements =
Collections.synchronizedSet(new HashSet());
/** Boolean to determine whether or not to use legacy sql (default: false) * */
private final boolean useLegacySql;
/**
* Enum that describes whether to create a job in projects that support stateless queries. Copied
* from google-cloud-bigquery
* 2.34.0
*/
public static enum JobCreationMode {
/** If unspecified JOB_CREATION_REQUIRED is the default. */
JOB_CREATION_MODE_UNSPECIFIED,
/** Default. Job creation is always required. */
JOB_CREATION_REQUIRED,
/**
* Job creation is optional. Returning immediate results is prioritized. BigQuery will
* automatically determine if a Job needs to be created. The conditions under which BigQuery can
* decide to not create a Job are subject to change. If Job creation is required,
* JOB_CREATION_REQUIRED mode should be used, which is the default.
*
* Note that no job ID will be created if the results were returned immediately.
*/
JOB_CREATION_OPTIONAL;
private JobCreationMode() {}
}
/** The job creation mode - */
private JobCreationMode jobCreationMode = JobCreationMode.JOB_CREATION_MODE_UNSPECIFIED;
/** getter for useLegacySql */
public boolean getUseLegacySql() {
return useLegacySql;
}
/** List to contain sql warnings in */
private List SQLWarningList = new ArrayList();
/** String to contain the url except the url prefix */
private String URLPART = null;
/**
* Extracts the JDBC URL then makes a connection to the Bigquery.
*
* @param url the JDBC connection URL
* @param loginProp
* @throws GeneralSecurityException
* @throws IOException
* @throws SQLException
*/
public BQConnection(String url, Properties loginProp) throws SQLException {
this(url, loginProp, Oauth2Bigquery.HTTP_TRANSPORT);
}
/**
* Like {@link BQConnection(String,Properties)} but allows setting the {@link HttpTransport} for
* testing.
*/
public BQConnection(String url, Properties loginProp, HttpTransport httpTransport)
throws SQLException {
this.logger = LoggerFactory.getLogger(this.getClass());
this.URLPART = url;
this.isclosed = false;
try {
Pattern pathParamsMatcher =
Pattern.compile("^jdbc:BQDriver::?([^?]*)", Pattern.CASE_INSENSITIVE);
Matcher pathParamsMatchData = pathParamsMatcher.matcher(URLDecoder.decode(url, "UTF-8"));
String pathParams;
if (pathParamsMatchData.find()) {
pathParams = pathParamsMatchData.group(1);
} else {
pathParams =
URLDecoder.decode(url.substring(url.lastIndexOf(":") + 1, url.indexOf('?')), "UTF-8");
}
Pattern projectAndDatasetMatcher = Pattern.compile("^([^/$]+)(?:/([^$]*))?$");
Matcher matchData = projectAndDatasetMatcher.matcher(pathParams);
if (matchData.find()) {
this.projectId = CatalogName.toProjectId(matchData.group(1));
configureDataSet(matchData.group(2), this.projectId);
} else {
this.projectId = CatalogName.toProjectId(pathParams);
}
} catch (UnsupportedEncodingException e1) {
throw new BQSQLException(e1);
}
Properties caseInsensitiveLoginProps = new Properties();
if (loginProp != null) {
Iterator props = loginProp.keySet().iterator();
while (props.hasNext()) {
String prop = (String) props.next();
caseInsensitiveLoginProps.setProperty(prop.toLowerCase(), loginProp.getProperty(prop));
}
}
Properties caseInsensitiveProps;
try {
// parse the connection string and override anything passed via loginProps.
caseInsensitiveProps = BQSupportFuncts.getUrlQueryComponents(url, caseInsensitiveLoginProps);
} catch (UnsupportedEncodingException e2) {
throw new BQSQLException(e2);
}
String userId = caseInsensitiveProps.getProperty("user");
String userKey = caseInsensitiveProps.getProperty("password");
String userPath = caseInsensitiveProps.getProperty("path");
// extract a list of "delegate" service accounts leading to a "target" service account to use
// for impersonation. if only a single account is provided, then it will be used as the "target"
List targetServiceAccounts =
parseArrayQueryParam(caseInsensitiveProps.getProperty("targetserviceaccount"), ',');
// extract OAuth access token
String oAuthAccessToken = caseInsensitiveProps.getProperty("oauthaccesstoken");
// extract withServiceAccount property
boolean serviceAccount =
parseBooleanQueryParam(caseInsensitiveProps.getProperty("withserviceaccount"), false);
// extract withApplicationDefaultCredentials
boolean applicationDefaultCredentials =
parseBooleanQueryParam(
caseInsensitiveProps.getProperty("withapplicationdefaultcredentials"), false);
// extract useLegacySql property
this.useLegacySql =
parseBooleanQueryParam(caseInsensitiveProps.getProperty("uselegacysql"), false);
String jsonAuthContents = caseInsensitiveProps.getProperty("jsonauthcontents");
// extract timeoutMs property
this.timeoutMs = parseIntQueryParam("timeoutMs", caseInsensitiveProps.getProperty("timeoutms"));
// extract readTimeout property
Integer readTimeout =
parseIntQueryParam("readTimeout", caseInsensitiveProps.getProperty("readtimeout"));
// extract connectTimeout property
Integer connectTimeout =
parseIntQueryParam("connectTimeout", caseInsensitiveProps.getProperty("connecttimeout"));
String maxBillingBytesParam = caseInsensitiveProps.getProperty("maxbillingbytes");
if (maxBillingBytesParam != null) {
try {
this.maxBillingBytes = Long.parseLong(maxBillingBytesParam);
} catch (NumberFormatException e) {
throw new BQSQLException("Bad number for maxBillingBytes", e);
}
}
// extract UA String
String userAgent = caseInsensitiveProps.getProperty("useragent");
// extract any labels
this.labels = tryParseLabels(caseInsensitiveProps.getProperty("labels"));
// extract custom endpoint for connections through restricted VPC
String rootUrl = caseInsensitiveProps.getProperty("rooturl");
// see if the user wants to use or bypass BigQuery's cache
// by default, the cache is enabled
this.useQueryCache =
parseBooleanQueryParam(caseInsensitiveProps.getProperty("querycache"), true);
final String jobCreationModeString = caseInsensitiveProps.getProperty("jobcreationmode");
if (jobCreationModeString == null) {
jobCreationMode = null;
} else {
try {
jobCreationMode = JobCreationMode.valueOf(jobCreationModeString);
} catch (IllegalArgumentException e) {
throw new BQSQLException(
"could not parse " + jobCreationModeString + " as job creation mode", e);
}
}
// Create Connection to BigQuery
if (serviceAccount) {
try {
// Support for old behavior, passing no actual password, but passing the path as 'password'
if (userPath == null) {
userPath = userKey;
userKey = null;
}
this.bigquery =
Oauth2Bigquery.authorizeViaService(
userId,
userPath,
userKey,
userAgent,
jsonAuthContents,
readTimeout,
connectTimeout,
rootUrl,
httpTransport,
targetServiceAccounts,
this.getProjectId());
this.logger.info("Authorized with service account");
} catch (GeneralSecurityException e) {
throw new BQSQLException(e);
} catch (IOException e) {
throw new BQSQLException(e);
}
} else if (oAuthAccessToken != null) {
try {
this.bigquery =
Oauth2Bigquery.authorizeViaToken(
oAuthAccessToken,
userAgent,
connectTimeout,
readTimeout,
rootUrl,
httpTransport,
targetServiceAccounts,
this.getProjectId());
this.logger.info("Authorized with OAuth access token");
} catch (SQLException e) {
throw new BQSQLException(e);
}
} else if (applicationDefaultCredentials) {
try {
this.bigquery =
Oauth2Bigquery.authorizeViaApplicationDefault(
userAgent,
connectTimeout,
readTimeout,
rootUrl,
httpTransport,
targetServiceAccounts,
this.getProjectId());
} catch (IOException e) {
throw new BQSQLException(e);
}
} else {
throw new IllegalArgumentException("Must provide a valid mechanism to authenticate.");
}
logger.debug("The project id for this connections is: " + projectId);
}
private static Map tryParseLabels(@Nullable String labels) {
if (labels == null) {
return Collections.emptyMap();
}
try {
return Splitter.on(",").withKeyValueSeparator("=").split(labels);
} catch (IllegalArgumentException ex) {
return Collections.emptyMap();
}
}
/**
* Return {@code defaultValue} if {@code paramValue} is null. Otherwise, return true iff {@code
* paramValue} is "true" (case-insensitive).
*/
private static boolean parseBooleanQueryParam(@Nullable String paramValue, boolean defaultValue) {
return paramValue == null ? defaultValue : Boolean.parseBoolean(paramValue);
}
/**
* Return null if {@code paramValue} is null. Otherwise, return an Integer iff {@code paramValue}
* can be parsed as a positive int.
*/
private static Integer parseIntQueryParam(String param, @Nullable String paramValue)
throws BQSQLException {
Integer val = null;
if (paramValue != null) {
try {
val = Integer.parseInt(paramValue);
if (val < 0) {
throw new BQSQLException(param + " must be positive.");
}
} catch (NumberFormatException e) {
throw new BQSQLException("could not parse " + param + " parameter.", e);
}
}
return val;
}
/**
* Return an empty list if {@code string} is null. Otherwise, return an array of strings iff
* {@code string} can be parsed as an array when split by {@code delimiter}.
*/
private static List parseArrayQueryParam(@Nullable String string, Character delimiter) {
return string == null
? Collections.emptyList()
: Arrays.asList(string.split(delimiter + "\\s*"));
}
/**
*
*
* Implementation Details:
*
*
* Uses SQLWarningList.clear() to clear all warnings
*/
@Override
public void clearWarnings() throws SQLException {
if (this.isclosed) {
throw new BQSQLException("Connection is closed.");
}
this.SQLWarningList.clear();
}
/**
* Returns a series of labels to add to every query.
* https://cloud.google.com/bigquery/docs/adding-labels#job-label
*
* A label that has a key with an empty value is used as a tag.
* https://cloud.google.com/bigquery/docs/adding-labels#adding_a_tag
*/
public Map getLabels() {
return this.labels;
}
/**
* Return whether or not to use BigQuery's cache, as determined by the {@code cache} JDBC
* parameter. Note that use of the cache is enabled by default.
*/
public boolean getUseQueryCache() {
return this.useQueryCache;
}
/**
*
*
* Implementation Details:
*
*
* Sets bigquery to null and isclosed to true if the connection is not already closed else no
* operation is performed
*/
@Override
public void close() throws SQLException {
if (!this.isclosed) {
this.cancelRunningQueries();
this.bigquery = null;
this.isclosed = true;
}
}
/**
* Parses the input dataset expression and sets the values for the dataset and datasetProjectId
* instance variables.
*
* @param datasetExpr Dataset expression, generally taken from the BQJDBC connection string. Can
* be null.
* @param defaultProjectId Project id to set as the value for datasetProjectId if no project id is
* found in {@code datasetExpr}.
*/
private void configureDataSet(String datasetExpr, String defaultProjectId) {
DatasetReference datasetRef = parseDatasetRef(datasetExpr);
this.dataset = datasetRef.getDatasetId();
this.datasetProjectId =
datasetRef.getProjectId() == null ? defaultProjectId : datasetRef.getProjectId();
}
/**
* Returns the default dataset that should be configured on queries processed by this connection.
*/
public String getDataSet() {
return this.dataset;
}
/**
*
*
* Implementation Details:
*
*
* Throws Exception
*
* @throws SQLException
* There is no Commit in Google BigQuery + Connection Status
*/
@Override
public void commit() throws SQLException {
if (this.isclosed) {
throw new BQSQLException("There's no commit in Google BigQuery.\nConnection Status: Closed.");
} /*
else {
throw new BQSQLException(
"There's no commit in Google BigQuery.\nConnection Status: Open.");
}*/
}
/**
*
*
*
Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
throw new BQSQLException(
"Not implemented." + "createArrayOf(String typeName, Object[] elements)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Blob createBlob() throws SQLException {
throw new BQSQLException("Not implemented." + "createBlob()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Clob createClob() throws SQLException {
throw new BQSQLException("Not implemented." + "createClob()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public NClob createNClob() throws SQLException {
throw new BQSQLException("Not implemented." + "createNClob()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public SQLXML createSQLXML() throws SQLException {
throw new BQSQLException("Not implemented." + "createSQLXML()");
}
/**
*
*
* Implementation Details:
*
*
* Creates a new BQStatement object with the projectid in this Connection
*
* @return a new BQStatement object with the projectid in this Connection
* @throws SQLException if the Connection is closed
*/
@Override
public Statement createStatement() throws SQLException {
if (this.isclosed) {
throw new BQSQLException("Connection is closed.");
}
logger.debug(
"Creating statement with resultsettype: forward only," + " concurrency: read only");
return new BQStatement(projectId, this);
}
/** {@inheritDoc} */
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
if (this.isClosed()) {
throw new BQSQLException("The Connection is Closed");
}
logger.debug(
"Creating statement with resultsettype: "
+ resultSetType
+ " concurrency: "
+ resultSetConcurrency);
return new BQStatement(projectId, this, resultSetType, resultSetConcurrency);
}
/** {@inheritDoc} */
@Override
public Statement createStatement(
int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
throw new BQSQLException("Not implemented." + "createStaement(int,int,int)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
throw new BQSQLException("Not implemented." + "createStruct(string,object[])");
}
@Override
public void setSchema(String schema) {
configureDataSet(schema, this.projectId);
}
@Override
public String getSchema() throws SQLException {
return getDataSet();
}
public void abort(Executor executor) throws SQLException {
throw new BQSQLException("Not implemented.");
}
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
throw new BQSQLException("Not implemented.");
}
public int getNetworkTimeout() throws SQLException {
throw new BQSQLException("Not implemented.");
}
/**
*
*
* Implementation Details:
*
*
* Getter for autoCommitEnabled
*
* @return auto commit state;
*/
@Override
public boolean getAutoCommit() throws SQLException {
return this.autoCommitEnabled;
}
/** Getter method for the authorized bigquery client */
public Bigquery getBigquery() {
return this.bigquery;
}
/**
*
*
* Implementation Details:
*
*
* Return projectid
*
* @return projectid Contained in this Connection instance
*/
@Override
public String getCatalog() throws SQLException {
logger.debug("function call getCatalog returning projectId: " + projectId);
return projectId;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Properties getClientInfo() throws SQLException {
throw new BQSQLException("Not implemented." + "getClientInfo()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public String getClientInfo(String name) throws SQLException {
throw new BQSQLException("Not implemented." + "");
}
/**
*
*
* Implementation Details:
*
*
* There's no commit.
*
* @return CLOSE_CURSORS_AT_COMMIT
*/
@Override
public int getHoldability() throws SQLException {
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
/**
*
*
* Implementation Details:
*
*
* Return a new BQDatabaseMetadata object constructed from this Connection instance
*
* @return a new BQDatabaseMetadata object constructed from this Connection instance
*/
@Override
public DatabaseMetaData getMetaData() throws SQLException {
BQDatabaseMetadata metadata = new BQDatabaseMetadata(this);
return metadata;
}
/** Getter method for the projectId to use for billing. */
public String getProjectId() {
return projectId;
}
/**
* Returns the default dataset project id that should be configured on queries processed by this
* connection.
*/
public String getDataSetProjectId() {
return this.datasetProjectId;
}
/**
*
*
* Implementation Details:
*
*
* Transactions are not supported.
*
* @return TRANSACTION_NONE
*/
@Override
public int getTransactionIsolation() throws SQLException {
return java.sql.Connection.TRANSACTION_NONE;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Map> getTypeMap() throws SQLException {
throw new BQSQLException("Not implemented." + "getTypeMap()");
}
/** @return The URL which is in the JDBC drivers connection URL */
public String getURLPART() {
return this.URLPART;
}
/**
*
*
* Implementation Details:
*
*
* If SQLWarningList is empty returns null else it returns the first item Contained inside
* Subsequent warnings will be chained to this SQLWarning.
*
* @return SQLWarning (The First item Contained in SQLWarningList) + all others chained to it
*/
@Override
public SQLWarning getWarnings() throws SQLException {
if (this.isclosed) {
throw new BQSQLException("Connection is closed.");
}
if (this.SQLWarningList.isEmpty()) {
return null;
}
SQLWarning forreturn = this.SQLWarningList.get(0);
this.SQLWarningList.remove(0);
if (!this.SQLWarningList.isEmpty()) {
for (SQLWarning warning : this.SQLWarningList) {
forreturn.setNextWarning(warning);
}
}
return forreturn;
}
/**
*
*
* Implementation Details:
*
*
* returns the status of isclosed boolean
*/
@Override
public boolean isClosed() throws SQLException {
return this.isclosed;
}
/**
*
*
* Implementation Details:
*
*
* The driver is read only at this stage.
*
* @return true
*/
@Override
public boolean isReadOnly() throws SQLException {
return true;
}
/**
*
*
* Implementation Details:
*
*
* Sends a query to BigQuery to get all the datasets contained in the project accociated with this
* Connection object and checks if it's succeeded
*
* @throws SQLException
*/
@Override
public boolean isValid(int timeout) throws SQLException {
if (this.isclosed) {
return false;
}
if (timeout < 0) {
throw new BQSQLException(
"Timeout value can't be negative. ie. it must be 0 or above; timeout value is: "
+ String.valueOf(timeout));
}
try {
this.bigquery.datasets().list(projectId).execute();
} catch (IOException e) {
return false;
}
return true;
}
/**
*
*
* Implementation Details:
*
*
* Returns false to everything
*
* @return false
*/
@Override
public boolean isWrapperFor(Class> arg0) throws SQLException {
// TODO Implement
return false;
}
/**
*
*
* Implementation Details:
*
*
* We returns the original sql statement
*
* @return sql - the original statement
*/
@Override
public String nativeSQL(String sql) throws SQLException {
logger.debug("Function called nativeSQL() " + sql);
return sql;
// TODO
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
throw new BQSQLException("Not implemented." + "prepareCall(string)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
throw new BQSQLException("Not implemented." + "prepareCall(String,int,int)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public CallableStatement prepareCall(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
throw new BQSQLException("Not implemented." + "prepareCall(string,int,int,int)");
}
/**
*
*
* Implementation Details:
*
*
* Creates and returns a PreparedStatement object
*
* @throws BQSQLException
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
this.logger.debug(
"Creating Prepared Statement project id is: " + projectId + " with parameters:");
this.logger.debug(sql);
PreparedStatement stm = new BQPreparedStatement(sql, projectId, this);
return stm;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
throw new BQSQLException("Not implemented." + "prepareStatement(string,int)");
}
/** {@inheritDoc} */
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
this.logger.debug(
"Creating Prepared Statement"
+ " project id is: "
+ projectId
+ ", resultSetType (int) is: "
+ String.valueOf(resultSetType)
+ ", resultSetConcurrency (int) is: "
+ String.valueOf(resultSetConcurrency)
+ " with parameters:");
this.logger.debug(sql);
PreparedStatement stm =
new BQPreparedStatement(sql, projectId, this, resultSetType, resultSetConcurrency);
return stm;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public PreparedStatement prepareStatement(
String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
throw new BQSQLException("Not implemented." + "prepareStatement(String,int,int,int)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
throw new BQSQLException("Not implemented." + "prepareStatement(String,int[])");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
throw new BQSQLException("Not implemented." + "prepareStatement(String,String[])");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
throw new BQSQLException("Not implemented." + "releaseSavepoint(Savepoint)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void rollback() throws SQLException {
logger.debug("function call: rollback() not implemented ");
// throw new BQSQLException("Not implemented." + "rollback()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void rollback(Savepoint savepoint) throws SQLException {
throw new BQSQLException("Not implemented." + "rollback(savepoint)");
}
/**
*
*
* Implementation Details:
*
*
* Setter for autoCommitEnabled
*/
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
this.autoCommitEnabled = autoCommit;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setCatalog(String catalog) throws SQLException {
throw new BQSQLException("Not implemented." + "setCatalog(catalog)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
SQLClientInfoException e = new SQLClientInfoException();
e.setNextException(new BQSQLException("Not implemented. setClientInfo(properties)"));
throw e;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
SQLClientInfoException e = new SQLClientInfoException();
e.setNextException(new BQSQLException("Not implemented. setClientInfo(properties)"));
throw e;
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setHoldability(int holdability) throws SQLException {
if (this.isclosed) {
throw new BQSQLException("Connection is closed.");
}
throw new BQSQLException("Not implemented." + "setHoldability(int)");
}
/**
*
*
* Implementation Details:
*
*
* BigQuery is ReadOnly always so this is a noop
*
* @throws BQSQLException
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
if (this.isClosed()) {
throw new BQSQLException("This Connection is Closed");
}
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Savepoint setSavepoint() throws SQLException {
throw new BQSQLException("Not implemented." + "setSavepoint()");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public Savepoint setSavepoint(String name) throws SQLException {
throw new BQSQLException("Not implemented." + "setSavepoint(String)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setTransactionIsolation(int level) throws SQLException {
throw new BQSQLException("Not implemented." + "setTransactionIsolation(int)");
}
/**
*
*
* Implementation Details:
*
*
* Not implemented yet.
*
* @throws BQSQLException
*/
@Override
public void setTypeMap(Map> map) throws SQLException {
throw new BQSQLException("Not implemented." + "setTypeMap(Map>");
}
/**
*
*
* Implementation Details:
*
*
* Always throws SQLException
*
* @return nothing
* @throws SQLException Always
*/
@Override
public T unwrap(Class arg0) throws SQLException {
throw new BQSQLException("Not found");
}
public void addRunningStatement(BQStatementRoot stmt) {
this.runningStatements.add(stmt);
}
public void removeRunningStatement(BQStatementRoot stmt) {
this.runningStatements.remove(stmt);
}
public int getNumberRunningQueries() {
return this.runningStatements.size();
}
public int cancelRunningQueries() {
int numFailed = 0;
synchronized (this.runningStatements) {
for (BQStatementRoot stmt : this.runningStatements) {
try {
stmt.cancel();
} catch (SQLException e) {
numFailed++;
}
}
}
return numFailed;
}
public Long getMaxBillingBytes() {
return maxBillingBytes;
}
public Integer getTimeoutMs() {
return timeoutMs;
}
public JobCreationMode getJobCreationMode() {
return jobCreationMode;
}
/**
* Returns a DatasetReference extracted from the input dataset expression, which may optionally
* include a project id reference.
*
* This method parses the dataset expression into discrete components, using either a dot ('.')
* or colon (':') as the delimiter between the dataset and project identifiers. We split the
* string on the last occurrence of either delimiter, and we use the length of the split array to
* determine whether the input contains a project id reference. If no project id is found, we set
* the project id field to null in the returned DatasetReference. If the {@code datasetExpr} is
* null, we return a DatasetReference with both its project id and dataset id set to null.
*
*
We don't perform any validation on the result; while there are well-defined rules regarding
* project
* ids and dataset
* names, we must handle references to both that don't adhere to the documented requirements
* (such as having a domain name with a colon as part of the project id). Rather than deal with
* the various corner cases for each type of identifier, we defer to the BigQuery API to validate
* the default dataset configured on queries.
*
*
Visible for testing.
*
* @param datasetExpr Dataset expression, generally taken from the BQJDBC connection string. Can
* be null.
* @return DatasetReference
*/
static DatasetReference parseDatasetRef(String datasetExpr) {
if (datasetExpr == null) {
return new DatasetReference().setDatasetId(null).setProjectId(null);
}
// We split datasetExpr on the last occurrence of a project delimiter. To account for each
// delimiter appearance in the expression, we pass -1 as the limit value to disable discarding
// trailing empty strings.
String[] datasetComponents = datasetExpr.split(LAST_PROJECT_DELIMITER_REGEX, -1);
boolean isDatasetIdOnly = datasetComponents.length == 1;
String datasetId = isDatasetIdOnly ? datasetComponents[0] : datasetComponents[1];
String datasetProjectId =
isDatasetIdOnly ? null : CatalogName.toProjectId(datasetComponents[0]);
return new DatasetReference().setDatasetId(datasetId).setProjectId(datasetProjectId);
}
}