Mercurial > hg > monetdb-java
view src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @ 102:08bc9009d190 embedded
Merged with default
author | Pedro Ferreira <pedro.ferreira@monetdbsolutions.com> |
---|---|
date | Fri, 13 Jan 2017 18:16:30 +0100 (2017-01-13) |
parents | 551093abca52 b9b35ca2eec2 |
children | a00241382675 |
line wrap: on
line source
package nl.cwi.monetdb.jdbc; import nl.cwi.monetdb.mcl.connection.*; import nl.cwi.monetdb.mcl.connection.SenderThread; import nl.cwi.monetdb.mcl.connection.mapi.MapiLanguage; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import nl.cwi.monetdb.mcl.protocol.AbstractProtocol; import nl.cwi.monetdb.mcl.protocol.ServerResponses; import nl.cwi.monetdb.mcl.protocol.StarterHeaders; import nl.cwi.monetdb.mcl.responses.*; import nl.cwi.monetdb.mcl.responses.DataBlockResponse; import nl.cwi.monetdb.mcl.responses.ResultSetResponse; import java.io.*; import java.net.SocketTimeoutException; import java.sql.*; import java.util.*; import java.util.concurrent.Executor; /** * A {@link Connection} suitable for the MonetDB database. * * This connection represents a connection (session) to a MonetDB * database. SQL statements are executed and results are returned within * the context of a connection. This Connection object holds a physical * connection to the MonetDB database. * * A Connection object's database should able to provide information * describing its tables, its supported SQL grammar, its stored * procedures, the capabilities of this connection, and so on. This * information is obtained with the getMetaData method. * * Note: By default a Connection object is in auto-commit mode, which * means that it automatically commits changes after executing each * statement. If auto-commit mode has been disabled, the method commit * must be called explicitly in order to commit changes; otherwise, * database changes will not be saved. * * The current state of this connection is that it nearly implements the * whole Connection interface. * * @author Fabian Groffen, Martin van Dinther, Pedro Ferreira * @version 1.3 */ public abstract class MonetConnection extends MonetWrapper implements Connection { /** The sequence counter */ private static int SeqCounter = 0; public static int GetSeqCounter() { return SeqCounter; } /** The successful processed input properties */ protected final Properties conn_props; /** The language to connect with */ protected IMonetDBLanguage language; /** Authentication hash method */ protected final String hash; /** An optional thread that is used for sending large queries */ private SenderThread senderThread; /** Whether this Connection is closed (and cannot be used anymore) */ private boolean closed; /** Whether this Connection is in autocommit mode */ private boolean autoCommit = true; /** The stack of warnings for this Connection object */ private SQLWarning warnings; /** The Connection specific mapping of user defined types to Java types */ private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() { private static final long serialVersionUID = 1L; { put("inet", MonetINET.class); } }; // See javadoc for documentation about WeakHashMap if you don't know what it does !!!NOW!!! // (only when you deal with it of course) /** A Map containing all (active) Statements created from this Connection */ private Map<Statement,?> statements = new WeakHashMap<Statement, Object>(); /** The number of results we receive from the server at once */ private int curReplySize = -1; // the server by default uses -1 (all) /** Whether or not BLOB is mapped to LONGVARBINARY within the driver */ private final boolean blobIsBinary; /** Whether or not CLOB is mapped to LONGVARCHAR within the driver */ private final boolean clobIsLongChar; /** The underlying proticol provided by the connection (MAPI or embedded) */ protected AbstractProtocol protocol; /** * Constructor of a Connection for MonetDB. At this moment the current implementation limits itself to storing the * given host, database, username and password for later use by the createStatement() call. This constructor is * only accessible to classes from the jdbc package. * * @throws IOException if an error occurs */ public MonetConnection(Properties props, String hash, IMonetDBLanguage language, boolean blobIsBinary, boolean clobIsLongChar) throws IOException { this.conn_props = props; this.hash = hash; this.language = language; this.blobIsBinary = blobIsBinary; this.clobIsLongChar = clobIsLongChar; } /** * Gets the connection's language data. * * @return The connection's language data */ public IMonetDBLanguage getLanguage() { return language; } /** * Gets the connection's protocol. * * @return The connection's protocol */ public AbstractProtocol getProtocol() { return this.protocol; } /** * Connects to the server, authenticating the user. * * @param user The user name to authenticate * @param pass The user's password * @return A List with informational (warning) messages. If this list is empty; then there are no warnings. * @throws IOException if an I/O error occurs when creating the socket * @throws ProtocolException if bogus data is received * @throws MCLException if an MCL related error occurs */ public abstract List<String> connect(String user, String pass) throws IOException, ProtocolException, MCLException; /** * Gets the underlying connection block size length. * * @return The block size length */ public abstract int getBlockSize(); /** * Gets the underlying connection default fetch size for DataBlock responses. * * @return The default fetch size */ public abstract int getDefFetchsize(); /** * Gets the underlying connection socket timeout. * * @return The underlying connection socket timeout */ public abstract int getSoTimeout(); /** * Sets the underlying connection socket timeout. * * @param timeout The specified timeout, in milliseconds. A timeout of zero is interpreted as an infinite timeout */ public abstract void setSoTimeout(int timeout); /** * Closes the underlying connection implementation. * * @throws IOException if an I/O error occurs while closing the connection */ public abstract void closeUnderlyingConnection() throws IOException; /** * Gets the underlying connection JDBC String URL. * * @return The underlying connection JDBC String URL */ public abstract String getJDBCURL(); /** * Sends a control command to the server. * * @param commandID the command identifier according to {@link ControlCommands} listing * @param data The integer to send according to the control command * @throws SQLException if an IO exception or a database error occurs */ public abstract void sendControlCommand(int commandID, int data) throws SQLException; /** * Creates a ResponseList. * * @param fetchSize the nubmer of rows per block in the response list * @param maxRows maximum number of rows to allow in the set * @param resultSetType the type of result sets to produce * @param resultSetConcurrency the concurrency of result sets to produce * @return A ResponseList instance * @throws SQLException if an IO exception or a database error occurs */ public abstract ResponseList createResponseList(int fetchSize, int maxRows, int resultSetType, int resultSetConcurrency) throws SQLException; /** * Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be * automatically released. All Statements created from this Connection will be closed when this method is called. * * Calling the method close on a Connection object that is already closed is a no-op. */ @Override public void close() { for (Statement st : statements.keySet()) { try { st.close(); } catch (SQLException e) { // better luck next time! } } // close the socket or the embedded server try { this.closeUnderlyingConnection(); } catch (IOException e) { // ignore it } // close active SendThread if any if (senderThread != null) { senderThread.shutdown(); senderThread = null; } // report ourselves as closed closed = true; } /** * Destructor called by garbage collector before destroying this object tries to disconnect the MonetDB connection * if it has not been disconnected already. */ @Override protected void finalize() throws Throwable { this.close(); super.finalize(); } //== methods of interface Connection /** * Clears all warnings reported for this Connection object. After a call to this method, the method getWarnings * returns null until a new warning is reported for this Connection object. */ @Override public void clearWarnings() { warnings = null; } private void createResponseList(String query) throws SQLException { // create a container for the result ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY); // send commit to the server try { l.processQuery(query); } finally { l.close(); } } /** * Makes all changes made since the previous commit/rollback permanent and releases any database locks currently * held by this Connection object. This method should be used only when auto-commit mode has been disabled. * * @throws SQLException if a database access error occurs or this Connection object is in auto-commit mode * @see #setAutoCommit(boolean) */ @Override public void commit() throws SQLException { // note: can't use sendIndependentCommand here because we need to process the auto_commit state the server gives this.createResponseList("COMMIT"); } /** * Creates a Statement object for sending SQL statements to the database. SQL statements without parameters are * normally executed using Statement objects. If the same SQL statement is executed many times, it may be more * efficient to use a PreparedStatement object. * * Result sets created using the returned Statement object will by default be type TYPE_FORWARD_ONLY and have a * concurrency level of CONCUR_READ_ONLY. * * @return a new default Statement object * @throws SQLException if a database access error occurs */ @Override public Statement createStatement() throws SQLException { return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); } /** * Creates a Statement object that will generate ResultSet objects with the given type and concurrency. This method * is the same as the createStatement method above, but it allows the default result set type and concurrency to be * overridden. * * @param resultSetType a result set type; one of ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, * or ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency a concurrency type; one of ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE * @return a new Statement object that will generate ResultSet objects with the given type and concurrency * @throws SQLException if a database access error occurs */ @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return createStatement(resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); } /** * Creates a Statement object that will generate ResultSet objects * with the given type, concurrency, and holdability. This method * is the same as the createStatement method above, but it allows * the default result set type, concurrency, and holdability to be * overridden. * * @param resultSetType one of the following ResultSet constants: * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, * or ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency one of the following ResultSet * constants: ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability one of the following ResultSet * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * * @return a new Statement object that will generate ResultSet * objects with the given type, concurrency, and holdability * @throws SQLException if a database access error occurs or the * given parameters are not ResultSet constants indicating type, * concurrency, and holdability */ @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { try { Statement ret = new MonetStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability); // store it in the map for when we close... statements.put(ret, null); return ret; } catch (IllegalArgumentException e) { throw new SQLException(e.toString(), "M0M03"); } // we don't have to catch SQLException because that is declared to // be thrown } /** * Retrieves the current auto-commit mode for this Connection object. * * @return the current state of this Connection object's auto-commit mode * @see #setAutoCommit(boolean) */ @Override public boolean getAutoCommit() throws SQLException { return autoCommit; } /** * Retrieves this Connection object's current catalog name. * * @return the current catalog name or null if there is none * @throws SQLException if a database access error occurs or the current language is not SQL */ @Override public String getCatalog() throws SQLException { // MonetDB does NOT support catalogs return null; } /** * Retrieves the current holdability of ResultSet objects created using this Connection object. * * @return the holdability, one of ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT */ @Override public int getHoldability() { // TODO: perhaps it is better to have the server implement // CLOSE_CURSORS_AT_COMMIT return ResultSet.HOLD_CURSORS_OVER_COMMIT; } /** * Retrieves a DatabaseMetaData object that contains metadata about * the database to which this Connection object represents a * connection. The metadata includes information about the * database's tables, its supported SQL grammar, its stored * procedures, the capabilities of this connection, and so on. * * @throws SQLException if the current language is not SQL * @return a DatabaseMetaData object for this Connection object */ @Override public DatabaseMetaData getMetaData() throws SQLException { if (this.language != MapiLanguage.LANG_SQL) { throw new SQLException("This method is only supported in SQL mode", "M0M04"); } return new MonetDatabaseMetaData(this); } /** * Retrieves this Connection object's current transaction isolation level. * * @return the current transaction isolation level, which will be Connection.TRANSACTION_SERIALIZABLE */ @Override public int getTransactionIsolation() { return TRANSACTION_SERIALIZABLE; } /** * Retrieves the Map object associated with this Connection object. Unless the application has added an entry, * the type map returned will be empty. * * @return the java.util.Map object associated with this Connection object */ @Override public Map<String,Class<?>> getTypeMap() { return typeMap; } /** * Retrieves the first warning reported by calls on this Connection * object. If there is more than one warning, subsequent warnings * will be chained to the first one and can be retrieved by calling * the method SQLWarning.getNextWarning on the warning that was * retrieved previously. * * This method may not be called on a closed connection; doing so will cause an SQLException to be thrown. * * Note: Subsequent warnings will be chained to this SQLWarning. * * @return the first SQLWarning object or null if there are none * @throws SQLException if a database access error occurs or this method is called on a closed connection */ @Override public SQLWarning getWarnings() throws SQLException { if (closed) { throw new SQLException("Cannot call on closed Connection", "M1M20"); } // if there are no warnings, this will be null, which fits with the specification. return warnings; } /** * Retrieves whether this Connection object has been closed. A * connection is closed if the method close has been called on it or * if certain fatal errors have occurred. This method is guaranteed * to return true only when it is called after the method * Connection.close has been called. * * This method generally cannot be called to determine whether a * connection to a database is valid or invalid. A typical client * can determine that a connection is invalid by catching any * exceptions that might be thrown when an operation is attempted. * * @return true if this Connection object is closed; false if it is still open */ @Override public boolean isClosed() { return closed; } /** * Retrieves whether this Connection object is in read-only mode. * MonetDB currently doesn't support updateable result sets, but * updates are possible. Hence the Connection object is never in * read-only mode. * * @return true if this Connection object is read-only; false otherwise */ @Override public boolean isReadOnly() { return false; } @Override public String nativeSQL(String sql) {return sql;} @Override public CallableStatement prepareCall(String sql) {return null;} @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) {return null;} @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) {return null;} /** * Creates a PreparedStatement object for sending parameterized SQL * statements to the database. * * A SQL statement with or without IN parameters can be pre-compiled * and stored in a PreparedStatement object. This object can then be * used to efficiently execute this statement multiple times. * * Note: This method is optimized for handling parametric SQL * statements that benefit from precompilation. If the driver * supports precompilation, the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement object is * executed. This has no direct effect on users; however, it does * affect which methods throw certain SQLException objects. * * Result sets created using the returned PreparedStatement object * will by default be type TYPE_FORWARD_ONLY and have a concurrency * level of CONCUR_READ_ONLY. * * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @return a new default PreparedStatement object containing the * pre-compiled SQL statement * @throws SQLException if a database access error occurs */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); } /** * Creates a PreparedStatement object that will generate ResultSet * objects with the given type and concurrency. This method is the * same as the prepareStatement method above, but it allows the * default result set type and concurrency to be overridden. * * @param sql a String object that is the SQL statement to be sent to the * database; may contain one or more ? IN parameters * @param resultSetType a result set type; one of * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, * or ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency a concurrency type; one of * ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE * @return a new PreparedStatement object containing the pre-compiled SQL * statement that will produce ResultSet objects with the given * type and concurrency * @throws SQLException if a database access error occurs or the given * parameters are not ResultSet constants indicating * type and concurrency */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, ResultSet.HOLD_CURSORS_OVER_COMMIT); } /** * Creates a PreparedStatement object that will generate ResultSet * objects with the given type, concurrency, and holdability. * * This method is the same as the prepareStatement method above, but * it allows the default result set type, concurrency, and * holdability to be overridden. * * @param sql a String object that is the SQL statement to be sent * to the database; may contain one or more ? IN parameters * @param resultSetType one of the following ResultSet constants: * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, * or ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency one of the following ResultSet * constants: ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability one of the following ResultSet * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @return a new PreparedStatement object, containing the * pre-compiled SQL statement, that will generate ResultSet objects * with the given type, concurrency, and holdability * @throws SQLException if a database access error occurs or the * given parameters are not ResultSet constants indicating type, * concurrency, and holdability */ @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { try { PreparedStatement ret = new MonetPreparedStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability, sql); // store it in the map for when we close... statements.put(ret, null); return ret; } catch (IllegalArgumentException e) { throw new SQLException(e.toString(), "M0M03"); } // we don't have to catch SQLException because that is declared to // be thrown } /** * Creates a default PreparedStatement object that has the * capability to retrieve auto-generated keys. The given constant * tells the driver whether it should make auto-generated keys * available for retrieval. This parameter is ignored if the SQL * statement is not an INSERT statement. * * Note: This method is optimized for handling parametric SQL * statements that benefit from precompilation. If the driver * supports precompilation, the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement object is * executed. This has no direct effect on users; however, it does * affect which methods throw certain SQLExceptions. * * Result sets created using the returned PreparedStatement object * will by default be type TYPE_FORWARD_ONLY and have a concurrency * level of CONCUR_READ_ONLY. * * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @param autoGeneratedKeys a flag indicating whether auto-generated * keys should be returned; one of * Statement.RETURN_GENERATED_KEYS or * Statement.NO_GENERATED_KEYS * @return a new PreparedStatement object, containing the * pre-compiled SQL statement, that will have the capability * of returning auto-generated keys * @throws SQLException - if a database access error occurs or the * given parameter is not a Statement constant indicating * whether auto-generated keys should be returned */ @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS && autoGeneratedKeys != Statement.NO_GENERATED_KEYS) { throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05"); } /* MonetDB has no way to disable this, so just do the normal thing ;) */ return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) {return null;} @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) {return null;} /** * Removes the given Savepoint object from the current transaction. * Any reference to the savepoint after it have been removed will * cause an SQLException to be thrown. * * @param savepoint the Savepoint object to be removed * @throws SQLException if a database access error occurs or the given * Savepoint object is not a valid savepoint in the current * transaction */ @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { if (!(savepoint instanceof MonetSavepoint)) { throw new SQLException("This driver can only handle savepoints it created itself", "M0M06"); } MonetSavepoint sp = (MonetSavepoint) savepoint; // note: can't use sendIndependentCommand here because we need // to process the auto_commit state the server gives // create a container for the result this.createResponseList("RELEASE SAVEPOINT " + sp.getName()); } /** * Undoes all changes made in the current transaction and releases * any database locks currently held by this Connection object. This * method should be used only when auto-commit mode has been * disabled. * * @throws SQLException if a database access error occurs or this * Connection object is in auto-commit mode * @see #setAutoCommit(boolean) */ @Override public void rollback() throws SQLException { // note: can't use sendIndependentCommand here because we need // to process the auto_commit state the server gives // create a container for the result this.createResponseList("ROLLBACK"); } /** * Undoes all changes made after the given Savepoint object was set. * * This method should be used only when auto-commit has been * disabled. * * @param savepoint the Savepoint object to roll back to * @throws SQLException if a database access error occurs, the * Savepoint object is no longer valid, or this Connection * object is currently in auto-commit mode */ @Override public void rollback(Savepoint savepoint) throws SQLException { if (!(savepoint instanceof MonetSavepoint)) { throw new SQLException("This driver can only handle savepoints it created itself", "M0M06"); } MonetSavepoint sp = (MonetSavepoint)savepoint; // note: can't use sendIndependentCommand here because we need // to process the auto_commit state the server gives // create a container for the result this.createResponseList("ROLLBACK TO SAVEPOINT " + sp.getName()); } /** * Sets this connection's auto-commit mode to the given state. If a * connection is in auto-commit mode, then all its SQL statements * will be executed and committed as individual transactions. * Otherwise, its SQL statements are grouped into transactions that * are terminated by a call to either the method commit or the * method rollback. By default, new connections are in auto-commit * mode. * * The commit occurs when the statement completes or the next * execute occurs, whichever comes first. In the case of statements * returning a ResultSet object, the statement completes when the * last row of the ResultSet object has been retrieved or the * ResultSet object has been closed. In advanced cases, a single * statement may return multiple results as well as output parameter * values. In these cases, the commit occurs when all results and * output parameter values have been retrieved. * * NOTE: If this method is called during a transaction, the * transaction is committed. * * @param autoCommit true to enable auto-commit mode; false to disable it * @throws SQLException if a database access error occurs * @see #getAutoCommit() */ @Override public void setAutoCommit(boolean autoCommit) throws SQLException { if (this.autoCommit != autoCommit) { this.sendControlCommand(ControlCommands.AUTO_COMMIT, (autoCommit ? 1 : 0)); this.autoCommit = autoCommit; } } /** * Sets the given catalog name in order to select a subspace of this * Connection object's database in which to work. If the driver * does not support catalogs, it will silently ignore this request. */ @Override public void setCatalog(String catalog) throws SQLException { throw new SQLFeatureNotSupportedException("setCatalog(String catalog) not supported", "0A000"); } /** * Changes the default holdability of ResultSet objects created using this * Connection object to the given holdability. The default holdability of * ResultSet objects can be be determined by invoking DatabaseMetaData.getResultSetHoldability(). * * @param holdability - a ResultSet holdability constant; one of * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @see #getHoldability() */ @Override public void setHoldability(int holdability) throws SQLException { // we only support ResultSet.HOLD_CURSORS_OVER_COMMIT if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) throw new SQLFeatureNotSupportedException("setHoldability(CLOSE_CURSORS_AT_COMMIT) not supported", "0A000"); } /** * Puts this connection in read-only mode as a hint to the driver to * enable database optimizations. MonetDB doesn't support any mode * here, hence an SQLWarning is generated if attempted to set * to true here. * * @param readOnly true enables read-only mode; false disables it * @throws SQLException if a database access error occurs or this * method is called during a transaction. */ @Override public void setReadOnly(boolean readOnly) throws SQLException { if (readOnly) { addWarning("cannot setReadOnly(true): read-only Connection mode not supported", "01M08"); } } /** * Creates an unnamed savepoint in the current transaction and * returns the new Savepoint object that represents it. * * @return the new Savepoint object * @throws SQLException if a database access error occurs or this Connection * object is currently in auto-commit mode */ @Override public Savepoint setSavepoint() throws SQLException { // create a new Savepoint object MonetSavepoint sp = new MonetSavepoint(); // note: can't use sendIndependentCommand here because we need // to process the auto_commit state the server gives // create a container for the result this.createResponseList("SAVEPOINT " + sp.getName()); return sp; } /** * Creates a savepoint with the given name in the current * transaction and returns the new Savepoint object that represents * it. * * @param name a String containing the name of the savepoint * @return the new Savepoint object * @throws SQLException if a database access error occurs or this Connection * object is currently in auto-commit mode */ @Override public Savepoint setSavepoint(String name) throws SQLException { // create a new Savepoint object MonetSavepoint sp; try { sp = new MonetSavepoint(name); } catch (IllegalArgumentException e) { throw new SQLException(e.getMessage(), "M0M03"); } // note: can't use sendIndependentCommand here because we need // to process the auto_commit state the server gives // create a container for the result this.createResponseList("SAVEPOINT " + sp.getName()); return sp; } /** * Attempts to change the transaction isolation level for this * Connection object to the one given. The constants defined in the * interface Connection are the possible transaction isolation * levels. * * @param level one of the following Connection constants: Connection.TRANSACTION_READ_UNCOMMITTED, * Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ, or * Connection.TRANSACTION_SERIALIZABLE. */ @Override public void setTransactionIsolation(int level) { if (level != TRANSACTION_SERIALIZABLE) { addWarning("MonetDB only supports fully serializable " + "transactions, continuing with transaction level raised to TRANSACTION_SERIALIZABLE", "01M09"); } } /** * Installs the given TypeMap object as the type map for this * Connection object. The type map will be used for the custom * mapping of SQL structured types and distinct types. * * @param map the java.util.Map object to install as the replacement for * this Connection object's default type map */ @Override public void setTypeMap(Map<String, Class<?>> map) { typeMap = map; } /** * Returns a string identifying this Connection to the MonetDB server. * * @return a String representing this Object */ @Override public String toString() { return "MonetDB Connection (" + this.getJDBCURL() + ") " + (closed ? "disconnected" : "connected"); } //== Java 1.6 methods (JDBC 4.0) /** * Factory method for creating Array objects. * * Note: When createArrayOf is used to create an array object that * maps to a primitive data type, then it is implementation-defined * whether the Array object is an array of that primitive data type * or an array of Object. * * Note: The JDBC driver is responsible for mapping the elements * Object array to the default JDBC SQL type defined in * java.sql.Types for the given class of Object. The default mapping * is specified in Appendix B of the JDBC specification. If the * resulting JDBC type is not the appropriate type for the given * typeName then it is implementation defined whether an * SQLException is thrown or the driver supports the resulting conversion. * * @param typeName the SQL name of the type the elements of the * array map to. The typeName is a database-specific name * which may be the name of a built-in type, a user-defined * type or a standard SQL type supported by this database. * This is the value returned by Array.getBaseTypeName * @return an Array object whose elements map to the specified SQL type * @throws SQLException if a database error occurs, the JDBC type * is not appropriate for the typeName and the conversion is * not supported, the typeName is null or this method is * called on a closed connection * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this data type * @since 1.6 */ @Override public java.sql.Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw new SQLFeatureNotSupportedException("createArrayOf() not supported", "0A000"); } /** * Constructs an object that implements the Clob interface. The * object returned initially contains no data. The setAsciiStream, * setCharacterStream and setString methods of the Clob interface * may be used to add data to the Clob. * * @return a MonetClob instance * @throws SQLFeatureNotSupportedException the JDBC driver does * not support MonetClob objects that can be filled in * @since 1.6 */ @Override public java.sql.Clob createClob() throws SQLException { return new MonetClob(""); } /** * Constructs an object that implements the Blob interface. The * object returned initially contains no data. The setBinaryStream * and setBytes methods of the Blob interface may be used to add * data to the Blob. * * @return a MonetBlob instance * @throws SQLFeatureNotSupportedException the JDBC driver does * not support MonetBlob objects that can be filled in * @since 1.6 */ @Override public java.sql.Blob createBlob() throws SQLException { return new MonetBlob(new byte[1]); } /** * Constructs an object that implements the NClob interface. The * object returned initially contains no data. The setAsciiStream, * setCharacterStream and setString methods of the NClob interface * may be used to add data to the NClob. * * @return an NClob instance * @throws SQLFeatureNotSupportedException the JDBC driver does * not support MonetNClob objects that can be filled in * @since 1.6 */ @Override public java.sql.NClob createNClob() throws SQLException { throw new SQLFeatureNotSupportedException("createNClob() not supported", "0A000"); } /** * Factory method for creating Struct objects. * * @param typeName the SQL type name of the SQL structured type that * this Struct object maps to. The typeName is the name of a * user-defined type that has been defined for this database. * It is the value returned by Struct.getSQLTypeName. * @param attributes the attributes that populate the returned object * @return a Struct object that maps to the given SQL type and is * populated with the given attributes * @throws SQLException if a database error occurs, the typeName * is null or this method is called on a closed connection * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this data type * @since 1.6 */ @Override public java.sql.Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw new SQLFeatureNotSupportedException("createStruct() not supported", "0A000"); } /** * Constructs an object that implements the SQLXML interface. The * object returned initially contains no data. The * createXmlStreamWriter object and setString method of the SQLXML * interface may be used to add data to the SQLXML object. * * @return An object that implements the SQLXML interface * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this data type * @since 1.6 */ @Override public java.sql.SQLXML createSQLXML() throws SQLException { throw new SQLFeatureNotSupportedException("createSQLXML() not supported", "0A000"); } /** * Returns true if the connection has not been closed and is still * valid. The driver shall submit a query on the connection or use * some other mechanism that positively verifies the connection is * still valid when this method is called. * * The query submitted by the driver to validate the connection * shall be executed in the context of the current transaction. * * @param timeout The time in seconds to wait for the database * operation used to validate the connection to complete. If * the timeout period expires before the operation completes, * this method returns false. A value of 0 indicates a * timeout is not applied to the database operation. * @return true if the connection is valid, false otherwise * @throws SQLException if the value supplied for timeout is less than 0 * @since 1.6 */ @Override public boolean isValid(int timeout) throws SQLException { if (timeout < 0) throw new SQLException("timeout is less than 0", "M1M05"); if (closed) return false; // ping db using query: select 1; Statement stmt = null; ResultSet rs = null; try { stmt = createStatement(); stmt.setQueryTimeout(timeout); rs = stmt.executeQuery("SELECT 1"); rs.close(); rs = null; stmt.close(); return true; } catch (Exception e) { if (rs != null) { try { rs.close(); } catch (Exception e2) {} } if (stmt != null) { try { stmt.close(); } catch (Exception e2) {} } } return false; } /** * Returns the value of the client info property specified by name. * This method may return null if the specified client info property * has not been set and does not have a default value. * This method will also return null if the specified client info * property name is not supported by the driver. * Applications may use the DatabaseMetaData.getClientInfoProperties method * to determine the client info properties supported by the driver. * * @param name - The name of the client info property to retrieve * @return The value of the client info property specified or null * @throws SQLException - if the database server returns an error * when fetching the client info value from the database * or this method is called on a closed connection * @since 1.6 */ @Override public String getClientInfo(String name) throws SQLException { if (name == null || name.isEmpty()) return null; return conn_props.getProperty(name); } /** * Returns a list containing the name and current value of each client info * property supported by the driver. The value of a client info property may * be null if the property has not been set and does not have a default value. * * @return A Properties object that contains the name and current value * of each of the client info properties supported by the driver. * @throws SQLException - if the database server returns an error * when fetching the client info value from the database * or this method is called on a closed connection * @since 1.6 */ @Override public Properties getClientInfo() throws SQLException { // return a clone of the connection properties object return new Properties(conn_props); } /** * Sets the value of the client info property specified by name to the value specified by value. * Applications may use the DatabaseMetaData.getClientInfoProperties method to determine * the client info properties supported by the driver and the maximum length that may be specified * for each property. * * The driver stores the value specified in a suitable location in the database. For example * in a special register, session parameter, or system table column. For efficiency the driver * may defer setting the value in the database until the next time a statement is executed * or prepared. Other than storing the client information in the appropriate place in the * database, these methods shall not alter the behavior of the connection in anyway. * The values supplied to these methods are used for accounting, diagnostics and debugging purposes only. * * The driver shall generate a warning if the client info name specified is not recognized by the driver. * * If the value specified to this method is greater than the maximum length for the property * the driver may either truncate the value and generate a warning or generate a SQLClientInfoException. * If the driver generates a SQLClientInfoException, the value specified was not set on the connection. * * The following are standard client info properties. Drivers are not required to support these * properties however if the driver supports a client info property that can be described by one * of the standard properties, the standard property name should be used. * * ApplicationName - The name of the application currently utilizing the connection * ClientUser - The name of the user that the application using the connection is performing work for. * This may not be the same as the user name that was used in establishing the connection. * ClientHostname - The hostname of the computer the application using the connection is running on. * * @param name - The name of the client info property to set * @param value - The value to set the client info property to. If the * value is null, the current value of the specified property is cleared. * @throws SQLClientInfoException - if the database server returns an error * while setting the clientInfo values on the database server * or this method is called on a closed connection * @since 1.6 */ @Override public void setClientInfo(String name, String value) throws java.sql.SQLClientInfoException { if (name == null || name.isEmpty()) { addWarning("setClientInfo: missing property name", "01M07"); return; } // If the value is null, the current value of the specified property is cleared. if (value == null) { if (conn_props.containsKey(name)) conn_props.remove(name); return; } // only set value for supported property names if (name.equals("host") || name.equals("port") || name.equals("user") || name.equals("password") || name.equals("database") || name.equals("language") || name.equals("so_timeout") || name.equals("hash") || name.equals("treat_blob_as_binary") || name.equals("follow_redirects") || name.equals("treat_clob_as_longvarchar") || name.equals("embedded") || name.equals("directory")) { conn_props.setProperty(name, value); } else { addWarning("setClientInfo: " + name + "is not a recognised property", "01M07"); } } /** * Sets the value of the connection's client info properties. * The Properties object contains the names and values of the client info * properties to be set. The set of client info properties contained in the * properties list replaces the current set of client info properties on the connection. * If a property that is currently set on the connection is not present in the * properties list, that property is cleared. Specifying an empty properties list * will clear all of the properties on the connection. * See setClientInfo (String, String) for more information. * * If an error occurs in setting any of the client info properties, a * SQLClientInfoException is thrown. The SQLClientInfoException contains information * indicating which client info properties were not set. The state of the client * information is unknown because some databases do not allow multiple client info * properties to be set atomically. For those databases, one or more properties may * have been set before the error occurred. * * @param props - The list of client info properties to set * @throws SQLClientInfoException - if the database server returns an error * while setting the clientInfo values on the database server * or this method is called on a closed connection * @since 1.6 */ @Override public void setClientInfo(Properties props) throws java.sql.SQLClientInfoException { if (props != null) { for (Map.Entry<Object, Object> entry : props.entrySet()) { setClientInfo(entry.getKey().toString(), entry.getValue().toString()); } } } //== Java 1.7 methods (JDBC 4.1) /** * Sets the given schema name to access. * * @param schema the name of a schema in which to work * @throws SQLException if a database access error occurs or this method is called on a closed connection * @since 1.7 */ @Override public void setSchema(String schema) throws SQLException { if (closed) throw new SQLException("Cannot call on closed Connection", "M1M20"); if (schema == null) throw new SQLException("Missing schema name", "M1M05"); Statement st = createStatement(); try { st.execute("SET SCHEMA \"" + schema + "\""); } finally { st.close(); } } /** * Retrieves this Connection object's current schema name. * * @return the current schema name or null if there is none * @throws SQLException if a database access error occurs or this method is called on a closed connection */ @Override public String getSchema() throws SQLException { if (closed) { throw new SQLException("Cannot call on closed Connection", "M1M20"); } String cur_schema; Statement st = createStatement(); ResultSet rs = null; try { rs = st.executeQuery("SELECT CURRENT_SCHEMA"); if (!rs.next()) throw new SQLException("Row expected", "02000"); cur_schema = rs.getString(1); } finally { if (rs != null) rs.close(); st.close(); } return cur_schema; } /** * Terminates an open connection. Calling abort results in: * * The connection marked as closed * * Closes any physical connection to the database * * Releases resources used by the connection * * Insures that any thread that is currently accessing the * connection will either progress to completion or throw an * SQLException. * Calling abort marks the connection closed and releases any * resources. Calling abort on a closed connection is a no-op. * * @param executor The Executor implementation which will be used by abort * @throws SQLException if a database access error occurs or the executor is null * @throws SecurityException if a security manager exists and its checkPermission method denies calling abort */ @Override public void abort(Executor executor) throws SQLException { if (closed) return; if (executor == null) throw new SQLException("executor is null", "M1M05"); // this is really the simplest thing to do, it destroys // everything (in particular the server connection) close(); } /** * Sets the maximum period a Connection or objects created from the * Connection will wait for the database to reply to any one * request. If any request remains unanswered, the waiting method * will return with a SQLException, and the Connection or objects * created from the Connection will be marked as closed. Any * subsequent use of the objects, with the exception of the close, * isClosed or Connection.isValid methods, will result in a * SQLException. * * @param executor The Executor implementation which will be used by setNetworkTimeout * @param millis The time in milliseconds to wait for the database operation to complete * @throws SQLException if a database access error occurs, this method is called on a closed connection, the * executor is null, or the value specified for seconds is less than 0. */ @Override public void setNetworkTimeout(Executor executor, int millis) throws SQLException { if (closed) { throw new SQLException("Cannot call on closed Connection", "M1M20"); } if (executor == null) throw new SQLException("executor is null", "M1M05"); if (millis < 0) throw new SQLException("milliseconds is less than zero", "M1M05"); this.setSoTimeout(millis); } /** * Retrieves the number of milliseconds the driver will wait for a database request to complete. If the limit is * exceeded, a SQLException is thrown. * * @return the current timeout limit in milliseconds; zero means there is no limit * @throws SQLException if a database access error occurs or this method is called on a closed Connection */ @Override public int getNetworkTimeout() throws SQLException { if (closed) { throw new SQLException("Cannot call on closed Connection", "M1M20"); } return this.getSoTimeout(); } //== end methods of interface Connection /** * Returns whether the BLOB type should be mapped to BINARY type. */ public boolean getBlobAsBinary() { return blobIsBinary; } /** * Returns whether the CLOB type should be mapped to LONGVARCHAR type. */ public boolean getClobAsLongChar() { return clobIsLongChar; } /** * Sends the given string to MonetDB as regular statement, making sure there is a prompt after the command is sent. * All possible returned information is discarded. Encountered errors are reported. * * @param command the exact string to send to MonetDB * @throws SQLException if an IO exception or a database error occurs */ public void sendIndependentCommand(String command) throws SQLException { try { protocol.writeNextQuery(language.getQueryTemplateIndex(0), command, language.getQueryTemplateIndex(1)); protocol.waitUntilPrompt(); int csrh = protocol.getCurrentServerResponse(); if (csrh == ServerResponses.ERROR) { String error = protocol.getRemainingStringLine(0); throw new SQLException(error.substring(6), error.substring(0, 5)); } } catch (SocketTimeoutException e) { close(); // JDBC 4.1 semantics: abort() throw new SQLException("connection timed out", "08M33"); } catch (IOException e) { throw new SQLException(e.getMessage(), "08000"); } } /** * Adds a warning to the pile of warnings this Connection object has. If there were no warnings (or clearWarnings * was called) this warning will be the first, otherwise this warning will get appended to the current warning. * * @param reason the warning message */ public void addWarning(String reason, String sqlstate) { if (warnings == null) { warnings = new SQLWarning(reason, sqlstate); } else { warnings.setNextWarning(new SQLWarning(reason, sqlstate)); } } /** * A list of Response objects. Responses are added to this list. Methods of this class are not synchronized. This is * left as responsibility to the caller to prevent concurrent access. */ public class ResponseList { /** The cache size (number of rows in a DataBlockResponse object) */ private final int cachesize; /** The maximum number of results for this query */ private final int maxrows; /** The ResultSet type to produce */ private final int rstype; /** The ResultSet concurrency to produce */ private final int rsconcur; /** The sequence number of this ResponseList */ private final int seqnr; /** A list of the Responses associated with the query, in the right order */ private final List<IResponse> responses = new ArrayList<>(); /** A map of ResultSetResponses, used for additional DataBlockResponse mapping */ private Map<Integer, ResultSetResponse> rsresponses; /** The current header returned by getNextResponse() */ private int curResponse = -1; /** * Main constructor. The query argument can either be a String or List. An SQLException is thrown if another * object instance is supplied. * * @param cachesize overall cachesize to use * @param maxrows maximum number of rows to allow in the set * @param rstype the type of result sets to produce * @param rsconcur the concurrency of result sets to produce */ public ResponseList(int cachesize, int maxrows, int rstype, int rsconcur) throws SQLException { this.cachesize = cachesize; this.maxrows = maxrows; this.rstype = rstype; this.rsconcur = rsconcur; this.seqnr = SeqCounter++; } public int getCachesize() { return cachesize; } public int getRstype() { return rstype; } public int getRsconcur() { return rsconcur; } public int getMaxrows() { return maxrows; } /** * Retrieves the next available response, or null if there are no more responses. * * @return the next Response available or null */ IResponse getNextResponse() throws SQLException { if (rstype == ResultSet.TYPE_FORWARD_ONLY) { // free resources if we're running forward only if (curResponse >= 0 && curResponse < responses.size()) { IResponse tmp = responses.get(curResponse); if (tmp != null) { tmp.close(); } responses.set(curResponse, null); } } curResponse++; if (curResponse >= responses.size()) { // ResponseList is obviously completed so, there are no more responses return null; } else { // return this response return responses.get(curResponse); } } /** * Closes the Response at index i, if not null. * * @param i the index position of the header to close */ void closeResponse(int i) { if (i < 0 || i >= responses.size()) return; IResponse tmp = responses.set(i, null); if (tmp != null) tmp.close(); } /** * Closes the current response. */ void closeCurrentResponse() { closeResponse(curResponse); } /** * Closes the current and previous responses. */ void closeCurOldResponses() { for (int i = curResponse; i >= 0; i--) { closeResponse(i); } } /** * Closes this ResponseList by closing all the Responses in this ResponseList. */ public void close() { for (int i = 0; i < responses.size(); i++) { closeResponse(i); } } /** * Returns whether this ResponseList has still unclosed Responses. */ boolean hasUnclosedResponses() { for (IResponse r : responses) { if (r != null) return true; } return false; } /** * Executes the query contained in this ResponseList, and stores the Responses resulting from this query in this * ResponseList. * * @throws SQLException if a database error occurs */ void processQuery(String query) throws SQLException { this.executeQuery(language.getQueryTemplates(), query); } /** * Internal executor of queries. * * @param templ the template to fill in * @param query the query to execute * @throws SQLException if a database error occurs */ @SuppressWarnings("fallthrough") public void executeQuery(String[] templ, String query) throws SQLException { String error = null; try { // make sure we're ready to send query; read data till we have the prompt it is possible (and most // likely) that we already have the prompt and do not have to skip any lines. Ignore errors from // previous result sets. protocol.waitUntilPrompt(); // {{{ set reply size /** * Change the reply size of the server. If the given value is the same as the current value known * to use, then ignore this call. If it is set to 0 we get a prompt after the server sent it's * header. */ int size = cachesize == 0 ? MonetConnection.this.getDefFetchsize() : cachesize; size = maxrows != 0 ? Math.min(maxrows, size) : size; // don't do work if it's not needed if (language == MapiLanguage.LANG_SQL && size != curReplySize && !Arrays.deepEquals(templ, language.getCommandTemplates())) { sendControlCommand(ControlCommands.REPLY_SIZE, size); // store the reply size after a successful change curReplySize = size; } // }}} set reply size // If the query is larger than the TCP buffer size, use a special send thread to avoid deadlock with // the server due to blocking behaviour when the buffer is full. Because the server will be writing // back results to us, it will eventually block as well when its TCP buffer gets full, as we are // blocking an not consuming from it. The result is a state where both client and server want to // write, but block. if (query.length() > getBlockSize()) { // get a reference to the send thread if (senderThread == null) { senderThread = new SenderThread(protocol); } // tell it to do some work! senderThread.runQuery(templ, query); } else { // this is a simple call, which is a lot cheaper and will always succeed for small queries. protocol.writeNextQuery((templ[0] == null) ? "" : templ[0], query, (templ[1] == null) ? "" : templ[1]); } // go for new results protocol.fetchNextResponseData(); int nextResponse = protocol.getCurrentServerResponse(); IResponse res = null; while (nextResponse != ServerResponses.PROMPT) { // each response should start with a start of header (or error) switch (nextResponse) { case ServerResponses.SOHEADER: // make the response object, and fill it int nextStartHeader = protocol.getNextStarterHeader(); try { switch (nextStartHeader) { case StarterHeaders.Q_PARSE: throw new ProtocolException("Q_PARSE header not allowed here"); case StarterHeaders.Q_TABLE: case StarterHeaders.Q_PREPARE: { res = protocol.getNextResultSetResponse(MonetConnection.this, ResponseList.this, this.seqnr); ResultSetResponse rsreponse = (ResultSetResponse) res; // only add this resultset to the hashmap if it can possibly // have an additional datablock if (rsreponse.getRowcount() < rsreponse.getTuplecount()) { if (rsresponses == null) { rsresponses = new HashMap<>(); } rsresponses.put(rsreponse.getId(), rsreponse); } } break; case StarterHeaders.Q_UPDATE: res = protocol.getNextUpdateResponse(); break; case StarterHeaders.Q_SCHEMA: res = protocol.getNextSchemaResponse(); break; case StarterHeaders.Q_TRANS: res = protocol.getNextAutoCommitResponse(); boolean isAutoCommit = ((AutoCommitResponse) res).isAutocommit(); if (MonetConnection.this.getAutoCommit() && isAutoCommit) { MonetConnection.this.addWarning("Server enabled auto commit mode " + "while local state already was auto commit.", "01M11"); } MonetConnection.this.autoCommit = isAutoCommit; break; case StarterHeaders.Q_BLOCK: { DataBlockResponse next = protocol.getNextDatablockResponse(rsresponses); if (next == null) { error = "M0M12!No ResultSetResponse for a DataBlock found"; break; } res = next; } break; } } catch (ProtocolException e) { error = "M0M10!error while parsing start of header:\n" + e.getMessage() + " found: '" + protocol.getRemainingStringLine(0).charAt(e.getErrorOffset()) + "'" + " in: \"" + protocol.getRemainingStringLine(0) + "\"" + " at pos: " + e.getErrorOffset(); // flush all the rest protocol.waitUntilPrompt(); nextResponse = protocol.getCurrentServerResponse(); break; } // immediately handle errors after parsing the header (res may be null) if (error != null) { protocol.waitUntilPrompt(); nextResponse = protocol.getCurrentServerResponse(); break; } // here we have a res object, which we can start filling if (res instanceof IIncompleteResponse) { IIncompleteResponse iter = (IIncompleteResponse) res; while (iter.wantsMore()) { try { protocol.fetchNextResponseData(); iter.addLines(protocol); } catch (ProtocolException ex) { // right, some protocol violation, skip the rest of the result error = "M0M10!" + ex.getMessage(); protocol.waitUntilPrompt(); nextResponse = protocol.getCurrentServerResponse(); break; } } } if (error != null) { break; } // it is of no use to store DataBlockResponses, you never want to retrieve them directly // anyway if (!(res instanceof DataBlockResponse)) { responses.add(res); } // read the next line (can be prompt, new result, error, etc.) before we start the loop over protocol.fetchNextResponseData(); nextResponse = protocol.getCurrentServerResponse(); break; case ServerResponses.INFO: addWarning(protocol.getRemainingStringLine(0), "01000"); // read the next line (can be prompt, new result, error, etc.) before we start the loop over protocol.fetchNextResponseData(); nextResponse = protocol.getCurrentServerResponse(); break; case ServerResponses.ERROR: // read everything till the prompt (should be error) we don't know if we ignore some // garbage here... but the log should reveal that error = protocol.getRemainingStringLine(0); protocol.waitUntilPrompt(); nextResponse = protocol.getCurrentServerResponse(); break; default: throw new SQLException("Protocol violation, unexpected line!", "M0M10"); } } // if we used the senderThread, make sure it has finished if (senderThread != null) { String tmp = senderThread.getErrors(); if (tmp != null) { if (error == null) { error = "08000!" + tmp; } else { error += "\n08000!" + tmp; } } } if (error != null) { SQLException ret = null; String[] errors = error.split("\n"); for (String error1 : errors) { if (ret == null) { ret = new SQLException(error1.substring(6), error1.substring(0, 5)); } else { ret.setNextException(new SQLException(error1.substring(6), error1.substring(0, 5))); } } throw ret; } } catch (SocketTimeoutException e) { this.close(); // JDBC 4.1 semantics, abort() throw new SQLException("connection timed out", "08M33"); } catch (IOException e) { closed = true; throw new SQLException(e.getMessage() + " (mserver still alive?)", "08000"); } } } }