/**
* Copyright The Apache Software Foundation
*
* 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 com.alibaba.wasp.jdbc;
import com.alibaba.wasp.FConstants;
import com.alibaba.wasp.SQLErrorCode;
import com.alibaba.wasp.client.FClient;
import com.alibaba.wasp.jdbc.command.CommandInterface;
import com.alibaba.wasp.jdbc.result.JdbcDatabaseMetaData;
import com.alibaba.wasp.jdbc.value.Value;
import com.alibaba.wasp.session.ExecuteSession;
import com.alibaba.wasp.session.SessionFactory;
import com.alibaba.wasp.session.SessionInterface;
import com.alibaba.wasp.util.StringUtils;
import com.alibaba.wasp.util.Utils;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* <p>
* Represents a connection (session) to wasp cluster.
* </p>
* <p>
* Thread safety: the connection is thread-safe, because access is synchronized.
* However, for compatibility with other wasp cluster, a connection should only
* be used in one thread at any time.
* </p>
*/
public class JdbcConnection implements Connection {
private Log log = LogFactory.getLog(JdbcConnection.class);
private static boolean keepOpenStackTrace = false;
private final String url;
private final String user;
private int holdability = 1;
private SessionInterface session;
private final FClient fClient;
private final Configuration conf;
private Statement executingStatement;
private final CloseWatcher watcher;
private int queryTimeoutCache = -1;
private Properties properties;
private boolean autoCommit;
/**
* Constructor.
*
* @param url
*
* @param info
*
* @throws java.sql.SQLException
*/
public JdbcConnection(String url, Properties info) throws SQLException {
this(new ConnectionInfo(url, info), Utils
.convertPropertiesToConfiguration(info));
this.properties = info;
}
/**
* Wasp's JDBC connection.
*
* @param ci
* @throws java.sql.SQLException
*/
private JdbcConnection(ConnectionInfo ci, Configuration conf)
throws SQLException {
try {
// this will return an embedded or server connection
session = SessionFactory.createConnectionSession(ci);
this.user = ci.getUserName();
log.info("Connection conn = DriverManager.getConnection("
+ StringUtils.quoteJavaString(ci.getOriginalURL()) + ", "
+ StringUtils.quoteJavaString(user) + ", \"\");");
this.url = ci.getURL();
closeOld();
watcher = CloseWatcher.register(this, session, keepOpenStackTrace);
this.conf = conf;
this.fClient = new FClient(this.conf);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
public JdbcConnection(JdbcConnection conn) throws SQLException {
this(conn.getURL(), conn.getClientInfo());
}
private void closeOld() {
while (true) {
CloseWatcher w = CloseWatcher.pollUnclosed();
if (w == null) {
break;
}
try {
w.getCloseable().close();
} catch (Exception e) {
log.error("closing session", e);
}
}
}
/**
* Creates a new statement.
*
* @return the new statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public Statement createStatement() throws SQLException {
try {
checkClosed();
return new JdbcStatement(this, ResultSet.TYPE_FORWARD_ONLY,
FConstants.DEFAULT_RESULT_SET_CONCURRENCY, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Creates a statement with the specified result set type and concurrency.
*
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @return the statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type or concurrency
* are not supported
*/
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
try {
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkClosed();
return new JdbcStatement(this, resultSetType, resultSetConcurrency, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Creates a statement with the specified result set type, concurrency, and
* holdability.
*
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability
* the holdability (ResultSet.HOLD* / CLOSE*)
* @return the statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type, concurrency,
* or holdability are not supported
*/
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
try {
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkHoldability(resultSetHoldability);
checkClosed();
return new JdbcStatement(this, resultSetType, resultSetConcurrency, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Creates a new prepared statement.
*
* @param sql
* the SQL statement
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
try {
checkClosed();
return new JdbcPreparedStatement(this, sql, ResultSet.TYPE_FORWARD_ONLY,
FConstants.DEFAULT_RESULT_SET_CONCURRENCY, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Prepare a statement that will automatically close when the result set is
* closed. This method is used to retrieve database meta data.
*
* @param sql
* the SQL statement
* @return the prepared statement
*/
PreparedStatement prepareAutoCloseStatement(String sql) throws SQLException {
try {
checkClosed();
return new JdbcPreparedStatement(this, sql, ResultSet.TYPE_FORWARD_ONLY,
FConstants.DEFAULT_RESULT_SET_CONCURRENCY, true);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Gets the database meta data for this database.
*
* @return the database meta data
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public DatabaseMetaData getMetaData() throws SQLException {
try {
checkClosed();
return new JdbcDatabaseMetaData(this);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* INTERNAL
*/
public SessionInterface getSession() {
return session;
}
/**
* Closes this connection. All open statements, prepared statements and result
* sets that where created by this connection become invalid after calling
* this method. If there is an uncommitted transaction, it will be rolled
* back.
*/
@Override
public synchronized void close() throws SQLException {
try {
if (session == null) {
return;
}
CloseWatcher.unregister(watcher);
session.close();
if (executingStatement != null) {
try {
executingStatement.close();
} catch (NullPointerException e) {
// ignore
}
}
synchronized (session) {
try {
if (!session.isClosed()) {
session.close();
}
} finally {
session = null;
}
}
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
this.autoCommit = autoCommit;
}
/**
* Gets the current setting for auto commit.
*
* @return true for on, false for off
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public synchronized boolean getAutoCommit() throws SQLException {
return this.autoCommit;
}
/**
*
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public synchronized void commit() throws SQLException {
autoCommit = true;
}
/**
* unsupported rollback
*
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public synchronized void rollback() throws SQLException {
throw JdbcException.getUnsupportedException("rollback unsupported");
}
/**
* Returns true if this connection has been closed.
*
* @return true if close was called
*/
@Override
public boolean isClosed() throws SQLException {
return session == null || session.isClosed();
}
/**
* Translates a SQL statement into the database grammar.
*
* @param sql
* the SQL statement with or without JDBC escape sequences
* @return the translated statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public String nativeSQL(String sql) throws SQLException {
try {
checkClosed();
return sql;
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* According to the JDBC specs, this setting is only a hint to the database to
* enable optimizations - it does not cause writes to be prohibited.
*
* @param readOnly
* ignored
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
checkClosed();
}
/**
* Returns true if the database is read-only.
*
* @return if the database is read-only
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public boolean isReadOnly() throws SQLException {
checkClosed();
return false;
}
/**
* Set the default catalog name. This call is ignored.
*
* @param catalog
* ignored
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public void setCatalog(String catalog) throws SQLException {
throw JdbcException.getUnsupportedException("setCatalog unsupported");
}
/**
* Gets the current catalog name.
*
* @return the catalog name
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public String getCatalog() throws SQLException {
throw JdbcException.getUnsupportedException("getCatalog unsupported");
}
/**
* Gets the first warning reported by calls on this object.
*
* @return null
*/
@Override
public SQLWarning getWarnings() throws SQLException {
throw JdbcException.getUnsupportedException("getWarnings unsupported");
}
/**
* Clears all warnings.
*/
@Override
public void clearWarnings() throws SQLException {
throw JdbcException.getUnsupportedException("clearWarnings unsupported");
}
/**
* Creates a prepared statement with the specified result set type and
* concurrency.
*
* @param sql
* the SQL statement
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type or concurrency
* are not supported
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
try {
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkClosed();
return new JdbcPreparedStatement(this, sql, resultSetType,
resultSetConcurrency, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
// default wasp level
}
public void setQueryTimeout(int seconds) throws SQLException {
queryTimeoutCache = seconds;
}
public int getQueryTimeout() throws SQLException {
return queryTimeoutCache;
}
/**
* Returns the current transaction isolation level.
*
* @return the isolation level.
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public int getTransactionIsolation() throws SQLException {
// default wasp level. the return value no needed.
return 0;
}
/**
* Changes the current result set holdability.
*
* @param holdability
* ResultSet.HOLD_CURSORS_OVER_COMMIT or
* ResultSet.CLOSE_CURSORS_AT_COMMIT;
* @throws java.sql.SQLException
* if the connection is closed or the holdability is not supported
*/
@Override
public void setHoldability(int holdability) throws SQLException {
try {
checkClosed();
checkHoldability(holdability);
this.holdability = holdability;
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Returns the current result set holdability.
*
* @return the holdability
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public int getHoldability() throws SQLException {
try {
checkClosed();
return holdability;
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Gets the type map.
*
* @return null
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
try {
checkClosed();
return null;
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* [Partially supported] Sets the type map. This is only supported if the map
* is empty or null.
*/
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
try {
checkMap(map);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* unsupported
*
* @param sql
* the SQL statement
* @return the callable statement
* @throws java.sql.SQLException
* if the connection is closed or the statement is not valid
*/
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
throw JdbcException.getUnsupportedException("prepareCall unsupported");
}
/**
* unsupported
*
* @param sql
* the SQL statement
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @return the callable statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type or concurrency
* are not supported
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
throw JdbcException.getUnsupportedException("prepareCall unsupported");
}
/**
* unsupported
*
* @param sql
* the SQL statement
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability
* the holdability (ResultSet.HOLD* / CLOSE*)
* @return the callable statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type, concurrency,
* or holdability are not supported
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability) throws SQLException {
throw JdbcException.getUnsupportedException("prepareCall unsupported");
}
/**
* Creates a new unnamed savepoint.
*
* @return the new savepoint
*/
@Override
public Savepoint setSavepoint() throws SQLException {
throw JdbcException.getUnsupportedException("setSavePoint unsupported");
}
/**
* Creates a new named savepoint.
*
* @param name
* the savepoint name
* @return the new savepoint
*/
@Override
public Savepoint setSavepoint(String name) throws SQLException {
throw JdbcException.getUnsupportedException("setSavePoint unsupported");
}
/**
* Rolls back to a savepoint.
*
* @param savepoint
* the savepoint
*/
@Override
public void rollback(Savepoint savepoint) throws SQLException {
throw JdbcException.getUnsupportedException("rollback unsupported");
}
/**
* Releases a savepoint.
*
* @param savepoint
* the savepoint to release
*/
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
throw JdbcException.getUnsupportedException("releaseSavePoint unsupported");
}
/**
* Creates a prepared statement with the specified result set type,
* concurrency, and holdability.
*
* @param sql
* the SQL statement
* @param resultSetType
* the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency
* the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability
* the holdability (ResultSet.HOLD* / CLOSE*)
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed or the result set type, concurrency,
* or holdability are not supported
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability) throws SQLException {
try {
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkHoldability(resultSetHoldability);
checkClosed();
return new JdbcPreparedStatement(this, sql, resultSetType,
resultSetConcurrency, false);
} catch (Exception e) {
throw Logger.logAndConvert(log, e);
}
}
/**
* Creates a new prepared statement. This method just calls
* prepareStatement(String sql) internally. The method getGeneratedKeys only
* supports one column.
*
* @param sql
* the SQL statement
* @param autoGeneratedKeys
* ignored
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
return prepareStatement(sql);
}
/**
* Creates a new prepared statement. This method just calls
* prepareStatement(String sql) internally. The method getGeneratedKeys only
* supports one column.
*
* @param sql
* the SQL statement
* @param columnIndexes
* ignored
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
return prepareStatement(sql);
}
/**
* Creates a new prepared statement. This method just calls
* prepareStatement(String sql) internally. The method getGeneratedKeys only
* supports one column.
*
* @param sql
* the SQL statement
* @param columnNames
* ignored
* @return the prepared statement
* @throws java.sql.SQLException
* if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
return prepareStatement(sql);
}
@Override
public Clob createClob() throws SQLException {
throw JdbcException.getUnsupportedException("createClob unsupported");
}
@Override
public Blob createBlob() throws SQLException {
throw JdbcException.getUnsupportedException("createBlob unsupported");
}
@Override
public NClob createNClob() throws SQLException {
throw JdbcException.getUnsupportedException("createNClob unsupported");
}
private void checkTypeConcurrency(int resultSetType, int resultSetConcurrency) {
switch (resultSetType) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
case ResultSet.TYPE_SCROLL_SENSITIVE:
break;
default:
throw JdbcException.getInvalidValueException("resultSetType",
resultSetType);
}
switch (resultSetConcurrency) {
case ResultSet.CONCUR_READ_ONLY:
case ResultSet.CONCUR_UPDATABLE:
break;
default:
throw JdbcException.getInvalidValueException("resultSetConcurrency",
resultSetConcurrency);
}
}
private void checkHoldability(int resultSetHoldability) {
if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT
&& resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
throw JdbcException.getInvalidValueException("resultSetHoldability",
resultSetHoldability);
}
}
/**
* Check if this connection is closed. The next operation is a read request.
*
* @throws JdbcException
* if the connection or session is closed
*/
public void checkClosed() {
checkClosed(false);
}
/**
* Check if this connection is closed.
*
* @param write
* if the next operation is possibly writing
* @throws JdbcException
* if the connection or session is closed
*/
protected void checkClosed(boolean write) {
if (session == null) {
throw JdbcException.get(SQLErrorCode.OBJECT_CLOSED);
}
if (session.isClosed()) {
throw JdbcException.get(SQLErrorCode.DATABASE_CALLED_AT_SHUTDOWN);
}
}
public String getURL() {
checkClosed();
return url;
}
public String getUser() {
checkClosed();
return user;
}
/**
*
*/
public void setExecutingStatement(Statement stat) {
executingStatement = stat;
}
/**
* [Not supported] Create a new empty SQLXML object.
*/
@Override
public SQLXML createSQLXML() throws SQLException {
throw JdbcException.getUnsupportedException("createSQLXML unsupported");
}
/**
* [Not supported] Create a new empty Array object.
*/
@Override
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
throw JdbcException.getUnsupportedException("createArrayOf unsupported");
}
/**
* [Not supported] Create a new empty Struct object.
*/
@Override
public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
throw JdbcException.getUnsupportedException("createStruct unsupported");
}
/**
* Returns true if this connection is still valid.
*
* @param timeout
* the number of seconds to wait for the database to respond
* (ignored)
* @return true if the connection is valid.
*/
@Override
public synchronized boolean isValid(int timeout) {
try {
if (session == null || session.isClosed()) {
return false;
}
// force a network round trip (if networked)
getTransactionIsolation();
return true;
} catch (Exception e) {
// this method doesn't throw an exception, but it logs it
Logger.logAndConvert(log, e);
return false;
}
}
/**
* [Not supported] Set a client property.
*/
@Override
public void setClientInfo(String name, String value)
throws SQLClientInfoException {
this.properties.setProperty(name, value);
// throw new SQLClientInfoException();
}
/**
* [Not supported] Set the client properties.
*/
@Override
public void setClientInfo(Properties properties)
throws SQLClientInfoException {
this.properties.putAll(properties);
// throw new SQLClientInfoException();
}
/**
* [Not supported] Get the client properties.
*/
@Override
public Properties getClientInfo() throws SQLClientInfoException {
return this.properties;
}
/**
* [Not supported] Set a client property.
*/
@Override
public String getClientInfo(String name) throws SQLException {
return this.properties.getProperty(name);
}
/**
* [Not supported] Return an object of this class if possible.
*
* @param iface
* the class
*/
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw JdbcException.getUnsupportedException("unwrap unsupported");
}
/**
* [Not supported] Checks if unwrap can return an object of this class.
*
* @param iface
* the class
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
throw JdbcException.getUnsupportedException("isWrapperFor unsupported");
}
private static void checkMap(Map<String, Class<?>> map) {
if (map != null && map.size() > 0) {
throw JdbcException.getUnsupportedException("map.size > 0");
}
}
public String toString() {
return "Conn : url=" + url + " user=" + user;
}
public CommandInterface prepareCommand(String sql, int fetchSize, ExecuteSession statementSession) {
String readModel = properties.getProperty(FConstants.READ_MODEL);
return session.prepareCommand(fClient, sql, fetchSize,
Utils.getReadModel(readModel), autoCommit, statementSession);
}
public CommandInterface prepareCommand(List<String> sqls, ExecuteSession statementSession) {
String readModel = properties.getProperty(FConstants.READ_MODEL);
return session.prepareCommand(fClient, sqls, autoCommit, statementSession);
}
public Object convertToDefaultObject(Value v) {
return v.getObject();
}
public Configuration getConf() {
return this.conf;
}
/**
* INTERNAL. Called after executing a command that could have written
* something.
*/
protected void afterWriting() {
if (session != null) {
session.afterWriting();
}
}
public void setSchema(String schema) throws SQLException {
throw new NotImplementedException();
}
public String getSchema() throws SQLException {
throw new NotImplementedException();
}
public void abort(Executor executor) throws SQLException {
throw new NotImplementedException();
}
public void setNetworkTimeout(Executor executor, int milliseconds)
throws SQLException {
throw new NotImplementedException();
}
public int getNetworkTimeout() throws SQLException {
throw new NotImplementedException();
}
}