From ff246d7b64e41278cf1ac68a9245529a29dc98eb Mon Sep 17 00:00:00 2001 From: "Marc G. Fournier" Date: Sat, 16 Aug 1997 20:51:53 +0000 Subject: [PATCH] Bring in Adrian's JDBC driver as an interface --- src/interfaces/jdbc/JDBC_Test.java | 61 + .../jdbc/postgresql/CallableStatement.java | 126 ++ .../jdbc/postgresql/Connection.java | 847 +++++++++ .../jdbc/postgresql/DatabaseMetaData.java | 1556 +++++++++++++++++ src/interfaces/jdbc/postgresql/Driver.java | 269 +++ src/interfaces/jdbc/postgresql/Field.java | 89 + src/interfaces/jdbc/postgresql/PG_Object.java | 31 + .../jdbc/postgresql/PreparedStatement.java | 538 ++++++ src/interfaces/jdbc/postgresql/ResultSet.java | 845 +++++++++ .../jdbc/postgresql/ResultSetMetaData.java | 429 +++++ src/interfaces/jdbc/postgresql/Statement.java | 306 ++++ 11 files changed, 5097 insertions(+) create mode 100644 src/interfaces/jdbc/JDBC_Test.java create mode 100644 src/interfaces/jdbc/postgresql/CallableStatement.java create mode 100644 src/interfaces/jdbc/postgresql/Connection.java create mode 100644 src/interfaces/jdbc/postgresql/DatabaseMetaData.java create mode 100644 src/interfaces/jdbc/postgresql/Driver.java create mode 100644 src/interfaces/jdbc/postgresql/Field.java create mode 100644 src/interfaces/jdbc/postgresql/PG_Object.java create mode 100644 src/interfaces/jdbc/postgresql/PreparedStatement.java create mode 100644 src/interfaces/jdbc/postgresql/ResultSet.java create mode 100644 src/interfaces/jdbc/postgresql/ResultSetMetaData.java create mode 100644 src/interfaces/jdbc/postgresql/Statement.java diff --git a/src/interfaces/jdbc/JDBC_Test.java b/src/interfaces/jdbc/JDBC_Test.java new file mode 100644 index 0000000000..30cda33304 --- /dev/null +++ b/src/interfaces/jdbc/JDBC_Test.java @@ -0,0 +1,61 @@ +import java.io.*; +import java.lang.*; +import java.sql.*; + +class JDBC_Test +{ + public JDBC_Test() + { + } + + public static void main(String argv[]) + { + String url = new String(argv[0]); + Connection db; + Statement s; + ResultSet rs; + + // Load the driver + try + { + Class.forName("postgresql.Driver"); + } catch (ClassNotFoundException e) { + System.err.println("Exception: " + e.toString()); + } + + // Lets do a few things -- it doesn't do everything, but + // it tests out basic functionality + try + { + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, "adrian", ""); + System.out.println("Connected...Now creating a statement"); + s = db.createStatement(); + System.out.println("Ok...now we will create a table"); + s.executeUpdate("create table test (a int2, b int2)"); + System.out.println("Now we will insert some columns"); + s.executeUpdate("insert into test values (1, 1)"); + s.executeUpdate("insert into test values (2, 1)"); + s.executeUpdate("insert into test values (3, 1)"); + System.out.println("Inserted some data"); + System.out.println("Now lets try a select"); + rs = s.executeQuery("select a, b from test"); + System.out.println("Back from the select...the following are results"); + int i = 0; + while (rs.next()) + { + int a = rs.getInt("a"); + int b = rs.getInt("b"); + System.out.println("row " + i + " " + a + " " + b); + i++; + } + System.out.println("Ok...dropping the table"); + s.executeUpdate("drop table test"); + System.out.println("Now closing the connection"); + s.close(); + db.close(); + } catch (SQLException e) { + System.out.println("Exception: " + e.toString()); + } + } +} diff --git a/src/interfaces/jdbc/postgresql/CallableStatement.java b/src/interfaces/jdbc/postgresql/CallableStatement.java new file mode 100644 index 0000000000..ff7ec7c26f --- /dev/null +++ b/src/interfaces/jdbc/postgresql/CallableStatement.java @@ -0,0 +1,126 @@ +package postgresql; + +import java.math.*; +import java.sql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * CallableStatement is used to execute SQL stored procedures. + * + * JDBC provides a stored procedure SQL escape that allows stored procedures + * to be called in a standard way for all RDBMS's. This escape syntax has + * one form that includes a result parameter and one that does not. If used, + * the result parameter must be generated as an OUT parameter. The other + * parameters may be used for input, output or both. Parameters are refered + * to sequentially, by number. The first parameter is 1. + * + *
+ *	{?= call [,, ...]}
+ *	{call [,, ...]}
+ * 
+ * + * IN parameters are set using the set methods inherited from + * PreparedStatement. The type of all OUT parameters must be registered + * prior to executing the stored procedure; their values are retrieved + * after execution via the get methods provided here. + * + * A CallableStatement may return a ResultSet or multiple ResultSets. Multiple + * ResultSets are handled using operations inherited from Statement. + * + * For maximum portability, a call's ResultSets and update counts should be + * processed prior to getting the values of output parameters. + * + * @see java.sql.Connection#prepareCall + * @see java.sql.ResultSet + * @see java.sql.CallableStatement + */ +public class CallableStatement implements java.sql.CallableStatement +{ + public void registerOutParameter (int paramterIndex, int sqlType) throws SQLException + { + // XXX-Not Implemented + } + + public void registerOutParameter (int parameterIndex, int sqlType, int scale) throws SQLException + { + // XXX-Not Implemented + } + + public boolean wasNull () throws SQLException + { + // XXX-Not Implemented + } + + public String getString (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public boolean getBoolean (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public byte getByte (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public short getShort (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public int getInt (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public long getLong (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public float getFloat (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public double getDouble (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public BigDecimal getBigDecimal (int parameterIndex, int scale) throws SQLException + { + // XXX-Not Implemented + } + + public byte[] getBytes (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public Date getDate (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public Time getTime (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public Timestamp getTimestamp (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + + public Object getObject (int parameterIndex) throws SQLException + { + // XXX-Not Implemented + } + +} diff --git a/src/interfaces/jdbc/postgresql/Connection.java b/src/interfaces/jdbc/postgresql/Connection.java new file mode 100644 index 0000000000..aa354b61fe --- /dev/null +++ b/src/interfaces/jdbc/postgresql/Connection.java @@ -0,0 +1,847 @@ +package postgresql; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import postgresql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * A Connection represents a session with a specific database. Within the + * context of a Connection, SQL statements are executed and results are + * returned. + * + * A Connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is obtained + * with the getMetaData method. + * + * Note: By default, the Connection automatically commits changes + * after executing each statement. If auto-commit has been disabled, an + * explicit commit must be done or database changes will not be saved. + * + * @see java.sql.Connection + */ +public class Connection implements java.sql.Connection +{ + private PG_Stream pg_stream; + + private String PG_HOST; + private int PG_PORT; + private String PG_USER; + private String PG_PASSWORD; + private String PG_DATABASE; + private boolean PG_STATUS; + + public boolean CONNECTION_OK = true; + public boolean CONNECTION_BAD = false; + + private int STARTUP_CODE = 7; + + private boolean autoCommit = true; + private boolean readOnly = false; + + private Driver this_driver; + private String this_url; + private String cursor = null; // The positioned update cursor name + + /** + * Connect to a PostgreSQL database back end. + * + * @param host the hostname of the database back end + * @param port the port number of the postmaster process + * @param info a Properties[] thing of the user and password + * @param database the database to connect to + * @param u the URL of the connection + * @param d the Driver instantation of the connection + * @return a valid connection profile + * @exception SQLException if a database access error occurs + */ + public Connection(String host, int port, Properties info, String database, String url, Driver d) throws SQLException + { + int len = 288; // Length of a startup packet + + this_driver = d; + this_url = new String(url); + PG_DATABASE = new String(database); + PG_PASSWORD = new String(info.getProperty("password")); + PG_USER = new String(info.getProperty("user")); + PG_PORT = port; + PG_HOST = new String(host); + PG_STATUS = CONNECTION_BAD; + + try + { + pg_stream = new PG_Stream(host, port); + } catch (IOException e) { + throw new SQLException ("Connection failed: " + e.toString()); + } + + // Now we need to construct and send a startup packet + try + { + pg_stream.SendInteger(len, 4); len -= 4; + pg_stream.SendInteger(STARTUP_CODE, 4); len -= 4; + pg_stream.Send(database.getBytes(), 64); len -= 64; + pg_stream.Send(PG_USER.getBytes(), len); + } catch (IOException e) { + throw new SQLException("Connection failed: " + e.toString()); + } + ExecSQL(" "); // Test connection + PG_STATUS = CONNECTION_OK; + } + + /** + * SQL statements without parameters are normally executed using + * Statement objects. If the same SQL statement is executed many + * times, it is more efficient to use a PreparedStatement + * + * @return a new Statement object + * @exception SQLException passed through from the constructor + */ + public java.sql.Statement createStatement() throws SQLException + { + return new Statement(this); + } + + /** + * 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 drivers + * supports precompilation. PostgreSQL does not support precompilation. + * In this case, the statement is not sent to the database until the + * PreparedStatement is executed. This has no direct effect on users; + * however it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' IN + * parameter placeholders + * @return a new PreparedStatement object containing the pre-compiled + * statement. + * @exception SQLException if a database access error occurs. + */ + public java.sql.PreparedStatement prepareStatement(String sql) throws SQLException + { + return new PreparedStatement(this, sql); + } + + /** + * A SQL stored procedure call statement is handled by creating a + * CallableStatement for it. The CallableStatement provides methods + * for setting up its IN and OUT parameters and methods for executing + * it. + * + * Note: This method is optimised for handling stored procedure + * call statements. Some drivers may send the call statement to the + * database when the prepareCall is done; others may wait until the + * CallableStatement is executed. This has no direct effect on users; + * however, it does affect which method throws certain SQLExceptions + * + * @param sql a SQL statement that may contain one or more '?' parameter + * placeholders. Typically this statement is a JDBC function call + * escape string. + * @return a new CallableStatement object containing the pre-compiled + * SQL statement + * @exception SQLException if a database access error occurs + */ + public java.sql.CallableStatement prepareCall(String sql) throws SQLException + { + throw new SQLException("Callable Statements are not supported at this time"); +// return new CallableStatement(this, sql); + } + + /** + * A driver may convert the JDBC sql grammar into its system's + * native SQL grammar prior to sending it; nativeSQL returns the + * native form of the statement that the driver would have sent. + * + * @param sql a SQL statement that may contain one or more '?' + * parameter placeholders + * @return the native form of this statement + * @exception SQLException if a database access error occurs + */ + public String nativeSQL(String sql) throws SQLException + { + return sql; + } + + /** + * If a connection is in auto-commit mode, than all its SQL + * statements will be executed and committed as individual + * transactions. Otherwise, its SQL statements are grouped + * into transactions that are terminated by either commit() + * or 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, the statement + * completes when the last row of the ResultSet has been retrieved + * or the ResultSet has been closed. In advanced cases, a single + * statement may return multiple results as well as output parameter + * values. Here the commit occurs when all results and output param + * values have been retrieved. + * + * @param autoCommit - true enables auto-commit; false disables it + * @exception SQLException if a database access error occurs + */ + public void setAutoCommit(boolean autoCommit) throws SQLException + { + if (this.autoCommit == autoCommit) + return; + if (autoCommit) + ExecSQL("end"); + else + ExecSQL("begin"); + this.autoCommit = autoCommit; + } + + /** + * gets the current auto-commit state + * + * @return Current state of the auto-commit mode + * @exception SQLException (why?) + * @see setAutoCommit + */ + public boolean getAutoCommit() throws SQLException + { + return this.autoCommit; + } + + /** + * The method commit() makes all changes made since the previous + * commit/rollback permanent and releases any database locks currently + * held by the Connection. This method should only be used when + * auto-commit has been disabled. (If autoCommit == true, then we + * just return anyhow) + * + * @exception SQLException if a database access error occurs + * @see setAutoCommit + */ + public void commit() throws SQLException + { + if (autoCommit) + return; + ExecSQL("commit"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * The method rollback() drops all changes made since the previous + * commit/rollback and releases any database locks currently held by + * the Connection. + * + * @exception SQLException if a database access error occurs + * @see commit + */ + public void rollback() throws SQLException + { + if (autoCommit) + return; + ExecSQL("rollback"); + autoCommit = true; + ExecSQL("begin"); + autoCommit = false; + } + + /** + * In some cases, it is desirable to immediately release a Connection's + * database and JDBC resources instead of waiting for them to be + * automatically released (cant think why off the top of my head) + * + * Note: A Connection is automatically closed when it is + * garbage collected. Certain fatal errors also result in a closed + * connection. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + if (pg_stream != null) + { + try + { + pg_stream.close(); + } catch (IOException e) {} + pg_stream = null; + } + } + + /** + * Tests to see if a Connection is closed + * + * @return the status of the connection + * @exception SQLException (why?) + */ + public boolean isClosed() throws SQLException + { + return (pg_stream == null); + } + + /** + * A connection's database is able to provide information describing + * its tables, its supported SQL grammar, its stored procedures, the + * capabilities of this connection, etc. This information is made + * available through a DatabaseMetaData object. + * + * @return a DatabaseMetaData object for this connection + * @exception SQLException if a database access error occurs + */ + public java.sql.DatabaseMetaData getMetaData() throws SQLException + { +// return new DatabaseMetaData(this); + throw new SQLException("DatabaseMetaData not supported"); + } + + /** + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * Note: setReadOnly cannot be called while in the middle + * of a transaction + * + * @param readOnly - true enables read-only mode; false disables it + * @exception SQLException if a database access error occurs + */ + public void setReadOnly (boolean readOnly) throws SQLException + { + this.readOnly = readOnly; + } + + /** + * Tests to see if the connection is in Read Only Mode. Note that + * we cannot really put the database in read only mode, but we pretend + * we can by returning the value of the readOnly flag + * + * @return true if the connection is read only + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return readOnly; + } + + /** + * A sub-space of this Connection's database may be selected by + * setting a catalog name. If the driver does not support catalogs, + * it will silently ignore this request + * + * @exception SQLException if a database access error occurs + */ + public void setCatalog(String catalog) throws SQLException + { + // No-op + } + + /** + * Return the connections current catalog name, or null if no + * catalog name is set, or we dont support catalogs. + * + * @return the current catalog name or null + * @exception SQLException if a database access error occurs + */ + public String getCatalog() throws SQLException + { + return null; + } + + /** + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * Note: setTransactionIsolation cannot be called while + * in the middle of a transaction + * + * @param level one of the TRANSACTION_* isolation values with + * the exception of TRANSACTION_NONE; some databases may + * not support other values + * @exception SQLException if a database access error occurs + * @see java.sql.DatabaseMetaData#supportsTransactionIsolationLevel + */ + public void setTransactionIsolation(int level) throws SQLException + { + throw new SQLException("Transaction Isolation Levels are not implemented"); + } + + /** + * Get this Connection's current transaction isolation mode. + * + * @return the current TRANSACTION_* mode value + * @exception SQLException if a database access error occurs + */ + public int getTransactionIsolation() throws SQLException + { + return java.sql.Connection.TRANSACTION_SERIALIZABLE; + } + + /** + * The first warning reported by calls on this Connection is + * returned. + * + * Note: Sebsequent warnings will be changed to this + * SQLWarning + * + * @return the first SQLWarning or null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return null; // We handle warnings as errors + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this connection. + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + // Not handles since we handle wanrings as errors + } + + // ********************************************************** + // END OF PUBLIC INTERFACE + // ********************************************************** + + /** + * Send a query to the backend. Returns one of the ResultSet + * objects. + * + * Note: there does not seem to be any method currently + * in existance to return the update count. + * + * @param sql the SQL statement to be executed + * @return a ResultSet holding the results + * @exception SQLException if a database error occurs + */ + public synchronized ResultSet ExecSQL(String sql) throws SQLException + { + Field[] fields = null; + Vector tuples = new Vector(); + byte[] buf = new byte[sql.length()]; + int fqp = 0; + boolean hfr = false; + String recv_status = null, msg; + SQLException final_error = null; + + if (sql.length() > 8192) + throw new SQLException("SQL Statement too long: " + sql); + try + { + pg_stream.SendChar('Q'); + buf = sql.getBytes(); + pg_stream.Send(buf); + pg_stream.SendChar(0); + } catch (IOException e) { + throw new SQLException("I/O Error: " + e.toString()); + } + + while (!hfr || fqp > 0) + { + int c = pg_stream.ReceiveChar(); + + switch (c) + { + case 'A': // Asynchronous Notify + int pid = pg_stream.ReceiveInteger(4); + msg = pg_stream.ReceiveString(8192); + break; + case 'B': // Binary Data Transfer + if (fields == null) + throw new SQLException("Tuple received before MetaData"); + tuples.addElement(pg_stream.ReceiveTuple(fields.length, true)); + break; + case 'C': // Command Status + recv_status = pg_stream.ReceiveString(8192); + if (fields != null) + hfr = true; + else + { + try + { + pg_stream.SendChar('Q'); + pg_stream.SendChar(' '); + pg_stream.SendChar(0); + } catch (IOException e) { + throw new SQLException("I/O Error: " + e.toString()); + } + fqp++; + } + break; + case 'D': // Text Data Transfer + if (fields == null) + throw new SQLException("Tuple received before MetaData"); + tuples.addElement(pg_stream.ReceiveTuple(fields.length, false)); + break; + case 'E': // Error Message + msg = pg_stream.ReceiveString(4096); + final_error = new SQLException(msg); + hfr = true; + break; + case 'I': // Empty Query + int t = pg_stream.ReceiveChar(); + + if (t != 0) + throw new SQLException("Garbled Data"); + if (fqp > 0) + fqp--; + if (fqp == 0) + hfr = true; + break; + case 'N': // Error Notification + msg = pg_stream.ReceiveString(4096); + PrintStream log = DriverManager.getLogStream(); + log.println(msg); + break; + case 'P': // Portal Name + String pname = pg_stream.ReceiveString(8192); + break; + case 'T': // MetaData Field Description + if (fields != null) + throw new SQLException("Cannot handle multiple result groups"); + fields = ReceiveFields(); + break; + default: + throw new SQLException("Unknown Response Type: " + (char)c); + } + } + if (final_error != null) + throw final_error; + return new ResultSet(this, fields, tuples, recv_status, 1); + } + + /** + * Receive the field descriptions from the back end + * + * @return an array of the Field object describing the fields + * @exception SQLException if a database error occurs + */ + private Field[] ReceiveFields() throws SQLException + { + int nf = pg_stream.ReceiveInteger(2), i; + Field[] fields = new Field[nf]; + + for (i = 0 ; i < nf ; ++i) + { + String typname = pg_stream.ReceiveString(8192); + int typid = pg_stream.ReceiveInteger(4); + int typlen = pg_stream.ReceiveInteger(2); + fields[i] = new Field(this, typname, typid, typlen); + } + return fields; + } + + /** + * In SQL, a result table can be retrieved through a cursor that + * is named. The current row of a result can be updated or deleted + * using a positioned update/delete statement that references the + * cursor name. + * + * We support one cursor per connection. + * + * setCursorName sets the cursor name. + * + * @param cursor the cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String cursor) throws SQLException + { + this.cursor = cursor; + } + + /** + * getCursorName gets the cursor name. + * + * @return the current cursor name + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return cursor; + } + + /** + * We are required to bring back certain information by + * the DatabaseMetaData class. These functions do that. + * + * Method getURL() brings back the URL (good job we saved it) + * + * @return the url + * @exception SQLException just in case... + */ + public String getURL() throws SQLException + { + return this_url; + } + + /** + * Method getUserName() brings back the User Name (again, we + * saved it) + * + * @return the user name + * @exception SQLException just in case... + */ + public String getUserName() throws SQLException + { + return PG_USER; + } +} + +// *********************************************************************** + +// This class handles all the Streamed I/O for a postgresql connection +class PG_Stream +{ + private Socket connection; + private InputStream pg_input; + private OutputStream pg_output; + + /** + * Constructor: Connect to the PostgreSQL back end and return + * a stream connection. + * + * @param host the hostname to connect to + * @param port the port number that the postmaster is sitting on + * @exception IOException if an IOException occurs below it. + */ + public PG_Stream(String host, int port) throws IOException + { + connection = new Socket(host, port); + pg_input = connection.getInputStream(); + pg_output = connection.getOutputStream(); + } + + /** + * Sends a single character to the back end + * + * @param val the character to be sent + * @exception IOException if an I/O error occurs + */ + public void SendChar(int val) throws IOException + { + pg_output.write(val); + } + + /** + * Sends an integer to the back end + * + * @param val the integer to be sent + * @param siz the length of the integer in bytes (size of structure) + * @exception IOException if an I/O error occurs + */ + public void SendInteger(int val, int siz) throws IOException + { + byte[] buf = new byte[siz]; + + while (siz-- > 0) + { + buf[siz] = (byte)(val & 0xff); + val >>= 8; + } + Send(buf); + } + + /** + * Send an array of bytes to the backend + * + * @param buf The array of bytes to be sent + * @exception IOException if an I/O error occurs + */ + public void Send(byte buf[]) throws IOException + { + pg_output.write(buf); + } + + /** + * Send an exact array of bytes to the backend - if the length + * has not been reached, send nulls until it has. + * + * @param buf the array of bytes to be sent + * @param siz the number of bytes to be sent + * @exception IOException if an I/O error occurs + */ + public void Send(byte buf[], int siz) throws IOException + { + int i; + + pg_output.write(buf, 0, (buf.length < siz ? buf.length : siz)); + if (buf.length < siz) + { + for (i = buf.length ; i < siz ; ++i) + { + pg_output.write(0); + } + } + } + + /** + * Receives a single character from the backend + * + * @return the character received + * @exception SQLException if an I/O Error returns + */ + public int ReceiveChar() throws SQLException + { + int c = 0; + + try + { + c = pg_input.read(); + if (c < 0) throw new IOException("EOF"); + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return c; + } + + /** + * Receives an integer from the backend + * + * @param siz length of the integer in bytes + * @return the integer received from the backend + * @exception SQLException if an I/O error occurs + */ + public int ReceiveInteger(int siz) throws SQLException + { + int n = 0; + + try + { + for (int i = 0 ; i < siz ; i++) + { + int b = pg_input.read(); + + if (b < 0) + throw new IOException("EOF"); + n = n | (b >> (8 * i)) ; + } + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return n; + } + + /** + * Receives a null-terminated string from the backend. Maximum of + * maxsiz bytes - if we don't see a null, then we assume something + * has gone wrong. + * + * @param maxsiz maximum length of string + * @return string from back end + * @exception SQLException if an I/O error occurs + */ + public String ReceiveString(int maxsiz) throws SQLException + { + byte[] rst = new byte[maxsiz]; + int s = 0; + + try + { + while (s < maxsiz) + { + int c = pg_input.read(); + if (c < 0) + throw new IOException("EOF"); + else if (c == 0) + break; + else + rst[s++] = (byte)c; + } + if (s >= maxsiz) + throw new IOException("Too Much Data"); + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + String v = new String(rst, 0, s); + return v; + } + + /** + * Read a tuple from the back end. A tuple is a two dimensional + * array of bytes + * + * @param nf the number of fields expected + * @param bin true if the tuple is a binary tuple + * @return null if the current response has no more tuples, otherwise + * an array of strings + * @exception SQLException if a data I/O error occurs + */ + public byte[][] ReceiveTuple(int nf, boolean bin) throws SQLException + { + int i, bim = (nf + 7)/8; + byte[] bitmask = Receive(bim); + byte[][] answer = new byte[nf][0]; + + int whichbit = 0x80; + int whichbyte = 0; + + for (i = 0 ; i < nf ; ++i) + { + boolean isNull = ((bitmask[whichbyte] & whichbit) == 0); + whichbit >>= 1; + if (whichbit == 0) + { + ++whichbyte; + whichbit = 0x80; + } + if (isNull) + answer[i] = null; + else + { + int len = ReceiveInteger(4); + if (!bin) + len -= 4; + if (len < 0) + len = 0; + answer[i] = Receive(len); + } + } + return answer; + } + + /** + * Reads in a given number of bytes from the backend + * + * @param siz number of bytes to read + * @return array of bytes received + * @exception SQLException if a data I/O error occurs + */ + private byte[] Receive(int siz) throws SQLException + { + byte[] answer = new byte[siz]; + int s = 0; + + try + { + while (s < siz) + { + int w = pg_input.read(answer, s, siz - s); + if (w < 0) + throw new IOException("EOF"); + s += w; + } + } catch (IOException e) { + throw new SQLException("Error reading from backend: " + e.toString()); + } + return answer; + } + + /** + * Closes the connection + * + * @exception IOException if a IO Error occurs + */ + public void close() throws IOException + { + pg_output.close(); + pg_input.close(); + connection.close(); + } +} diff --git a/src/interfaces/jdbc/postgresql/DatabaseMetaData.java b/src/interfaces/jdbc/postgresql/DatabaseMetaData.java new file mode 100644 index 0000000000..259829c3fb --- /dev/null +++ b/src/interfaces/jdbc/postgresql/DatabaseMetaData.java @@ -0,0 +1,1556 @@ +package postgresql; + +import java.sql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * This class provides information about the database as a whole. + * + * Many of the methods here return lists of information in ResultSets. You + * can use the normal ResultSet methods such as getString and getInt to + * retrieve the data from these ResultSets. If a given form of metadata is + * not available, these methods should throw a SQLException. + * + * Some of these methods take arguments that are String patterns. These + * arguments all have names such as fooPattern. Within a pattern String, + * "%" means match any substring of 0 or more characters, and "_" means + * match any one character. Only metadata entries matching the search + * pattern are returned. if a search pattern argument is set to a null + * ref, it means that argument's criteria should be dropped from the + * search. + * + * A SQLException will be throws if a driver does not support a meta + * data method. In the case of methods that return a ResultSet, either + * a ResultSet (which may be empty) is returned or a SQLException is + * thrown. + * + * @see java.sql.DatabaseMetaData + */ +public class DatabaseMetaData implements java.sql.DatabaseMetaData +{ + Connection connection; // The connection association + + public DatabaseMetaData(Connection conn) + { + this.connection = conn; + } + + /** + * Can all the procedures returned by getProcedures be called + * by the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allProceduresAreCallable() throws SQLException + { + return true; // For now... + } + + /** + * Can all the tables returned by getTable be SELECTed by + * the current user? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean allTablesAreSelectable() throws SQLException + { + return true; // For now... + } + + /** + * What is the URL for this database? + * + * @return the url or null if it cannott be generated + * @exception SQLException if a database access error occurs + */ + public String getURL() throws SQLException + { + return connection.getURL(); + } + + /** + * What is our user name as known to the database? + * + * @return our database user name + * @exception SQLException if a database access error occurs + */ + public String getUserName() throws SQLException + { + return connection.getUserName(); + } + + /** + * Is the database in read-only mode? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return connection.isReadOnly(); + } + + /** + * Are NULL values sorted high? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedHigh() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted low? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedLow() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the start regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtStart() throws SQLException + { + return false; + } + + /** + * Are NULL values sorted at the end regardless of sort order? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedAtEnd() throws SQLException + { + return true; + } + + /** + * What is the name of this database product - we hope that it is + * PostgreSQL, so we return that explicitly. + * + * @return the database product name + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductName() throws SQLException + { + return new String("PostgreSQL"); + } + + /** + * What is the version of this database product. Note that + * PostgreSQL 6.1 has a system catalog called pg_version - + * however, select * from pg_version on any database retrieves + * no rows. For now, we will return the version 6.1 (in the + * hopes that we change this driver as often as we change the + * database) + * + * @return the database version + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductVersion() throws SQLException + { + return ("6.1"); + } + + /** + * What is the name of this JDBC driver? If we don't know this + * we are doing something wrong! + * + * @return the JDBC driver name + * @exception SQLException why? + */ + public String getDriverName() throws SQLException + { + return new String("PostgreSQL Native Driver"); + } + + /** + * What is the version string of this JDBC driver? Again, this is + * static. + * + * @return the JDBC driver name. + * @exception SQLException why? + */ + public String getDriverVersion() throws SQLException + { + return new String("1.0"); + } + + /** + * What is this JDBC driver's major version number? + * + * @return the JDBC driver major version + */ + public int getDriverMajorVersion() + { + return 1; + } + + /** + * What is this JDBC driver's minor version number? + * + * @return the JDBC driver minor version + */ + public int getDriverMinorVersion() + { + return 0; + } + + /** + * Does the database store tables in a local file? No - it + * stores them in a file on the server. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFiles() throws SQLException + { + return false; + } + + /** + * Does the database use a file for each table? Well, not really, + * since it doesnt use local files. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean usesLocalFilePerTable() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers + * as case sensitive and as a result store them in mixed case? + * A JDBC-Compliant driver will always return false. + * + * Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case unquoted SQL identifiers as + * case insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case sensitive and as a result store them in mixed case? A + * JDBC compliant driver will always return true. + * + * Predicament - what do they mean by "SQL identifiers" - if it + * means the names of the tables and columns, then the answers + * given below are correct - otherwise I don't know. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException + { + return true; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as + * case insensitive and store them in upper case? + * + * @return true if so + */ + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in lower case? + * + * @return true if so + */ + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * Does the database treat mixed case quoted SQL identifiers as case + * insensitive and store them in mixed case? + * + * @return true if so + */ + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException + { + return false; + } + + /** + * What is the string used to quote SQL identifiers? This returns + * a space if identifier quoting isn't supported. A JDBC Compliant + * driver will always use a double quote character. + * + * If an SQL identifier is a table name, column name, etc. then + * we do not support it. + * + * @return the quoting string + * @exception SQLException if a database access error occurs + */ + public String getIdentifierQuoteString() throws SQLException + { + return new String(" "); + } + + /** + * Get a comma separated list of all a database's SQL keywords that + * are NOT also SQL92 keywords. + * + * Within PostgreSQL, the keywords are found in + * src/backend/parser/keywords.c + * For SQL Keywords, I took the list provided at + * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt + * which is for SQL3, not SQL-92, but it is close enough for + * this purpose. + * + * @return a comma separated list of keywords we use + * @exception SQLException if a database access error occurs + */ + public String getSQLKeywords() throws SQLException + { + return new String("abort,acl,add,aggregate,append,archive,arch_store,backward,binary,change,cluster,copy,database,delimiters,do,extend,explain,forward,heavy,index,inherits,isnull,light,listen,load,merge,nothing,notify,notnull,oids,purge,rename,replace,retrieve,returns,rule,recipe,setof,stdin,stdout,store,vacuum,verbose,version"); + } + + public String getNumericFunctions() throws SQLException + { + // XXX-Not Implemented + } + + public String getStringFunctions() throws SQLException + { + // XXX-Not Implemented + } + + public String getSystemFunctions() throws SQLException + { + // XXX-Not Implemented + } + + public String getTimeDateFunctions() throws SQLException + { + // XXX-Not Implemented + } + + /** + * This is the string that can be used to escape '_' and '%' in + * a search string pattern style catalog search parameters + * + * @return the string used to escape wildcard characters + * @exception SQLException if a database access error occurs + */ + public String getSearchStringEscape() throws SQLException + { + return new String("\\"); + } + + /** + * Get all the "extra" characters that can bew used in unquoted + * identifier names (those beyond a-zA-Z0-9 and _) + * + * From the file src/backend/parser/scan.l, an identifier is + * {letter}{letter_or_digit} which makes it just those listed + * above. + * + * @return a string containing the extra characters + * @exception SQLException if a database access error occurs + */ + public String getExtraNameCharacters() throws SQLException + { + return new String(""); + } + + /** + * Is "ALTER TABLE" with an add column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithAddColumn() throws SQLException + { + return true; + } + + /** + * Is "ALTER TABLE" with a drop column supported? + * Yes for PostgreSQL 6.1 + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithDropColumn() throws SQLException + { + return true; + } + + /** + * Is column aliasing supported? + * + * If so, the SQL AS clause can be used to provide names for + * computed columns or to provide alias names for columns as + * required. A JDBC Compliant driver always returns true. + * + * e.g. + * + * select count(C) as C_COUNT from T group by C; + * + * should return a column named as C_COUNT instead of count(C) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsColumnAliasing() throws SQLException + { + return true; + } + + /** + * Are concatenations between NULL and non-NULL values NULL? A + * JDBC Compliant driver always returns true + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullPlusNonNullIsNull() throws SQLException + { + return true; + } + + public boolean supportsConvert() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsConvert(int fromType, int toType) throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsDifferentTableCorrelationNames() throws SQLException + { + // XXX-Not Implemented + } + + /** + * Are expressions in "ORCER BY" lists supported? + * + * e.g. select * from t order by a + b; + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExpressionsInOrderBy() throws SQLException + { + return false; + } + + /** + * Can an "ORDER BY" clause use columns not in the SELECT? + * I checked it, and you can't. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOrderByUnrelated() throws SQLException + { + return false; + } + + /** + * Is some form of "GROUP BY" clause supported? + * I checked it, and yes it is. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupBy() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause use columns not in the SELECT? + * I checked it - it seems to allow it + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByUnrelated() throws SQLException + { + return true; + } + + /** + * Can a "GROUP BY" clause add columns not in the SELECT provided + * it specifies all the columns in the SELECT? Does anyone actually + * understand what they mean here? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByBeyondSelect() throws SQLException + { + return true; // For now... + } + + /** + * Is the escape character in "LIKE" clauses supported? A + * JDBC compliant driver always returns true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLikeEscapeClause() throws SQLException + { + return true; + } + + /** + * Are multiple ResultSets from a single execute supported? + * Well, I implemented it, but I dont think this is possible from + * the back ends point of view. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleResultSets() throws SQLException + { + return false; + } + + /** + * Can we have multiple transactions open at once (on different + * connections?) + * I guess we can have, since Im relying on it. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMultipleTransactions() throws SQLException + { + return true; + } + + /** + * Can columns be defined as non-nullable. A JDBC Compliant driver + * always returns true. We dont support NOT NULL, so we are not + * JDBC compliant. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsNonNullableColumns() throws SQLException + { + return false; + } + + /** + * Does this driver support the minimum ODBC SQL grammar. This + * grammar is defined at: + * + * http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm + * + * In Appendix C. From this description, we seem to support the + * ODBC minimal (Level 0) grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMinimumSQLGrammar() throws SQLException + { + return true; + } + + /** + * Does this driver support the Core ODBC SQL grammar. We need + * SQL-92 conformance for this. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCoreSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the Extended (Level 2) ODBC SQL + * grammar. We don't conform to the Core (Level 1), so we can't + * conform to the Extended SQL Grammar. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsExtendedSQLGrammar() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 entry level SQL grammar? + * All JDBC Compliant drivers must return true. I think we have + * to support outer joins for this to be true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92EntryLevelSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 intermediate level SQL + * grammar? Anyone who does not support Entry level cannot support + * Intermediate level. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92IntermediateSQL() throws SQLException + { + return false; + } + + /** + * Does this driver support the ANSI-92 full SQL grammar? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92FullSQL() throws SQLException + { + return false; + } + + /** + * Is the SQL Integrity Enhancement Facility supported? + * I haven't seen this mentioned anywhere, so I guess not + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsIntegrityEnhancementFacility() throws SQLException + { + return false; + } + + /** + * Is some form of outer join supported? From my knowledge, nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOuterJoins() throws SQLException + { + return false; + } + + /** + * Are full nexted outer joins supported? Well, we dont support any + * form of outer join, so this is no as well + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsFullOuterJoins() throws SQLException + { + return false; + } + + /** + * Is there limited support for outer joins? (This will be true if + * supportFullOuterJoins is true) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLimitedOuterJoins() throws SQLException + { + return false; + } + + /** + * What is the database vendor's preferred term for "schema" - well, + * we do not provide support for schemas, so lets just use that + * term. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getSchemaTerm() throws SQLException + { + return new String("Schema"); + } + + /** + * What is the database vendor's preferred term for "procedure" - + * I kind of like "Procedure" myself. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getProcedureTerm() throws SQLException + { + return new String("Procedure"); + } + + /** + * What is the database vendor's preferred term for "catalog"? - + * we dont have a preferred term, so just use Catalog + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getCatalogTerm() throws SQLException + { + return new String("Catalog"); + } + + /** + * Does a catalog appear at the start of a qualified table name? + * (Otherwise it appears at the end). + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCatalogAtStart() throws SQLException + { + return false; + } + + /** + * What is the Catalog separator. Hmmm....well, I kind of like + * a period (so we get catalog.table definitions). - I don't think + * PostgreSQL supports catalogs anyhow, so it makes no difference. + * + * @return the catalog separator string + * @exception SQLException if a database access error occurs + */ + public String getCatalogSeparator() throws SQLException + { + return new String("."); + } + + /** + * Can a schema name be used in a data manipulation statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a procedure call statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a schema be used in a table definition statement? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in an index definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a schema name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a data manipulation statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInDataManipulation() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a procedure call statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInProcedureCalls() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a table definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInTableDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in an index definition? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInIndexDefinitions() throws SQLException + { + return false; + } + + /** + * Can a catalog name be used in a privilege definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException + { + return false; + } + + /** + * We support cursors for gets only it seems. I dont see a method + * to get a positioned delete. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedDelete() throws SQLException + { + return false; // For now... + } + + /** + * Is positioned UPDATE supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsPositionedUpdate() throws SQLException + { + return false; // For now... + } + + public boolean supportsSelectForUpdate() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsStoredProcedures() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsSubqueriesInComparisons() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsSubqueriesInExists() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsSubqueriesInIns() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsSubqueriesInQuantifieds() throws SQLException + { + // XXX-Not Implemented + } + + public boolean supportsCorrelatedSubqueries() throws SQLException + { + // XXX-Not Implemented + } + + /** + * Is SQL UNION supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnion() throws SQLException + { + return false; + } + + /** + * Is SQL UNION ALL supported? Nope. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnionAll() throws SQLException + { + return false; + } + + /** + * In PostgreSQL, Cursors are only open within transactions. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossCommit() throws SQLException + { + return false; + } + + /** + * Do we support open cursors across multiple transactions? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenCursorsAcrossRollback() throws SQLException + { + return false; + } + + /** + * Can statements remain open across commits? They may, but + * this driver cannot guarentee that. In further reflection. + * we are talking a Statement object jere, so the answer is + * yes, since the Statement is only a vehicle to ExecSQL() + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossCommit() throws SQLException + { + return true; + } + + /** + * Can statements remain open across rollbacks? They may, but + * this driver cannot guarentee that. In further contemplation, + * we are talking a Statement object here, so the answer is yes, + * since the Statement is only a vehicle to ExecSQL() in Connection + * + * @return true if they always remain open; false otherwise + * @exception SQLException if a database access error occurs + */ + public boolean supportsOpenStatementsAcrossRollback() throws SQLException + { + return true; + } + + /** + * How many hex characters can you have in an inline binary literal + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxBinaryLiteralLength() throws SQLException + { + return 0; // For now... + } + + /** + * What is the maximum length for a character literal + * I suppose it is 8190 (8192 - 2 for the quotes) + * + * @return the max literal length + * @exception SQLException if a database access error occurs + */ + public int getMaxCharLiteralLength() throws SQLException + { + return 8190; + } + + /** + * Whats the limit on column name length. The description of + * pg_class would say '32' (length of pg_class.relname) - we + * should probably do a query for this....but.... + * + * @return the maximum column name length + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of columns in a "GROUP BY" clause? + * + * @return the max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInGroupBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns allowed in an index? + * 6.0 only allowed one column, but 6.1 introduced multi-column + * indices, so, theoretically, its all of them. + * + * @return max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInIndex() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What's the maximum number of columns in an "ORDER BY clause? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInOrderBy() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a "SELECT" list? + * Theoretically, all of them! + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInSelect() throws SQLException + { + return getMaxColumnsInTable(); + } + + /** + * What is the maximum number of columns in a table? From the + * create_table(l) manual page... + * + * "The new class is created as a heap with no initial data. A + * class can have no more than 1600 attributes (realistically, + * this is limited by the fact that tuple sizes must be less than + * 8192 bytes)..." + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInTable() throws SQLException + { + return 1600; + } + + /** + * How many active connection can we have at a time to this + * database? Well, since it depends on postmaster, which just + * does a listen() followed by an accept() and fork(), its + * basically very high. Unless the system runs out of processes, + * it can be 65535 (the number of aux. ports on a TCP/IP system). + * I will return 8192 since that is what even the largest system + * can realistically handle, + * + * @return the maximum number of connections + * @exception SQLException if a database access error occurs + */ + public int getMaxConnections() throws SQLException + { + return 8192; + } + + /** + * What is the maximum cursor name length (the same as all + * the other F***** identifiers!) + * + * @return max cursor name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxCursorNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum length of an index (in bytes)? Now, does + * the spec. mean name of an index (in which case its 32, the + * same as a table) or does it mean length of an index element + * (in which case its 8192, the size of a row) or does it mean + * the number of rows it can access (in which case it 2^32 - + * a 4 byte OID number)? I think its the length of an index + * element, personally, so Im setting it to 8192. + * + * @return max index length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxIndexLength() throws SQLException + { + return 8192; + } + + public int getMaxSchemaNameLength() throws SQLException + { + // XXX-Not Implemented + } + + /** + * What is the maximum length of a procedure name? + * (length of pg_proc.proname used) - again, I really + * should do a query here to get it. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxProcedureNameLength() throws SQLException + { + return 32; + } + + public int getMaxCatalogNameLength() throws SQLException + { + // XXX-Not Implemented + } + + /** + * What is the maximum length of a single row? (not including + * blobs). 8192 is defined in PostgreSQL. + * + * @return max row size in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxRowSize() throws SQLException + { + return 8192; + } + + /** + * Did getMaxRowSize() include LONGVARCHAR and LONGVARBINARY + * blobs? We don't handle blobs yet + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException + { + return false; + } + + /** + * What is the maximum length of a SQL statement? + * + * @return max length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxStatementLength() throws SQLException + { + return 8192; + } + + /** + * How many active statements can we have open at one time to + * this database? Basically, since each Statement downloads + * the results as the query is executed, we can have many. However, + * we can only really have one statement per connection going + * at once (since they are executed serially) - so we return + * one. + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxStatements() throws SQLException + { + return 1; + } + + /** + * What is the maximum length of a table name? This was found + * from pg_class.relname length + * + * @return max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxTableNameLength() throws SQLException + { + return 32; + } + + /** + * What is the maximum number of tables that can be specified + * in a SELECT? Theoretically, this is the same number as the + * number of tables allowable. In practice tho, it is much smaller + * since the number of tables is limited by the statement, we + * return 1024 here - this is just a number I came up with (being + * the number of tables roughly of three characters each that you + * can fit inside a 8192 character buffer with comma separators). + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxTablesInSelect() throws SQLException + { + return 1024; + } + + /** + * What is the maximum length of a user name? Well, we generally + * use UNIX like user names in PostgreSQL, so I think this would + * be 8. However, showing the schema for pg_user shows a length + * for username of 32. + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxUserNameLength() throws SQLException + { + return 32; + } + + + /** + * What is the database's default transaction isolation level? We + * do not support this, so all transactions are SERIALIZABLE. + * + * @return the default isolation level + * @exception SQLException if a database access error occurs + * @see Connection + */ + public int getDefaultTransactionIsolation() throws SQLException + { + return Connection.TRANSACTION_SERIALIZABLE; + } + + /** + * Are transactions supported? If not, commit and rollback are noops + * and the isolation level is TRANSACTION_NONE. We do support + * transactions. + * + * @return true if transactions are supported + * @exception SQLException if a database access error occurs + */ + public boolean supportsTransactions() throws SQLException + { + return true; + } + + /** + * Does the database support the given transaction isolation level? + * We only support TRANSACTION_SERIALIZABLE + * + * @param level the values are defined in java.sql.Connection + * @return true if so + * @exception SQLException if a database access error occurs + * @see Connection + */ + public boolean supportsTransactionIsolationLevel(int level) throws SQLException + { + if (level == Connection.TRANSACTION_SERIALIZABLE) + return true; + else + return false; + } + + /** + * Are both data definition and data manipulation transactions + * supported? I checked it, and could not do a CREATE TABLE + * within a transaction, so I am assuming that we don't + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException + { + return false; + } + + /** + * Are only data manipulation statements withing a transaction + * supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataManipulationTransactionsOnly() throws SQLException + { + return true; + } + + /** + * Does a data definition statement within a transaction force + * the transaction to commit? I think this means something like: + * + * CREATE TABLE T (A INT); + * INSERT INTO T (A) VALUES (2); + * BEGIN; + * UPDATE T SET A = A + 1; + * CREATE TABLE X (A INT); + * SELECT A FROM T INTO X; + * COMMIT; + * + * does the CREATE TABLE call cause a commit? The answer is no. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionCausesTransactionCommit() throws SQLException + { + return false; + } + + /** + * Is a data definition statement within a transaction ignored? + * It seems to be (from experiment in previous method) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionIgnoredInTransactions() throws SQLException + { + return false; + } + + /** + * Get a description of stored procedures available in a catalog + * + * Only procedure descriptions matching the schema and procedure + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME + * + * Each procedure description has the following columns: + * PROCEDURE_CAT String => procedure catalog (may be null) + * PROCEDURE_SCHEM String => procedure schema (may be null) + * PROCEDURE_NAME String => procedure name + * Field 4 reserved (make it null) + * Field 5 reserved (make it null) + * Field 6 reserved (make it null) + * REMARKS String => explanatory comment on the procedure + * PROCEDURE_TYPE short => kind of procedure + * * procedureResultUnknown - May return a result + * * procedureNoResult - Does not return a result + * * procedureReturnsResult - Returns a result + * + * @param catalog - a catalog name; "" retrieves those without a + * catalog; null means drop catalog name from criteria + * @param schemaParrern - a schema name pattern; "" retrieves those + * without a schema - we ignore this parameter + * @param procedureNamePattern - a procedure name pattern + * @return ResultSet - each row is a procedure description + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException + { + Field[] f = new Field[8]; // the field descriptors for the new ResultSet + static final int iVarcharOid = 1043; // This is the OID for a varchar() + static final int iInt2Oid = 21; // This is the OID for an int2 + ResultSet r; // ResultSet for the SQL query that we need to do + Vector v; // The new ResultSet tuple stuff + String remarks = new String("no remarks"); + + Field[0] = new Field(conn, new String("PROCEDURE_CAT"), iVarcharOid, 32); + Field[1] = new Field(conn, new String("PROCEDURE_SCHEM"), iVarcharOid, 32); + Field[2] = new Field(conn, new String("PROCEDURE_NAME"), iVarcharOid, 32); + Field[3] = null; + Field[4] = null; + Field[5] = null; + Field[6] = new Field(conn, new String("REMARKS"), iVarcharOid, 8192); + Field[7] = new Field(conn, new String("PROCEDURE_TYPE"), iInt2Oid, 2); + r = conn.ExecSQL("select proname, proretset from pg_proc order by proname"); + if (r.getColumnCount() != 2 || r.getTupleCount() <= 1) + throw new SQLException("Unexpected return from query for procedure list"); + while (r.next()) + { + byte[][] tuple = new byte[8][0]; + + String name = r.getString(1); + String remarks = new String("no remarks"); + boolean retset = r.getBoolean(2); + + byte[0] = null; // Catalog name + byte[1] = null; // Schema name + byte[2] = name.getBytes(); // Procedure name + byte[3] = null; // Reserved + byte[4] = null; // Reserved + byte[5] = null; // Reserved + byte[6] = remarks.getBytes(); // Remarks + if (retset) + byte[7] = procedureReturnsResult; + else + byte[7] = procedureNoResult; + v.addElement(byte); + } + return new ResultSet(conn, f, v, "OK", 1); + } + + public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getSchemas() throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getCatalogs() throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getTableTypes() throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getTypeInfo() throws SQLException + { + // XXX-Not Implemented + } + + public java.sql.ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException + { + // XXX-Not Implemented + } +} diff --git a/src/interfaces/jdbc/postgresql/Driver.java b/src/interfaces/jdbc/postgresql/Driver.java new file mode 100644 index 0000000000..055a65601a --- /dev/null +++ b/src/interfaces/jdbc/postgresql/Driver.java @@ -0,0 +1,269 @@ +package postgresql; + +import java.sql.*; +import java.util.*; +import postgresql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * The Java SQL framework allows for multiple database drivers. Each + * driver should supply a class that implements the Driver interface + * + * The DriverManager will try to load as many drivers as it can find and then + * for any given connection request, it will ask each driver in turn to try + * to connect to the target URL. + * + * It is strongly recommended that each Driver class should be small and + * standalone so that the Driver class can be loaded and queried without + * bringing in vast quantities of supporting code. + * + * When a Driver class is loaded, it should create an instance of itself and + * register it with the DriverManager. This means that a user can load and + * register a driver by doing Class.forName("foo.bah.Driver") + * + * @see postgresql.Connection + * @see java.sql.Driver + */ +public class Driver implements java.sql.Driver +{ + + static + { + try + { + new Driver(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + /** + * Construct a new driver and register it with DriverManager + * + * @exception SQLException for who knows what! + */ + public Driver() throws SQLException + { + java.sql.DriverManager.registerDriver(this); + } + + /** + * Try to make a database connection to the given URL. The driver + * should return "null" if it realizes it is the wrong kind of + * driver to connect to the given URL. This will be common, as + * when the JDBC driverManager is asked to connect to a given URL, + * it passes the URL to each loaded driver in turn. + * + * The driver should raise an SQLException if it is the right driver + * to connect to the given URL, but has trouble connecting to the + * database. + * + * The java.util.Properties argument can be used to pass arbitrary + * string tag/value pairs as connection arguments. Normally, at least + * "user" and "password" properties should be included in the + * properties. + * + * Our protocol takes the form: + *
+	 *	jdbc:postgresql://host:port/database
+	 * 
+ * + * @param url the URL of the database to connect to + * @param info a list of arbitrary tag/value pairs as connection + * arguments + * @return a connection to the URL or null if it isnt us + * @exception SQLException if a database access error occurs + * @see java.sql.Driver#connect + */ + public java.sql.Connection connect(String url, Properties info) throws SQLException + { + DriverURL dr = new DriverURL(url); + int port; + + if (!(dr.protocol().equals("jdbc"))) + return null; + if (!(dr.subprotocol().equals("postgresql"))) + return null; + if (dr.host().equals("unknown")) + return null; + port = dr.port(); + if (port == -1) + port = 5432; // Default PostgreSQL port + return new Connection (dr.host(), port, info, dr.database(), url, this); + } + + /** + * Returns true if the driver thinks it can open a connection to the + * given URL. Typically, drivers will return true if they understand + * the subprotocol specified in the URL and false if they don't. Our + * protocols start with jdbc:postgresql: + * + * @see java.sql.Driver#acceptsURL + * @param url the URL of the driver + * @return true if this driver accepts the given URL + * @exception SQLException if a database-access error occurs + * (Dont know why it would *shrug*) + */ + public boolean acceptsURL(String url) throws SQLException + { + DriverURL dr = new DriverURL(url); + + if (dr.protocol().equals("jdbc")) + if (dr.subprotocol().equals("postgresql")) + return true; + return false; + } + + /** + * The getPropertyInfo method is intended to allow a generic GUI + * tool to discover what properties it should prompt a human for + * in order to get enough information to connect to a database. + * Note that depending on the values the human has supplied so + * far, additional values may become necessary, so it may be necessary + * to iterate through several calls to getPropertyInfo + * + * @param url the Url of the database to connect to + * @param info a proposed list of tag/value pairs that will be sent on + * connect open. + * @return An array of DriverPropertyInfo objects describing + * possible properties. This array may be an empty array if + * no properties are required + * @exception SQLException if a database-access error occurs + * @see java.sql.Driver#getPropertyInfo + */ + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException + { + return null; // We don't need anything except + // the username, which is a default + } + + /** + * Gets the drivers major version number + * + * @return the drivers major version number + */ + public int getMajorVersion() + { + return 1; + } + + /** + * Get the drivers minor version number + * + * @return the drivers minor version number + */ + public int getMinorVersion() + { + return 0; + } + + /** + * Report whether the driver is a genuine JDBC compliant driver. A + * driver may only report "true" here if it passes the JDBC compliance + * tests, otherwise it is required to return false. JDBC compliance + * requires full support for the JDBC API and full support for SQL 92 + * Entry Level. + */ + public boolean jdbcCompliant() + { + return false; + } +} + +/** + * The DriverURL class splits a JDBC URL into its subcomponents + * + * protocol:subprotocol:/[/host[:port]/][database] + */ +class DriverURL +{ + private String protocol, subprotocol, host, database; + private int port = -1; + + /** + * Constructs a new DriverURL, splitting the specified URL into its + * component parts + */ + public DriverURL(String url) throws SQLException + { + int a, b, c; + String tmp, hostport, dbportion; + + a = url.indexOf(':'); + if (a == -1) + throw new SQLException("Bad URL Protocol specifier"); + b = url.indexOf(':', a+1); + if (b == -1) + throw new SQLException("Bad URL Subprotocol specifier"); + protocol = new String(url.substring(0, a)); + subprotocol = new String(url.substring(a+1, b)); + tmp = new String(url.substring(b+1, url.length())); + if (tmp.length() < 2) + throw new SQLException("Bad URL Database specifier"); + if (!tmp.substring(0, 2).equals("//")) + { + host = new String("unknown"); + port = -1; + database = new String(tmp.substring(1, tmp.length())); + return; + } + dbportion = new String(tmp.substring(2, tmp.length())); + c = dbportion.indexOf('/'); + if (c == -1) + throw new SQLException("Bad URL Database specifier"); + a = dbportion.indexOf(':'); + if (a == -1) + { + host = new String(dbportion.substring(0, c)); + port = -1; + database = new String(dbportion.substring(c+1, dbportion.length())); + } else { + host = new String(dbportion.substring(0, a)); + port = Integer.valueOf(dbportion.substring(a+1, c)).intValue(); + database = new String(dbportion.substring(c+1, dbportion.length())); + } + } + + /** + * Returns the protocol name of the DriverURL + */ + public String protocol() + { + return protocol; + } + + /** + * Returns the subprotocol name of the DriverURL + */ + public String subprotocol() + { + return subprotocol; + } + + /** + * Returns the hostname portion of the URL + */ + public String host() + { + return host; + } + + /** + * Returns the port number portion of the URL + * or -1 if no port was specified + */ + public int port() + { + return port; + } + + /** + * Returns the database name of the URL + */ + public String database() + { + return database; + } +} diff --git a/src/interfaces/jdbc/postgresql/Field.java b/src/interfaces/jdbc/postgresql/Field.java new file mode 100644 index 0000000000..3d27dc6c78 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/Field.java @@ -0,0 +1,89 @@ +package postgresql; + +import java.lang.*; +import java.sql.*; +import java.util.*; +import postgresql.*; + +/** + * postgresql.Field is a class used to describe fields in a PostgreSQL ResultSet + * + * @version 1.0 15-APR-1997 + * @author Adrian Hall + */ +public class Field +{ + int length; // Internal Length of this field + int oid; // OID of the type + Connection conn; // Connection Instantation + String name; // Name of this field + + int sql_type = -1; // The entry in java.sql.Types for this field + String type_name = null;// The sql type name + + /** + * Construct a field based on the information fed to it. + * + * @param conn the connection this field came from + * @param name the name of the field + * @param oid the OID of the field + * @param len the length of the field + */ + public Field(Connection conn, String name, int oid, int length) + { + this.conn = conn; + this.name = name; + this.oid = oid; + this.length = length; + } + + /** + * the ResultSet and ResultMetaData both need to handle the SQL + * type, which is gained from another query. Note that we cannot + * use getObject() in this, since getObject uses getSQLType(). + * + * @return the entry in Types that refers to this field + * @exception SQLException if a database access error occurs + */ + public int getSQLType() throws SQLException + { + if (sql_type == -1) + { + ResultSet result = (postgresql.ResultSet)conn.ExecSQL("select typname from pg_type where oid = " + oid); + if (result.getColumnCount() != 1 || result.getTupleCount() != 1) + throw new SQLException("Unexpected return from query for type"); + result.next(); + type_name = result.getString(1); + if (type_name.equals("int2")) sql_type = Types.SMALLINT; + else if (type_name.equals("int4")) sql_type = Types.INTEGER; + else if (type_name.equals("int8")) sql_type = Types.BIGINT; + else if (type_name.equals("cash")) sql_type = Types.DECIMAL; + else if (type_name.equals("money")) sql_type = Types.DECIMAL; + else if (type_name.equals("float4")) sql_type = Types.REAL; + else if (type_name.equals("float8")) sql_type = Types.DOUBLE; + else if (type_name.equals("bpchar")) sql_type = Types.CHAR; + else if (type_name.equals("varchar")) sql_type = Types.VARCHAR; + else if (type_name.equals("bool")) sql_type = Types.BIT; + else if (type_name.equals("date")) sql_type = Types.DATE; + else if (type_name.equals("time")) sql_type = Types.TIME; + else if (type_name.equals("abstime")) sql_type = Types.TIMESTAMP; + else sql_type = Types.OTHER; + } + return sql_type; + } + + /** + * We also need to get the type name as returned by the back end. + * This is held in type_name AFTER a call to getSQLType. Since + * we get this information within getSQLType (if it isn't already + * done), we can just call getSQLType and throw away the result. + * + * @return the String representation of the type of this field + * @exception SQLException if a database access error occurs + */ + public String getTypeName() throws SQLException + { + int sql = getSQLType(); + return type_name; + } +} diff --git a/src/interfaces/jdbc/postgresql/PG_Object.java b/src/interfaces/jdbc/postgresql/PG_Object.java new file mode 100644 index 0000000000..89518dc0d8 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/PG_Object.java @@ -0,0 +1,31 @@ +package postgresql; + +import java.lang.*; +import java.sql.*; +import java.util.*; +import postgresql.*; + +/** + * postgresql.PG_Object is a class used to describe unknown types + * An unknown type is any type that is unknown by JDBC Standards + * + * @version 1.0 15-APR-1997 + * @author Adrian Hall + */ +public class PG_Object +{ + public String type; + public String value; + + /** + * Constructor for the PostgreSQL generic object + * + * @param type a string describing the type of the object + * @param value a string representation of the value of the object + */ + public PG_Object(String type, String value) + { + this.type = type; + this.value = value; + } +} diff --git a/src/interfaces/jdbc/postgresql/PreparedStatement.java b/src/interfaces/jdbc/postgresql/PreparedStatement.java new file mode 100644 index 0000000000..98fdb6fb10 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/PreparedStatement.java @@ -0,0 +1,538 @@ +package postgresql; + +import java.io.*; +import java.math.*; +import java.sql.*; +import java.text.*; +import java.util.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * A SQL Statement is pre-compiled and stored in a PreparedStatement object. + * This object can then be used to efficiently execute this statement multiple + * times. + * + * Note: The setXXX methods for setting IN parameter values must + * specify types that are compatible with the defined SQL type of the input + * parameter. For instance, if the IN parameter has SQL type Integer, then + * setInt should be used. + * + * If arbitrary parameter type conversions are required, then the setObject + * method should be used with a target SQL type. + * + * @see ResultSet + * @see java.sql.PreparedStatement + */ +public class PreparedStatement extends Statement implements java.sql.PreparedStatement +{ + String sql; + String[] templateStrings; + String[] inStrings; + Connection connection; + + /** + * Constructor for the PreparedStatement class. Split the SQL statement + * into segments - separated by the arguments. When we rebuild the + * thing with the arguments, we can substitute the args and join the + * whole thing together. + * + * @param conn the instanatiating connection + * @param sql the SQL statement with ? for IN markers + * @exception SQLException if something bad occurs + */ + public PreparedStatement(Connection connection, String sql) throws SQLException + { + super(connection); + + Vector v = new Vector(); + boolean inQuotes = false; + int lastParmEnd = 0, i; + + this.sql = sql; + this.connection = connection; + for (i = 0; i < sql.length(); ++i) + { + int c = sql.charAt(i); + + if (c == '\'') + inQuotes = !inQuotes; + if (c == '?' && !inQuotes) + { + v.addElement(sql.substring (lastParmEnd, i)); + lastParmEnd = i + 1; + } + } + v.addElement(sql.substring (lastParmEnd, sql.length())); + + templateStrings = new String[v.size()]; + inStrings = new String[v.size() - 1]; + clearParameters(); + + for (i = 0 ; i < templateStrings.length; ++i) + templateStrings[i] = (String)v.elementAt(i); + } + + /** + * A Prepared SQL query is executed and its ResultSet is returned + * + * @return a ResultSet that contains the data produced by the + * query - never null + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new SQLException("No value specified for parameter " + (i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeQuery(s.toString()); // in Statement class + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, + * SQL statements that return nothing such as SQL DDL statements can + * be executed. + * + * @return either the row count for INSERT, UPDATE or DELETE; or + * 0 for SQL statements that return nothing. + * @exception SQLException if a database access error occurs + */ + public int executeUpdate() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new SQLException("No value specified for parameter " + (i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.executeUpdate(s.toString()); // in Statement class + } + + /** + * Set a parameter to SQL NULL + * + * Note: You must specify the parameters SQL type (although + * PostgreSQL ignores it) + * + * @param parameterIndex the first parameter is 1, etc... + * @param sqlType the SQL type code defined in java.sql.Types + * @exception SQLException if a database access error occurs + */ + public void setNull(int parameterIndex, int sqlType) throws SQLException + { + set(parameterIndex, "null"); + } + + /** + * Set a parameter to a Java boolean value. The driver converts this + * to a SQL BIT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBoolean(int parameterIndex, boolean x) throws SQLException + { + set(parameterIndex, x ? "'t'" : "'f'"); + } + + /** + * Set a parameter to a Java byte value. The driver converts this to + * a SQL TINYINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setByte(int parameterIndex, byte x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java short value. The driver converts this + * to a SQL SMALLINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setShort(int parameterIndex, short x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java int value. The driver converts this to + * a SQL INTEGER value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setInt(int parameterIndex, int x) throws SQLException + { + set(parameterIndex, (new Integer(x)).toString()); + } + + /** + * Set a parameter to a Java long value. The driver converts this to + * a SQL BIGINT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setLong(int parameterIndex, long x) throws SQLException + { + set(parameterIndex, (new Long(x)).toString()); + } + + /** + * Set a parameter to a Java float value. The driver converts this + * to a SQL FLOAT value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setFloat(int parameterIndex, float x) throws SQLException + { + set(parameterIndex, (new Float(x)).toString()); + } + + /** + * Set a parameter to a Java double value. The driver converts this + * to a SQL DOUBLE value when it sends it to the database + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDouble(int parameterIndex, double x) throws SQLException + { + set(parameterIndex, (new Double(x)).toString()); + } + + /** + * Set a parameter to a java.lang.BigDecimal value. The driver + * converts this to a SQL NUMERIC value when it sends it to the + * database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException + { + set(parameterIndex, x.toString()); + } + + /** + * Set a parameter to a Java String value. The driver converts this + * to a SQL VARCHAR or LONGVARCHAR value (depending on the arguments + * size relative to the driver's limits on VARCHARs) when it sends it + * to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setString(int parameterIndex, String x) throws SQLException + { + StringBuffer b = new StringBuffer(); + int i; + + b.append('\''); + for (i = 0 ; i < x.length() ; ++i) + { + char c = x.charAt(i); + if (c == '\\' || c == '\'') + b.append((char)'\\'); + b.append(c); + } + b.append('\''); + set(parameterIndex, b.toString()); + } + + /** + * Set a parameter to a Java array of bytes. The driver converts this + * to a SQL VARBINARY or LONGVARBINARY (depending on the argument's + * size relative to the driver's limits on VARBINARYs) when it sends + * it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBytes(int parameterIndex, byte x[]) throws SQLException + { + throw new SQLException("Binary Data not supported"); + } + + /** + * Set a parameter to a java.sql.Date value. The driver converts this + * to a SQL DATE value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setDate(int parameterIndex, java.sql.Date x) throws SQLException + { + DateFormat df = DateFormat.getDateInstance(); + + set(parameterIndex, "'" + df.format(x) + "'"); + } + + /** + * Set a parameter to a java.sql.Time value. The driver converts + * this to a SQL TIME value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTime(int parameterIndex, Time x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * Set a parameter to a java.sql.Timestamp value. The driver converts + * this to a SQL TIMESTAMP value when it sends it to the database. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException + { + set(parameterIndex, "'" + x.toString() + "'"); + } + + /** + * When a very large ASCII value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * ASCII to the database char format. + * + * Note: This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @param length the number of bytes in the stream + * @exception SQLException if a database access error occurs + */ + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large Unicode value is input to a LONGVARCHAR parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. The JDBC driver will do any necessary conversion from + * UNICODE to the database char format. + * + * Note: This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException + { + setBinaryStream(parameterIndex, x, length); + } + + /** + * When a very large binary value is input to a LONGVARBINARY parameter, + * it may be more practical to send it via a java.io.InputStream. + * JDBC will read the data from the stream as needed, until it reaches + * end-of-file. + * + * Note: This stream object can either be a standard Java + * stream object or your own subclass that implements the standard + * interface. + * + * @param parameterIndex the first parameter is 1... + * @param x the parameter value + * @exception SQLException if a database access error occurs + */ + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException + { + throw new SQLException("InputStream as parameter not supported"); + } + + /** + * In general, parameter values remain in force for repeated used of a + * Statement. Setting a parameter value automatically clears its + * previous value. However, in coms cases, it is useful to immediately + * release the resources used by the current parameter values; this + * can be done by calling clearParameters + * + * @exception SQLException if a database access error occurs + */ + public void clearParameters() throws SQLException + { + int i; + + for (i = 0 ; i < inStrings.length ; i++) + inStrings[i] = null; + } + + /** + * Set the value of a parameter using an object; use the java.lang + * equivalent objects for integral values. + * + * The given Java object will be converted to the targetSqlType before + * being sent to the database. + * + * note that this method may be used to pass database-specific + * abstract data types. This is done by using a Driver-specific + * Java type and using a targetSqlType of java.sql.Types.OTHER + * + * @param parameterIndex the first parameter is 1... + * @param x the object containing the input parameter value + * @param targetSqlType The SQL type to be send to the database + * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC + * types this is the number of digits after the decimal. For + * all other types this value will be ignored. + * @exception SQLException if a database access error occurs + */ + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException + { + switch (targetSqlType) + { + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + case Types.DECIMAL: + case Types.NUMERIC: + if (x instanceof Boolean) + set(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0"); + else + set(parameterIndex, x.toString()); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + case Types.DATE: + setDate(parameterIndex, (java.sql.Date)x); + case Types.TIME: + setTime(parameterIndex, (Time)x); + case Types.TIMESTAMP: + setTimestamp(parameterIndex, (Timestamp)x); + case Types.OTHER: + setString(parameterIndex, ((PG_Object)x).value); + default: + throw new SQLException("Unknown Types value"); + } + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException + { + setObject(parameterIndex, x, targetSqlType, 0); + } + + public void setObject(int parameterIndex, Object x) throws SQLException + { + if (x instanceof String) + setString(parameterIndex, (String)x); + else if (x instanceof BigDecimal) + setBigDecimal(parameterIndex, (BigDecimal)x); + else if (x instanceof Integer) + setInt(parameterIndex, ((Integer)x).intValue()); + else if (x instanceof Long) + setLong(parameterIndex, ((Long)x).longValue()); + else if (x instanceof Float) + setFloat(parameterIndex, ((Float)x).floatValue()); + else if (x instanceof Double) + setDouble(parameterIndex, ((Double)x).doubleValue()); + else if (x instanceof byte[]) + setBytes(parameterIndex, (byte[])x); + else if (x instanceof java.sql.Date) + setDate(parameterIndex, (java.sql.Date)x); + else if (x instanceof Time) + setTime(parameterIndex, (Time)x); + else if (x instanceof Timestamp) + setTimestamp(parameterIndex, (Timestamp)x); + else if (x instanceof Boolean) + setBoolean(parameterIndex, ((Boolean)x).booleanValue()); + else if (x instanceof PG_Object) + setString(parameterIndex, ((PG_Object)x).value); + else + throw new SQLException("Unknown object type"); + } + + /** + * Some prepared statements return multiple results; the execute method + * handles these complex statements as well as the simpler form of + * statements handled by executeQuery and executeUpdate + * + * @return true if the next result is a ResultSet; false if it is an + * update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute() throws SQLException + { + StringBuffer s = new StringBuffer(); + int i; + + for (i = 0 ; i < inStrings.length ; ++i) + { + if (inStrings[i] == null) + throw new SQLException("No value specified for parameter " + (i + 1)); + s.append (templateStrings[i]); + s.append (inStrings[i]); + } + s.append(templateStrings[inStrings.length]); + return super.execute(s.toString()); // in Statement class + } + + // ************************************************************** + // END OF PUBLIC INTERFACE + // ************************************************************** + + /** + * There are a lot of setXXX classes which all basically do + * the same thing. We need a method which actually does the + * set for us. + * + * @param paramIndex the index into the inString + * @param s a string to be stored + * @exception SQLException if something goes wrong + */ + private void set(int paramIndex, String s) throws SQLException + { + if (paramIndex < 1 || paramIndex > inStrings.length) + throw new SQLException("Parameter index out of range"); + inStrings[paramIndex - 1] = s; + } +} diff --git a/src/interfaces/jdbc/postgresql/ResultSet.java b/src/interfaces/jdbc/postgresql/ResultSet.java new file mode 100644 index 0000000000..c5894e7bff --- /dev/null +++ b/src/interfaces/jdbc/postgresql/ResultSet.java @@ -0,0 +1,845 @@ +package postgresql; + +import java.lang.*; +import java.io.*; +import java.math.*; +import java.text.*; +import java.util.*; +import java.sql.*; +import postgresql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * A ResultSet provides access to a table of data generated by executing a + * Statement. The table rows are retrieved in sequence. Within a row its + * column values can be accessed in any order. + * + * A ResultSet maintains a cursor pointing to its current row of data. + * Initially the cursor is positioned before the first row. The 'next' + * method moves the cursor to the next row. + * + * The getXXX methods retrieve column values for the current row. You can + * retrieve values either using the index number of the column, or by using + * the name of the column. In general using the column index will be more + * efficient. Columns are numbered from 1. + * + * For maximum portability, ResultSet columns within each row should be read + * in left-to-right order and each column should be read only once. + * + * For the getXXX methods, the JDBC driver attempts to convert the underlying + * data to the specified Java type and returns a suitable Java value. See the + * JDBC specification for allowable mappings from SQL types to Java types with + * the ResultSet getXXX methods. + * + * Column names used as input to getXXX methods are case insenstive. When + * performing a getXXX using a column name, if several columns have the same + * name, then the value of the first matching column will be returned. The + * column name option is designed to be used when column names are used in the + * SQL Query. For columns that are NOT explicitly named in the query, it is + * best to use column numbers. If column names were used there is no way for + * the programmer to guarentee that they actually refer to the intended + * columns. + * + * A ResultSet is automatically closed by the Statement that generated it + * when that Statement is closed, re-executed, or is used to retrieve the + * next result from a sequence of multiple results. + * + * The number, types and properties of a ResultSet's columns are provided by + * the ResultSetMetaData object returned by the getMetaData method. + * + * @see ResultSetMetaData + * @see java.sql.ResultSet + */ +public class ResultSet implements java.sql.ResultSet +{ + Vector rows; // The results + Field fields[]; // The field descriptions + String status; // Status of the result + int updateCount; // How many rows did we get back? + int current_row; // Our pointer to where we are at + byte[][] this_row; // the current row result + Connection connection; // the connection which we returned from + SQLWarning warnings = null; // The warning chain + boolean wasNullFlag = false; // the flag for wasNull() + + // We can chain multiple resultSets together - this points to + // next resultSet in the chain. + private ResultSet next = null; + + /** + * Create a new ResultSet - Note that we create ResultSets to + * represent the results of everything. + * + * @param fields an array of Field objects (basically, the + * ResultSet MetaData) + * @param tuples Vector of the actual data + * @param status the status string returned from the back end + * @param updateCount the number of rows affected by the operation + * @param cursor the positioned update/delete cursor name + */ + public ResultSet(Connection conn, Field[] fields, Vector tuples, String status, int updateCount) + { + this.connection = conn; + this.fields = fields; + this.rows = tuples; + this.status = status; + this.updateCount = updateCount; + this.this_row = null; + this.current_row = -1; + } + + /** + * A ResultSet is initially positioned before its first row, + * the first call to next makes the first row the current row; + * the second call makes the second row the current row, etc. + * + * If an input stream from the previous row is open, it is + * implicitly closed. The ResultSet's warning chain is cleared + * when a new row is read + * + * @return true if the new current is valid; false if there are no + * more rows + * @exception SQLException if a database access error occurs + */ + public boolean next() throws SQLException + { + if (++current_row >= rows.size()) + return false; + this_row = (byte [][])rows.elementAt(current_row); + return true; + } + + /** + * In some cases, it is desirable to immediately release a ResultSet + * database and JDBC resources instead of waiting for this to happen + * when it is automatically closed. The close method provides this + * immediate release. + * + * Note: A ResultSet is automatically closed by the Statement + * the Statement that generated it when that Statement is closed, + * re-executed, or is used to retrieve the next result from a sequence + * of multiple results. A ResultSet is also automatically closed + * when it is garbage collected. + * + * @exception SQLException if a database access error occurs + */ + public void close() throws SQLException + { + // No-op + } + + /** + * A column may have the value of SQL NULL; wasNull() reports whether + * the last column read had this special value. Note that you must + * first call getXXX on a column to try to read its value and then + * call wasNull() to find if the value was SQL NULL + * + * @return true if the last column read was SQL NULL + * @exception SQLException if a database access error occurred + */ + public boolean wasNull() throws SQLException + { + return wasNullFlag; + } + + /** + * Get the value of a column in the current row as a Java String + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, null for SQL NULL + * @exception SQLException if a database access error occurs + */ + public String getString(int columnIndex) throws SQLException + { + byte[] bytes = getBytes(columnIndex); + + if (bytes == null) + return null; + return new String(bytes); + } + + /** + * Get the value of a column in the current row as a Java boolean + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value, false for SQL NULL + * @exception SQLException if a database access error occurs + */ + public boolean getBoolean(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + int c = s.charAt(0); + return ((c == 't') || (c == 'T')); + } + return false; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public byte getByte(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Byte.parseByte(s); + } catch (NumberFormatException e) { + throw new SQLException("Bad Byte Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java short. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public short getShort(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Short.parseShort(s); + } catch (NumberFormatException e) { + throw new SQLException("Bad Short Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java int. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public int getInt(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new SQLException ("Bad Integer Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java long. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public long getLong(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new SQLException ("Bad Long Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java float. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public float getFloat(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Float.valueOf(s).floatValue(); + } catch (NumberFormatException e) { + throw new SQLException ("Bad Float Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java double. + * + * @param columnIndex the first column is 1, the second is 2,... + * @return the column value; 0 if SQL NULL + * @exception SQLException if a database access error occurs + */ + public double getDouble(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + return Double.valueOf(s).doubleValue(); + } catch (NumberFormatException e) { + throw new SQLException ("Bad Double Form: " + s); + } + } + return 0; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.lang.BigDecimal object + * + * @param columnIndex the first column is 1, the second is 2... + * @param scale the number of digits to the right of the decimal + * @return the column value; if the value is SQL NULL, null + * @exception SQLException if a database access error occurs + */ + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException + { + String s = getString(columnIndex); + BigDecimal val; + + if (s != null) + { + try + { + val = new BigDecimal(s); + } catch (NumberFormatException e) { + throw new SQLException ("Bad BigDecimal Form: " + s); + } + try + { + return val.setScale(scale); + } catch (ArithmeticException e) { + throw new SQLException ("Bad BigDecimal Form: " + s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a Java byte array + * The bytes represent the raw values returned by the driver. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return the column value; if the value is SQL NULL, the result + * is null + * @exception SQLException if a database access error occurs + */ + public byte[] getBytes(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new SQLException("Column Index out of range"); + wasNullFlag = (this_row[columnIndex - 1] == null); + return this_row[columnIndex - 1]; + } + + /** + * Get the value of a column in the current row as a java.sql.Date + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public java.sql.Date getDate(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + if (s.length() != 10) + throw new NumberFormatException("Wrong Length!"); + int mon = Integer.parseInt(s.substring(0,2)); + int day = Integer.parseInt(s.substring(3,5)); + int yr = Integer.parseInt(s.substring(6)); + return new java.sql.Date(yr - 1900, mon -1, day); + } catch (NumberFormatException e) { + throw new SQLException("Bad Date Form: " + s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a java.sql.Time + * object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Time getTime(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null) + { + try + { + if (s.length() != 5 && s.length() != 8) + throw new NumberFormatException("Wrong Length!"); + int hr = Integer.parseInt(s.substring(0,2)); + int min = Integer.parseInt(s.substring(3,5)); + int sec = (s.length() == 5) ? 0 : Integer.parseInt(s.substring(6)); + return new Time(hr, min, sec); + } catch (NumberFormatException e) { + throw new SQLException ("Bad Time Form: " + s); + } + } + return null; // SQL NULL + } + + /** + * Get the value of a column in the current row as a + * java.sql.Timestamp object + * + * @param columnIndex the first column is 1, the second is 2... + * @return the column value; null if SQL NULL + * @exception SQLException if a database access error occurs + */ + public Timestamp getTimestamp(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + DateFormat df = DateFormat.getDateInstance(); + + if (s != null) + { + try + { + java.sql.Date d = (java.sql.Date)df.parse(s); + return new Timestamp(d.getTime()); + } catch (ParseException e) { + throw new SQLException("Bad Timestamp Format: " + s); + } + } + return null; // SQL NULL + } + + /** + * A column value can be retrieved as a stream of ASCII characters + * and then read in chunks from the stream. This method is + * particular suitable for retrieving large LONGVARCHAR values. + * The JDBC driver will do any necessary conversion from the + * database format into ASCII. + * + * Note: All the data in the returned stream must be read + * prior to getting the value of any other column. The next call + * to a get method implicitly closes the stream. Also, a stream + * may return 0 for available() whether there is data available + * or not. + * + * We implement an ASCII stream as a Binary stream - we should really + * do the data conversion, but I cannot be bothered to implement this + * right now. + * + * @param columnIndex the first column is 1, the second is 2, ... + * @return a Java InputStream that delivers the database column + * value as a stream of one byte ASCII characters. If the + * value is SQL NULL then the result is null + * @exception SQLException if a database access error occurs + * @see getBinaryStream + */ + public InputStream getAsciiStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a stream of Unicode + * characters. We implement this as a binary stream. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of two byte Unicode characters. If the value is + * SQL NULL, then the result is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getBinaryStream + */ + public InputStream getUnicodeStream(int columnIndex) throws SQLException + { + return getBinaryStream(columnIndex); + } + + /** + * A column value can also be retrieved as a binary strea. This + * method is suitable for retrieving LONGVARBINARY values. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Java InputStream that delivers the database column value + * as a stream of two byte Unicode characters. If the value is + * SQL NULL, then the result is null + * @exception SQLException if a database access error occurs + * @see getAsciiStream + * @see getUnicodeStream + */ + public InputStream getBinaryStream(int columnIndex) throws SQLException + { + byte b[] = getBytes(columnIndex); + + if (b != null) + return new ByteArrayInputStream(b); + return null; // SQL NULL + } + + /** + * The following routines simply convert the columnName into + * a columnIndex and then call the appropriate routine above. + * + * @param columnName is the SQL name of the column + * @return the column value + * @exception SQLException if a database access error occurs + */ + public String getString(String columnName) throws SQLException + { + return getString(findColumn(columnName)); + } + + public boolean getBoolean(String columnName) throws SQLException + { + return getBoolean(findColumn(columnName)); + } + + public byte getByte(String columnName) throws SQLException + { + + return getByte(findColumn(columnName)); + } + + public short getShort(String columnName) throws SQLException + { + return getShort(findColumn(columnName)); + } + + public int getInt(String columnName) throws SQLException + { + return getInt(findColumn(columnName)); + } + + public long getLong(String columnName) throws SQLException + { + return getLong(findColumn(columnName)); + } + + public float getFloat(String columnName) throws SQLException + { + return getFloat(findColumn(columnName)); + } + + public double getDouble(String columnName) throws SQLException + { + return getDouble(findColumn(columnName)); + } + + public BigDecimal getBigDecimal(String columnName, int scale) throws SQLException + { + return getBigDecimal(findColumn(columnName), scale); + } + + public byte[] getBytes(String columnName) throws SQLException + { + return getBytes(findColumn(columnName)); + } + + public java.sql.Date getDate(String columnName) throws SQLException + { + return getDate(findColumn(columnName)); + } + + public Time getTime(String columnName) throws SQLException + { + return getTime(findColumn(columnName)); + } + + public Timestamp getTimestamp(String columnName) throws SQLException + { + return getTimestamp(findColumn(columnName)); + } + + public InputStream getAsciiStream(String columnName) throws SQLException + { + return getAsciiStream(findColumn(columnName)); + } + + public InputStream getUnicodeStream(String columnName) throws SQLException + { + return getUnicodeStream(findColumn(columnName)); + } + + public InputStream getBinaryStream(String columnName) throws SQLException + { + return getBinaryStream(findColumn(columnName)); + } + + /** + * The first warning reported by calls on this ResultSet is + * returned. Subsequent ResultSet warnings will be chained + * to this SQLWarning. + * + * The warning chain is automatically cleared each time a new + * row is read. + * + * Note: This warning chain only covers warnings caused by + * ResultSet methods. Any warnings caused by statement methods + * (such as reading OUT parameters) will be chained on the + * Statement object. + * + * @return the first SQLWarning or null; + * @exception SQLException if a database access error occurs. + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this ResultSet + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * Get the name of the SQL cursor used by this ResultSet + * + * In SQL, a result table is retrieved though a cursor that is + * named. The current row of a result can be updated or deleted + * using a positioned update/delete statement that references + * the cursor name. + * + * JDBC supports this SQL feature by providing the name of the + * SQL cursor used by a ResultSet. The current row of a ResulSet + * is also the current row of this SQL cursor. + * + * Note: If positioned update is not supported, a SQLException + * is thrown. + * + * @return the ResultSet's SQL cursor name. + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return connection.getCursorName(); + } + + /** + * The numbers, types and properties of a ResultSet's columns are + * provided by the getMetaData method + * + * @return a description of the ResultSet's columns + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSetMetaData getMetaData() throws SQLException + { + return new ResultSetMetaData(rows, fields); + } + + /** + * Get the value of a column in the current row as a Java object + * + * This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * This method may also be used to read database specific abstract + * data types. + * + * @param columnIndex the first column is 1, the second is 2... + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(int columnIndex) throws SQLException + { + Field field; + + if (columnIndex < 1 || columnIndex > fields.length) + throw new SQLException("Column index out of range"); + field = fields[columnIndex - 1]; + + switch (field.getSQLType()) + { + case Types.BIT: + return new Boolean(getBoolean(columnIndex)); + case Types.SMALLINT: + return new Integer(getInt(columnIndex)); + case Types.INTEGER: + return new Integer(getInt(columnIndex)); + case Types.BIGINT: + return new Long(getLong(columnIndex)); + case Types.NUMERIC: + return getBigDecimal(columnIndex, 0); + case Types.REAL: + return new Float(getFloat(columnIndex)); + case Types.DOUBLE: + return new Double(getDouble(columnIndex)); + case Types.CHAR: + case Types.VARCHAR: + return getString(columnIndex); + case Types.DATE: + return getDate(columnIndex); + case Types.TIME: + return getTime(columnIndex); + case Types.TIMESTAMP: + return getTimestamp(columnIndex); + default: + return new PG_Object(field.getTypeName(), getString(columnIndex)); + } + } + + /** + * Get the value of a column in the current row as a Java object + * + * This method will return the value of the given column as a + * Java object. The type of the Java object will be the default + * Java Object type corresponding to the column's SQL type, following + * the mapping specified in the JDBC specification. + * + * This method may also be used to read database specific abstract + * data types. + * + * @param columnName is the SQL name of the column + * @return a Object holding the column value + * @exception SQLException if a database access error occurs + */ + public Object getObject(String columnName) throws SQLException + { + return getObject(findColumn(columnName)); + } + + /** + * Map a ResultSet column name to a ResultSet column index + * + * @param columnName the name of the column + * @return the column index + * @exception SQLException if a database access error occurs + */ + public int findColumn(String columnName) throws SQLException + { + int i; + + for (i = 0 ; i < fields.length; ++i) + if (fields[i].name.equalsIgnoreCase(columnName)) + return (i+1); + throw new SQLException ("Column name not found"); + } + + // ************************************************************ + // END OF PUBLIC INTERFACE + // ************************************************************ + + /** + * We at times need to know if the resultSet we are working + * with is the result of an UPDATE, DELETE or INSERT (in which + * case, we only have a row count), or of a SELECT operation + * (in which case, we have multiple fields) - this routine + * tells us. + * + * @return true if we have tuples available + */ + public boolean reallyResultSet() + { + return (fields != null); + } + + /** + * Since ResultSets can be chained, we need some method of + * finding the next one in the chain. The method getNext() + * returns the next one in the chain. + * + * @return the next ResultSet, or null if there are none + */ + public ResultSet getNext() + { + return next; + } + + /** + * This following method allows us to add a ResultSet object + * to the end of the current chain. + * + * @param r the resultset to add to the end of the chain. + */ + public void append(ResultSet r) + { + if (next == null) + next = r; + else + next.append(r); + } + + /** + * If we are just a place holder for results, we still need + * to get an updateCount. This method returns it. + * + * @return the updateCount + */ + public int getResultCount() + { + return updateCount; + } + + /** + * We also need to provide a couple of auxiliary functions for + * the implementation of the ResultMetaData functions. In + * particular, we need to know the number of rows and the + * number of columns. Rows are also known as Tuples + * + * getTupleCount returns the number of rows + * + * @return the number of rows + */ + public int getTupleCount() + { + return rows.size(); + } + + /** + * getColumnCount returns the number of columns + * + * @return the number of columns + */ + public int getColumnCount() + { + return fields.length; + } +} diff --git a/src/interfaces/jdbc/postgresql/ResultSetMetaData.java b/src/interfaces/jdbc/postgresql/ResultSetMetaData.java new file mode 100644 index 0000000000..a6974b3076 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/ResultSetMetaData.java @@ -0,0 +1,429 @@ +package postgresql; + +import java.lang.*; +import java.sql.*; +import java.util.*; +import postgresql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * A ResultSetMetaData object can be used to find out about the types and + * properties of the columns in a ResultSet + * + * @see java.sql.ResultSetMetaData + */ +public class ResultSetMetaData implements java.sql.ResultSetMetaData +{ + Vector rows; + Field[] fields; + + /** + * Initialise for a result with a tuple set and + * a field descriptor set + * + * @param rows the Vector of rows returned by the ResultSet + * @param fields the array of field descriptors + */ + public ResultSetMetaData(Vector rows, Field[] fields) + { + this.rows = rows; + this.fields = fields; + } + + /** + * Whats the number of columns in the ResultSet? + * + * @return the number + * @exception SQLException if a database access error occurs + */ + public int getColumnCount() throws SQLException + { + return fields.length; + } + + /** + * Is the column automatically numbered (and thus read-only) + * I believe that PostgreSQL does not support this feature. + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isAutoIncrement(int column) throws SQLException + { + return false; + } + + /** + * Does a column's case matter? ASSUMPTION: Any field that is + * not obviously case insensitive is assumed to be case sensitive + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isCaseSensitive(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; + default: + return true; + } + } + + /** + * Can the column be used in a WHERE clause? Basically for + * this, I split the functions into two types: recognised + * types (which are always useable), and OTHER types (which + * may or may not be useable). The OTHER types, for now, I + * will assume they are useable. We should really query the + * catalog to see if they are useable. + * + * @param column the first column is 1, the second is 2... + * @return true if they can be used in a WHERE clause + * @exception SQLException if a database access error occurs + */ + public boolean isSearchable(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + // This switch is pointless, I know - but it is a set-up + // for further expansion. + switch (sql_type) + { + case Types.OTHER: + return true; + default: + return true; + } + } + + /** + * Is the column a cash value? 6.1 introduced the cash/money + * type, which haven't been incorporated as of 970414, so I + * just check the type name for both 'cash' and 'money' + * + * @param column the first column is 1, the second is 2... + * @return true if its a cash column + * @exception SQLException if a database access error occurs + */ + public boolean isCurrency(int column) throws SQLException + { + String type_name = getField(column).getTypeName(); + + if (type_name.equals("cash")) + return true; + if (type_name.equals("money")) + return true; + return false; + } + + /** + * Can you put a NULL in this column? I think this is always + * true in 6.1's case. It would only be false if the field had + * been defined NOT NULL (system catalogs could be queried?) + * + * @param column the first column is 1, the second is 2... + * @return one of the columnNullable values + * @exception SQLException if a database access error occurs + */ + public int isNullable(int column) throws SQLException + { + return columnNullable; // We can always put NULL in + } + + /** + * Is the column a signed number? In PostgreSQL, all numbers + * are signed, so this is trivial. However, strings are not + * signed (duh!) + * + * @param column the first column is 1, the second is 2... + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isSigned(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + case Types.INTEGER: + case Types.FLOAT: + case Types.REAL: + case Types.DOUBLE: + return true; + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + return false; // I don't know about these? + default: + return false; + } + } + + /** + * What is the column's normal maximum width in characters? + * + * @param column the first column is 1, the second is 2, etc. + * @return the maximum width + * @exception SQLException if a database access error occurs + */ + public int getColumnDisplaySize(int column) throws SQLException + { + int max = getColumnLabel(column).length(); + int i; + + for (i = 0 ; i < rows.size(); ++i) + { + byte[][] x = (byte[][])(rows.elementAt(i)); + int xl = x[column - 1].length; + if (xl > max) + max = xl; + } + return max; + } + + /** + * What is the suggested column title for use in printouts and + * displays? We suggest the ColumnName! + * + * @param column the first column is 1, the second is 2, etc. + * @return the column label + * @exception SQLException if a database access error occurs + */ + public String getColumnLabel(int column) throws SQLException + { + return getColumnName(column); + } + + /** + * What's a column's name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the column name + * @exception SQLException if a databvase access error occurs + */ + public String getColumnName(int column) throws SQLException + { + return getField(column).name; + } + + /** + * What is a column's table's schema? This relies on us knowing + * the table name....which I don't know how to do as yet. The + * JDBC specification allows us to return "" if this is not + * applicable. + * + * @param column the first column is 1, the second is 2... + * @return the Schema + * @exception SQLException if a database access error occurs + */ + public String getSchemaName(int column) throws SQLException + { + String table_name = getTableName(column); + + // If the table name is invalid, so are we. + if (table_name.equals("")) + return ""; + return ""; // Ok, so I don't know how to + // do this as yet. + } + + /** + * What is a column's number of decimal digits. + * + * @param column the first column is 1, the second is 2... + * @return the precision + * @exception SQLException if a database access error occurs + */ + public int getPrecision(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 5; + case Types.INTEGER: + return 10; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + default: + throw new SQLException("no precision for non-numeric data types."); + } + } + + /** + * What is a column's number of digits to the right of the + * decimal point? + * + * @param column the first column is 1, the second is 2... + * @return the scale + * @exception SQLException if a database access error occurs + */ + public int getScale(int column) throws SQLException + { + int sql_type = getField(column).getSQLType(); + + switch (sql_type) + { + case Types.SMALLINT: + return 0; + case Types.INTEGER: + return 0; + case Types.REAL: + return 8; + case Types.FLOAT: + return 16; + case Types.DOUBLE: + return 16; + default: + throw new SQLException("no scale for non-numeric data types"); + } + } + + /** + * Whats a column's table's name? How do I find this out? Both + * getSchemaName() and getCatalogName() rely on knowing the table + * Name, so we need this before we can work on them. + * + * @param column the first column is 1, the second is 2... + * @return column name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getTableName(int column) throws SQLException + { + return ""; + } + + /** + * What's a column's table's catalog name? As with getSchemaName(), + * we can say that if getTableName() returns n/a, then we can too - + * otherwise, we need to work on it. + * + * @param column the first column is 1, the second is 2... + * @return catalog name, or "" if not applicable + * @exception SQLException if a database access error occurs + */ + public String getCatalogName(int column) throws SQLException + { + String table_name = getTableName(column); + + if (table_name.equals("")) + return ""; + return ""; // As with getSchemaName(), this + // is just the start of it. + } + + /** + * What is a column's SQL Type? (java.sql.Type int) + * + * @param column the first column is 1, the second is 2, etc. + * @return the java.sql.Type value + * @exception SQLException if a database access error occurs + * @see postgresql.Field#getSQLType + * @see java.sql.Types + */ + public int getColumnType(int column) throws SQLException + { + return getField(column).getSQLType(); + } + + /** + * Whats is the column's data source specific type name? + * + * @param column the first column is 1, the second is 2, etc. + * @return the type name + * @exception SQLException if a database access error occurs + */ + public String getColumnTypeName(int column) throws SQLException + { + return getField(column).getTypeName(); + } + + /** + * Is the column definitely not writable? In reality, we would + * have to check the GRANT/REVOKE stuff for this to be effective, + * and I haven't really looked into that yet, so this will get + * re-visited. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly(int column) throws SQLException + { + return false; + } + + /** + * Is it possible for a write on the column to succeed? Again, we + * would in reality have to check the GRANT/REVOKE stuff, which + * I haven't worked with as yet. However, if it isn't ReadOnly, then + * it is obviously writable. + * + * @param column the first column is 1, the second is 2, etc. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isWritable(int column) throws SQLException + { + if (isReadOnly(column)) + return true; + else + return false; + } + + /** + * Will a write on this column definately succeed? Hmmm...this + * is a bad one, since the two preceding functions have not been + * really defined. I cannot tell is the short answer. I thus + * return isWritable() just to give us an idea. + * + * @param column the first column is 1, the second is 2, etc.. + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isDefinitelyWritable(int column) throws SQLException + { + return isWritable(column); + } + + // ******************************************************** + // END OF PUBLIC INTERFACE + // ******************************************************** + + /** + * For several routines in this package, we need to convert + * a columnIndex into a Field[] descriptor. Rather than do + * the same code several times, here it is. + * + * @param columnIndex the first column is 1, the second is 2... + * @return the Field description + * @exception SQLException if a database access error occurs + */ + private Field getField(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new SQLException("Column index out of range"); + return fields[columnIndex - 1]; + } +} diff --git a/src/interfaces/jdbc/postgresql/Statement.java b/src/interfaces/jdbc/postgresql/Statement.java new file mode 100644 index 0000000000..464a263621 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/Statement.java @@ -0,0 +1,306 @@ +package postgresql; + +import java.sql.*; + +/** + * @version 1.0 15-APR-1997 + * @author Adrian Hall + * + * A Statement object is used for executing a static SQL statement and + * obtaining the results produced by it. + * + * Only one ResultSet per Statement can be open at any point in time. + * Therefore, if the reading of one ResultSet is interleaved with the + * reading of another, each must have been generated by different + * Statements. All statement execute methods implicitly close a + * statement's current ResultSet if an open one exists. + * + * @see java.sql.Statement + * @see ResultSet + */ +public class Statement implements java.sql.Statement +{ + Connection connection; // The connection who created us + ResultSet result = null; // The current results + SQLWarning warnings = null; // The warnings chain. + int maxrows = 0; // maximum no. of rows; 0 = unlimited + int timeout = 0; // The timeout for a query (not used) + boolean escapeProcessing = true;// escape processing flag + + /** + * Constructor for a Statement. It simply sets the connection + * that created us. + * + * @param c the Connection instantation that creates us + */ + public Statement (Connection c) + { + connection = c; + } + + /** + * Execute a SQL statement that retruns a single ResultSet + * + * @param sql typically a static SQL SELECT statement + * @return a ResulSet that contains the data produced by the query + * @exception SQLException if a database access error occurs + */ + public java.sql.ResultSet executeQuery(String sql) throws SQLException + { + this.execute(sql); + while (result != null && !result.reallyResultSet()) + result = result.getNext(); + if (result == null) + throw new SQLException("no results returned"); + return result; + } + + /** + * Execute a SQL INSERT, UPDATE or DELETE statement. In addition + * SQL statements that return nothing such as SQL DDL statements + * can be executed + * + * @param sql a SQL statement + * @return either a row count, or 0 for SQL commands + * @exception SQLException if a database access error occurs + */ + public int executeUpdate(String sql) throws SQLException + { + this.execute(sql); + if (result.reallyResultSet()) + throw new SQLException("results returned"); + return this.getUpdateCount(); + } + + /** + * In many cases, it is desirable to immediately release a + * Statement's database and JDBC resources instead of waiting + * for this to happen when it is automatically closed. The + * close method provides this immediate release. + * + * Note: A Statement is automatically closed when it is + * garbage collected. When a Statement is closed, its current + * ResultSet, if one exists, is also closed. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void close() throws SQLException + { + result = null; + } + + /** + * The maxFieldSize limit (in bytes) is the maximum amount of + * data returned for any column value; it only applies to + * BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR and LONGVARCHAR + * columns. If the limit is exceeded, the excess data is silently + * discarded. + * + * @return the current max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxFieldSize() throws SQLException + { + return 8192; // We cannot change this + } + + /** + * Sets the maxFieldSize - NOT! - We throw an SQLException just + * to inform them to stop doing this. + * + * @param max the new max column size limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public void setMaxFieldSize(int max) throws SQLException + { + throw new SQLException("Attempt to setMaxFieldSize failed - compile time default"); + } + + /** + * The maxRows limit is set to limit the number of rows that + * any ResultSet can contain. If the limit is exceeded, the + * excess rows are silently dropped. + * + * @return the current maximum row limit; zero means unlimited + * @exception SQLException if a database access error occurs + */ + public int getMaxRows() throws SQLException + { + return maxrows; + } + + /** + * Set the maximum number of rows + * + * @param max the new max rows limit; zero means unlimited + * @exception SQLException if a database access error occurs + * @see getMaxRows + */ + public void setMaxRows(int max) throws SQLException + { + maxrows = max; + } + + /** + * If escape scanning is on (the default), the driver will do escape + * substitution before sending the SQL to the database. + * + * @param enable true to enable; false to disable + * @exception SQLException if a database access error occurs + */ + public void setEscapeProcessing(boolean enable) throws SQLException + { + escapeProcessing = enable; + } + + /** + * The queryTimeout limit is the number of seconds the driver + * will wait for a Statement to execute. If the limit is + * exceeded, a SQLException is thrown. + * + * @return the current query timeout limit in seconds; 0 = unlimited + * @exception SQLException if a database access error occurs + */ + public int getQueryTimeout() throws SQLException + { + return timeout; + } + + /** + * Sets the queryTimeout limit + * + * @param seconds - the new query timeout limit in seconds + * @exception SQLException if a database access error occurs + */ + public void setQueryTimeout(int seconds) throws SQLException + { + timeout = seconds; + } + + /** + * Cancel can be used by one thread to cancel a statement that + * is being executed by another thread. However, PostgreSQL is + * a sync. sort of thing, so this really has no meaning - we + * define it as a no-op (i.e. you can't cancel, but there is no + * error if you try.) + * + * @exception SQLException only because thats the spec. + */ + public void cancel() throws SQLException + { + // No-op + } + + /** + * The first warning reported by calls on this Statement is + * returned. A Statement's execute methods clear its SQLWarning + * chain. Subsequent Statement warnings will be chained to this + * SQLWarning. + * + * The Warning chain is automatically cleared each time a statement + * is (re)executed. + * + * Note: If you are processing a ResultSet then any warnings + * associated with ResultSet reads will be chained on the ResultSet + * object. + * + * @return the first SQLWarning on null + * @exception SQLException if a database access error occurs + */ + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + /** + * After this call, getWarnings returns null until a new warning + * is reported for this Statement. + * + * @exception SQLException if a database access error occurs (why?) + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /** + * setCursorName defines the SQL cursor name that will be used by + * subsequent execute methods. This name can then be used in SQL + * positioned update/delete statements to identify the current row + * in the ResultSet generated by this statement. If a database + * doesn't support positioned update/delete, this method is a + * no-op. + * + * Note: By definition, positioned update/delete execution + * must be done by a different Statement than the one which + * generated the ResultSet being used for positioning. Also, cursor + * names must be unique within a Connection. + * + * We throw an additional constriction. There can only be one + * cursor active at any one time. + * + * @param name the new cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String name) throws SQLException + { + connection.setCursorName(name); + } + + /** + * Execute a SQL statement that may return multiple results. We + * don't have to worry about this since we do not support multiple + * ResultSets. You can use getResultSet or getUpdateCount to + * retrieve the result. + * + * @param sql any SQL statement + * @return true if the next result is a ResulSet, false if it is + * an update count or there are no more results + * @exception SQLException if a database access error occurs + */ + public boolean execute(String sql) throws SQLException + { + result = connection.ExecSQL(sql); + return (result != null && result.reallyResultSet()); + } + + /** + * getResultSet returns the current result as a ResultSet. It + * should only be called once per result. + * + * @return the current result set; null if there are no more + * @exception SQLException if a database access error occurs (why?) + */ + public java.sql.ResultSet getResultSet() throws SQLException + { + return result; + } + + /** + * getUpdateCount returns the current result as an update count, + * if the result is a ResultSet or there are no more results, -1 + * is returned. It should only be called once per result. + * + * @return the current result as an update count. + * @exception SQLException if a database access error occurs + */ + public int getUpdateCount() throws SQLException + { + if (result == null) return -1; + if (result.reallyResultSet()) return -1; + return result.getResultCount(); + } + + /** + * getMoreResults moves to a Statement's next result. If it returns + * true, this result is a ResulSet. + * + * @return true if the next ResultSet is valid + * @exception SQLException if a database access error occurs + */ + public boolean getMoreResults() throws SQLException + { + result = result.getNext(); + return (result != null && result.reallyResultSet()); + } +}