/* Copyright (c) 1997 George Reese */
package COM.imaginary.sql.msql;
import java.io.IOException;
import java.net.Socket;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Properties;
/**
* The MsqlConnection class is an implementation of the JDBC Connection
* interface that represents a database transaction. This class should
* never be directly instantiated by an application, but instead should
* be gotten by calling DriverManager.getConnection().<BR>
* Last modified 97/04/19
* @version @(#) MsqlConnection.java 1.3@(#)
* @author George Reese (borg@imaginary.com)
* @see java.sql.DriverManager#getConnection
*/
public class MsqlConnection implements Connection {
private String catalog = null;
private boolean connecting = true;
private Socket connection = null;
private Statement current = null;
private MsqlInputStream input = null;
private int isolation = 0;
private MsqlOutputStream output = null;
private String user = null;
private int version = 0;
private SQLWarning warnings = null;
// Since this should only be instantiated by MsqlDriver, this is
// package visibility
MsqlConnection(String host, int port, String db, Properties p)
throws SQLException {
super();
user = (String)p.get("user");
if( user == null || user.equals("") ) {
user = "nobody";
}
connect(host, port, user);
selectDatabase(db);
synchronized( this ) {
connecting = false;
notify();
}
}
/**
* @return true if this Connection has been closed
* @exception java.sql.SQLException never thrown
*/
public synchronized boolean isClosed() throws SQLException {
awaitConnection();
return (connection == null);
}
/**
* @return false--always
*/
public boolean isReadOnly() throws SQLException {
return false;
}
/**
* This method always returns true since mSQL is always in auto-commit
* mode.
* @return true--always
* @exception java.sql.SQLException never thrown
*/
public boolean getAutoCommit() throws SQLException {
return true;
}
/**
* This method will thrown an exception if you try to turn auto-commit
* off since JDBC does not support transactional logic.
* @param b should always be true
* @exception java.sql.SQLException thrown if the param is false
*/
public void setAutoCommit(boolean b) throws SQLException {
if( b ) {
return;
}
throw new SQLException("mSQL must always be auto-commit = true.");
}
/**
* Provides the catalog name. mSQL does not support catalogs,
* however.
* @exception java.sql.SQLException never thrown
*/
public synchronized String getCatalog() throws SQLException {
return catalog;
}
/**
* Sets the catalog name for this connection. mSQL does not support
* catalogs, so this method is a NO-OP.
* @exception java.sql.SQLException never thrown
*/
public synchronized void setCatalog(String str) throws SQLException {
catalog = str;
}
/**
* This method is not yet implemented.
*/
public DatabaseMetaData getMetaData() throws SQLException {
return null;
}
/**
* mSQL does not support read-only mode.
* @exception java.sql.SQLException alway thrown
*/
public void setReadOnly(boolean b) throws SQLException {
throw new SQLException("mSQL does not support read-only mode.");
}
/**
* @return the transaction isolation, not supported by mSQL
* @exception java.sql.SQLException never thrown
*/
public synchronized int getTransactionIsolation() throws SQLException {
return isolation;
}
/**
* This is not supported by mSQL, thus this is a NO-OP.
* @exception java.sql.SQLException never thrown
*/
public synchronized void setTransactionIsolation(int x)
throws SQLException {
isolation = x;
}
/**
* @return the user name used for this Connection
* @exception java.sql.SQLException thrown if the connection is never made
*/
public String getUser() throws SQLException {
awaitConnection();
return user;
}
/**
* @return the SQLWarning chain for this Connection
* @exception java.sql.SQLException never thrown
*/
public synchronized SQLWarning getWarnings() throws SQLException {
return warnings;
}
// Tests if the statement is in the middle of a connect. If so, it
// will await connection.
private void awaitConnection() throws MsqlException {
synchronized( this ) {
while( connecting ) {
try {
wait();
}
catch( InterruptedException e ) {
if( connecting ) {
throw new MsqlException(e);
}
}
}
}
}
// Cleans up when the connection is lost
private void cleanConnection() {
synchronized( this ) {
if( current != null ) {
try {
current.close();
}
catch( SQLException e ) {
e.printStackTrace();
}
current = null;
}
connecting = false;
connection = null;
}
input = null;
output = null;
user = null;
}
/**
* Sets the SQLWarning chain to null.
* @exception java.sql.SQLException never thrown
*/
public synchronized void clearWarnings() throws SQLException {
warnings = null;
}
/**
* Closes this database connection. It will also close any associated
* Statement instance.
* @exception java.sql.SQLException thrown if problems occur
*/
public synchronized void close() throws SQLException {
Exception except = null;
awaitConnection();
if( isClosed() ) {
throw new SQLException("This Connection is already closed.");
}
// Any current statement has ownership of the streams
// We therefore have to close *before* sending data
if( current != null ) {
current.close();
current = null;
}
// Let mSQL know we are closing down and close the output stream
try {
output.writeAsciiString("1");
output.flush();
output.close();
output = null;
}
catch( IOException e ) {
output = null;
except = e;
}
// close the input stream
try {
input.close();
input = null;
}
catch( IOException e ) {
input = null;
except = e;
}
// close the connection
try {
connection.close();
}
catch( IOException e ) {
connection = null;
throw new MsqlException(e);
}
if( except != null ) {
throw new MsqlException(except);
}
}
/**
* This is a NO-OP for mSQL. All statements are always auto-committed.
* @exception java.sql.SQLException never thrown
*/
public void commit() throws SQLException {
}
// Performs the actual database connection and creates the
// MsqlOutputStream and MsqlInputStream used to talk to mSQL
private void connect(String host, int port, String user)
throws SQLException {
try {
String tmp;
connection = new Socket(host, port);
input = new MsqlInputStream(connection.getInputStream());
output = new MsqlOutputStream(connection.getOutputStream());
tmp = input.readAsciiString();
// Check for mSQL version
if( tmp.startsWith("0:22:") ) {
version = 2; // version 2.0
}
else if( tmp.startsWith("0:6:") ) {
version = 1; // version 1.0.x
}
else {
throw new SQLException("Unsupported mSQL version.");
}
// Check if user was validated
output.writeAsciiString(user);
tmp = input.readAsciiString();
if( !tmp.startsWith("-100:") ) {
throw new SQLException("Access to server denied.");
}
}
catch( IOException e ) {
cleanConnection();
throw new SQLException("Connection failed.");
}
}
/**
* This JDBC method creates an instance of MsqlStatement and
* returns it. If there are currently any open MsqlStatement's, it
* will close them. Not that mSQL does not provide a way to
* interrupt executing statements, so it has to wait for any
* pending statements to finish before closing them.
* @return a new MsqlStatement instance
* @exception java.sql.SQLException an error occurred in creating the
* Statement instance, likely raised by the constructor
*/
public Statement createStatement() throws SQLException {
awaitConnection();
synchronized( this ) {
if( current != null ) {
try {
current.close();
}
catch( SQLException e ) {
e.printStackTrace();
}
}
current = new MsqlStatement(this, input, output);
return current;
}
}
/**
* This gives the driver an opportunity to turn JDBC compliant SQL
* into mSQL specific SQL. My feeling is why bother.
* @exception java.sql.SQLException never thrown
*/
public String nativeSQL(String sql) throws SQLException {
return sql;
}
/**
* Callable statements are not supported by mSQL. This will therefore
* always throw an exception.
*/
public CallableStatement prepareCall(String sql) throws SQLException {
throw new SQLException("mSQL does not support stored procedures.");
}
/**
* Prepared statements are not supported by mSQL. This will
* therefore always throw an exception.
*/
public PreparedStatement prepareStatement(String sql) throws SQLException {
throw new SQLException("mSQL does not support prepared statements.");
}
/**
* This method always errors since you cannot rollback an mSQL
* transaction.
*/
public void rollback() throws SQLException {
throw new SQLException("mSQL exception: mSQL does not support " +
"rollbacks.");
}
// Tells mSQL which database we want to use
private void selectDatabase(String database) throws MsqlException {
String tmp;
try {
output.writeAsciiString("2 " + database);
tmp = input.readAsciiString();
if( tmp.startsWith("-1:") ) {
throw new MsqlException(tmp);
}
}
catch( IOException e ) {
cleanConnection();
throw new MsqlException(e);
}
}
}
|