postgresql-jdbc.git / summary / log / commit / refs

commit    df097f56c2e4993e78fcd36b2f2b5de5e6841bd7
Author:   Kris Jurka <books@ejurka.com>
Date:     Fri Jul 16 06:14:28 2004 +0000

    Now that JDBC1 is no longer supported merge the implemention of
    JDBC1 methods into the JDBC2 classes.


build.xml | 29 +- doc/pgjdbc.xml | 17 +- org/postgresql/jdbc1/AbstractJdbc1Connection.java | 1057 ------ .../jdbc1/AbstractJdbc1DatabaseMetaData.java | 3646 -------------------- org/postgresql/jdbc1/AbstractJdbc1ResultSet.java | 1034 ------ .../jdbc1/AbstractJdbc1ResultSetMetaData.java | 586 ---- org/postgresql/jdbc1/AbstractJdbc1Statement.java | 2218 ------------ org/postgresql/jdbc2/AbstractJdbc2Connection.java | 961 +++++- .../jdbc2/AbstractJdbc2DatabaseMetaData.java | 3631 ++++++++++++++++++- org/postgresql/jdbc2/AbstractJdbc2ResultSet.java | 1006 +++++- .../jdbc2/AbstractJdbc2ResultSetMetaData.java | 605 +++- org/postgresql/jdbc2/AbstractJdbc2Statement.java | 2185 +++++++++++- org/postgresql/jdbc2/Array.java | 2 +- jdbc2}/ResultWrapper.java | 2 +- jdbc2}/TimestampUtils.java | 4 +- org/postgresql/test/TestUtil.java | 4 +- 16 files changed, 8325 insertions(+), 8662 deletions(-) diff --git a/build.xml b/build.xml index 86c3d827..868d7cd0 100644 --- a/build.xml +++ b/build.xml @@ -29,17 +29,7 @@ <property file="build.local.properties" /> <property file="build.properties"/> - <!-- - This is a simpler method than utils.CheckVersion - It defaults to jdbc1, but builds jdbc2 if the java.lang.Byte class is - in the CLASSPATH (ie JDK1.2 or later), and then enterprise if the - javax.sql.DataSource class is present. - Important: This must have the following order: jdbc1, jdbc2, jdbc3 - --> <target name="check_versions"> - <condition property="jdbc1"> - <equals arg1="${ant.java.version}" arg2="1.1"/> - </condition> <condition property="jdbc2"> <or> <equals arg1="${ant.java.version}" arg2="1.2"/> @@ -130,19 +120,15 @@ <!-- Each jdbcN subpackage is used only if the driver supports *at least* that - revision of JDBC. That is, a JDBC1 build uses only jdbc1, a JDBC2 build - uses both jdbc1 and jdbc2, etc. + revision of JDBC. That is, a JDBC2 build uses only jdbc2, a JDBC3 build + uses both jdbc2 and jdbc3, etc. Within those subpackages, classes beginning with "JdbcN" are assumed to be the concrete implementations for JDBC version N and are built only if the - driver supports *exactly* that version. For example, jdbc1/Jdbc1Statement.java - is built only if the driver build is a JDBC1 build. + driver supports *exactly* that version. For example, jdbc2/Jdbc2Statement.java + is built only if the driver build is a JDBC2 build. --> - <!-- jdbc1 subpackage --> - <include name="${package}/jdbc1/**"/> - <exclude name="${package}/jdbc1/Jdbc1*.java" unless="jdbc1"/> - <!-- jdbc2 subpackage --> <include name="${package}/jdbc2/**" if="jdbc2"/> <include name="${package}/jdbc2/**" if="jdbc3"/> @@ -174,9 +160,6 @@ <target name="driver" depends="prepare,check_versions,check_driver" unless="driver.uptodate"> <!-- determine the edition text --> - <condition property="edition" value="JDBC1"> - <equals arg1="${jdbc1}" arg2="true"/> - </condition> <condition property="edition" value="JDBC2"> <equals arg1="${jdbc2}" arg2="true"/> </condition> @@ -191,9 +174,6 @@ </condition> <!-- determine the connection class --> - <condition property="connectclass" value="org.postgresql.jdbc1.Jdbc1Connection"> - <equals arg1="${jdbc1}" arg2="true"/> - </condition> <condition property="connectclass" value="org.postgresql.jdbc2.Jdbc2Connection"> <equals arg1="${jdbc2}" arg2="true"/> </condition> @@ -287,7 +267,6 @@ <javac srcdir="${srcdir}" destdir="${builddir}" debug="${debug}"> <include name="example/**" /> <exclude name="example/corba/**"/> - <exclude name="example/blobtest.java" if="jdbc1"/> </javac> </target> diff --git a/doc/pgjdbc.xml b/doc/pgjdbc.xml index 3c7f9093..1e2d9d02 100644 --- a/doc/pgjdbc.xml +++ b/doc/pgjdbc.xml @@ -1,9 +1,7 @@ <?xml version="1.0"?> <!-- $Header$ --> -<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ - <!ENTITY majorversion "7.5devel"> -]> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> <book id="jdbc"> <title>The <productname>PostgreSQL</productname> <acronym>JDBC</acronym> Interface</title> @@ -81,12 +79,10 @@ JAVACMD=$JAVA_HOME/bin/java To compile the driver simply run <command>ant</command> in the top level directory. The compiled driver will be placed in a <filename>jars/postgresql.jar</filename>. The resulting driver will be built for the version of Java you are - running. If you build with a 1.1 <acronym>JDK</acronym> you will - build a version that supports the <acronym>JDBC</acronym> 1 - specification, if you build with a 1.2 or 1.3 + running. If you build with a 1.2 or 1.3 <acronym>JDK</acronym> you will build a version that supports the <acronym>JDBC</acronym> 2 specification, and finally if you build - with a 1.4 <acronym>JDK</acronym> you will build a version that + with a 1.4 or 1.5 <acronym>JDK</acronym> you will build a version that supports the <acronym>JDBC</acronym> 3 specification. </para> </sect1> @@ -105,10 +101,9 @@ JAVACMD=$JAVA_HOME/bin/java <para> To use the driver, the JAR archive (named <filename>postgresql.jar</filename> if you built from source, otherwise - it will likely be named <filename>pg&majorversion;jdbc1.jar</filename>, - <filename>pg&majorversion;jdbc2.jar</filename>, or - <filename>pg&majorversion;jdbc3.jar</filename> for the <acronym>JDBC</acronym> 1, - <acronym>JDBC</acronym> 2, and <acronym>JDBC</acronym> 3 versions respectively) + it will likely be named with the following convention: + <filename>pg<replaceable>[server version]</replaceable>.<replaceable>[buildnumber]</replaceable>jdbc<replaceable>[JDBC version]</replaceable>.jar</filename>, + for example pg75.304.jdbc3.jar) needs to be included in the class path, either by putting it in the <envar>CLASSPATH</envar> environment variable, or by using flags on the <command>java</command> command line. diff --git a/org/postgresql/jdbc1/AbstractJdbc1Connection.java b/org/postgresql/jdbc1/AbstractJdbc1Connection.java deleted file mode 100644 index 0291f5e9..00000000 --- a/org/postgresql/jdbc1/AbstractJdbc1Connection.java +++ /dev/null @@ -1,1057 +0,0 @@ -/*------------------------------------------------------------------------- - * - * AbstractJdbc1Connection.java - * This class defines methods of the jdbc1 specification. This class is - * extended by org.postgresql.jdbc2.AbstractJdbc2Connection which adds - * the jdbc2 methods. The real Connection class (for jdbc1) is - * org.postgresql.jdbc1.Jdbc1Connection - * - * Copyright (c) 2003, PostgreSQL Global Development Group - * - * IDENTIFICATION - * $PostgreSQL: pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java,v 1.28 2003/11/29 19:52:09 pgsql Exp $ - * - *------------------------------------------------------------------------- - */ -package org.postgresql.jdbc1; - - -import java.io.IOException; -import java.net.ConnectException; -import java.sql.*; -import java.util.*; -import org.postgresql.Driver; -import org.postgresql.PGNotification; -import org.postgresql.core.*; -import org.postgresql.fastpath.Fastpath; -import org.postgresql.largeobject.LargeObjectManager; -import org.postgresql.util.MD5Digest; -import org.postgresql.util.PGobject; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; -import org.postgresql.util.PSQLWarning; -import org.postgresql.util.ServerErrorMessage; -import org.postgresql.util.UnixCrypt; - -public abstract class AbstractJdbc1Connection implements BaseConnection -{ - // - // Data initialized on construction: - // - - /* URL we were created via */ - private final String creatingURL; - - /* Actual network handler */ - private final ProtocolConnection protoConnection; - /* Compatibility version */ - private final String compatible; - /* Actual server version */ - private final String dbVersionNumber; - - /* Query that runs COMMIT */ - private final Query commitQuery; - /* Query that runs ROLLBACK */ - private final Query rollbackQuery; - - // These are used to cache the oids to PGType mappings. - private Hashtable oidTypeCache = new Hashtable(); // oid -> PGType - private Hashtable typeOidCache = new Hashtable(); // PGType -> oid - - // Default statement prepare threshold. - protected int prepareThreshold; - // Connection's autocommit state. - public boolean autoCommit = true; - // Connection's readonly state. - public boolean readOnly = false; - - // Current warnings; there might be more on protoConnection too. - public SQLWarning firstWarning = null; - - public abstract Statement createStatement() throws SQLException; - public abstract DatabaseMetaData getMetaData() throws SQLException; - public abstract PreparedStatement prepareStatement(String sql) throws SQLException; - public abstract CallableStatement prepareCall(String sql) throws SQLException; - - // - // Ctor. - // - protected AbstractJdbc1Connection(String host, int port, String user, String database, Properties info, String url) throws SQLException - { - this.creatingURL = url; - - //Read loglevel arg and set the loglevel based on this value - //in addition to setting the log level enable output to - //standard out if no other printwriter is set - - // XXX revisit: need a debug level *per connection*. - - int logLevel = 0; - try - { - logLevel = Integer.parseInt(info.getProperty("loglevel", "0")); - if (logLevel > Driver.DEBUG || logLevel < Driver.INFO) - { - logLevel = 0; - } - } - catch (Exception l_e) - { - // XXX revisit - // invalid value for loglevel; ignore it - } - - if (logLevel > 0) - { - Driver.setLogLevel(logLevel); - enableDriverManagerLogging(); - } - - prepareThreshold = 0; - try { - prepareThreshold = Integer.parseInt(info.getProperty("prepareThreshold", "0")); - } catch (Exception e) {} - - if (prepareThreshold < 0) - prepareThreshold = 0; - - //Print out the driver version number - if (Driver.logInfo) - Driver.info(Driver.getVersion()); - - // Now make the initial connection and set up local state - this.protoConnection = ConnectionFactory.openConnection(host, port, user, database, info); - this.dbVersionNumber = protoConnection.getServerVersion(); - this.compatible = info.getProperty("compatible", Driver.MAJORVERSION + "." + Driver.MINORVERSION); - - if (Driver.logDebug) { - Driver.debug(" compatible = " + compatible); - Driver.debug(" loglevel = " + logLevel); - Driver.debug(" prepare threshold = " + prepareThreshold); - } - - // Initialize common queries. - commitQuery = getQueryExecutor().createSimpleQuery("COMMIT"); - rollbackQuery = getQueryExecutor().createSimpleQuery("ROLLBACK"); - - // Initialize object handling - initObjectTypes(); - } - - // Query executor associated with this connection. - public QueryExecutor getQueryExecutor() { - return protoConnection.getQueryExecutor(); - } - - /* - * This adds a warning to the warning chain. - * @param warn warning to add - */ - public void addWarning(SQLWarning warn) - { - // Add the warning to the chain - if (firstWarning != null) - firstWarning.setNextWarning(warn); - else - firstWarning = warn; - - } - - /** - * Simple query execution. - */ - public ResultSet execSQLQuery(String s) throws SQLException - { - Statement stat = (Statement) createStatement(); - boolean hasResultSet = stat.execute(s); - - // Transfer warnings to the connection, since the user never - // has a chance to see the statement itself. - SQLWarning warnings = stat.getWarnings(); - if (warnings != null) - addWarning(warnings); - - while (!hasResultSet && stat.getUpdateCount() != -1) - hasResultSet = stat.getMoreResults(); - - return stat.getResultSet(); - } - - public void execSQLUpdate(String s) throws SQLException - { - execSQLUpdate(s, 0); - } - - private void execSQLUpdate(String s, int flags) throws SQLException - { - BaseStatement stmt = (BaseStatement) createStatement(); - stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | flags); - - // Transfer warnings to the connection, since the user never - // has a chance to see the statement itself. - SQLWarning warnings = stmt.getWarnings(); - if (warnings != null) - addWarning(warnings); - - stmt.close(); - } - - /* - * 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 do not support positioned update/delete, so this is a no-op. - * - * @param cursor the cursor name - * @exception SQLException if a database access error occurs - */ - public void setCursorName(String cursor) throws SQLException - { - // No-op. - } - - /* - * getCursorName gets the cursor name. - * - * @return the current cursor name - * @exception SQLException if a database access error occurs - */ - public String getCursorName() throws SQLException - { - return null; - } - - /* - * 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 creatingURL; - } - - /* - * 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 protoConnection.getUser(); - } - - /* - * This returns the Fastpath API for the current connection. - * - * <p><b>NOTE:</b> This is not part of JDBC, but allows access to - * functions on the org.postgresql backend itself. - * - * <p>It is primarily used by the LargeObject API - * - * <p>The best way to use this is as follows: - * - * <p><pre> - * import org.postgresql.fastpath.*; - * ... - * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI(); - * </pre> - * - * <p>where myconn is an open Connection to org.postgresql. - * - * @return Fastpath object allowing access to functions on the org.postgresql - * backend. - * @exception SQLException by Fastpath when initialising for first time - */ - public Fastpath getFastpathAPI() throws SQLException - { - if (fastpath == null) - fastpath = new Fastpath(this); - return fastpath; - } - - // This holds a reference to the Fastpath API if already open - private Fastpath fastpath = null; - - /* - * This returns the LargeObject API for the current connection. - * - * <p><b>NOTE:</b> This is not part of JDBC, but allows access to - * functions on the org.postgresql backend itself. - * - * <p>The best way to use this is as follows: - * - * <p><pre> - * import org.postgresql.largeobject.*; - * ... - * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI(); - * </pre> - * - * <p>where myconn is an open Connection to org.postgresql. - * - * @return LargeObject object that implements the API - * @exception SQLException by LargeObject when initialising for first time - */ - public LargeObjectManager getLargeObjectAPI() throws SQLException - { - if (largeobject == null) - largeobject = new LargeObjectManager(this); - return largeobject; - } - - // This holds a reference to the LargeObject API if already open - private LargeObjectManager largeobject = null; - - /* - * This method is used internally to return an object based around - * org.postgresql's more unique data types. - * - * <p>It uses an internal Hashtable to get the handling class. If the - * type is not supported, then an instance of org.postgresql.util.PGobject - * is returned. - * - * You can use the getValue() or setValue() methods to handle the returned - * object. Custom objects can have their own methods. - * - * @return PGobject for this type, and set to value - * @exception SQLException if value is not correct for this type - */ - public Object getObject(String type, String value) throws SQLException - { - PGobject obj = null; - - if (Driver.logDebug) - Driver.debug("Constructing object from type=" + type + " value=<" + value + ">"); - - try - { - Class klass; - - synchronized (objectTypes) { - klass = (Class)objectTypes.get(type); - } - - // If className is not null, then try to instantiate it, - // It must be basetype PGobject - - // This is used to implement the org.postgresql unique types (like lseg, - // point, etc). - - if (klass != null) - { - obj = (PGobject) (klass.newInstance()); - obj.setType(type); - obj.setValue(value); - } - else - { - // If className is null, then the type is unknown. - // so return a PGobject with the type set, and the value set - obj = new PGobject(); - obj.setType( type ); - obj.setValue( value ); - } - - return obj; - } - catch (SQLException sx) - { - // rethrow the exception. Done because we capture any others next - throw sx; - } - catch (Exception ex) - { - throw new PSQLException("postgresql.con.creobj", PSQLState.CONNECTION_FAILURE, type, ex); - } - } - - public void addDataType(String type, String name) - { - try { - addDataType(type, Class.forName(name)); - } catch (Exception e) { - throw new RuntimeException("Cannot register new type: " + e); - } - } - - public void addDataType(String type, Class klass) throws SQLException - { - if (!org.postgresql.util.PGobject.class.isAssignableFrom(klass)) - throw new PSQLException("postgresql.adddatatype.notpgobject", PSQLState.INVALID_PARAMETER_TYPE, klass.toString()); - - synchronized (objectTypes) { - objectTypes.put(type, klass); - } - } - - // This holds the available types, a String to Class mapping. - private final HashMap objectTypes = new HashMap(); - - // This array contains the types that are supported as standard. - // - // The first entry is the types name on the database, the second - // the full class name of the handling class. - // - private static final Object[][] defaultObjectTypes = { - { "box", org.postgresql.geometric.PGbox.class }, - { "circle", org.postgresql.geometric.PGcircle.class }, - { "line", org.postgresql.geometric.PGline.class }, - { "lseg", org.postgresql.geometric.PGlseg.class }, - { "path", org.postgresql.geometric.PGpath.class }, - { "point", org.postgresql.geometric.PGpoint.class }, - { "polygon", org.postgresql.geometric.PGpolygon.class }, - { "money", org.postgresql.util.PGmoney.class }, - { "interval", org.postgresql.util.PGInterval.class } - }; - - // This initialises the objectTypes hashtable - private void initObjectTypes() throws SQLException - { - for (int i = 0; i < defaultObjectTypes.length; ++i) - addDataType((String) defaultObjectTypes[i][0], (Class) defaultObjectTypes[i][1]); - } - - /** - * 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. - * - * <B>Note:</B> 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() - { - protoConnection.close(); - } - - /* - * 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; - } - - /* - * The first warning reported by calls on this Connection is - * returned. - * - * <B>Note:</B> Sebsequent warnings will be changed to this - * SQLWarning - * - * @return the first SQLWarning or null - * @exception SQLException if a database access error occurs - */ - public synchronized SQLWarning getWarnings() throws SQLException - { - SQLWarning newWarnings = protoConnection.getWarnings(); // NB: also clears them. - if (firstWarning == null) - firstWarning = newWarnings; - else - firstWarning.setNextWarning(newWarnings); // Chain them on. - - return firstWarning; - } - - /* - * After this call, getWarnings returns null until a new warning - * is reported for this connection. - * - * @exception SQLException if a database access error occurs - */ - public synchronized void clearWarnings() throws SQLException - { - protoConnection.getWarnings(); // Clear and discard. - firstWarning = null; - } - - - /* - * You can put a connection in read-only mode as a hunt to enable - * database optimizations - * - * <B>Note:</B> 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 - { - if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) - throw new PSQLException("postgresql.con.changereadonly"); - - if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) { - String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); - execSQLUpdate(readOnlySql, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting - } - - this.readOnly = readOnly; - } - - /* - * Tests to see if the connection is in Read Only Mode. - * - * @return true if the connection is read only - * @exception SQLException if a database access error occurs - */ - public boolean isReadOnly() throws SQLException - { - return readOnly; - } - - /* - * 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 (!this.autoCommit) - commit(); - - this.autoCommit = autoCommit; - } - - /* - * gets the current auto-commit state - * - * @return Current state of the auto-commit mode - * @see setAutoCommit - */ - public boolean getAutoCommit() - { - return this.autoCommit; - } - - private void executeTransactionCommand(Query query) throws SQLException { - getQueryExecutor().execute(query, null, new TransactionCommandHandler(), - 0, 0, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN); - } - - /* - * 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; - - if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) - executeTransactionCommand(commitQuery); - } - - /* - * 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; - - if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) - executeTransactionCommand(rollbackQuery); - } - - /* - * 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 - { - String level = null; - Statement stmt = createStatement(); - - if (haveMinimumServerVersion("7.3")) { - ResultSet rs = stmt.executeQuery("SHOW TRANSACTION ISOLATION LEVEL"); - if (rs.next()) - level = rs.getString(1); - - // Transfer warnings to the connection, since the user never - // has a chance to see the statement itself. - SQLWarning warnings = stmt.getWarnings(); - if (warnings != null) - addWarning(warnings); - } else { - stmt.executeUpdate("SHOW TRANSACTION ISOLATION LEVEL"); - SQLWarning warning = stmt.getWarnings(); - if (warning != null) - level = warning.getMessage(); - } - - stmt.close(); - - // XXX revisit: throw exception instead of silently eating the error in unkwon cases? - if (level == null) - return Connection.TRANSACTION_READ_COMMITTED; // Best guess. - - level = level.toUpperCase(); - if (level.indexOf("READ COMMITTED") != -1) - return Connection.TRANSACTION_READ_COMMITTED; - if (level.indexOf("READ UNCOMMITTED") != -1) - return Connection.TRANSACTION_READ_UNCOMMITTED; - if (level.indexOf("REPEATABLE READ") != -1) - return Connection.TRANSACTION_REPEATABLE_READ; - if (level.indexOf("SERIALIZABLE") != -1) - return Connection.TRANSACTION_SERIALIZABLE; - - return Connection.TRANSACTION_READ_COMMITTED; // Best guess. - } - - /* - * You can call this method to try to change the transaction - * isolation level using one of the TRANSACTION_* values. - * - * <B>Note:</B> 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 - { - if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) - throw new PSQLException("postgresql.con.changeisolevel"); - - String isolationLevelName = getIsolationLevelName(level); - if (isolationLevelName == null) - throw new PSQLException("postgresql.con.isolevel", PSQLState.TRANSACTION_STATE_INVALID, new Integer(level)); - - String isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevelName; - execSQLUpdate(isolationLevelSQL, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting - } - - protected String getIsolationLevelName(int level) - { - boolean pg75 = haveMinimumServerVersion("7.5"); - - if (level == Connection.TRANSACTION_READ_COMMITTED) { - return "READ COMMITTED"; - } else if (level == Connection.TRANSACTION_SERIALIZABLE) { - return "SERIALIZABLE"; - } else if (pg75 && level == Connection.TRANSACTION_READ_UNCOMMITTED) { - return "READ UNCOMMITTED"; - } else if (pg75 && level == Connection.TRANSACTION_REPEATABLE_READ) { - return "REPEATABLE READ"; - } - - return null; - } - - /* - * 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 protoConnection.getDatabase(); - } - - /* - * Overides finalize(). If called, it closes the connection. - * - * This was done at the request of Rachel Greenham - * <rachel@enlarion.demon.co.uk> who hit a problem where multiple - * clients didn't close the connection, and once a fortnight enough - * clients were open to kill the org.postgres server. - */ - public void finalize() throws Throwable - { - close(); - } - - /* - * Get server version number - */ - public String getDBVersionNumber() - { - return dbVersionNumber; - } - - // Parse a "dirty" integer surrounded by non-numeric characters - private static int integerPart(String dirtyString) - { - int start, end; - - for (start = 0; start < dirtyString.length() && !Character.isDigit(dirtyString.charAt(start)); ++start) - ; - - for (end = start; end < dirtyString.length() && Character.isDigit(dirtyString.charAt(end)); ++end) - ; - - if (start == end) - return 0; - - return Integer.parseInt(dirtyString.substring(start, end)); - } - - /* - * Get server major version - */ - public int getServerMajorVersion() - { - try - { - StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd - return integerPart(versionTokens.nextToken()); // return X - } - catch (NoSuchElementException e) - { - return 0; - } - } - - /* - * Get server minor version - */ - public int getServerMinorVersion() - { - try - { - StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd - versionTokens.nextToken(); // Skip aaXbb - return integerPart(versionTokens.nextToken()); // return Y - } - catch (NoSuchElementException e) - { - return 0; - } - } - - /** - * Is the server we are connected to running at least this version? - * This comparison method will fail whenever a major or minor version - * goes to two digits (10.3.0) or (7.10.1). - */ - public boolean haveMinimumServerVersion(String ver) - { - return (dbVersionNumber.compareTo(ver) >= 0); - } - - /* - * This method returns true if the compatible level set in the connection - * (which can be passed into the connection or specified in the URL) - * is at least the value passed to this method. This is used to toggle - * between different functionality as it changes across different releases - * of the jdbc driver code. The values here are versions of the jdbc client - * and not server versions. For example in 7.1 get/setBytes worked on - * LargeObject values, in 7.2 these methods were changed to work on bytea - * values. This change in functionality could be disabled by setting the - * "compatible" level to be 7.1, in which case the driver will revert to - * the 7.1 functionality. - */ - public boolean haveMinimumCompatibleVersion(String ver) - { - return (compatible.compareTo(ver) >= 0); - } - - - public Encoding getEncoding() { - return protoConnection.getEncoding(); - } - - public byte[] encodeString(String str) throws SQLException { - try { - return getEncoding().encode(str); - } catch (IOException ioe) { - throw new PSQLException("postgresql.con.invalidchar", PSQLState.DATA_ERROR, ioe); - } - } - - /* - * This returns the java.sql.Types type for a PG type oid - * - * @param oid PostgreSQL type oid - * @return the java.sql.Types type - * @exception SQLException if a database access error occurs - */ - public int getSQLType(int oid) throws SQLException - { - return getSQLType(getPGType(oid)); - } - - /* - * This returns the oid for a given PG data type - * @param typeName PostgreSQL type name - * @return PostgreSQL oid value for a field of this type, or 0 if not found - */ - public int getPGType(String typeName) throws SQLException - { - if (typeName == null) - return 0; - - synchronized (this) { - Integer oidValue = (Integer) typeOidCache.get(typeName); - if (oidValue != null) - return oidValue.intValue(); - - // it's not in the cache, so perform a query, and add the result to the cache - int oid = 0; - - PreparedStatement query; - if (haveMinimumServerVersion("7.3")) - query = prepareStatement("SELECT oid FROM pg_catalog.pg_type WHERE typname=?"); - else - query = prepareStatement("SELECT oid FROM pg_type WHERE typname=?"); - - query.setString(1, typeName); - - BaseResultSet result = (BaseResultSet)query.executeQuery(); - if (result.next()) { - oid = result.getInt(1); - oidTypeCache.put(new Integer(oid), typeName); - } - - typeOidCache.put(typeName, new Integer(oid)); - result.close(); - return oid; - } - } - - /* - * We also need to get the PG type name as returned by the back end. - * - * @return the String representation of the type, or null if not fould - * @exception SQLException if a database access error occurs - */ - public String getPGType(int oid) throws SQLException - { - if (oid == 0) - return null; - - synchronized (this) { - String cachedValue = (String)oidTypeCache.get(new Integer(oid)); - if (cachedValue != null) - return cachedValue; - - // it's not in the cache, so perform a query, and add the result to the cache - String typeName = null; - - PreparedStatement query; - if (haveMinimumServerVersion("7.3")) - query = prepareStatement("SELECT typname FROM pg_catalog.pg_type WHERE oid=?"); - else - query = prepareStatement("SELECT typname FROM pg_type WHERE oid=?"); - - query.setInt(1, oid); - - ResultSet result = query.executeQuery(); - if (result.next()) { - typeName = result.getString(1); - typeOidCache.put(typeName, new Integer(oid)); - } - - oidTypeCache.put(new Integer(oid), typeName); - result.close(); - return typeName; - } - } - - //Because the get/setLogStream methods are deprecated in JDBC2 - //we use them for JDBC1 here and override this method in the jdbc2 - //version of this class - protected void enableDriverManagerLogging() - { - if (DriverManager.getLogStream() == null) - { - DriverManager.setLogStream(System.out); - } - } - - // This is a cache of the DatabaseMetaData instance for this connection - protected java.sql.DatabaseMetaData metadata; - - /* - * Tests to see if a Connection is closed - * - * @return the status of the connection - * @exception SQLException (why?) - */ - public boolean isClosed() throws SQLException - { - return protoConnection.isClosed(); - } - - /* - * This implemetation uses the jdbc1Types array to support the jdbc1 - * datatypes. Basically jdbc1 and jdbc2 are the same, except that - * jdbc2 adds the Array types. - */ - public int getSQLType(String pgTypeName) - { - if (pgTypeName == null) - return Types.OTHER; - - for (int i = 0;i < jdbc1Types.length;i++) - if (pgTypeName.equals(jdbc1Types[i])) - return jdbc1Typei[i]; - - return Types.OTHER; - } - - /* - * This table holds the org.postgresql names for the types supported. - * Any types that map to Types.OTHER (eg POINT) don't go into this table. - * They default automatically to Types.OTHER - * - * Note: This must be in the same order as below. - * - * Tip: keep these grouped together by the Types. value - */ - private static final String jdbc1Types[] = { - "int2", - "int4", "oid", - "int8", - "cash", "money", - "numeric", - "float4", - "float8", - "bpchar", "char", "char2", "char4", "char8", "char16", - "varchar", "text", "name", "filename", - "bytea", - "bool", - "bit", - "date", - "time", "timetz", - "abstime", "timestamp", "timestamptz" - }; - - /* - * This table holds the JDBC type for each entry above. - * - * Note: This must be in the same order as above - * - * Tip: keep these grouped together by the Types. value - */ - private static final int jdbc1Typei[] = { - Types.SMALLINT, - Types.INTEGER, Types.INTEGER, - Types.BIGINT, - Types.DOUBLE, Types.DOUBLE, - Types.NUMERIC, - Types.REAL, - Types.DOUBLE, - Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, Types.CHAR, - Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, - Types.BINARY, - Types.BIT, - Types.BIT, - Types.DATE, - Types.TIME, Types.TIME, - Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP - }; - - public void cancelQuery() throws SQLException - { - protoConnection.sendQueryCancel(); - } - - public PGNotification[] getNotifications() - { - // Backwards-compatibility hand-holding. - PGNotification[] notifications = protoConnection.getNotifications(); - return (notifications.length == 0 ? null : notifications); - } - - // - // Handler for transaction queries - // - private class TransactionCommandHandler implements ResultHandler { - private SQLException error; - - public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) {} - public void handleCommandStatus(String status, int updateCount, long insertOID) {} - - public void handleWarning(SQLWarning warning) { - AbstractJdbc1Connection.this.addWarning(warning); - } - - public void handleError(SQLException newError) { - if (error == null) - error = newError; - else - error.setNextException(newError); - } - - public void handleCompletion() throws SQLException { - if (error != null) - throw error; - } - } - - public int getPrepareThreshold() { - return prepareThreshold; - } - - public void setPrepareThreshold(int newThreshold) { - this.prepareThreshold = (newThreshold <= 0 ? 0 : newThreshold); - } -} - - diff --git a/org/postgresql/jdbc1/AbstractJdbc1DatabaseMetaData.java b/org/postgresql/jdbc1/AbstractJdbc1DatabaseMetaData.java deleted file mode 100644 index 571076bc..00000000 --- a/org/postgresql/jdbc1/AbstractJdbc1DatabaseMetaData.java +++ /dev/null @@ -1,3646 +0,0 @@ -package org.postgresql.jdbc1; - - -import java.sql.*; -import java.util.*; -import org.postgresql.core.*; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; -import org.postgresql.Driver; - -public abstract class AbstractJdbc1DatabaseMetaData -{ - - private static final String keywords = "abort,acl,add,aggregate,append,archive," + - "arch_store,backward,binary,boolean,change,cluster," + - "copy,database,delimiter,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"; - - protected final AbstractJdbc1Connection connection; // The connection association - - protected static final int VARHDRSZ = 4; // length for int4 - - private int NAMEDATALEN = 0; // length for name datatype - private int INDEX_MAX_KEYS = 0; // maximum number of keys in an index. - - protected int getMaxIndexKeys() throws SQLException { - if (INDEX_MAX_KEYS == 0) { - String from; - if (connection.haveMinimumServerVersion("7.3")) { - from = "pg_catalog.pg_namespace n, pg_catalog.pg_type t1, pg_catalog.pg_type t2 WHERE t1.typnamespace=n.oid AND n.nspname='pg_catalog' AND "; - } else { - from = "pg_type t1, pg_type t2 WHERE "; - } - String sql = "SELECT t1.typlen/t2.typlen FROM "+from+" t1.typelem=t2.oid AND t1.typname='oidvector'"; - ResultSet rs = connection.createStatement().executeQuery(sql); - if (!rs.next()) { - throw new PSQLException("postgresql.unexpected", PSQLState.UNEXPECTED_ERROR); - } - INDEX_MAX_KEYS = rs.getInt(1); - rs.close(); - } - return INDEX_MAX_KEYS; - } - - protected int getMaxNameLength() throws SQLException { - if (NAMEDATALEN == 0) { - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT t.typlen FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE t.typnamespace=n.oid AND t.typname='name' AND n.nspname='pg_catalog'"; - } else { - sql = "SELECT typlen FROM pg_type WHERE typname='name'"; - } - ResultSet rs = connection.createStatement().executeQuery(sql); - if (!rs.next()) { - throw new PSQLException("postgresql.unexpected", PSQLState.UNEXPECTED_ERROR); - } - NAMEDATALEN = rs.getInt("typlen"); - rs.close(); - } - return NAMEDATALEN - 1; - } - - public AbstractJdbc1DatabaseMetaData(AbstractJdbc1Connection 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 - { - if (Driver.logDebug) - Driver.debug("allProceduresAreCallable"); - 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 - { - if (Driver.logDebug) - Driver.debug("allTablesAreSelectable"); - 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 - { - String url = connection.getURL(); - if (Driver.logDebug) - Driver.debug("getURL " + url); - return url; - } - - /* - * 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 - { - String userName = connection.getUserName(); - if (Driver.logDebug) - Driver.debug("getUserName " + userName); - return userName; - } - - /* - * Is the database in read-only mode? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean isReadOnly() throws SQLException - { - boolean isReadOnly = connection.isReadOnly(); - if (Driver.logDebug) - Driver.debug("isReadOnly " + isReadOnly); - return isReadOnly; - } - - /* - * Are NULL values sorted high? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean nullsAreSortedHigh() throws SQLException - { - boolean nullSortedHigh = connection.haveMinimumServerVersion("7.2"); - if (Driver.logDebug) - Driver.debug("nullsAreSortedHigh " + nullSortedHigh); - return nullSortedHigh; - } - - /* - * Are NULL values sorted low? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean nullsAreSortedLow() throws SQLException - { - if (Driver.logDebug) - Driver.debug("nullsAreSortedLow false"); - 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 - { - if (Driver.logDebug) - Driver.debug("nullsAreSortedAtStart false"); - 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 - { - boolean nullsAreSortedAtEnd = ! connection.haveMinimumServerVersion("7.2"); - if (Driver.logDebug) - Driver.debug("nullsAreSortedAtEnd " + nullsAreSortedAtEnd); - return nullsAreSortedAtEnd; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("getDatabaseProductName PostgresSQL"); - return "PostgreSQL"; - } - - /* - * What is the version of this database product. - * - * @return the database version - * @exception SQLException if a database access error occurs - */ - public String getDatabaseProductVersion() throws SQLException - { - String versionNumber = connection.getDBVersionNumber(); - if (Driver.logDebug) - Driver.debug("getDatabaseProductVersion " + versionNumber); - return versionNumber; - } - - /* - * 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 - { - String driverName = "PostgreSQL Native Driver"; - if (Driver.logDebug) - Driver.debug("getDriverName" + driverName); - return driverName; - } - - /* - * 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 - { - String driverVersion = Driver.getVersion(); - if (Driver.logDebug) - Driver.debug("getDriverVersion " + driverVersion); - return driverVersion; - } - - /* - * What is this JDBC driver's major version number? - * - * @return the JDBC driver major version - */ - public int getDriverMajorVersion() - { - return Driver.MAJORVERSION; - } - - /* - * What is this JDBC driver's minor version number? - * - * @return the JDBC driver minor version - */ - public int getDriverMinorVersion() - { - return Driver.MINORVERSION; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("usesLocalFiles " + false); - 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 - { - if (Driver.logDebug) - Driver.debug("usesLocalFilePerTable " + false); - 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. - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsMixedCaseIdentifiers() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsMixedCaseIdentifiers " + false); - return false; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("storesUpperCaseIdentifiers " + false); - 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 - { - if (Driver.logDebug) - Driver.debug("storesLowerCaseIdentifiers " + true); - return true; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("storesMixedCaseIdentifiers " + false); - 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. - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsMixedCaseQuotedIdentifiers " + true); - 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 - { - if (Driver.logDebug) - Driver.debug("storesUpperCaseQuotedIdentifiers " + false); - 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 - { - if (Driver.logDebug) - Driver.debug("storesLowerCaseQuotedIdentifiers " + false); - 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 - { - if (Driver.logDebug) - Driver.debug("storesMixedCaseQuotedIdentifiers " + false); - 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. - * - * @return the quoting string - * @exception SQLException if a database access error occurs - */ - public String getIdentifierQuoteString() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getIdentifierQuoteString \"" ); - return "\""; - } - - /* - * Get a comma separated list of all a database's SQL keywords that - * are NOT also SQL92 keywords. - * - * <p>Within PostgreSQL, the keywords are found in - * src/backend/parser/keywords.c - * - * <p>For SQL Keywords, I took the list provided at - * <a href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> - * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt</a> - * 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 keywords; - } - - public String getNumericFunctions() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getNumericFunctions"); - return ""; - } - - public String getStringFunctions() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getStringFunctions"); - return ""; - } - - public String getSystemFunctions() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getSystemFunctions"); - return ""; - } - - public String getTimeDateFunctions() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getTimeDateFunctions"); - return ""; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("getSearchStringEscape"); - return "\\"; - } - - /* - * Get all the "extra" characters that can be used in unquoted - * identifier names (those beyond a-zA-Z0-9 and _) - * - * <p>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 - { - if (Driver.logDebug) - Driver.debug("getExtraNameCharacters"); - return ""; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("supportsAlterTableWithAddColumn " + true); - return true; - } - - /* - * Is "ALTER TABLE" with a drop column supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsAlterTableWithDropColumn() throws SQLException - { - boolean dropColumn = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsAlterTableWithDropColumn " + dropColumn); - return dropColumn; - } - - /* - * Is column aliasing supported? - * - * <p>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. - * - * <p>e.g. - * - * <br><pre> - * select count(C) as C_COUNT from T group by C; - * - * </pre><br> - * 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 - { - if (Driver.logDebug) - Driver.debug("supportsColumnAliasing " + true); - 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 - { - if (Driver.logDebug) - Driver.debug("nullPlusNonNullIsNull " + true); - return true; - } - - public boolean supportsConvert() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsConvert " + false); - return false; - } - - public boolean supportsConvert(int fromType, int toType) throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsConvert " + false); - return false; - } - - /* - * Are table correlation names supported? A JDBC Compliant - * driver always returns true. - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsTableCorrelationNames() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsTableCorrelationNames " + true); - return true; - } - - /* - * If table correlation names are supported, are they restricted to - * be different from the names of the tables? - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsDifferentTableCorrelationNames() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsDifferentTableCorrelationNames " + false); - return false; - } - - /* - * Are expressions in "ORDER BY" lists supported? - * - * <br>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 - { - if (Driver.logDebug) - Driver.debug("supportsExpressionsInOrderBy " + true); - return true; - } - - /* - * Can an "ORDER BY" clause use columns not in the SELECT? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsOrderByUnrelated() throws SQLException - { - boolean supportsOrderByUnrelated = connection.haveMinimumServerVersion("6.4"); - if (Driver.logDebug) - Driver.debug("supportsOrderByUnrelated " + supportsOrderByUnrelated); - return supportsOrderByUnrelated; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("supportsGroupBy " + true); - return true; - } - - /* - * Can a "GROUP BY" clause use columns not in the SELECT? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsGroupByUnrelated() throws SQLException - { - boolean supportsGroupByUnrelated = connection.haveMinimumServerVersion("6.4"); - if (Driver.logDebug) - Driver.debug("supportsGroupByUnrelated " + supportsGroupByUnrelated); - return supportsGroupByUnrelated; - } - - /* - * 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? - * - * (I think this is a subset of the previous function. -- petere) - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsGroupByBeyondSelect() throws SQLException - { - boolean supportsGroupByBeyondSelect = connection.haveMinimumServerVersion("6.4"); - if (Driver.logDebug) - Driver.debug("supportsGroupByUnrelated " + supportsGroupByBeyondSelect); - return supportsGroupByBeyondSelect; - } - - /* - * 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 - { - boolean supportsLikeEscapeClause = connection.haveMinimumServerVersion("7.1"); - if (Driver.logDebug) - Driver.debug("supportsLikeEscapeClause " + supportsLikeEscapeClause); - return supportsLikeEscapeClause; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("supportsMultipleResultSets " + false); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsMultipleTransactions " + true); - return true; - } - - /* - * Can columns be defined as non-nullable. A JDBC Compliant driver - * always returns true. - * - * <p>This changed from false to true in v6.2 of the driver, as this - * support was added to the backend. - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsNonNullableColumns() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsNonNullableColumns true"); - return true; - } - - /* - * Does this driver support the minimum ODBC SQL grammar. This - * grammar is defined at: - * - * <p><a href="http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm">http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm</a> - * - * <p>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 - { - if (Driver.logDebug) - Driver.debug("supportsMinimumSQLGrammar TRUE"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsCoreSQLGrammar FALSE "); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsExtendedSQLGrammar FALSE"); - return false; - } - - /* - * Does this driver support the ANSI-92 entry level SQL grammar? - * All JDBC Compliant drivers must return true. We currently - * report false until 'schema' support is added. Then this - * should be changed to return true, since we will be mostly - * compliant (probably more compliant than many other databases) - * And since this is a requirement for all JDBC drivers we - * need to get to the point where we can return true. - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsANSI92EntryLevelSQL() throws SQLException - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsANSI92EntryLevelSQL " + schemas); - return schemas; - } - - /* - * Does this driver support the ANSI-92 intermediate level SQL - * grammar? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsANSI92IntermediateSQL() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsANSI92IntermediateSQL false "); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsANSI92FullSQL false "); - return false; - } - - /* - * Is the SQL Integrity Enhancement Facility supported? - * Our best guess is that this means support for constraints - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsIntegrityEnhancementFacility() throws SQLException - { - if (Driver.logDebug) - Driver.debug("supportsIntegrityEnhancementFacility true "); - return true; - } - - /* - * Is some form of outer join supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsOuterJoins() throws SQLException - { - boolean supportsOuterJoins = connection.haveMinimumServerVersion("7.1"); - if (Driver.logDebug) - Driver.debug("supportsOuterJoins " + supportsOuterJoins); - return supportsOuterJoins; - } - - /* - * Are full nexted outer joins supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsFullOuterJoins() throws SQLException - { - boolean supportsFullOuterJoins = connection.haveMinimumServerVersion("7.1"); - if (Driver.logDebug) - Driver.debug("supportsFullOuterJoins " + supportsFullOuterJoins); - return supportsFullOuterJoins; - } - - /* - * Is there limited support for outer joins? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsLimitedOuterJoins() throws SQLException - { - boolean supportsLimitedOuterJoins = connection.haveMinimumServerVersion("7.1"); - if (Driver.logDebug) - Driver.debug("supportsFullOuterJoins " + supportsLimitedOuterJoins); - return supportsLimitedOuterJoins; - } - - /* - * What is the database vendor's preferred term for "schema"? - * PostgreSQL doesn't have schemas, but when it does, we'll use the - * term "schema". - * - * @return the vendor term - * @exception SQLException if a database access error occurs - */ - public String getSchemaTerm() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getSchemaTerm schema"); - return "schema"; - } - - /* - * What is the database vendor's preferred term for "procedure"? - * Traditionally, "function" has been used. - * - * @return the vendor term - * @exception SQLException if a database access error occurs - */ - public String getProcedureTerm() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getProcedureTerm function "); - return "function"; - } - - /* - * What is the database vendor's preferred term for "catalog"? - * - * @return the vendor term - * @exception SQLException if a database access error occurs - */ - public String getCatalogTerm() throws SQLException - { - if (Driver.logDebug) - Driver.debug("getCatalogTerm database "); - return "database"; - } - - /* - * 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 true here; we return false for every other catalog function - // so it won't matter what we return here D.C. - if (Driver.logDebug) - Driver.debug("isCatalogAtStart not implemented"); - return true; - } - - /* - * What is the Catalog separator. - * - * @return the catalog separator string - * @exception SQLException if a database access error occurs - */ - public String getCatalogSeparator() throws SQLException - { - // Give them something to work with here - // everything else returns false so it won't matter what we return here D.C. - if (Driver.logDebug) - Driver.debug("getCatalogSeparator not implemented "); - return "."; - } - - /* - * Can a schema name be used in a data manipulation statement? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsSchemasInDataManipulation() throws SQLException - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsSchemasInDataManipulation "+schemas); - return schemas; - } - - /* - * Can a schema name be used in a procedure call statement? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsSchemasInProcedureCalls() throws SQLException - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsSchemasInProcedureCalls "+schemas); - return schemas; - } - - /* - * Can a schema be used in a table definition statement? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsSchemasInTableDefinitions() throws SQLException - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - - if (Driver.logDebug) - Driver.debug("supportsSchemasInTableDefinitions " + schemas); - return schemas; - } - - /* - * 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 - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsSchemasInIndexDefinitions "+schemas); - return schemas; - } - - /* - * 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 - { - boolean schemas = connection.haveMinimumServerVersion("7.3"); - if (Driver.logDebug) - Driver.debug("supportsSchemasInPrivilegeDefinitions "+schemas); - return schemas; - } - - /* - * 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 - { - if (Driver.logDebug) - Driver.debug("supportsCatalogsInDataManipulation false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsCatalogsInDataManipulation false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsCatalogsInTableDefinitions false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsCatalogsInIndexDefinitions false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsCatalogsInPrivilegeDefinitions false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsPositionedDelete false"); - 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 - { - if (Driver.logDebug) - Driver.debug("supportsPositionedUpdate false"); - return false; // For now... - } - - /* - * Is SELECT for UPDATE supported? - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsSelectForUpdate() throws SQLException - { - return connection.haveMinimumServerVersion("6.5"); - } - - /* - * Are stored procedure calls using the stored procedure escape - * syntax supported? - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsStoredProcedures() throws SQLException - { - return false; - } - - /* - * Are subqueries in comparison expressions supported? A JDBC - * Compliant driver always returns true. - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsSubqueriesInComparisons() throws SQLException - { - return true; - } - - /* - * Are subqueries in 'exists' expressions supported? A JDBC - * Compliant driver always returns true. - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsSubqueriesInExists() throws SQLException - { - return true; - } - - /* - * Are subqueries in 'in' statements supported? A JDBC - * Compliant driver always returns true. - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsSubqueriesInIns() throws SQLException - { - return true; - } - - /* - * Are subqueries in quantified expressions supported? A JDBC - * Compliant driver always returns true. - * - * (No idea what this is, but we support a good deal of - * subquerying.) - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsSubqueriesInQuantifieds() throws SQLException - { - return true; - } - - /* - * Are correlated subqueries supported? A JDBC Compliant driver - * always returns true. - * - * (a.k.a. subselect in from?) - * - * @return true if so; false otherwise - * @exception SQLException - if a database access error occurs - */ - public boolean supportsCorrelatedSubqueries() throws SQLException - { - return connection.haveMinimumServerVersion("7.1"); - } - - /* - * Is SQL UNION supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsUnion() throws SQLException - { - return true; // since 6.3 - } - - /* - * Is SQL UNION ALL supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsUnionAll() throws SQLException - { - return connection.haveMinimumServerVersion("7.1"); - } - - /* - * 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 here, 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; // no limit - } - - /* - * 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 0; // no limit - } - - /* - * Whats the limit on column name length. - * - * @return the maximum column name length - * @exception SQLException if a database access error occurs - */ - public int getMaxColumnNameLength() throws SQLException - { - return getMaxNameLength(); - } - - /* - * 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 0; // no limit - } - - /* - * What's the maximum number of columns allowed in an index? - * - * @return max number of columns - * @exception SQLException if a database access error occurs - */ - public int getMaxColumnsInIndex() throws SQLException - { - return getMaxIndexKeys(); - } - - /* - * What's the maximum number of columns in an "ORDER BY clause? - * - * @return the max columns - * @exception SQLException if a database access error occurs - */ - public int getMaxColumnsInOrderBy() throws SQLException - { - return 0; // no limit - } - - /* - * What is the maximum number of columns in a "SELECT" list? - * - * @return the max columns - * @exception SQLException if a database access error occurs - */ - public int getMaxColumnsInSelect() throws SQLException - { - return 0; // no limit - } - - /* - * What is the maximum number of columns in a table? From the - * CREATE TABLE reference page... - * - * <p>"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 - * - * @return max cursor name length in bytes - * @exception SQLException if a database access error occurs - */ - public int getMaxCursorNameLength() throws SQLException - { - return getMaxNameLength(); - } - - /* - * Retrieves the maximum number of bytes for an index, including all - * of the parts of the index. - * - * @return max index length in bytes, which includes the composite - * of all the constituent parts of the index; a result of zero means - * that there is no limit or the limit is not known - * @exception SQLException if a database access error occurs - */ - public int getMaxIndexLength() throws SQLException - { - return 0; // no limit (larger than an int anyway) - } - - public int getMaxSchemaNameLength() throws SQLException - { - return getMaxNameLength(); - } - - /* - * What is the maximum length of a procedure name - * - * @return the max name length in bytes - * @exception SQLException if a database access error occurs - */ - public int getMaxProcedureNameLength() throws SQLException - { - return getMaxNameLength(); - } - - public int getMaxCatalogNameLength() throws SQLException - { - return getMaxNameLength(); - } - - /* - * What is the maximum length of a single row? - * - * @return max row size in bytes - * @exception SQLException if a database access error occurs - */ - public int getMaxRowSize() throws SQLException - { - if (connection.haveMinimumServerVersion("7.1")) - return 1073741824; // 1 GB - else - return 8192; // XXX could be altered - } - - /* - * 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 - { - if (connection.haveMinimumServerVersion("7.0")) - return 0; // actually whatever fits in size_t - else - return 16384; - } - - /* - * 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 - * - * @return max name length in bytes - * @exception SQLException if a database access error occurs - */ - public int getMaxTableNameLength() throws SQLException - { - return getMaxNameLength(); - } - - /* - * What is the maximum number of tables that can be specified - * in a SELECT? - * - * @return the maximum - * @exception SQLException if a database access error occurs - */ - public int getMaxTablesInSelect() throws SQLException - { - return 0; // no limit - } - - /* - * What is the maximum length of a user name - * - * @return the max name length in bytes - * @exception SQLException if a database access error occurs - */ - public int getMaxUserNameLength() throws SQLException - { - return getMaxNameLength(); - } - - - /* - * 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_READ_COMMITTED; - } - - /* - * 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 and TRANSACTION_READ_COMMITTED - * - * @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 || - level == Connection.TRANSACTION_READ_COMMITTED) - return true; - else if (connection.haveMinimumServerVersion("7.5") && (level == Connection.TRANSACTION_READ_UNCOMMITTED || level == Connection.TRANSACTION_REPEATABLE_READ)) - return true; - else - return false; - } - - /* - * Are both data definition and data manipulation transactions - * supported? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException - { - return true; - } - - /* - * 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 false; - } - - /* - * Does a data definition statement within a transaction force - * the transaction to commit? I think this means something like: - * - * <p><pre> - * 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; - * </pre><p> - * - * 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? - * - * @return true if so - * @exception SQLException if a database access error occurs - */ - public boolean dataDefinitionIgnoredInTransactions() throws SQLException - { - return false; - } - - /** - * Escape single quotes with another single quote. - */ - protected static String escapeQuotes(String s) { - StringBuffer sb = new StringBuffer(); - int length = s.length(); - char prevChar = ' '; - char prevPrevChar = ' '; - for (int i=0; i<length; i++) { - char c = s.charAt(i); - sb.append(c); - if (c == '\'' && (prevChar != '\\' || (prevChar == '\\' && prevPrevChar == '\\'))) { - sb.append("'"); - } - prevPrevChar = prevChar; - prevChar = c; - } - return sb.toString(); - } - - /* - * Get a description of stored procedures available in a catalog - * - * <p>Only procedure descriptions matching the schema and procedure - * name criteria are returned. They are ordered by PROCEDURE_SCHEM - * and PROCEDURE_NAME - * - * <p>Each procedure description has the following columns: - * <ol> - * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) - * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null) - * <li><b>PROCEDURE_NAME</b> String => procedure name - * <li><b>Field 4</b> reserved (make it null) - * <li><b>Field 5</b> reserved (make it null) - * <li><b>Field 6</b> reserved (make it null) - * <li><b>REMARKS</b> String => explanatory comment on the procedure - * <li><b>PROCEDURE_TYPE</b> short => kind of procedure - * <ul> - * <li> procedureResultUnknown - May return a result - * <li> procedureNoResult - Does not return a result - * <li> procedureReturnsResult - Returns a result - * </ul> - * </ol> - * - * @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 - { - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT NULL AS PROCEDURE_CAT, n.nspname AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, d.description AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ - " FROM pg_catalog.pg_namespace n, pg_catalog.pg_proc p "+ - " LEFT JOIN pg_catalog.pg_description d ON (p.oid=d.objoid) "+ - " LEFT JOIN pg_catalog.pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') "+ - " LEFT JOIN pg_catalog.pg_namespace pn ON (c.relnamespace=pn.oid AND pn.nspname='pg_catalog') "+ - " WHERE p.pronamespace=n.oid "; - if (schemaPattern != null && !"".equals(schemaPattern)) { - sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; - } - if (procedureNamePattern != null) { - sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; - } - sql += " ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME "; - } else if (connection.haveMinimumServerVersion("7.1")) { - sql = "SELECT NULL AS PROCEDURE_CAT, NULL AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, d.description AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ - " FROM pg_proc p "+ - " LEFT JOIN pg_description d ON (p.oid=d.objoid) "; - if (connection.haveMinimumServerVersion("7.2")) { - sql += " LEFT JOIN pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') "; - } - if (procedureNamePattern != null) { - sql += " WHERE p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; - } - sql += " ORDER BY PROCEDURE_NAME "; - } else { - sql = "SELECT NULL AS PROCEDURE_CAT, NULL AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, NULL AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ - " FROM pg_proc p "; - if (procedureNamePattern != null) { - sql += " WHERE p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; - } - sql += " ORDER BY PROCEDURE_NAME "; - } - return createMetaDataStatement().executeQuery(sql); - } - - /* - * Get a description of a catalog's stored procedure parameters - * and result columns. - * - * <p>Only descriptions matching the schema, procedure and parameter - * name criteria are returned. They are ordered by PROCEDURE_SCHEM - * and PROCEDURE_NAME. Within this, the return value, if any, is - * first. Next are the parameter descriptions in call order. The - * column descriptions follow in column number order. - * - * <p>Each row in the ResultSet is a parameter description or column - * description with the following fields: - * <ol> - * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) - * <li><b>PROCEDURE_SCHE</b>M String => procedure schema (may be null) - * <li><b>PROCEDURE_NAME</b> String => procedure name - * <li><b>COLUMN_NAME</b> String => column/parameter name - * <li><b>COLUMN_TYPE</b> Short => kind of column/parameter: - * <ul><li>procedureColumnUnknown - nobody knows - * <li>procedureColumnIn - IN parameter - * <li>procedureColumnInOut - INOUT parameter - * <li>procedureColumnOut - OUT parameter - * <li>procedureColumnReturn - procedure return value - * <li>procedureColumnResult - result column in ResultSet - * </ul> - * <li><b>DATA_TYPE</b> short => SQL type from java.sql.Types - * <li><b>TYPE_NAME</b> String => Data source specific type name - * <li><b>PRECISION</b> int => precision - * <li><b>LENGTH</b> int => length in bytes of data - * <li><b>SCALE</b> short => scale - * <li><b>RADIX</b> short => radix - * <li><b>NULLABLE</b> short => can it contain NULL? - * <ul><li>procedureNoNulls - does not allow NULL values - * <li>procedureNullable - allows NULL values - * <li>procedureNullableUnknown - nullability unknown - * <li><b>REMARKS</b> String => comment describing parameter/column - * </ol> - * @param catalog This is ignored in org.postgresql, advise this is set to null - * @param schemaPattern - * @param procedureNamePattern a procedure name pattern - * @param columnNamePattern a column name pattern, this is currently ignored because postgresql does not name procedure parameters. - * @return each row is a stored procedure parameter or column description - * @exception SQLException if a database-access error occurs - * @see #getSearchStringEscape - */ - // Implementation note: This is required for Borland's JBuilder to work - public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException - { - Field f[] = new Field[13]; - Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field("PROCEDURE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("PROCEDURE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("PROCEDURE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("COLUMN_TYPE", Oid.INT2, 2); - f[5] = new Field("DATA_TYPE", Oid.INT2, 2); - f[6] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[7] = new Field("PRECISION", Oid.INT4, 4); - f[8] = new Field("LENGTH", Oid.INT4, 4); - f[9] = new Field("SCALE", Oid.INT2, 2); - f[10] = new Field("RADIX", Oid.INT2, 2); - f[11] = new Field("NULLABLE", Oid.INT2, 2); - f[12] = new Field("REMARKS", Oid.VARCHAR, getMaxNameLength()); - - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid "+ - " FROM pg_catalog.pg_proc p,pg_catalog.pg_namespace n, pg_catalog.pg_type t "+ - " WHERE p.pronamespace=n.oid AND p.prorettype=t.oid "; - if (schemaPattern != null && !"".equals(schemaPattern)) { - sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; - } - if (procedureNamePattern != null) { - sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; - } - sql += " ORDER BY n.nspname, p.proname "; - } else { - sql = "SELECT NULL AS nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid "+ - " FROM pg_proc p,pg_type t "+ - " WHERE p.prorettype=t.oid "; - if (procedureNamePattern != null) { - sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; - } - sql += " ORDER BY p.proname "; - } - - ResultSet rs = connection.createStatement().executeQuery(sql); - while (rs.next()) { - byte schema[] = rs.getBytes("nspname"); - byte procedureName[] = rs.getBytes("proname"); - int returnType = rs.getInt("prorettype"); - String returnTypeType = rs.getString("typtype"); - int returnTypeRelid = rs.getInt("typrelid"); - String strArgTypes = rs.getString("proargtypes"); - StringTokenizer st = new StringTokenizer(strArgTypes); - Vector argTypes = new Vector(); - while (st.hasMoreTokens()) { - argTypes.addElement(new Integer(st.nextToken())); - } - - // decide if we are returning a single column result. - if (!returnTypeType.equals("c")) { - byte[][] tuple = new byte[13][]; - tuple[0] = null; - tuple[1] = schema; - tuple[2] = procedureName; - tuple[3] = connection.encodeString("returnValue"); - tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnReturn)); - tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(returnType))); - tuple[6] = connection.encodeString(connection.getPGType(returnType)); - tuple[7] = null; - tuple[8] = null; - tuple[9] = null; - tuple[10] = null; - tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); - tuple[12] = null; - v.addElement(tuple); - } - - // Add a row for each argument. - for (int i=0; i<argTypes.size(); i++) { - int argOid = ((Integer)argTypes.elementAt(i)).intValue(); - byte[][] tuple = new byte[13][]; - tuple[0] = null; - tuple[1] = schema; - tuple[2] = procedureName; - tuple[3] = connection.encodeString("$"+(i+1)); - tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnIn)); - tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(argOid))); - tuple[6] = connection.encodeString(connection.getPGType(argOid)); - tuple[7] = null; - tuple[8] = null; - tuple[9] = null; - tuple[10] = null; - tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); - tuple[12] = null; - v.addElement(tuple); - } - - // if we are returning a multi-column result. - if (returnTypeType.equals("c")) { - String columnsql = "SELECT a.attname,a.atttypid FROM pg_catalog.pg_attribute a WHERE a.attrelid = "+returnTypeRelid+" ORDER BY a.attnum "; - ResultSet columnrs = connection.createStatement().executeQuery(columnsql); - while (columnrs.next()) { - int columnTypeOid = columnrs.getInt("atttypid"); - byte[][] tuple = new byte[13][]; - tuple[0] = null; - tuple[1] = schema; - tuple[2] = procedureName; - tuple[3] = columnrs.getBytes("attname"); - tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnResult)); - tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(columnTypeOid))); - tuple[6] = connection.encodeString(connection.getPGType(columnTypeOid)); - tuple[7] = null; - tuple[8] = null; - tuple[9] = null; - tuple[10] = null; - tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); - tuple[12] = null; - v.addElement(tuple); - } - } - } - rs.close(); - - return (ResultSet)((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - protected Statement createMetaDataStatement() throws SQLException - { - return connection.createStatement(); - } - - /* - * Get a description of tables available in a catalog. - * - * <p>Only table descriptions matching the catalog, schema, table - * name and type criteria are returned. They are ordered by - * TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. - * - * <p>Each table description has the following columns: - * - * <ol> - * <li><b>TABLE_CAT</b> String => table catalog (may be null) - * <li><b>TABLE_SCHEM</b> String => table schema (may be null) - * <li><b>TABLE_NAME</b> String => table name - * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE", - * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL - * TEMPORARY", "ALIAS", "SYNONYM". - * <li><b>REMARKS</b> String => explanatory comment on the table - * </ol> - * - * <p>The valid values for the types parameter are: - * "TABLE", "INDEX", "SEQUENCE", "VIEW", - * "SYSTEM TABLE", "SYSTEM INDEX", "SYSTEM VIEW", - * "SYSTEM TOAST TABLE", "SYSTEM TOAST INDEX", - * "TEMPORARY TABLE", and "TEMPORARY VIEW" - * - * @param catalog a catalog name; For org.postgresql, this is ignored, and - * should be set to null - * @param schemaPattern a schema name pattern - * @param tableNamePattern a table name pattern. For all tables this should be "%" - * @param types a list of table types to include; null returns - * all types - * @return each row is a table description - * @exception SQLException if a database-access error occurs. - */ - public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException - { - String select; - String orderby; - String useSchemas; - if (connection.haveMinimumServerVersion("7.3")) { - useSchemas = "SCHEMAS"; - select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+ - " CASE n.nspname LIKE 'pg\\\\_%' OR n.nspname = 'information_schema' "+ - " WHEN true THEN CASE "+ - " WHEN n.nspname = 'pg_catalog' OR n.nspname = 'information_schema' THEN CASE c.relkind "+ - " WHEN 'r' THEN 'SYSTEM TABLE' "+ - " WHEN 'v' THEN 'SYSTEM VIEW' "+ - " WHEN 'i' THEN 'SYSTEM INDEX' "+ - " ELSE NULL "+ - " END "+ - " WHEN n.nspname = 'pg_toast' THEN CASE c.relkind "+ - " WHEN 'r' THEN 'SYSTEM TOAST TABLE' "+ - " WHEN 'i' THEN 'SYSTEM TOAST INDEX' "+ - " ELSE NULL "+ - " END "+ - " ELSE CASE c.relkind "+ - " WHEN 'r' THEN 'TEMPORARY TABLE' "+ - " WHEN 'i' THEN 'TEMPORARY INDEX' "+ - " ELSE NULL "+ - " END "+ - " END "+ - " WHEN false THEN CASE c.relkind "+ - " WHEN 'r' THEN 'TABLE' "+ - " WHEN 'i' THEN 'INDEX' "+ - " WHEN 'S' THEN 'SEQUENCE' "+ - " WHEN 'v' THEN 'VIEW' "+ - " ELSE NULL "+ - " END "+ - " ELSE NULL "+ - " END "+ - " AS TABLE_TYPE, d.description AS REMARKS "+ - " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c "+ - " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0) "+ - " LEFT JOIN pg_catalog.pg_class dc ON (d.classoid=dc.oid AND dc.relname='pg_class') "+ - " LEFT JOIN pg_catalog.pg_namespace dn ON (dn.oid=dc.relnamespace AND dn.nspname='pg_catalog') "+ - " WHERE c.relnamespace = n.oid "; - if (schemaPattern != null && !"".equals(schemaPattern)) { - select += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; - } - orderby = " ORDER BY TABLE_TYPE,TABLE_SCHEM,TABLE_NAME "; - } else { - useSchemas = "NOSCHEMAS"; - String tableType = ""+ - " CASE c.relname LIKE 'pg\\\\_%' "+ - " WHEN true THEN CASE c.relname LIKE 'pg\\\\_toast\\\\_%' "+ - " WHEN true THEN CASE c.relkind "+ - " WHEN 'r' THEN 'SYSTEM TOAST TABLE' "+ - " WHEN 'i' THEN 'SYSTEM TOAST INDEX' "+ - " ELSE NULL "+ - " END "+ - " WHEN false THEN CASE c.relname LIKE 'pg\\\\_temp\\\\_%' "+ - " WHEN true THEN CASE c.relkind "+ - " WHEN 'r' THEN 'TEMPORARY TABLE' "+ - " WHEN 'i' THEN 'TEMPORARY INDEX' "+ - " ELSE NULL "+ - " END "+ - " WHEN false THEN CASE c.relkind "+ - " WHEN 'r' THEN 'SYSTEM TABLE' "+ - " WHEN 'v' THEN 'SYSTEM VIEW' "+ - " WHEN 'i' THEN 'SYSTEM INDEX' "+ - " ELSE NULL "+ - " END "+ - " ELSE NULL "+ - " END "+ - " ELSE NULL "+ - " END "+ - " WHEN false THEN CASE c.relkind "+ - " WHEN 'r' THEN 'TABLE' "+ - " WHEN 'i' THEN 'INDEX' "+ - " WHEN 'S' THEN 'SEQUENCE' "+ - " WHEN 'v' THEN 'VIEW' "+ - " ELSE NULL "+ - " END "+ - " ELSE NULL "+ - " END "; - orderby = " ORDER BY TABLE_TYPE,TABLE_NAME "; - if (connection.haveMinimumServerVersion("7.2")) { - select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, d.description AS REMARKS "+ - " FROM pg_class c "+ - " LEFT JOIN pg_description d ON (c.oid=d.objoid AND d.objsubid = 0) "+ - " LEFT JOIN pg_class dc ON (d.classoid = dc.oid AND dc.relname='pg_class') "+ - " WHERE true "; - } else if (connection.haveMinimumServerVersion("7.1")) { - select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, d.description AS REMARKS "+ - " FROM pg_class c "+ - " LEFT JOIN pg_description d ON (c.oid=d.objoid) "+ - " WHERE true "; - } else { - select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, NULL AS REMARKS "+ - " FROM pg_class c "+ - " WHERE true "; - } - } - - if (types == null) { - types = defaultTableTypes; - } - if (tableNamePattern != null) { - select += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; - } - String sql = select; - sql += " AND (false "; - for (int i=0; i<types.length; i++) { - Hashtable clauses = (Hashtable)tableTypeClauses.get(types[i]); - if (clauses != null) { - String clause = (String)clauses.get(useSchemas); - sql += " OR ( "+clause+" ) "; - } - } - sql += ") "; - sql += orderby; - - return createMetaDataStatement().executeQuery(sql); - } - - private static final Hashtable tableTypeClauses; - static { - tableTypeClauses = new Hashtable(); - Hashtable ht = new Hashtable(); - tableTypeClauses.put("TABLE",ht); - ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname NOT LIKE 'pg\\\\_%' AND n.nspname <> 'information_schema'"); - ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname NOT LIKE 'pg\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("VIEW",ht); - ht.put("SCHEMAS","c.relkind = 'v' AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema'"); - ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname NOT LIKE 'pg\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("INDEX",ht); - ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname NOT LIKE 'pg\\\\_%' AND n.nspname <> 'information_schema'"); - ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname NOT LIKE 'pg\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("SEQUENCE",ht); - ht.put("SCHEMAS","c.relkind = 'S'"); - ht.put("NOSCHEMAS","c.relkind = 'S'"); - ht = new Hashtable(); - tableTypeClauses.put("SYSTEM TABLE",ht); - ht.put("SCHEMAS","c.relkind = 'r' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema')"); - ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_%' AND c.relname NOT LIKE 'pg\\\\_toast\\\\_%' AND c.relname NOT LIKE 'pg\\\\_temp\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("SYSTEM TOAST TABLE",ht); - ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname = 'pg_toast'"); - ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_toast\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("SYSTEM TOAST INDEX",ht); - ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname = 'pg_toast'"); - ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname LIKE 'pg\\\\_toast\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("SYSTEM VIEW",ht); - ht.put("SCHEMAS","c.relkind = 'v' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); - ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname LIKE 'pg\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("SYSTEM INDEX",ht); - ht.put("SCHEMAS","c.relkind = 'i' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); - ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname LIKE 'pg\\\\_%' AND c.relname NOT LIKE 'pg\\\\_toast\\\\_%' AND c.relname NOT LIKE 'pg\\\\_temp\\\\_%'"); - ht = new Hashtable(); - tableTypeClauses.put("TEMPORARY TABLE",ht); - ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname LIKE 'pg\\\\_temp\\\\_%' "); - ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_temp\\\\_%' "); - ht = new Hashtable(); - tableTypeClauses.put("TEMPORARY INDEX",ht); - ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname LIKE 'pg\\\\_temp\\\\_%' "); - ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname LIKE 'pg\\\\_temp\\\\_%' "); - } - - // These are the default tables, used when NULL is passed to getTables - // The choice of these provide the same behaviour as psql's \d - private static final String defaultTableTypes[] = { - "TABLE", "VIEW", "INDEX", "SEQUENCE", "TEMPORARY TABLE" - }; - - /* - * Get the schema names available in this database. The results - * are ordered by schema name. - * - * <P>The schema column is: - * <OL> - * <LI><B>TABLE_SCHEM</B> String => schema name - * </OL> - * - * @return ResultSet each row has a single String column that is a - * schema name - */ - public java.sql.ResultSet getSchemas() throws SQLException - { - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT nspname AS TABLE_SCHEM FROM pg_catalog.pg_namespace WHERE nspname <> 'pg_toast' AND nspname NOT LIKE 'pg\\\\_temp\\\\_%' ORDER BY TABLE_SCHEM"; - } else { - sql = "SELECT ''::text AS TABLE_SCHEM ORDER BY TABLE_SCHEM"; - } - return createMetaDataStatement().executeQuery(sql); - } - - /* - * Get the catalog names available in this database. The results - * are ordered by catalog name. - * - * <P>The catalog column is: - * <OL> - * <LI><B>TABLE_CAT</B> String => catalog name - * </OL> - * - * @return ResultSet each row has a single String column that is a - * catalog name - */ - public java.sql.ResultSet getCatalogs() throws SQLException - { - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT datname AS TABLE_CAT FROM pg_catalog.pg_database ORDER BY TABLE_CAT"; - } else { - sql = "SELECT datname AS TABLE_CAT FROM pg_database ORDER BY TABLE_CAT"; - } - return createMetaDataStatement().executeQuery(sql); - } - - /* - * Get the table types available in this database. The results - * are ordered by table type. - * - * <P>The table type is: - * <OL> - * <LI><B>TABLE_TYPE</B> String => table type. Typical types are "TABLE", - * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", - * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". - * </OL> - * - * @return ResultSet each row has a single String column that is a - * table type - */ - public java.sql.ResultSet getTableTypes() throws SQLException - { - String types[] = new String[tableTypeClauses.size()]; - Enumeration e = tableTypeClauses.keys(); - int i=0; - while (e.hasMoreElements()) { - types[i++] = (String)e.nextElement();} - sortStringArray(types); - - Field f[] = new Field[1]; - Vector v = new Vector(); - f[0] = new Field(new String("TABLE_TYPE"), Oid.VARCHAR, getMaxNameLength()); - for (i=0; i < types.length; i++) - { - byte[][] tuple = new byte[1][]; - tuple[0] = connection.encodeString(types[i]); - v.addElement(tuple); - } - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of table columns available in a catalog. - * - * <P>Only column descriptions matching the catalog, schema, table - * and column name criteria are returned. They are ordered by - * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION. - * - * <P>Each column description has the following columns: - * <OL> - * <LI><B>TABLE_CAT</B> String => table catalog (may be null) - * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) - * <LI><B>TABLE_NAME</B> String => table name - * <LI><B>COLUMN_NAME</B> String => column name - * <LI><B>DATA_TYPE</B> short => SQL type from java.sql.Types - * <LI><B>TYPE_NAME</B> String => Data source dependent type name - * <LI><B>COLUMN_SIZE</B> int => column size. For char or date - * types this is the maximum number of characters, for numeric or - * decimal types this is precision. - * <LI><B>BUFFER_LENGTH</B> is not used. - * <LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits - * <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2) - * <LI><B>NULLABLE</B> int => is NULL allowed? - * <UL> - * <LI> columnNoNulls - might not allow NULL values - * <LI> columnNullable - definitely allows NULL values - * <LI> columnNullableUnknown - nullability unknown - * </UL> - * <LI><B>REMARKS</B> String => comment describing column (may be null) - * <LI><B>COLUMN_DEF</B> String => default value (may be null) - * <LI><B>SQL_DATA_TYPE</B> int => unused - * <LI><B>SQL_DATETIME_SUB</B> int => unused - * <LI><B>CHAR_OCTET_LENGTH</B> int => for char types the - * maximum number of bytes in the column - * <LI><B>ORDINAL_POSITION</B> int => index of column in table - * (starting at 1) - * <LI><B>IS_NULLABLE</B> String => "NO" means column definitely - * does not allow NULL values; "YES" means the column might - * allow NULL values. An empty string means nobody knows. - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schemaPattern a schema name pattern; "" retrieves those - * without a schema - * @param tableNamePattern a table name pattern - * @param columnNamePattern a column name pattern - * @return ResultSet each row is a column description - * @see #getSearchStringEscape - */ - public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException - { - Vector v = new Vector(); // The new ResultSet tuple stuff - Field f[] = new Field[18]; // The field descriptors for the new ResultSet - - f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("DATA_TYPE", Oid.INT2, 2); - f[5] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[6] = new Field("COLUMN_SIZE", Oid.INT4, 4); - f[7] = new Field("BUFFER_LENGTH", Oid.VARCHAR, getMaxNameLength()); - f[8] = new Field("DECIMAL_DIGITS", Oid.INT4, 4); - f[9] = new Field("NUM_PREC_RADIX", Oid.INT4, 4); - f[10] = new Field("NULLABLE", Oid.INT4, 4); - f[11] = new Field("REMARKS", Oid.VARCHAR, getMaxNameLength()); - f[12] = new Field("COLUMN_DEF", Oid.VARCHAR, getMaxNameLength()); - f[13] = new Field("SQL_DATA_TYPE", Oid.INT4, 4); - f[14] = new Field("SQL_DATETIME_SUB", Oid.INT4, 4); - f[15] = new Field("CHAR_OCTET_LENGTH", Oid.VARCHAR, getMaxNameLength()); - f[16] = new Field("ORDINAL_POSITION", Oid.INT4, 4); - f[17] = new Field("IS_NULLABLE", Oid.VARCHAR, getMaxNameLength()); - - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ - " FROM pg_catalog.pg_namespace n "+ - " JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) "+ - " JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) "+ - " LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ - " LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) "+ - " LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') "+ - " LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') "+ - " WHERE a.attnum > 0 AND NOT a.attisdropped "; - if (schemaPattern != null && !"".equals(schemaPattern)) { - sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; - } - } else if (connection.haveMinimumServerVersion("7.2")) { - sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ - " FROM pg_class c "+ - " JOIN pg_attribute a ON (a.attrelid=c.oid) "+ - " LEFT JOIN pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ - " LEFT JOIN pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) "+ - " LEFT JOIN pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') "+ - " WHERE a.attnum > 0 "; - } else if (connection.haveMinimumServerVersion("7.1")) { - sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ - " FROM pg_class c "+ - " JOIN pg_attribute a ON (a.attrelid=c.oid) "+ - " LEFT JOIN pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ - " LEFT JOIN pg_description dsc ON (a.oid=dsc.objoid) "+ - " WHERE a.attnum > 0 "; - } else { - // if < 7.1 then don't get defaults or descriptions. - sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,NULL AS adsrc,NULL AS description "+ - " FROM pg_class c, pg_attribute a "+ - " WHERE a.attrelid=c.oid AND a.attnum > 0 "; - } - - if (tableNamePattern != null && !"".equals(tableNamePattern)) { - sql += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; - } - if (columnNamePattern != null && !"".equals(columnNamePattern)) { - sql += " AND a.attname LIKE '"+escapeQuotes(columnNamePattern)+"' "; - } - sql += " ORDER BY nspname,relname,attnum "; - - ResultSet rs = connection.createStatement().executeQuery(sql); - while (rs.next()) - { - byte[][] tuple = new byte[18][]; - int typeOid = rs.getInt("atttypid"); - - tuple[0] = null; // Catalog name, not supported - tuple[1] = rs.getBytes("nspname"); // Schema - tuple[2] = rs.getBytes("relname"); // Table name - tuple[3] = rs.getBytes("attname"); // Column name - tuple[4] = connection.encodeString(Integer.toString(connection.getSQLType(typeOid))); - String pgType = connection.getPGType(typeOid); - tuple[5] = connection.encodeString(pgType); // Type name - - // by default no decimal_digits - // if the type is numeric or decimal we will - // overwrite later. - tuple[8] = connection.encodeString("0"); - - if (pgType.equals("bpchar") || pgType.equals("varchar")) - { - int atttypmod = rs.getInt("atttypmod"); - tuple[6] = connection.encodeString(Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0)); - } - else if (pgType.equals("numeric") || pgType.equals("decimal")) - { - int attypmod = rs.getInt("atttypmod") - VARHDRSZ; - tuple[6] = connection.encodeString(Integer.toString( ( attypmod >> 16 ) & 0xffff )); - tuple[8] = connection.encodeString(Integer.toString(attypmod & 0xffff)); - tuple[9] = connection.encodeString("10"); - } - else if (pgType.equals("bit") || pgType.equals("varbit")) { - tuple[6] = rs.getBytes("atttypmod"); - tuple[9] = connection.encodeString("2"); - } - else { - tuple[6] = rs.getBytes("attlen"); - tuple[9] = connection.encodeString("10"); - } - - tuple[7] = null; // Buffer length - - tuple[10] = connection.encodeString(Integer.toString(rs.getBoolean("attnotnull") ? java.sql.DatabaseMetaData.columnNoNulls : java.sql.DatabaseMetaData.columnNullable)); // Nullable - tuple[11] = rs.getBytes("description"); // Description (if any) - tuple[12] = rs.getBytes("adsrc"); // Column default - tuple[13] = null; // sql data type (unused) - tuple[14] = null; // sql datetime sub (unused) - tuple[15] = tuple[6]; // char octet length - tuple[16] = rs.getBytes("attnum"); // ordinal position - tuple[17] = connection.encodeString(rs.getBoolean("attnotnull") ? "NO" : "YES"); // Is nullable - - v.addElement(tuple); - } - rs.close(); - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of the access rights for a table's columns. - * - * <P>Only privileges matching the column name criteria are - * returned. They are ordered by COLUMN_NAME and PRIVILEGE. - * - * <P>Each privilige description has the following columns: - * <OL> - * <LI><B>TABLE_CAT</B> String => table catalog (may be null) - * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) - * <LI><B>TABLE_NAME</B> String => table name - * <LI><B>COLUMN_NAME</B> String => column name - * <LI><B>GRANTOR</B> => grantor of access (may be null) - * <LI><B>GRANTEE</B> String => grantee of access - * <LI><B>PRIVILEGE</B> String => name of access (SELECT, - * INSERT, UPDATE, REFRENCES, ...) - * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted - * to grant to others; "NO" if not; null if unknown - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name; "" retrieves those without a schema - * @param table a table name - * @param columnNamePattern a column name pattern - * @return ResultSet each row is a column privilege description - * @see #getSearchStringEscape - */ - public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException - { - Field f[] = new Field[8]; - Vector v = new Vector(); - - if (table == null) - table = "%"; - - if (columnNamePattern == null) - columnNamePattern = "%"; - - f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("GRANTOR", Oid.VARCHAR, getMaxNameLength()); - f[5] = new Field("GRANTEE", Oid.VARCHAR, getMaxNameLength()); - f[6] = new Field("PRIVILEGE", Oid.VARCHAR, getMaxNameLength()); - f[7] = new Field("IS_GRANTABLE", Oid.VARCHAR, getMaxNameLength()); - - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT n.nspname,c.relname,u.usename,c.relacl,a.attname "+ - " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_attribute a "+ - " WHERE c.relnamespace = n.oid "+ - " AND u.usesysid = c.relowner "+ - " AND c.oid = a.attrelid "+ - " AND c.relkind = 'r' "+ - " AND a.attnum > 0 AND NOT a.attisdropped "; - if (schema != null && !"".equals(schema)) { - sql += " AND n.nspname = '"+escapeQuotes(schema)+"' "; - } - } else { - sql = "SELECT NULL::text AS nspname,c.relname,u.usename,c.relacl,a.attname "+ - "FROM pg_class c, pg_user u,pg_attribute a "+ - " WHERE u.usesysid = c.relowner "+ - " AND c.oid = a.attrelid "+ - " AND a.attnum > 0 "+ - " AND c.relkind = 'r' "; - } - - sql += " AND c.relname = '"+escapeQuotes(table)+"' "; - if (columnNamePattern != null && !"".equals(columnNamePattern)) { - sql += " AND a.attname LIKE '"+escapeQuotes(columnNamePattern)+"' "; - } - sql += " ORDER BY attname "; - - ResultSet rs = connection.createStatement().executeQuery(sql); - while (rs.next()) { - byte schemaName[] = rs.getBytes("nspname"); - byte tableName[] = rs.getBytes("relname"); - byte column[] = rs.getBytes("attname"); - String owner = rs.getString("usename"); - String acl = rs.getString("relacl"); - Hashtable permissions = parseACL(acl, owner); - String permNames[] = new String[permissions.size()]; - Enumeration e = permissions.keys(); - int i=0; - while (e.hasMoreElements()) { - permNames[i++] = (String)e.nextElement(); - } - sortStringArray(permNames); - for (i=0; i<permNames.length; i++) { - byte[] privilege = connection.encodeString(permNames[i]); - Vector grantees = (Vector)permissions.get(permNames[i]); - for (int j=0; j<grantees.size(); j++) { - String grantee = (String)grantees.elementAt(j); - String grantable = owner.equals(grantee) ? "YES" : "NO"; - byte[][] tuple = new byte[8][]; - tuple[0] = null; - tuple[1] = schemaName; - tuple[2] = tableName; - tuple[3] = column; - tuple[4] = connection.encodeString(owner); - tuple[5] = connection.encodeString(grantee); - tuple[6] = privilege; - tuple[7] = connection.encodeString(grantable); - v.addElement(tuple); - } - } - } - rs.close(); - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of the access rights for each table available - * in a catalog. - * - * This method is currently unimplemented. - * - * <P>Only privileges matching the schema and table name - * criteria are returned. They are ordered by TABLE_SCHEM, - * TABLE_NAME, and PRIVILEGE. - * - * <P>Each privilige description has the following columns: - * <OL> - * <LI><B>TABLE_CAT</B> String => table catalog (may be null) - * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) - * <LI><B>TABLE_NAME</B> String => table name - * <LI><B>GRANTOR</B> => grantor of access (may be null) - * <LI><B>GRANTEE</B> String => grantee of access - * <LI><B>PRIVILEGE</B> String => name of access (SELECT, - * INSERT, UPDATE, REFRENCES, ...) - * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted - * to grant to others; "NO" if not; null if unknown - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schemaPattern a schema name pattern; "" retrieves those - * without a schema - * @param tableNamePattern a table name pattern - * @return ResultSet each row is a table privilege description - * @see #getSearchStringEscape - */ - public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException - { - Field f[] = new Field[7]; - Vector v = new Vector(); - - f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[3] = new Field("GRANTOR", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("GRANTEE", Oid.VARCHAR, getMaxNameLength()); - f[5] = new Field("PRIVILEGE", Oid.VARCHAR, getMaxNameLength()); - f[6] = new Field("IS_GRANTABLE", Oid.VARCHAR, getMaxNameLength()); - - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT n.nspname,c.relname,u.usename,c.relacl "+ - " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u "+ - " WHERE c.relnamespace = n.oid "+ - " AND u.usesysid = c.relowner "+ - " AND c.relkind = 'r' "; - if (schemaPattern != null && !"".equals(schemaPattern)) { - sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; - } - } else { - sql = "SELECT NULL::text AS nspname,c.relname,u.usename,c.relacl "+ - "FROM pg_class c, pg_user u "+ - " WHERE u.usesysid = c.relowner "+ - " AND c.relkind = 'r' "; - } - - if (tableNamePattern != null && !"".equals(tableNamePattern)) { - sql += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; - } - sql += " ORDER BY nspname, relname "; - - ResultSet rs = connection.createStatement().executeQuery(sql); - while (rs.next()) { - byte schema[] = rs.getBytes("nspname"); - byte table[] = rs.getBytes("relname"); - String owner = rs.getString("usename"); - String acl = rs.getString("relacl"); - Hashtable permissions = parseACL(acl, owner); - String permNames[] = new String[permissions.size()]; - Enumeration e = permissions.keys(); - int i=0; - while (e.hasMoreElements()) { - permNames[i++] = (String)e.nextElement(); - } - sortStringArray(permNames); - for (i=0; i<permNames.length; i++) { - byte[] privilege = connection.encodeString(permNames[i]); - Vector grantees = (Vector)permissions.get(permNames[i]); - for (int j=0; j<grantees.size(); j++) { - String grantee = (String)grantees.elementAt(j); - String grantable = owner.equals(grantee) ? "YES" : "NO"; - byte[][] tuple = new byte[7][]; - tuple[0] = null; - tuple[1] = schema; - tuple[2] = table; - tuple[3] = connection.encodeString(owner); - tuple[4] = connection.encodeString(grantee); - tuple[5] = privilege; - tuple[6] = connection.encodeString(grantable); - v.addElement(tuple); - } - } - } - rs.close(); - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - private static void sortStringArray(String s[]) { - for (int i=0; i<s.length-1; i++) { - for (int j=i+1; j<s.length; j++) { - if (s[i].compareTo(s[j]) > 0) { - String tmp = s[i]; - s[i] = s[j]; - s[j] = tmp; - } - } - } - } - - /** - * Parse an String of ACLs into a Vector of ACLs. - */ - private static Vector parseACLArray(String aclString) { - Vector acls = new Vector(); - if (aclString == null || aclString.length() == 0) { - return acls; - } - boolean inQuotes = false; - // start at 1 because of leading "{" - int beginIndex = 1; - char prevChar = ' '; - for (int i=beginIndex; i<aclString.length(); i++) { - - char c = aclString.charAt(i); - if (c == '"' && prevChar != '\\') { - inQuotes = !inQuotes; - } else if (c == ',' && !inQuotes) { - acls.addElement(aclString.substring(beginIndex,i)); - beginIndex = i+1; - } - prevChar = c; - } - // add last element removing the trailing "}" - acls.addElement(aclString.substring(beginIndex,aclString.length()-1)); - - // Strip out enclosing quotes, if any. - for (int i=0; i<acls.size(); i++) { - String acl = (String)acls.elementAt(i); - if (acl.startsWith("\"") && acl.endsWith("\"")) { - acl = acl.substring(1,acl.length()-1); - acls.setElementAt(acl,i); - } - } - return acls; - } - - /** - * Add the user described by the given acl to the Vectors of users - * with the privileges described by the acl. - */ - private void addACLPrivileges(String acl, Hashtable privileges) { - int equalIndex = acl.lastIndexOf("="); - String name = acl.substring(0,equalIndex); - if (name.length() == 0) { - name = "PUBLIC"; - } - String privs = acl.substring(equalIndex+1); - for (int i=0; i<privs.length(); i++) { - char c = privs.charAt(i); - String sqlpriv; - switch (c) { - case 'a': sqlpriv = "INSERT"; break; - case 'r': sqlpriv = "SELECT"; break; - case 'w': sqlpriv = "UPDATE"; break; - case 'd': sqlpriv = "DELETE"; break; - case 'R': sqlpriv = "RULE"; break; - case 'x': sqlpriv = "REFERENCES"; break; - case 't': sqlpriv = "TRIGGER"; break; - // the folloowing can't be granted to a table, but - // we'll keep them for completeness. - case 'X': sqlpriv = "EXECUTE"; break; - case 'U': sqlpriv = "USAGE"; break; - case 'C': sqlpriv = "CREATE"; break; - case 'T': sqlpriv = "CREATE TEMP"; break; - default: sqlpriv = "UNKNOWN"; - } - Vector usersWithPermission = (Vector)privileges.get(sqlpriv); - if (usersWithPermission == null) { - usersWithPermission = new Vector(); - privileges.put(sqlpriv,usersWithPermission); - } - usersWithPermission.addElement(name); - } - } - - /** - * Take the a String representing an array of ACLs and return - * a Hashtable mapping the SQL permission name to a Vector of - * usernames who have that permission. - */ - protected Hashtable parseACL(String aclArray, String owner) { - if (aclArray == null || aclArray == "") { - //null acl is a shortcut for owner having full privs - aclArray = "{" + owner + "=arwdRxt}"; - } - Vector acls = parseACLArray(aclArray); - Hashtable privileges = new Hashtable(); - for (int i=0; i<acls.size(); i++) { - String acl = (String)acls.elementAt(i); - addACLPrivileges(acl,privileges); - } - return privileges; - } - - /* - * Get a description of a table's optimal set of columns that - * uniquely identifies a row. They are ordered by SCOPE. - * - * <P>Each column description has the following columns: - * <OL> - * <LI><B>SCOPE</B> short => actual scope of result - * <UL> - * <LI> bestRowTemporary - very temporary, while using row - * <LI> bestRowTransaction - valid for remainder of current transaction - * <LI> bestRowSession - valid for remainder of current session - * </UL> - * <LI><B>COLUMN_NAME</B> String => column name - * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types - * <LI><B>TYPE_NAME</B> String => Data source dependent type name - * <LI><B>COLUMN_SIZE</B> int => precision - * <LI><B>BUFFER_LENGTH</B> int => not used - * <LI><B>DECIMAL_DIGITS</B> short => scale - * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column - * like an Oracle ROWID - * <UL> - * <LI> bestRowUnknown - may or may not be pseudo column - * <LI> bestRowNotPseudo - is NOT a pseudo column - * <LI> bestRowPseudo - is a pseudo column - * </UL> - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name; "" retrieves those without a schema - * @param table a table name - * @param scope the scope of interest; use same values as SCOPE - * @param nullable include columns that are nullable? - * @return ResultSet each row is a column description - */ - // Implementation note: This is required for Borland's JBuilder to work - public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException - { - Field f[] = new Field[8]; - Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field("SCOPE", Oid.INT2, 2); - f[1] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("DATA_TYPE", Oid.INT2, 2); - f[3] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("COLUMN_SIZE", Oid.INT4, 4); - f[5] = new Field("BUFFER_LENGTH", Oid.INT4, 4); - f[6] = new Field("DECIMAL_DIGITS", Oid.INT2, 2); - f[7] = new Field("PSEUDO_COLUMN", Oid.INT2, 2); - - /* At the moment this simply returns a table's primary key, - * if there is one. I believe other unique indexes, ctid, - * and oid should also be considered. -KJ - */ - - String from; - String where = ""; - if (connection.haveMinimumServerVersion("7.3")) { - from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_attribute a, pg_catalog.pg_index i "; - where = " AND ct.relnamespace = n.oid "; - if (schema != null && !"".equals(schema)) { - where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; - } - } else { - from = " FROM pg_class ct, pg_class ci, pg_attribute a, pg_index i "; - } - String sql = "SELECT a.attname, a.atttypid "+ - from+ - " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid "+ - " AND a.attrelid=ci.oid AND i.indisprimary "+ - " AND ct.relname = '"+escapeQuotes(table)+"' "+ - where+ - " ORDER BY a.attnum "; - - ResultSet rs = connection.createStatement().executeQuery(sql); - while (rs.next()) { - byte tuple[][] = new byte[8][]; - int columnTypeOid = rs.getInt("atttypid"); - tuple[0] = connection.encodeString(Integer.toString(scope)); - tuple[1] = rs.getBytes("attname"); - tuple[2] = connection.encodeString(Integer.toString(connection.getSQLType(columnTypeOid))); - tuple[3] = connection.encodeString(connection.getPGType(columnTypeOid)); - tuple[4] = null; - tuple[5] = null; - tuple[6] = null; - tuple[7] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.bestRowNotPseudo)); - v.addElement(tuple); - } - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of a table's columns that are automatically - * updated when any value in a row is updated. They are - * unordered. - * - * <P>Each column description has the following columns: - * <OL> - * <LI><B>SCOPE</B> short => is not used - * <LI><B>COLUMN_NAME</B> String => column name - * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types - * <LI><B>TYPE_NAME</B> String => Data source dependent type name - * <LI><B>COLUMN_SIZE</B> int => precision - * <LI><B>BUFFER_LENGTH</B> int => length of column value in bytes - * <LI><B>DECIMAL_DIGITS</B> short => scale - * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column - * like an Oracle ROWID - * <UL> - * <LI> versionColumnUnknown - may or may not be pseudo column - * <LI> versionColumnNotPseudo - is NOT a pseudo column - * <LI> versionColumnPseudo - is a pseudo column - * </UL> - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name; "" retrieves those without a schema - * @param table a table name - * @return ResultSet each row is a column description - */ - public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException - { - Field f[] = new Field[8]; - Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field("SCOPE", Oid.INT2, 2); - f[1] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("DATA_TYPE", Oid.INT2, 2); - f[3] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("COLUMN_SIZE", Oid.INT4, 4); - f[5] = new Field("BUFFER_LENGTH", Oid.INT4, 4); - f[6] = new Field("DECIMAL_DIGITS", Oid.INT2, 2); - f[7] = new Field("PSEUDO_COLUMN", Oid.INT2, 2); - - byte tuple[][] = new byte[8][]; - - /* Postgresql does not have any column types that are - * automatically updated like some databases' timestamp type. - * We can't tell what rules or triggers might be doing, so we - * are left with the system columns that change on an update. - * An update may change all of the following system columns: - * ctid, xmax, xmin, cmax, and cmin. Depending on if we are - * in a transaction and wether we roll it back or not the - * only guaranteed change is to ctid. -KJ - */ - - tuple[0] = null; - tuple[1] = connection.encodeString("ctid"); - tuple[2] = connection.encodeString(Integer.toString(connection.getSQLType("tid"))); - tuple[3] = connection.encodeString("tid"); - tuple[4] = null; - tuple[5] = null; - tuple[6] = null; - tuple[7] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.versionColumnPseudo)); - v.addElement(tuple); - - /* Perhaps we should check that the given - * catalog.schema.table actually exists. -KJ - */ - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of a table's primary key columns. They - * are ordered by COLUMN_NAME. - * - * <P>Each column description has the following columns: - * <OL> - * <LI><B>TABLE_CAT</B> String => table catalog (may be null) - * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) - * <LI><B>TABLE_NAME</B> String => table name - * <LI><B>COLUMN_NAME</B> String => column name - * <LI><B>KEY_SEQ</B> short => sequence number within primary key - * <LI><B>PK_NAME</B> String => primary key name (may be null) - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name pattern; "" retrieves those - * without a schema - * @param table a table name - * @return ResultSet each row is a primary key column description - */ - public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException - { - String select; - String from; - String where = ""; - if (connection.haveMinimumServerVersion("7.3")) { - select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "; - from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_attribute a, pg_catalog.pg_index i "; - where = " AND ct.relnamespace = n.oid "; - if (schema != null && !"".equals(schema)) { - where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; - } - } else { - select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, "; - from = " FROM pg_class ct, pg_class ci, pg_attribute a, pg_index i "; - } - String sql = select+ - " ct.relname AS TABLE_NAME, "+ - " a.attname AS COLUMN_NAME, "+ - " a.attnum AS KEY_SEQ, "+ - " ci.relname AS PK_NAME "+ - from+ - " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid "+ - " AND a.attrelid=ci.oid AND i.indisprimary "; - if (table != null && !"".equals(table)) { - sql += " AND ct.relname = '"+escapeQuotes(table)+"' "; - } - sql += where+ - " ORDER BY table_name, pk_name, key_seq"; - return createMetaDataStatement().executeQuery(sql); - } - - /** - * - * @param primaryCatalog - * @param primarySchema - * @param primaryTable if provided will get the keys exported by this table - * @param foreignTable if provided will get the keys imported by this table - * @return ResultSet - * @throws SQLException - */ - - protected java.sql.ResultSet getImportedExportedKeys(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException - { - Field f[] = new Field[14]; - - f[0] = new Field("PKTABLE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("PKTABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[2] = new Field("PKTABLE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[3] = new Field("PKCOLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("FKTABLE_CAT", Oid.VARCHAR, getMaxNameLength()); - f[5] = new Field("FKTABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); - f[6] = new Field("FKTABLE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[7] = new Field("FKCOLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); - f[8] = new Field("KEY_SEQ", Oid.INT2, 2); - f[9] = new Field("UPDATE_RULE", Oid.INT2, 2); - f[10] = new Field("DELETE_RULE", Oid.INT2, 2); - f[11] = new Field("FK_NAME", Oid.VARCHAR, getMaxNameLength()); - f[12] = new Field("PK_NAME", Oid.VARCHAR, getMaxNameLength()); - f[13] = new Field("DEFERRABILITY", Oid.INT2, 2); - - - String select; - String from; - String where = ""; - - /* - * The addition of the pg_constraint in 7.3 table should have really - * helped us out here, but it comes up just a bit short. - * - The conkey, confkey columns aren't really useful without - * contrib/array unless we want to issues separate queries. - * - Unique indexes that can support foreign keys are not necessarily - * added to pg_constraint. Also multiple unique indexes covering - * the same keys can be created which make it difficult to determine - * the PK_NAME field. - */ - - if (connection.haveMinimumServerVersion("7.4")) { - String sql = "SELECT NULL::text AS PKTABLE_CAT, pkn.nspname AS PKTABLE_SCHEM, pkc.relname AS PKTABLE_NAME, pka.attname AS PKCOLUMN_NAME, "+ - "NULL::text AS FKTABLE_CAT, fkn.nspname AS FKTABLE_SCHEM, fkc.relname AS FKTABLE_NAME, fka.attname AS FKCOLUMN_NAME, "+ - "pos.n AS KEY_SEQ, "+ - "CASE con.confupdtype "+ - " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + - " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + - " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + - " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + - " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + - " ELSE NULL END AS UPDATE_RULE, "+ - "CASE con.confdeltype "+ - " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + - " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + - " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + - " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + - " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + - " ELSE NULL END AS DELETE_RULE, "+ - "con.conname AS FK_NAME, pkic.relname AS PK_NAME, "+ - "CASE "+ - " WHEN con.condeferrable AND con.condeferred THEN " + DatabaseMetaData.importedKeyInitiallyDeferred + - " WHEN con.condeferrable THEN " + DatabaseMetaData.importedKeyInitiallyImmediate + - " ELSE " + DatabaseMetaData.importedKeyNotDeferrable+ - " END AS DEFERRABILITY "+ - " FROM "+ - " pg_catalog.pg_namespace pkn, pg_catalog.pg_class pkc, pg_catalog.pg_attribute pka, "+ - " pg_catalog.pg_namespace fkn, pg_catalog.pg_class fkc, pg_catalog.pg_attribute fka, "+ - " pg_catalog.pg_constraint con, information_schema._pg_keypositions() pos(n), "+ - " pg_catalog.pg_depend dep, pg_catalog.pg_class pkic "+ - " WHERE pkn.oid = pkc.relnamespace AND pkc.oid = pka.attrelid AND pka.attnum = con.confkey[pos.n] AND con.confrelid = pkc.oid "+ - " AND fkn.oid = fkc.relnamespace AND fkc.oid = fka.attrelid AND fka.attnum = con.conkey[pos.n] AND con.conrelid = fkc.oid "+ - " AND con.contype = 'f' AND con.oid = dep.objid AND pkic.oid = dep.refobjid AND pkic.relkind = 'i' AND dep.classid = 'pg_constraint'::regclass::oid AND dep.refclassid = 'pg_class'::regclass::oid "; - if (primarySchema != null && !"".equals(primarySchema)) { - sql += " AND pkn.nspname = '"+escapeQuotes(primarySchema)+"' "; - } - if (foreignSchema != null && !"".equals(foreignSchema)) { - sql += " AND fkn.nspname = '"+escapeQuotes(foreignSchema)+"' "; - } - if (primaryTable != null && !"".equals(primaryTable)) { - sql += " AND pkc.relname = '"+escapeQuotes(primaryTable)+"' "; - } - if (foreignTable != null && !"".equals(foreignTable)) { - sql += " AND fkc.relname = '"+escapeQuotes(foreignTable)+"' "; - } - - if (primaryTable != null) { - sql += " ORDER BY fkn.nspname,fkc.relname,pos.n"; - } else { - sql += " ORDER BY pkn.nspname,pkc.relname,pos.n"; - } - - return createMetaDataStatement().executeQuery(sql); - } else if (connection.haveMinimumServerVersion("7.3")) { - select = "SELECT DISTINCT n1.nspname as pnspname,n2.nspname as fnspname, "; - from = " FROM pg_catalog.pg_namespace n1 "+ - " JOIN pg_catalog.pg_class c1 ON (c1.relnamespace = n1.oid) "+ - " JOIN pg_catalog.pg_index i ON (c1.oid=i.indrelid) "+ - " JOIN pg_catalog.pg_class ic ON (i.indexrelid=ic.oid) "+ - " JOIN pg_catalog.pg_attribute a ON (ic.oid=a.attrelid), "+ - " pg_catalog.pg_namespace n2 "+ - " JOIN pg_catalog.pg_class c2 ON (c2.relnamespace=n2.oid), "+ - " pg_catalog.pg_trigger t1 "+ - " JOIN pg_catalog.pg_proc p1 ON (t1.tgfoid=p1.oid), "+ - " pg_catalog.pg_trigger t2 "+ - " JOIN pg_catalog.pg_proc p2 ON (t2.tgfoid=p2.oid) "; - if (primarySchema != null && !"".equals(primarySchema)) { - where += " AND n1.nspname = '"+escapeQuotes(primarySchema)+"' "; - } - if (foreignSchema != null && !"".equals(foreignSchema)) { - where += " AND n2.nspname = '"+escapeQuotes(foreignSchema)+"' "; - } - } else { - select = "SELECT DISTINCT NULL::text as pnspname, NULL::text as fnspname, "; - from = " FROM pg_class c1 "+ - " JOIN pg_index i ON (c1.oid=i.indrelid) "+ - " JOIN pg_class ic ON (i.indexrelid=ic.oid) "+ - " JOIN pg_attribute a ON (ic.oid=a.attrelid), "+ - " pg_class c2, "+ - " pg_trigger t1 "+ - " JOIN pg_proc p1 ON (t1.tgfoid=p1.oid), "+ - " pg_trigger t2 "+ - " JOIN pg_proc p2 ON (t2.tgfoid=p2.oid) "; - } - - String sql = select - + "c1.relname as prelname, " - + "c2.relname as frelname, " - + "t1.tgconstrname, " - + "a.attnum as keyseq, " - + "ic.relname as fkeyname, " - + "t1.tgdeferrable, " - + "t1.tginitdeferred, " - + "t1.tgnargs,t1.tgargs, " - + "p1.proname as updaterule, " - + "p2.proname as deleterule " - + from - + "WHERE " - // isolate the update rule - + "(t1.tgrelid=c1.oid " - + "AND t1.tgisconstraint " - + "AND t1.tgconstrrelid=c2.oid " - + "AND p1.proname LIKE 'RI\\\\_FKey\\\\_%\\\\_upd') " - - + "AND " - // isolate the delete rule - + "(t2.tgrelid=c1.oid " - + "AND t2.tgisconstraint " - + "AND t2.tgconstrrelid=c2.oid " - + "AND p2.proname LIKE 'RI\\\\_FKey\\\\_%\\\\_del') " - - + "AND i.indisprimary " - + where; - - if (primaryTable != null) { - sql += "AND c1.relname='" + escapeQuotes(primaryTable) + "' "; - } - if (foreignTable != null) { - sql += "AND c2.relname='" + escapeQuotes(foreignTable) + "' "; - } - - sql += "ORDER BY "; - - // orderby is as follows getExported, orders by FKTABLE, - // getImported orders by PKTABLE - // getCrossReference orders by FKTABLE, so this should work for both, - // since when getting crossreference, primaryTable will be defined - - if (primaryTable != null) { - if (connection.haveMinimumServerVersion("7.3")) { - sql += "fnspname,"; - } - sql += "frelname"; - } else { - if (connection.haveMinimumServerVersion("7.3")) { - sql += "pnspname,"; - } - sql += "prelname"; - } - - sql += ",keyseq"; - - ResultSet rs = connection.createStatement().executeQuery(sql); - - // returns the following columns - // and some example data with a table defined as follows - - // create table people ( id int primary key); - // create table policy ( id int primary key); - // create table users ( id int primary key, people_id int references people(id), policy_id int references policy(id)) - - // prelname | frelname | tgconstrname | keyseq | fkeyName | tgdeferrable | tginitdeferred - // 1 | 2 | 3 | 4 | 5 | 6 | 7 - - // people | users | <unnamed> | 1 | people_pkey | f | f - - // | tgnargs | tgargs | updaterule | deleterule - // | 8 | 9 | 10 | 11 - // | 6 | <unnamed>\000users\000people\000UNSPECIFIED\000people_id\000id\000 | RI_FKey_noaction_upd | RI_FKey_noaction_del - - Vector tuples = new Vector(); - - while ( rs.next() ) - { - byte tuple[][] = new byte[14][]; - - tuple[1] = rs.getBytes(1); //PKTABLE_SCHEM - tuple[5] = rs.getBytes(2); //FKTABLE_SCHEM - tuple[2] = rs.getBytes(3); //PKTABLE_NAME - tuple[6] = rs.getBytes(4); //FKTABLE_NAME - String fKeyName = rs.getString(5); - String updateRule = rs.getString(12); - - if (updateRule != null ) - { - // Rules look like this RI_FKey_noaction_del so we want to pull out the part between the 'Key_' and the last '_' s - - String rule = updateRule.substring(8, updateRule.length() - 4); - - int action = java.sql.DatabaseMetaData.importedKeyNoAction; - - if ( rule == null || "noaction".equals(rule) ) - action = java.sql.DatabaseMetaData.importedKeyNoAction; - if ("cascade".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeyCascade; - else if ("setnull".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeySetNull; - else if ("setdefault".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeySetDefault; - else if ("restrict".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeyRestrict; - - tuple[9] = connection.encodeString(Integer.toString(action)); - - } - - String deleteRule = rs.getString(13); - - if ( deleteRule != null ) - { - - String rule = deleteRule.substring(8, deleteRule.length() - 4); - - int action = java.sql.DatabaseMetaData.importedKeyNoAction; - if ("cascade".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeyCascade; - else if ("setnull".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeySetNull; - else if ("setdefault".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeySetDefault; - else if ("restrict".equals(rule)) - action = java.sql.DatabaseMetaData.importedKeyRestrict; - tuple[10] = connection.encodeString(Integer.toString(action)); - } - - - int keySequence = rs.getInt(6); //KEY_SEQ - - // Parse the tgargs data - String fkeyColumn = ""; - String pkeyColumn = ""; - String fkName = ""; - // Note, I am guessing at most of this, but it should be close - // if not, please correct - // the keys are in pairs and start after the first four arguments - // the arguments are seperated by \000 - - String targs = rs.getString(11); - - // args look like this - //<unnamed>\000ww\000vv\000UNSPECIFIED\000m\000a\000n\000b\000 - // we are primarily interested in the column names which are the last items in the string - - Vector tokens = tokenize(targs, "\\000"); - if (tokens.size() > 0) { - fkName = (String)tokens.elementAt(0); - } - - if (fkName.startsWith("<unnamed>")) { - fkName = targs; - } - - int element = 4 + (keySequence - 1) * 2; - if (tokens.size() > element) { - fkeyColumn = (String)tokens.elementAt(element); - } - - element++; - if (tokens.size() > element) { - pkeyColumn = (String)tokens.elementAt(element); - } - - tuple[3] = connection.encodeString(pkeyColumn); //PKCOLUMN_NAME - tuple[7] = connection.encodeString(fkeyColumn); //FKCOLUMN_NAME - - tuple[8] = rs.getBytes(6); //KEY_SEQ - tuple[11] = connection.encodeString(fkName); //FK_NAME this will give us a unique name for the foreign key - tuple[12] = rs.getBytes(7); //PK_NAME - - // DEFERRABILITY - int deferrability = java.sql.DatabaseMetaData.importedKeyNotDeferrable; - boolean deferrable = rs.getBoolean(8); - boolean initiallyDeferred = rs.getBoolean(9); - if (deferrable) - { - if (initiallyDeferred) - deferrability = java.sql.DatabaseMetaData.importedKeyInitiallyDeferred; - else - deferrability = java.sql.DatabaseMetaData.importedKeyInitiallyImmediate; - } - tuple[13] = connection.encodeString(Integer.toString(deferrability)); - - tuples.addElement(tuple); - } - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, tuples); - } - - /* - * Get a description of the primary key columns that are - * referenced by a table's foreign key columns (the primary keys - * imported by a table). They are ordered by PKTABLE_CAT, - * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. - * - * <P>Each primary key column description has the following columns: - * <OL> - * <LI><B>PKTABLE_CAT</B> String => primary key table catalog - * being imported (may be null) - * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema - * being imported (may be null) - * <LI><B>PKTABLE_NAME</B> String => primary key table name - * being imported - * <LI><B>PKCOLUMN_NAME</B> String => primary key column name - * being imported - * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) - * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) - * <LI><B>FKTABLE_NAME</B> String => foreign key table name - * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name - * <LI><B>KEY_SEQ</B> short => sequence number within foreign key - * <LI><B>UPDATE_RULE</B> short => What happens to - * foreign key when primary is updated: - * <UL> - * <LI> importedKeyCascade - change imported key to agree - * with primary key update - * <LI> importedKeyRestrict - do not allow update of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been updated - * </UL> - * <LI><B>DELETE_RULE</B> short => What happens to - * the foreign key when primary is deleted. - * <UL> - * <LI> importedKeyCascade - delete rows that import a deleted key - * <LI> importedKeyRestrict - do not allow delete of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been deleted - * </UL> - * <LI><B>FK_NAME</B> String => foreign key name (may be null) - * <LI><B>PK_NAME</B> String => primary key name (may be null) - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name pattern; "" retrieves those - * without a schema - * @param table a table name - * @return ResultSet each row is a primary key column description - * @see #getExportedKeys - */ - public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException - { - return getImportedExportedKeys(null,null,null,catalog, schema, table); - } - - /* - * Get a description of a foreign key columns that reference a - * table's primary key columns (the foreign keys exported by a - * table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, - * FKTABLE_NAME, and KEY_SEQ. - * - * This method is currently unimplemented. - * - * <P>Each foreign key column description has the following columns: - * <OL> - * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) - * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) - * <LI><B>PKTABLE_NAME</B> String => primary key table name - * <LI><B>PKCOLUMN_NAME</B> String => primary key column name - * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) - * being exported (may be null) - * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) - * being exported (may be null) - * <LI><B>FKTABLE_NAME</B> String => foreign key table name - * being exported - * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name - * being exported - * <LI><B>KEY_SEQ</B> short => sequence number within foreign key - * <LI><B>UPDATE_RULE</B> short => What happens to - * foreign key when primary is updated: - * <UL> - * <LI> importedKeyCascade - change imported key to agree - * with primary key update - * <LI> importedKeyRestrict - do not allow update of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been updated - * </UL> - * <LI><B>DELETE_RULE</B> short => What happens to - * the foreign key when primary is deleted. - * <UL> - * <LI> importedKeyCascade - delete rows that import a deleted key - * <LI> importedKeyRestrict - do not allow delete of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been deleted - * </UL> - * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) - * <LI><B>PK_NAME</B> String => primary key identifier (may be null) - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name pattern; "" retrieves those - * without a schema - * @param table a table name - * @return ResultSet each row is a foreign key column description - * @see #getImportedKeys - */ - public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException - { - return getImportedExportedKeys(catalog, schema, table, null,null,null); - } - - /* - * Get a description of the foreign key columns in the foreign key - * table that reference the primary key columns of the primary key - * table (describe how one table imports another's key.) This - * should normally return a single foreign key/primary key pair - * (most tables only import a foreign key from a table once.) They - * are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and - * KEY_SEQ. - * - * This method is currently unimplemented. - * - * <P>Each foreign key column description has the following columns: - * <OL> - * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) - * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) - * <LI><B>PKTABLE_NAME</B> String => primary key table name - * <LI><B>PKCOLUMN_NAME</B> String => primary key column name - * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) - * being exported (may be null) - * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) - * being exported (may be null) - * <LI><B>FKTABLE_NAME</B> String => foreign key table name - * being exported - * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name - * being exported - * <LI><B>KEY_SEQ</B> short => sequence number within foreign key - * <LI><B>UPDATE_RULE</B> short => What happens to - * foreign key when primary is updated: - * <UL> - * <LI> importedKeyCascade - change imported key to agree - * with primary key update - * <LI> importedKeyRestrict - do not allow update of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been updated - * </UL> - * <LI><B>DELETE_RULE</B> short => What happens to - * the foreign key when primary is deleted. - * <UL> - * <LI> importedKeyCascade - delete rows that import a deleted key - * <LI> importedKeyRestrict - do not allow delete of primary - * key if it has been imported - * <LI> importedKeySetNull - change imported key to NULL if - * its primary key has been deleted - * </UL> - * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) - * <LI><B>PK_NAME</B> String => primary key identifier (may be null) - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name pattern; "" retrieves those - * without a schema - * @param table a table name - * @return ResultSet each row is a foreign key column description - * @see #getImportedKeys - */ - public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException - { - return getImportedExportedKeys(primaryCatalog, primarySchema, primaryTable, foreignCatalog, foreignSchema, foreignTable); - } - - /* - * Get a description of all the standard SQL types supported by - * this database. They are ordered by DATA_TYPE and then by how - * closely the data type maps to the corresponding JDBC SQL type. - * - * <P>Each type description has the following columns: - * <OL> - * <LI><B>TYPE_NAME</B> String => Type name - * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types - * <LI><B>PRECISION</B> int => maximum precision - * <LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal - * (may be null) - * <LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal - (may be null) - * <LI><B>CREATE_PARAMS</B> String => parameters used in creating - * the type (may be null) - * <LI><B>NULLABLE</B> short => can you use NULL for this type? - * <UL> - * <LI> typeNoNulls - does not allow NULL values - * <LI> typeNullable - allows NULL values - * <LI> typeNullableUnknown - nullability unknown - * </UL> - * <LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive? - * <LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type: - * <UL> - * <LI> typePredNone - No support - * <LI> typePredChar - Only supported with WHERE .. LIKE - * <LI> typePredBasic - Supported except for WHERE .. LIKE - * <LI> typeSearchable - Supported for all WHERE .. - * </UL> - * <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned? - * <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value? - * <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an - * auto-increment value? - * <LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name - * (may be null) - * <LI><B>MINIMUM_SCALE</B> short => minimum scale supported - * <LI><B>MAXIMUM_SCALE</B> short => maximum scale supported - * <LI><B>SQL_DATA_TYPE</B> int => unused - * <LI><B>SQL_DATETIME_SUB</B> int => unused - * <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10 - * </OL> - * - * @return ResultSet each row is a SQL type description - */ - public java.sql.ResultSet getTypeInfo() throws SQLException - { - - Field f[] = new Field[18]; - Vector v = new Vector(); // The new ResultSet tuple stuff - - f[0] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[1] = new Field("DATA_TYPE", Oid.INT2, 2); - f[2] = new Field("PRECISION", Oid.INT4, 4); - f[3] = new Field("LITERAL_PREFIX", Oid.VARCHAR, getMaxNameLength()); - f[4] = new Field("LITERAL_SUFFIX", Oid.VARCHAR, getMaxNameLength()); - f[5] = new Field("CREATE_PARAMS", Oid.VARCHAR, getMaxNameLength()); - f[6] = new Field("NULLABLE", Oid.INT2, 2); - f[7] = new Field("CASE_SENSITIVE", Oid.BOOL, 1); - f[8] = new Field("SEARCHABLE", Oid.INT2, 2); - f[9] = new Field("UNSIGNED_ATTRIBUTE", Oid.BOOL, 1); - f[10] = new Field("FIXED_PREC_SCALE", Oid.BOOL, 1); - f[11] = new Field("AUTO_INCREMENT", Oid.BOOL, 1); - f[12] = new Field("LOCAL_TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); - f[13] = new Field("MINIMUM_SCALE", Oid.INT2, 2); - f[14] = new Field("MAXIMUM_SCALE", Oid.INT2, 2); - f[15] = new Field("SQL_DATA_TYPE", Oid.INT4, 4); - f[16] = new Field("SQL_DATETIME_SUB", Oid.INT4, 4); - f[17] = new Field("NUM_PREC_RADIX", Oid.INT4, 4); - - String sql; - if (connection.haveMinimumServerVersion("7.3")) { - sql = "SELECT typname FROM pg_catalog.pg_type"; - } else { - sql = "SELECT typname FROM pg_type"; - } - - ResultSet rs = connection.createStatement().executeQuery(sql); - // cache some results, this will keep memory useage down, and speed - // things up a little. - byte b9[] = connection.encodeString("9"); - byte b10[] = connection.encodeString("10"); - byte bf[] = connection.encodeString("f"); - byte bnn[] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeNoNulls)); - byte bts[] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeSearchable)); - - while (rs.next()) - { - byte[][] tuple = new byte[18][]; - String typname = rs.getString(1); - tuple[0] = connection.encodeString(typname); - tuple[1] = connection.encodeString(Integer.toString(connection.getSQLType(typname))); - tuple[2] = b9; // for now - tuple[6] = bnn; // for now - tuple[7] = bf; // false for now - not case sensitive - tuple[8] = bts; - tuple[9] = bf; // false for now - it's signed - tuple[10] = bf; // false for now - must handle money - tuple[11] = bf; // false for now - handle autoincrement - // 12 - LOCAL_TYPE_NAME is null - // 13 & 14 ? - // 15 & 16 are unused so we return null - tuple[17] = b10; // everything is base 10 - v.addElement(tuple); - } - rs.close(); - - return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); - } - - /* - * Get a description of a table's indices and statistics. They are - * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. - * - * <P>Each index column description has the following columns: - * <OL> - * <LI><B>TABLE_CAT</B> String => table catalog (may be null) - * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) - * <LI><B>TABLE_NAME</B> String => table name - * <LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique? - * false when TYPE is tableIndexStatistic - * <LI><B>INDEX_QUALIFIER</B> String => index catalog (may be null); - * null when TYPE is tableIndexStatistic - * <LI><B>INDEX_NAME</B> String => index name; null when TYPE is - * tableIndexStatistic - * <LI><B>TYPE</B> short => index type: - * <UL> - * <LI> tableIndexStatistic - this identifies table statistics that are - * returned in conjuction with a table's index descriptions - * <LI> tableIndexClustered - this is a clustered index - * <LI> tableIndexHashed - this is a hashed index - * <LI> tableIndexOther - this is some other style of index - * </UL> - * <LI><B>ORDINAL_POSITION</B> short => column sequence number - * within index; zero when TYPE is tableIndexStatistic - * <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is - * tableIndexStatistic - * <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending - * "D" => descending, may be null if sort sequence is not supported; - * null when TYPE is tableIndexStatistic - * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then - * this is the number of rows in the table; otherwise it is the - * number of unique values in the index. - * <LI><B>PAGES</B> int => When TYPE is tableIndexStatisic then - * this is the number of pages used for the table, otherwise it - * is the number of pages used for the current index. - * <LI><B>FILTER_CONDITION</B> String => Filter condition, if any. - * (may be null) - * </OL> - * - * @param catalog a catalog name; "" retrieves those without a catalog - * @param schema a schema name pattern; "" retrieves those without a schema - * @param table a table name - * @param unique when true, return only indices for unique values; - * when false, return indices regardless of whether unique or not - * @param approximate when true, result is allowed to reflect approximate - * or out of data values; when false, results are requested to be - * accurate - * @return ResultSet each row is an index column description - */ - // Implementation note: This is required for Borland's JBuilder to work - public java.sql.ResultSet getIndexInfo(String catalog, String schema, String tableName, boolean unique, boolean approximate) throws SQLException - { - String select; - String from; - String where = ""; - if (connection.haveMinimumServerVersion("7.3")) { - select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "; - from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_index i, pg_catalog.pg_attribute a, pg_catalog.pg_am am "; - where = " AND n.oid = ct.relnamespace "; - if (schema != null && ! "".equals(schema)) { - where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; - } - } else { - select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, "; - from = " FROM pg_class ct, pg_class ci, pg_index i, pg_attribute a, pg_am am "; - } - - String sql = select+ - " ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, "+ - " CASE i.indisclustered "+ - " WHEN true THEN "+java.sql.DatabaseMetaData.tableIndexClustered+ - " ELSE CASE am.amname "+ - " WHEN 'hash' THEN "+java.sql.DatabaseMetaData.tableIndexHashed+ - " ELSE "+java.sql.DatabaseMetaData.tableIndexOther+ - " END "+ - " END AS TYPE, "+ - " a.attnum AS ORDINAL_POSITION, "+ - " a.attname AS COLUMN_NAME, "+ - " NULL AS ASC_OR_DESC, "+ - " ci.reltuples AS CARDINALITY, "+ - " ci.relpages AS PAGES, "+ - " NULL AS FILTER_CONDITION "+ - from+ - " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid AND a.attrelid=ci.oid AND ci.relam=am.oid "+ - where+ - " AND ct.relname = '"+escapeQuotes(tableName)+"' "; - - if (unique) { - sql += " AND i.indisunique "; - } - sql += " ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION "; - return createMetaDataStatement().executeQuery(sql); - } - - /** - * Tokenize based on words not on single characters. - */ - private static Vector tokenize(String input, String delimiter) { - Vector result = new Vector(); - int start = 0; - int end = input.length(); - int delimiterSize = delimiter.length(); - - while (start < end) { - int delimiterIndex = input.indexOf(delimiter,start); - if (delimiterIndex < 0) { - result.addElement(input.substring(start)); - break; - } else { - String token = input.substring(start,delimiterIndex); - result.addElement(token); - start = delimiterIndex + delimiterSize; - } - } - return result; - } - - - -} diff --git a/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java b/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java deleted file mode 100644 index 3c22739a..00000000 --- a/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java +++ /dev/null @@ -1,1034 +0,0 @@ -/*------------------------------------------------------------------------- - * - * AbstractJdbc1ResultSet.java - * This class defines methods of the jdbc1 specification. This class is - * extended by org.postgresql.jdbc2.AbstractJdbc2ResultSet which adds the - * jdbc2 methods. The real ResultSet class (for jdbc1) is - * org.postgresql.jdbc1.Jdbc1ResultSet - * - * Copyright (c) 2003, PostgreSQL Global Development Group - * - * IDENTIFICATION - * $PostgreSQL: pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1ResultSet.java,v 1.24 2003/11/29 19:52:10 pgsql Exp $ - * - *------------------------------------------------------------------------- - */ -package org.postgresql.jdbc1; - -import java.math.BigDecimal; -import java.io.*; -import java.sql.*; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Vector; -import org.postgresql.Driver; -import org.postgresql.core.*; -import org.postgresql.largeobject.*; -import org.postgresql.util.PGbytea; -import org.postgresql.util.PGtokenizer; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; -import java.math.BigInteger; - -public abstract class AbstractJdbc1ResultSet implements BaseResultSet, org.postgresql.PGRefCursorResultSet -{ - protected final BaseConnection connection; // the connection we belong to - protected final BaseStatement statement; // the statement we belong to - protected final Field fields[]; // Field metadata for this resultset. - protected final Query originalQuery; // Query we originated from - - protected final int maxRows; // Maximum rows in this resultset (might be 0). - protected final int maxFieldSize; // Maximum field size in this resultset (might be 0). - - protected Vector rows; // Current page of results. - protected int current_row = -1; // Index into 'rows' of our currrent row (0-based) - protected int row_offset; // Offset of row 0 in the actual resultset - protected byte[][] this_row; // copy of the current result row - protected SQLWarning warnings = null; // The warning chain - protected boolean wasNullFlag = false; // the flag for wasNull() - protected boolean onInsertRow = false; // are we on the insert row (for JDBC2 updatable resultsets)? - - private StringBuffer sbuf = null; - public byte[][] rowBuffer = null; // updateable rowbuffer - - private SimpleDateFormat m_tsFormat = null; - private SimpleDateFormat m_tstzFormat = null; - private SimpleDateFormat m_dateFormat = null; - - protected int fetchSize; // Current fetch size (might be 0). - protected ResultCursor cursor; // Cursor for fetching additional data. - - public abstract ResultSetMetaData getMetaData() throws SQLException; - - public class CursorResultHandler implements ResultHandler { - private SQLException error; - - public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { - AbstractJdbc1ResultSet.this.rows = tuples; - AbstractJdbc1ResultSet.this.cursor = cursor; - } - - public void handleCommandStatus(String status, int updateCount, long insertOID) { - handleError(new SQLException("unexpected command status")); - } - - public void handleWarning(SQLWarning warning) { - AbstractJdbc1ResultSet.this.addWarning(warning); - } - - public void handleError(SQLException newError) { - if (error == null) - error = newError; - else - error.setNextException(newError); - } - - public void handleCompletion() throws SQLException { - if (error != null) - throw error; - } - }; - - public AbstractJdbc1ResultSet(Query originalQuery, - BaseStatement statement, - Field[] fields, - Vector tuples, - ResultCursor cursor, - int maxRows, - int maxFieldSize) throws SQLException - { - this.originalQuery = originalQuery; - this.connection = (BaseConnection) statement.getConnection(); - this.statement = statement; - this.fields = fields; - this.rows = tuples; - this.cursor = cursor; - this.maxRows = maxRows; - this.maxFieldSize = maxFieldSize; - } - - public BaseStatement getPGStatement() { - return statement; - } - - public StringBuffer getStringBuffer() { - return sbuf; - } - - // - // Backwards compatibility with PGRefCursorResultSet - // - - private String refCursorName; - - public String getRefCursor() { - return refCursorName; - } - - private void setRefCursor(String refCursorName) { - this.refCursorName = refCursorName; - } - - // - // Part of the JDBC2 support, but convenient to implement here. - // - - public void setFetchSize(int rows) throws SQLException - { - fetchSize = rows; - } - - public int getFetchSize() throws SQLException - { - return fetchSize; - } - - public boolean next() throws SQLException - { - if (rows == null) - throw new PSQLException("postgresql.con.closed", PSQLState.CONNECTION_DOES_NOT_EXIST); - - if (onInsertRow) - throw new PSQLException("postgresql.res.oninsertrow"); - - if (current_row+1 >= rows.size()) - { - if (cursor == null || (maxRows > 0 && row_offset + rows.size() >= maxRows)) { - current_row = rows.size(); - this_row = null; - rowBuffer = null; - return false; // End of the resultset. - } - - // Ask for some more data. - row_offset += rows.size(); // We are discarding some data. - - int fetchRows = fetchSize; - if (maxRows != 0) { - if (fetchRows == 0 || row_offset + fetchRows > maxRows) // Fetch would exceed maxRows, limit it. - fetchRows = maxRows - row_offset; - } - - // Execute the fetch and update this resultset. - connection.getQueryExecutor().fetch(cursor, new CursorResultHandler(), fetchRows); - - current_row = 0; - - // Test the new rows array. - if (rows.size() == 0) { - this_row = null; - rowBuffer = null; - return false; - } - } else { - current_row++; - } - - this_row = (byte [][])rows.elementAt(current_row); - - rowBuffer = new byte[this_row.length][]; - System.arraycopy(this_row, 0, rowBuffer, 0, this_row.length); - return true; - } - - public void close() throws SQLException - { - //release resources held (memory for tuples) - if (rows != null) - { - rows = null; - } - } - - public boolean wasNull() throws SQLException - { - return wasNullFlag; - } - - public String getString(int columnIndex) throws SQLException - { - checkResultSet( columnIndex ); - wasNullFlag = (this_row[columnIndex - 1] == null); - if (wasNullFlag) - return null; - - Encoding encoding = connection.getEncoding(); - try { - return trimString(columnIndex, encoding.decode(this_row[columnIndex-1])); - } catch (IOException ioe) { - throw new PSQLException("postgresql.con.invalidchar", PSQLState.DATA_ERROR, ioe); - } - } - - public boolean getBoolean(int columnIndex) throws SQLException - { - return toBoolean( getString(columnIndex) ); - } - - private static final BigInteger BYTEMAX = new BigInteger(Byte.toString(Byte.MAX_VALUE)); - private static final BigInteger BYTEMIN = new BigInteger(Byte.toString(Byte.MIN_VALUE)); - - public byte getByte(int columnIndex) throws SQLException - { - String s = getString(columnIndex); - - if (s != null ) - { - s= s.trim(); - if ( s.length() == 0 ) return 0; - try - { - // try the optimal parse - return Byte.parseByte(s); - } - catch (NumberFormatException e) - { - // didn't work, assume the column is not a byte - try - { - BigDecimal n = new BigDecimal(s); - BigInteger i = n.toBigInteger(); - - int gt = i.compareTo(BYTEMAX); - int lt = i.compareTo(BYTEMIN); - - if ( gt > 0 || lt < 0 ) - { - throw new PSQLException("postgresql.res.badbyte", - PSQLState. - NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - return i.byteValue(); - } - catch( NumberFormatException ex ) - { - throw new PSQLException("postgresql.res.badbyte", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - } - } - return 0; // SQL NULL - } - - private static final BigInteger SHORTMAX = new BigInteger(Short.toString(Short.MAX_VALUE)); - private static final BigInteger SHORTMIN = new BigInteger(Short.toString(Short.MIN_VALUE)); - - public short getShort(int columnIndex) throws SQLException - { - String s = getFixedString(columnIndex); - - if (s != null) - { - s = s.trim(); - try - { - return Short.parseShort(s); - } - catch (NumberFormatException e) - { - try - { - BigDecimal n = new BigDecimal(s); - BigInteger i = n.toBigInteger(); - int gt = i.compareTo(SHORTMAX); - int lt = i.compareTo(SHORTMIN); - - if ( gt > 0 || lt < 0 ) - { - throw new PSQLException("postgresql.res.badshort", - PSQLState. - NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - return i.shortValue(); - - } - catch ( NumberFormatException ne ) - { - throw new PSQLException("postgresql.res.badshort", - PSQLState. - NUMERIC_VALUE_OUT_OF_RANGE, s); - } - } - } - return 0; // SQL NULL - } - - public int getInt(int columnIndex) throws SQLException - { - return toInt( getFixedString(columnIndex) ); - } - - public long getLong(int columnIndex) throws SQLException - { - return toLong( getFixedString(columnIndex) ); - } - - public float getFloat(int columnIndex) throws SQLException - { - return toFloat( getFixedString(columnIndex) ); - } - - public double getDouble(int columnIndex) throws SQLException - { - return toDouble( getFixedString(columnIndex) ); - } - - public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException - { - return toBigDecimal( getFixedString(columnIndex), scale ); - } - - /* - * Get the value of a column in the current row as a Java byte array. - * - * <p>In normal use, the bytes represent the raw values returned by the - * backend. However, if the column is an OID, then it is assumed to - * refer to a Large Object, and that object is returned as a byte array. - * - * <p><b>Be warned</b> If the large object is huge, then you may run out - * of memory. - * - * @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 - { - checkResultSet( columnIndex ); - wasNullFlag = (this_row[columnIndex - 1] == null); - if (!wasNullFlag) - { - if (fields[columnIndex -1].getFormat() == Field.BINARY_FORMAT) - { - //If the data is already binary then just return it - return this_row[columnIndex - 1]; - } - else if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports the bytea datatype for byte arrays - if (fields[columnIndex - 1].getOID() == Oid.BYTEA) - { - return trimBytes(columnIndex, PGbytea.toBytes(this_row[columnIndex - 1])); - } - else - { - return trimBytes(columnIndex, this_row[columnIndex - 1]); - } - } - else - { - //Version 7.1 and earlier supports LargeObjects for byte arrays - // Handle OID's as BLOBS - if ( fields[columnIndex - 1].getOID() == 26) - { - LargeObjectManager lom = connection.getLargeObjectAPI(); - LargeObject lob = lom.open(getInt(columnIndex)); - byte buf[] = lob.read(lob.size()); - lob.close(); - return trimBytes(columnIndex, buf); - } - else - { - return trimBytes(columnIndex, this_row[columnIndex - 1]); - } - } - } - return null; - } - - public java.sql.Date getDate(int columnIndex) throws SQLException - { - return toDate( getString(columnIndex) ); - } - - public Time getTime(int columnIndex) throws SQLException - { - checkResultSet(columnIndex); - return TimestampUtils.toTime( getString(columnIndex), getPGType(columnIndex) ); - } - - public Timestamp getTimestamp(int columnIndex) throws SQLException - { - this.checkResultSet(columnIndex); - int sqlType = getSQLType(columnIndex); - - if ( sqlType == Types.TIME ) - { - Time time = TimestampUtils.toTime(getString( columnIndex ), getPGType(columnIndex)); - return new Timestamp(time.getTime() ); - } - return TimestampUtils.toTimestamp(getString(columnIndex), getPGType(columnIndex)); - } - - public InputStream getAsciiStream(int columnIndex) throws SQLException - { - checkResultSet( columnIndex ); - wasNullFlag = (this_row[columnIndex - 1] == null); - if (wasNullFlag) - return null; - - if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports AsciiStream for all the PG text types - //As the spec/javadoc for this method indicate this is to be used for - //large text values (i.e. LONGVARCHAR) PG doesn't have a separate - //long string datatype, but with toast the text datatype is capable of - //handling very large values. Thus the implementation ends up calling - //getString() since there is no current way to stream the value from the server - try - { - return new ByteArrayInputStream(getString(columnIndex).getBytes("ASCII")); - } - catch (UnsupportedEncodingException l_uee) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); - } - } - else - { - // In 7.1 Handle as BLOBS so return the LargeObject input stream - return getBinaryStream(columnIndex); - } - } - - public InputStream getUnicodeStream(int columnIndex) throws SQLException - { - checkResultSet( columnIndex ); - wasNullFlag = (this_row[columnIndex - 1] == null); - if (wasNullFlag) - return null; - - if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports AsciiStream for all the PG text types - //As the spec/javadoc for this method indicate this is to be used for - //large text values (i.e. LONGVARCHAR) PG doesn't have a separate - //long string datatype, but with toast the text datatype is capable of - //handling very large values. Thus the implementation ends up calling - //getString() since there is no current way to stream the value from the server - try - { - return new ByteArrayInputStream(getString(columnIndex).getBytes("UTF-8")); - } - catch (UnsupportedEncodingException l_uee) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); - } - } - else - { - // In 7.1 Handle as BLOBS so return the LargeObject input stream - return getBinaryStream(columnIndex); - } - } - - public InputStream getBinaryStream(int columnIndex) throws SQLException - { - checkResultSet( columnIndex ); - wasNullFlag = (this_row[columnIndex - 1] == null); - if (wasNullFlag) - return null; - - if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports BinaryStream for all PG bytea type - //As the spec/javadoc for this method indicate this is to be used for - //large binary values (i.e. LONGVARBINARY) PG doesn't have a separate - //long binary datatype, but with toast the bytea datatype is capable of - //handling very large values. Thus the implementation ends up calling - //getBytes() since there is no current way to stream the value from the server - byte b[] = getBytes(columnIndex); - if (b != null) - return new ByteArrayInputStream(b); - } - else - { - // In 7.1 Handle as BLOBS so return the LargeObject input stream - if ( fields[columnIndex - 1].getOID() == 26) - { - LargeObjectManager lom = connection.getLargeObjectAPI(); - LargeObject lob = lom.open(getInt(columnIndex)); - return lob.getInputStream(); - } - } - return null; - } - - 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)); - } - - public SQLWarning getWarnings() throws SQLException - { - return warnings; - } - - public void clearWarnings() throws SQLException - { - warnings = null; - } - - protected void addWarning(SQLWarning warnings) - { - if (this.warnings != null) - this.warnings.setNextWarning(warnings); - else - this.warnings = warnings; - } - - public String getCursorName() throws SQLException - { - return null; - } - - /* - * Get the value of a column in the current row as a Java object - * - * <p>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. - * - * <p>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; - - checkResultSet(columnIndex); - - wasNullFlag = (this_row[columnIndex - 1] == null); - if (wasNullFlag) - return null; - - field = fields[columnIndex - 1]; - - // some fields can be null, mainly from those returned by MetaData methods - if (field == null) - { - wasNullFlag = true; - return null; - } - - Object result = internalGetObject(columnIndex, field); - if (result != null) - return result; - - return connection.getObject(getPGType(columnIndex), getString(columnIndex)); - } - - protected Object internalGetObject(int columnIndex, Field field) throws SQLException - { - switch (getSQLType(columnIndex)) { - case Types.BIT: - return getBoolean(columnIndex) ? Boolean.TRUE : Boolean.FALSE; - case Types.SMALLINT: - return new Short(getShort(columnIndex)); - case Types.INTEGER: - return new Integer(getInt(columnIndex)); - case Types.BIGINT: - return new Long(getLong(columnIndex)); - case Types.NUMERIC: - return getBigDecimal - (columnIndex, (field.getMod() == -1) ? -1 : ((field.getMod() - 4) & 0xffff)); - 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); - case Types.BINARY: - case Types.VARBINARY: - return getBytes(columnIndex); - - default: - String type = getPGType(columnIndex); - - // if the backend doesn't know the type then coerce to String - if (type.equals("unknown")) - return getString(columnIndex); - - // Specialized support for ref cursors is neater. - if (type.equals("refcursor")) { - // Fetch all results. - String cursorName = getString(columnIndex); - String fetchSql = "FETCH ALL IN \"" + cursorName + "\""; - ResultSet rs = connection.execSQLQuery(fetchSql); - ((AbstractJdbc1ResultSet)rs).setRefCursor(cursorName); - return rs; - } - - // Caller determines what to do (JDBC2 overrides in this case) - return null; - } - } - - public Object getObject(String columnName) throws SQLException - { - return getObject(findColumn(columnName)); - } - - /* - * Map a ResultSet column name to a ResultSet column index - */ - public int findColumn(String columnName) throws SQLException - { - int i; - - final int flen = fields.length; - for (i = 0 ; i < flen; ++i) - if (fields[i].getColumnLabel().equalsIgnoreCase(columnName)) - return (i + 1); - throw new PSQLException ("postgresql.res.colname", null, columnName); - } - - /* - * returns the OID of a field.<p> - * It is used internally by the driver. - */ - public int getColumnOID(int field) - { - return fields[field -1].getOID(); - } - - /* - * This is used to fix get*() methods on Money fields. It should only be - * used by those methods! - * - * It converts ($##.##) to -##.## and $##.## to ##.## - */ - public String getFixedString(int col) throws SQLException - { - String s = getString(col); - - // Handle SQL Null - wasNullFlag = (this_row[col - 1] == null); - if (wasNullFlag) - return null; - - // if we don't have at least 2 characters it can't be money. - if (s.length() < 2) - return s; - - // Handle Money - if (s.charAt(0) == '(') - { - s = "-" + PGtokenizer.removePara(s).substring(1); - } - if (s.charAt(0) == '$') - { - s = s.substring(1); - } - else if (s.charAt(0) == '-' && s.charAt(1) == '$') - { - s = "-" + s.substring(2); - } - - return s; - } - - protected String getPGType( int column ) throws SQLException - { - return connection.getPGType(fields[column - 1].getOID()); - } - - protected int getSQLType( int column ) throws SQLException - { - return connection.getSQLType(fields[column - 1].getOID()); - } - - protected void checkResultSet( int column ) throws SQLException - { - if ( this_row == null ) - throw new PSQLException("postgresql.res.nextrequired"); - if ( column < 1 || column > fields.length ) - throw new PSQLException("postgresql.res.colrange", PSQLState.INVALID_PARAMETER_VALUE ); - } - - //----------------- Formatting Methods ------------------- - - public static boolean toBoolean(String s) - { - if (s != null) - { - s = s.trim(); - - if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t")) - return true; - - try - { - if (Double.valueOf(s).doubleValue()==1) - return true; - } - catch (NumberFormatException e) - { - } - } - return false; // SQL NULL - } - - private static final BigInteger INTMAX = new BigInteger(Integer.toString(Integer.MAX_VALUE)); - private static final BigInteger INTMIN = new BigInteger(Integer.toString(Integer.MIN_VALUE)); - - public static int toInt(String s) throws SQLException - { - if (s != null) - { - try - { - s = s.trim(); - return Integer.parseInt(s); - } - catch (NumberFormatException e) - { - try - { - BigDecimal n = new BigDecimal(s); - BigInteger i = n.toBigInteger(); - - int gt = i.compareTo(INTMAX); - int lt = i.compareTo(INTMIN); - - if (gt > 0 || lt < 0) - { - throw new PSQLException("postgresql.res.badint", - PSQLState. - NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - return i.intValue(); - - } - catch( NumberFormatException ne ) - { - throw new PSQLException("postgresql.res.badint", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - } - } - return 0; // SQL NULL - } - private final static BigInteger LONGMAX = new BigInteger(Long.toString(Long.MAX_VALUE)); - private final static BigInteger LONGMIN = new BigInteger(Long.toString(Long.MIN_VALUE)); - - public static long toLong(String s) throws SQLException - { - if (s != null) - { - try - { - s = s.trim(); - return Long.parseLong(s); - } - catch (NumberFormatException e) - { - try - { - BigDecimal n = new BigDecimal(s); - BigInteger i = n.toBigInteger(); - int gt = i.compareTo(LONGMAX); - int lt = i.compareTo(LONGMIN); - - if ( gt > 0 || lt < 0 ) - { - throw new PSQLException("postgresql.res.badint", - PSQLState. - NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - return i.longValue(); - } - catch( NumberFormatException ne ) - { - throw new PSQLException("postgresql.res.badint", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, - s); - } - } - } - return 0; // SQL NULL - } - - public static BigDecimal toBigDecimal(String s, int scale) throws SQLException - { - BigDecimal val; - if (s != null) - { - try - { - s = s.trim(); - val = new BigDecimal(s); - } - catch (NumberFormatException e) - { - throw new PSQLException("postgresql.res.badbigdec", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); - } - if (scale == -1) - return val; - try - { - return val.setScale(scale); - } - catch (ArithmeticException e) - { - throw new PSQLException("postgresql.res.badbigdec", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); - } - } - return null; // SQL NULL - } - - public static float toFloat(String s) throws SQLException - { - if (s != null) - { - try - { - s = s.trim(); - return Float.valueOf(s).floatValue(); - } - catch (NumberFormatException e) - { - throw new PSQLException("postgresql.res.badfloat", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); - } - } - return 0; // SQL NULL - } - - public static double toDouble(String s) throws SQLException - { - if (s != null) - { - try - { - s = s.trim(); - return Double.valueOf(s).doubleValue(); - } - catch (NumberFormatException e) - { - throw new PSQLException("postgresql.res.baddouble", - PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); - } - } - return 0; // SQL NULL - } - - public static java.sql.Date toDate(String s) throws SQLException - { - if (s == null) - return null; - // length == 10: SQL Date - // length > 10: SQL Timestamp, assumes PGDATESTYLE=ISO - try - { - s = s.trim(); - return java.sql.Date.valueOf((s.length() == 10) ? s : s.substring(0, 10)); - } - catch (NumberFormatException e) - { - throw new PSQLException("postgresql.res.baddate",PSQLState.BAD_DATETIME_FORMAT, s); - } - } - - private boolean isColumnTrimmable(int columnIndex) throws SQLException - { - switch (getSQLType(columnIndex)) - { - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - return true; - } - return false; - } - - private byte[] trimBytes(int p_columnIndex, byte[] p_bytes) throws SQLException - { - //we need to trim if maxsize is set and the length is greater than maxsize and the - //type of this column is a candidate for trimming - if (maxFieldSize > 0 && p_bytes.length > maxFieldSize && isColumnTrimmable(p_columnIndex)) - { - byte[] l_bytes = new byte[maxFieldSize]; - System.arraycopy (p_bytes, 0, l_bytes, 0, maxFieldSize); - return l_bytes; - } - else - { - return p_bytes; - } - } - - private String trimString(int p_columnIndex, String p_string) throws SQLException - { - //we need to trim if maxsize is set and the length is greater than maxsize and the - //type of this column is a candidate for trimming - if (maxFieldSize > 0 && p_string.length() > maxFieldSize && isColumnTrimmable(p_columnIndex)) - { - return p_string.substring(0, maxFieldSize); - } - else - { - return p_string; - } - } - } - diff --git a/org/postgresql/jdbc1/AbstractJdbc1ResultSetMetaData.java b/org/postgresql/jdbc1/AbstractJdbc1ResultSetMetaData.java deleted file mode 100644 index 30930445..00000000 --- a/org/postgresql/jdbc1/AbstractJdbc1ResultSetMetaData.java +++ /dev/null @@ -1,586 +0,0 @@ -package org.postgresql.jdbc1; - -import org.postgresql.core.*; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; - -import java.sql.*; -import java.util.*; - -public abstract class AbstractJdbc1ResultSetMetaData -{ - protected final BaseConnection connection; - protected final Field[] fields; - - private Hashtable tableNameCache; - private Hashtable schemaNameCache; - - /* - * Initialise for a result with a tuple set and - * a field descriptor set - * - * @param fields the array of field descriptors - */ - public AbstractJdbc1ResultSetMetaData(BaseConnection connection, Field[] fields) - { - this.connection = connection; - 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 = getSQLType(column); - - 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 = getSQLType(column); - - // 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 = getPGType(column); - - return type_name.equals("cash") || type_name.equals("money"); - } - - /* - * Indicates the nullability of values in the designated column. - * - * @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 - { - Field field = getField(column); - return field.getNullable(connection); - } - - /* - * 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 = getSQLType(column); - - 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 - { - Field f = getField(column); - String type_name = getPGType(column); - int typmod = f.getMod(); - - // I looked at other JDBC implementations and couldn't find a consistent - // interpretation of the "display size" for numeric values, so this is our's - // FIXME: currently, only types with a SQL92 or SQL3 pendant are implemented - jens@jens.de - - // fixed length data types - if (type_name.equals( "int2" )) - return 6; // -32768 to +32768 (5 digits and a sign) - if (type_name.equals( "int4" ) - || type_name.equals( "oid" )) - return 11; // -2147483648 to +2147483647 - if (type_name.equals( "int8" )) - return 20; // -9223372036854775808 to +9223372036854775807 - if (type_name.equals( "money" )) - return 12; // MONEY = DECIMAL(9,2) - if (type_name.equals( "float4" )) - return 11; // i checked it out ans wasn't able to produce more than 11 digits - if (type_name.equals( "float8" )) - return 20; // dito, 20 - if (type_name.equals( "char" )) - return 1; - if (type_name.equals( "bool" )) - return 1; - - int secondSize; - switch(typmod) { - case 0: - secondSize = 0; - break; - case -1: - // six digits plus the decimal point - secondSize = 7; - break; - default: - // with an odd scale an even number of digits - // are always show so timestamp(1) will print - // two fractional digits. - secondSize = typmod + (typmod%2) + 1; - break; - } - - if (type_name.equals( "date" )) - return 13; // "01/01/4713 BC" - "31/12/32767" - - // If we knew the timezone we could avoid having to possibly - // account for fractional hour offsets (which adds three chars). - // - // Also the range of timestamp types is not exactly clear. - // 4 digits is the common case for a year, but there are - // version/compilation dependencies on the exact date ranges, - // (notably --enable-integer-datetimes), but for now we'll - // just ignore them and assume that a year is four digits. - // - if (type_name.equals( "time" )) - return 8 + secondSize; // 00:00:00 + seconds - if (type_name.equals( "timetz" )) - return 8 + secondSize + 6; // 00:00.00 + .000000 + -00:00 - if (type_name.equals( "timestamp" )) - return 19 + secondSize; // 0000-00-00 00:00:00 + .000000; - if (type_name.equals( "timestamptz" )) - return 19 + secondSize + 6; // 0000-00-00 00:00:00 + .000000 + -00:00; - - // variable length fields - typmod -= 4; - if (type_name.equals( "bpchar" ) - || type_name.equals( "varchar" )) - return typmod; // VARHDRSZ=sizeof(int32)=4 - if (type_name.equals( "numeric" )) - return ( (typmod >> 16) & 0xffff ) - + 1 + ( typmod & 0xffff ); // DECIMAL(p,s) = (p digits).(s digits) - - // if we don't know better - return f.getLength(); - } - - /* - * @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 - { - Field f = getField(column); - if (f != null) - return f.getColumnLabel(); - return "field" + 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 database access error occurs - */ - public String getColumnName(int column) throws SQLException - { - Field field = getField(column); - return field.getColumnName(connection); - } - - /* - * @param column the first column is 1, the second is 2... - * @return the Schema Name - * @exception SQLException if a database access error occurs - */ - public String getSchemaName(int column) throws SQLException - { - Field field = getField(column); - if (field.getTableOid() == 0) - { - return ""; - } - Integer tableOid = new Integer(field.getTableOid()); - if (schemaNameCache == null) - { - schemaNameCache = new Hashtable(); - } - String schemaName = (String) schemaNameCache.get(tableOid); - if (schemaName != null) - { - return schemaName; - } else - { - ResultSet res = null; - PreparedStatement ps = null; - try - { - String sql = "SELECT n.nspname FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE n.oid = c.relnamespace AND c.oid = ?;"; - ps = ((Connection)connection).prepareStatement(sql); - ps.setInt(1, tableOid.intValue()); - res = ps.executeQuery(); - schemaName = ""; - if (res.next()) - { - schemaName = res.getString(1); - } - schemaNameCache.put(tableOid, schemaName); - return schemaName; - } finally - { - if (res != null) - res.close(); - if (ps != null) - ps.close(); - } - } - } - - /* - * 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 = getSQLType(column); - - 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; - case Types.VARCHAR: - return 0; - case Types.NUMERIC: - Field f = getField(column); - if (f != null) { - // no specified precision or scale - if (f.getMod() == -1) { - return 1000; - } - return ((0xFFFF0000)&f.getMod()) >> 16; - } else { - return 0; - } - default: - return 0; - } - } - - /* - * 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 = getSQLType(column); - - 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; - case Types.VARCHAR: - return 0; - case Types.NUMERIC: - Field f = getField(column); - if (f != null) { - // no specified precision or scale - if (f.getMod() == -1) { - return 1000; - } - return (((0x0000FFFF)&f.getMod()) - 4); - } else { - return 0; - } - case Types.TIME: - case Types.TIMESTAMP: - int typmod = -1; - - Field fld = getField(column); - if (fld != null) - typmod = fld.getMod(); - - if (typmod == -1) - return 6; - - return typmod; - default: - return 0; - } - } - - /* - * @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 - { - Field field = getField(column); - if (field.getTableOid() == 0) - { - return ""; - } - Integer tableOid = new Integer(field.getTableOid()); - if (tableNameCache == null) - { - tableNameCache = new Hashtable(); - } - String tableName = (String) tableNameCache.get(tableOid); - if (tableName != null) - { - return tableName; - } else - { - ResultSet res = null; - PreparedStatement ps = null; - try - { - ps = ((Connection)connection).prepareStatement("SELECT relname FROM pg_catalog.pg_class WHERE oid = ?"); - ps.setInt(1, tableOid.intValue()); - res = ps.executeQuery(); - tableName = ""; - if (res.next()) - { - tableName = res.getString(1); - } - tableNameCache.put(tableOid, tableName); - return tableName; - } finally - { - if (res != null) - res.close(); - if (ps != null) - ps.close(); - } - } - } - - /* - * 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 - { - return ""; - } - - /* - * 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 org.postgresql.Field#getSQLType - * @see java.sql.Types - */ - public int getColumnType(int column) throws SQLException - { - return getSQLType(column); - } - - /* - * 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 getPGType(column); - } - - /* - * 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 - { - return !isReadOnly(column); - } - - /* - * 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 false; - } - - // ******************************************************** - // 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 - */ - protected Field getField(int columnIndex) throws SQLException - { - if (columnIndex < 1 || columnIndex > fields.length) - throw new PSQLException("postgresql.res.colrange", PSQLState.INVALID_PARAMETER_VALUE); - return fields[columnIndex - 1]; - } - - protected String getPGType(int columnIndex) throws SQLException - { - return connection.getPGType(getField(columnIndex).getOID()); - } - - protected int getSQLType(int columnIndex) throws SQLException - { - return connection.getSQLType(getField(columnIndex).getOID()); - } -} - diff --git a/org/postgresql/jdbc1/AbstractJdbc1Statement.java b/org/postgresql/jdbc1/AbstractJdbc1Statement.java deleted file mode 100644 index 138420e2..00000000 --- a/org/postgresql/jdbc1/AbstractJdbc1Statement.java +++ /dev/null @@ -1,2218 +0,0 @@ -package org.postgresql.jdbc1; - -import org.postgresql.core.*; -import org.postgresql.largeobject.LargeObject; -import org.postgresql.largeobject.LargeObjectManager; -import org.postgresql.util.PGbytea; -import org.postgresql.util.PGobject; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; -import java.io.IOException; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.sql.CallableStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.Vector; - -/* $PostgreSQL: pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java,v 1.43 2003/12/12 00:26:20 davec Exp $ - * This class defines methods of the jdbc1 specification. This class is - * extended by org.postgresql.jdbc2.AbstractJdbc2Statement which adds the jdbc2 - * methods. The real Statement class (for jdbc1) is org.postgresql.jdbc1.Jdbc1Statement - */ -public abstract class AbstractJdbc1Statement implements BaseStatement -{ - // The connection who created us - protected BaseConnection connection; - - /** The warnings chain. */ - protected SQLWarning warnings = null; - - /** Maximum number of rows to return, 0 = unlimited */ - protected int maxrows = 0; - - /** Number of rows to get in a batch. */ - protected int fetchSize = 0; - - /** Timeout (in seconds) for a query (not used) */ - protected int timeout = 0; - - protected boolean replaceProcessingEnabled = true; - - /** The current results. */ - protected ResultWrapper result = null; - - /** The first unclosed result. */ - protected ResultWrapper firstUnclosedResult = null; - - // Static variables for parsing SQL when replaceProcessing is true. - private static final short IN_SQLCODE = 0; - private static final short IN_STRING = 1; - private static final short BACKSLASH = 2; - private static final short ESC_TIMEDATE = 3; - - // Some performance caches - private StringBuffer sbuf = new StringBuffer(32); - - protected final Query preparedQuery; // Query fragments for prepared statement. - protected final ParameterList preparedParameters; // Parameter values for prepared statement. - - protected int m_prepareThreshold; // Reuse threshold to enable use of PREPARE - protected int m_useCount = 0; // Number of times this statement has been used - - //Used by the callablestatement style methods - private boolean isFunction; - // functionReturnType contains the user supplied value to check - // testReturn contains a modified version to make it easier to - // check the getXXX methods.. - private int functionReturnType; - private int testReturn; - // returnTypeSet is true when a proper call to registerOutParameter has been made - private boolean returnTypeSet; - protected Object callResult; - protected int maxfieldSize = 0; - - public ResultSet createDriverResultSet(Field[] fields, Vector tuples) - throws SQLException - { - return createResultSet(null,fields,tuples,null); - } - - public abstract ResultSet createResultSet(Query originalQuery, Field[] fields, Vector tuples, ResultCursor cursor) - throws SQLException; - - public AbstractJdbc1Statement (BaseConnection connection) throws SQLException - { - this.connection = connection; - this.preparedQuery = null; - this.preparedParameters = null; - } - - public AbstractJdbc1Statement (BaseConnection connection, String p_sql, boolean isCallable) throws SQLException - { - this.connection = connection; - - String parsed_sql = replaceProcessing(p_sql); - if (isCallable) - parsed_sql = modifyJdbcCall(parsed_sql); - - this.preparedQuery = connection.getQueryExecutor().createParameterizedQuery(parsed_sql); - this.preparedParameters = preparedQuery.createParameterList(); - } - - public BaseConnection getPGConnection() { - return connection; - } - - public String getFetchingCursorName() { - return null; - } - - public int getFetchSize() { - return fetchSize; - } - - // Overridden by JDBC2 code. - protected boolean wantsScrollableResultSet() { - return false; - } - - // - // ResultHandler implementations for updates, queries, and either-or. - // - - public class StatementResultHandler implements ResultHandler { - private SQLException error; - private ResultWrapper results; - - ResultWrapper getResults() { return results; } - - private void append(ResultWrapper newResult) { - if (results == null) - results = newResult; - else - results.append(newResult); - } - - public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { - try { - ResultSet rs = AbstractJdbc1Statement.this.createResultSet(fromQuery, fields, tuples, cursor); - append(new ResultWrapper(rs)); - } catch (SQLException e) { - handleError(e); - } - } - - public void handleCommandStatus(String status, int updateCount, long insertOID) { - append(new ResultWrapper(updateCount, insertOID)); - } - - public void handleWarning(SQLWarning warning) { - AbstractJdbc1Statement.this.addWarning(warning); - } - - public void handleError(SQLException newError) { - if (error == null) - error = newError; - else - error.setNextException(newError); - } - - public void handleCompletion() throws SQLException { - if (error != null) - throw error; - } - } - - /* - * 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 p_sql) throws SQLException - { - if (preparedQuery != null) - throw new PSQLException("postgresql.stmt.wrongstatementtype"); - - if (!executeWithFlags(p_sql, 0)) - throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); - - if (result.getNext() != null) - throw new PSQLException("postgresql.stat.multi"); - - return (ResultSet)result.getResultSet(); - } - - /* - * 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 - { - if (!executeWithFlags(0)) - throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); - - if (result.getNext() != null) - throw new PSQLException("postgresql.stat.multi"); - - return (ResultSet) result.getResultSet(); - } - - /* - * 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 p_sql) throws SQLException - { - if (preparedQuery != null) - throw new PSQLException("postgresql.stmt.wrongstatementtype"); - - if (executeWithFlags(p_sql, QueryExecutor.QUERY_NO_RESULTS)) - throw new PSQLException("postgresql.stat.result"); - - return getUpdateCount(); - } - - /* - * 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 - { - if (executeWithFlags(QueryExecutor.QUERY_NO_RESULTS)) - throw new PSQLException("postgresql.stat.result"); - - return getUpdateCount(); - } - - /* - * 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 p_sql) throws SQLException - { - if (preparedQuery != null) - throw new PSQLException("postgresql.stmt.wrongstatementtype"); - - return executeWithFlags(p_sql, 0); - } - - public boolean executeWithFlags(String p_sql, int flags) throws SQLException - { - checkClosed(); - p_sql = replaceProcessing(p_sql); - Query simpleQuery = connection.getQueryExecutor().createSimpleQuery(p_sql); - execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags); - return (result != null && result.getResultSet() != null); - } - - public boolean execute() throws SQLException - { - return executeWithFlags(0); - } - - private boolean executeWithFlags(int flags) throws SQLException - { - checkClosed(); - if (isFunction && !returnTypeSet) - throw new PSQLException("postgresql.call.noreturntype", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); - - execute(preparedQuery, preparedParameters, flags); - - // If we are executing a callable statement function set the return data - if (isFunction) - { - if (result == null || result.getResultSet() == null) - throw new PSQLException("postgresql.call.noreturnval", PSQLState.NO_DATA); - - ResultSet rs = result.getResultSet(); - if (!rs.next()) - throw new PSQLException ("postgresql.call.noreturnval", PSQLState.NO_DATA); - - callResult = rs.getObject(1); - int columnType = rs.getMetaData().getColumnType(1); - if (columnType != functionReturnType) - throw new PSQLException ("postgresql.call.wrongrtntype", PSQLState.DATA_TYPE_MISMATCH, null, - new Object[]{ - "java.sql.Types=" + columnType, "java.sql.Types=" + functionReturnType }); - - rs.close(); - result = null; - return false; - } - - return (result != null && result.getResultSet() != null); - } - - protected void execute(Query queryToExecute, ParameterList queryParameters, int flags) throws SQLException { - // Close any existing resultsets associated with this statement. - while (firstUnclosedResult != null) { - if (firstUnclosedResult.getResultSet() != null) - firstUnclosedResult.getResultSet().close(); - firstUnclosedResult = firstUnclosedResult.getNext(); - } - - - // Enable cursor-based resultset if possible. - if (fetchSize > 0 && !wantsScrollableResultSet()) - flags |= QueryExecutor.QUERY_FORWARD_CURSOR; - - // Only use named statements after we hit the threshold - if (preparedQuery != null) { - ++m_useCount; // We used this statement once more. - if (m_prepareThreshold == 0 || m_useCount < m_prepareThreshold) - flags |= QueryExecutor.QUERY_ONESHOT; - } - - if (connection.getAutoCommit()) - flags |= QueryExecutor.QUERY_SUPPRESS_BEGIN; - - StatementResultHandler handler = new StatementResultHandler(); - result = null; - connection.getQueryExecutor().execute(queryToExecute, - queryParameters, - handler, - maxrows, - fetchSize, - flags); - result = firstUnclosedResult = handler.getResults(); - } - - /* - * 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. - * - * <p><B>Note:</B> 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. - * - * @param name the new cursor name - * @exception SQLException if a database access error occurs - */ - public void setCursorName(String name) throws SQLException - { - checkClosed(); - // No-op. - } - - private boolean isClosed = false; - - /* - * 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 - { - checkClosed(); - if (result == null) - return -1; - - if (isFunction) - return 1; - - if (result.getResultSet() != null) - return -1; - - return result.getUpdateCount(); - } - - /* - * 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 - { - if (result == null) - return false; - - result = result.getNext(); - - // Close preceding resultsets. - while (firstUnclosedResult != result) { - if (firstUnclosedResult.getResultSet() != null) - firstUnclosedResult.getResultSet().close(); - firstUnclosedResult = firstUnclosedResult.getNext(); - } - - return (result != null && result.getResultSet() != null); - } - - /* - * 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 - { - checkClosed(); - 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 - { - checkClosed(); - if (max<0) throw new PSQLException("postgresql.input.rows.gt0"); - 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 - { - checkClosed(); - replaceProcessingEnabled = 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 - { - checkClosed(); - 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 - { - checkClosed(); - if (seconds<0) throw new PSQLException("postgresql.input.query.gt0"); - timeout = seconds; - } - - /** - * This adds a warning to the warning chain. - * @param warn warning to add - */ - public void addWarning(SQLWarning warn) - { - if (warnings != null) - warnings.setNextWarning(warn); - else - warnings = warn; - } - - /* - * 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. - * - * <p>The Warning chain is automatically cleared each time a statement - * is (re)executed. - * - * <p><B>Note:</B> 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; - } - - /* - * 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 maxfieldSize; - } - - /* - * Sets the maxFieldSize - * - * @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 - { - checkClosed(); - if (max < 0) throw new PSQLException("postgresql.input.field.gt0"); - maxfieldSize = max; - } - - /* - * After this call, getWarnings returns null until a new warning - * is reported for this Statement. - * - * @exception SQLException if a database access error occurs - */ - public void clearWarnings() throws SQLException - { - warnings = null; - } - - /* - * Cancel can be used by one thread to cancel a statement that - * is being executed by another thread. - * <p> - * Not implemented, this method is a no-op. - * - * @exception SQLException only because thats the spec. - */ - public void cancel() throws SQLException - { - throw org.postgresql.Driver.notImplemented(); - } - - /* - * 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 - { - checkClosed(); - - if (result == null) - return null; - - return (ResultSet) result.getResultSet(); - } - - /* - * 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. - * - * <p><B>Note:</B> 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 - { - // closing an already closed Statement is a no-op. - if (isClosed) - return; - - // Force the ResultSet(s) to close - while (firstUnclosedResult != null) { - if (firstUnclosedResult.getResultSet() != null) - firstUnclosedResult.getResultSet().close(); - firstUnclosedResult = firstUnclosedResult.getNext(); - } - - - if (preparedQuery != null) - preparedQuery.close(); - - // Disasociate it from us - result = firstUnclosedResult = null; - isClosed = true; - } - - /** - * This finalizer ensures that statements that have allocated server-side - * resources free them when they become unreferenced. - */ - protected void finalize() { - try { close(); } - catch (SQLException e) {} - } - - /* - * Filter the SQL string of Java SQL Escape clauses. - * - * Currently implemented Escape clauses are those mentioned in 11.3 - * in the specification. Basically we look through the sql string for - * {d xxx}, {t xxx} or {ts xxx} in non-string sql code. When we find - * them, we just strip the escape part leaving only the xxx part. - * So, something like "select * from x where d={d '2001-10-09'}" would - * return "select * from x where d= '2001-10-09'". - */ - protected String replaceProcessing(String p_sql) - { - if (replaceProcessingEnabled) - { - // Since escape codes can only appear in SQL CODE, we keep track - // of if we enter a string or not. - StringBuffer newsql = new StringBuffer(p_sql.length()); - short state = IN_SQLCODE; - - int i = -1; - int len = p_sql.length(); - while (++i < len) - { - char c = p_sql.charAt(i); - switch (state) - { - case IN_SQLCODE: - if (c == '\'') // start of a string? - state = IN_STRING; - else if (c == '{') // start of an escape code? - if (i + 1 < len) - { - char next = p_sql.charAt(i + 1); - if (next == 'd') - { - state = ESC_TIMEDATE; - i++; - break; - } - else if (next == 't') - { - state = ESC_TIMEDATE; - i += (i + 2 < len && p_sql.charAt(i + 2) == 's') ? 2 : 1; - break; - } - } - newsql.append(c); - break; - - case IN_STRING: - if (c == '\'') // end of string? - state = IN_SQLCODE; - else if (c == '\\') // a backslash? - state = BACKSLASH; - - newsql.append(c); - break; - - case BACKSLASH: - state = IN_STRING; - - newsql.append(c); - break; - - case ESC_TIMEDATE: - if (c == '}') - state = IN_SQLCODE; // end of escape code. - else - newsql.append(c); - break; - } // end switch - } - - return newsql.toString(); - } - else - { - return p_sql; - } - } - - /* - * - * The following methods are postgres extensions and are defined - * in the interface BaseStatement - * - */ - - /* - * Returns the Last inserted/updated oid. Deprecated in 7.2 because - * range of OID values is greater than a java signed int. - * @deprecated Replaced by getLastOID in 7.2 - */ - public int getInsertedOID() throws SQLException - { - checkClosed(); - if (result == null) - return 0; - return (int) result.getInsertOID(); - } - - /* - * Returns the Last inserted/updated oid. - * @return OID of last insert - * @since 7.2 - */ - public long getLastOID() throws SQLException - { - checkClosed(); - if (result == null) - return 0; - return result.getInsertOID(); - } - - /* - * Set a parameter to SQL NULL - * - * <p><B>Note:</B> 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 - { - checkClosed(); - - int oid; - switch (sqlType) - { - case Types.INTEGER: - oid = Oid.INT4; - break; - case Types.TINYINT: - case Types.SMALLINT: - oid = Oid.INT2; - break; - case Types.BIGINT: - oid = Oid.INT8; - break; - case Types.REAL: - case Types.FLOAT: - oid = Oid.FLOAT4; - break; - case Types.DOUBLE: - oid = Oid.FLOAT8; - break; - case Types.DECIMAL: - case Types.NUMERIC: - oid = Oid.NUMERIC; - break; - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - oid = Oid.TEXT; - break; - case Types.DATE: - oid = Oid.DATE; - break; - case Types.TIME: - oid = Oid.TIME; - break; - case Types.TIMESTAMP: - oid = Oid.TIMESTAMPTZ; - break; - case Types.BIT: - oid = Oid.BOOL; - break; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - oid = Oid.BYTEA; - break; - case Types.OTHER: - default: - oid = 0; - break; - } - - preparedParameters.setNull(adjustParamIndex(parameterIndex), oid); - } - - /* - * 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 - { - checkClosed(); - bindString(parameterIndex, x ? "1" : "0", Oid.BOOL); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Long.toString(x), Oid.INT8); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Float.toString(x), Oid.FLOAT4); - } - - /* - * 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 - { - checkClosed(); - bindLiteral(parameterIndex, Double.toString(x), Oid.FLOAT8); - } - - /* - * 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 - { - checkClosed(); - if (x == null) - setNull(parameterIndex, Types.DECIMAL); - else - { - bindLiteral(parameterIndex, x.toString(), Oid.NUMERIC); - } - } - - /* - * 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 - { - checkClosed(); - setString(parameterIndex, x, Oid.TEXT); - } - - protected void setString(int parameterIndex, String x, int oid) throws SQLException - { - // if the passed string is null, then set this column to null - checkClosed(); - if (x == null) - preparedParameters.setNull(adjustParamIndex(parameterIndex), oid); - else - bindString(parameterIndex, x, oid); - } - - /* - * 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. - * - * <p>Implementation note: - * <br>With org.postgresql, this creates a large object, and stores the - * objects oid in this column. - * - * @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 - { - checkClosed(); - - if (null == x) { - setNull(parameterIndex, Types.VARBINARY); - return; - } - - if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports the bytea datatype for byte arrays - byte[] copy = new byte[x.length]; - System.arraycopy(x, 0, copy, 0, x.length); - preparedParameters.setBytea(adjustParamIndex(parameterIndex), copy, 0, x.length); - } - else - { - //Version 7.1 and earlier support done as LargeObjects - LargeObjectManager lom = connection.getLargeObjectAPI(); - int oid = lom.create(); - LargeObject lob = lom.open(oid); - lob.write(x); - lob.close(); - setInt(parameterIndex, oid); - } - } - - /* - * 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 - { - checkClosed(); - if (null == x) - { - setNull(parameterIndex, Types.DATE); - } - else - { - bindString(parameterIndex, x.toString(), Oid.DATE); - } - } - - /* - * 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 - { - checkClosed(); - if (null == x) - { - setNull(parameterIndex, Types.TIME); - } - else - { - bindString(parameterIndex, x.toString(), Oid.TIME); - } - } - - /* - * 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 - { - checkClosed(); - if (null == x) - { - setNull(parameterIndex, Types.TIMESTAMP); - } - else - { - // Use the shared StringBuffer - synchronized (sbuf) - { - sbuf.setLength(0); - sbuf.ensureCapacity(32); - //format the timestamp - //we do our own formating so that we can get a format - //that works with both timestamp with time zone and - //timestamp without time zone datatypes. - //The format is '2002-01-01 23:59:59.123456-0130' - //we need to include the local time and timezone offset - //so that timestamp without time zone works correctly - int l_year = x.getYear() + 1900; - - // always use four digits for the year so very - // early years, like 2, don't get misinterpreted - int l_yearlen = String.valueOf(l_year).length(); - for (int i=4; i>l_yearlen; i--) { - sbuf.append("0"); - } - - sbuf.append(l_year); - sbuf.append('-'); - int l_month = x.getMonth() + 1; - if (l_month < 10) - sbuf.append('0'); - sbuf.append(l_month); - sbuf.append('-'); - int l_day = x.getDate(); - if (l_day < 10) - sbuf.append('0'); - sbuf.append(l_day); - sbuf.append(' '); - int l_hours = x.getHours(); - if (l_hours < 10) - sbuf.append('0'); - sbuf.append(l_hours); - sbuf.append(':'); - int l_minutes = x.getMinutes(); - if (l_minutes < 10) - sbuf.append('0'); - sbuf.append(l_minutes); - sbuf.append(':'); - int l_seconds = x.getSeconds(); - if (l_seconds < 10) - sbuf.append('0'); - sbuf.append(l_seconds); - // Make decimal from nanos. - char[] l_decimal = {'0', '0', '0', '0', '0', '0', '0', '0', '0'}; - char[] l_nanos = Integer.toString(x.getNanos()).toCharArray(); - System.arraycopy(l_nanos, 0, l_decimal, l_decimal.length - l_nanos.length, l_nanos.length); - sbuf.append('.'); - if (connection.haveMinimumServerVersion("7.2")) - { - sbuf.append(l_decimal, 0, 6); - } - else - { - // Because 7.1 include bug that "hh:mm:59.999" becomes "hh:mm:60.00". - sbuf.append(l_decimal, 0, 2); - } - //add timezone offset - int l_offset = -(x.getTimezoneOffset()); - int l_houros = l_offset / 60; - if (l_houros >= 0) - { - sbuf.append('+'); - } - else - { - sbuf.append('-'); - } - if (l_houros > -10 && l_houros < 10) - sbuf.append('0'); - if (l_houros >= 0) - { - sbuf.append(l_houros); - } - else - { - sbuf.append(-l_houros); - } - int l_minos = l_offset - (l_houros * 60); - if (l_minos != 0) - { - if (l_minos > -10 && l_minos < 10) - sbuf.append('0'); - if (l_minos >= 0) - { - sbuf.append(l_minos); - } - else - { - sbuf.append(-l_minos); - } - } - bindString(parameterIndex, sbuf.toString(), Oid.TIMESTAMPTZ); - } - - } - } - - private void setCharacterStreamPost71(int parameterIndex, InputStream x, int length, String encoding) throws SQLException - { - - if (x == null) - { - setNull(parameterIndex, Types.VARCHAR); - return; - } - - //Version 7.2 supports AsciiStream for all PG text types (char, varchar, text) - //As the spec/javadoc for this method indicate this is to be used for - //large String values (i.e. LONGVARCHAR) PG doesn't have a separate - //long varchar datatype, but with toast all text datatypes are capable of - //handling very large values. Thus the implementation ends up calling - //setString() since there is no current way to stream the value to the server - try - { - InputStreamReader l_inStream = new InputStreamReader(x, encoding); - char[] l_chars = new char[length]; - int l_charsRead = 0; - while (true) - { - int n = l_inStream.read(l_chars, l_charsRead, length - l_charsRead); - if (n == -1) - break; - - l_charsRead += n; - - if (l_charsRead == length) - break; - } - - setString(parameterIndex, new String(l_chars, 0, l_charsRead), Oid.TEXT); - } - catch (UnsupportedEncodingException l_uee) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); - } - catch (IOException l_ioe) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_ioe); - } - } - - /* - * 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. - * - * <P><B>Note:</B> 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 - { - checkClosed(); - if (connection.haveMinimumCompatibleVersion("7.2")) - { - setCharacterStreamPost71(parameterIndex, x, length, "ASCII"); - } - else - { - //Version 7.1 supported only LargeObjects by treating everything - //as binary data - 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. - * - * <P><B>Note:</B> 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 - { - checkClosed(); - if (connection.haveMinimumCompatibleVersion("7.2")) - { - setCharacterStreamPost71(parameterIndex, x, length, "UTF-8"); - } - else - { - //Version 7.1 supported only LargeObjects by treating everything - //as binary data - 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. - * - * <P><B>Note:</B> 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 - { - checkClosed(); - - if (x == null) { - setNull(parameterIndex, Types.VARBINARY); - return; - } - - if (connection.haveMinimumCompatibleVersion("7.2")) - { - //Version 7.2 supports BinaryStream for for the PG bytea type - //As the spec/javadoc for this method indicate this is to be used for - //large binary values (i.e. LONGVARBINARY) PG doesn't have a separate - //long binary datatype, but with toast the bytea datatype is capable of - //handling very large values. - - preparedParameters.setBytea(adjustParamIndex(parameterIndex), x, length); - } - else - { - //Version 7.1 only supported streams for LargeObjects - //but the jdbc spec indicates that streams should be - //available for LONGVARBINARY instead - LargeObjectManager lom = connection.getLargeObjectAPI(); - int oid = lom.create(); - LargeObject lob = lom.open(oid); - OutputStream los = lob.getOutputStream(); - try - { - // could be buffered, but then the OutputStream returned by LargeObject - // is buffered internally anyhow, so there would be no performance - // boost gained, if anything it would be worse! - int c = x.read(); - int p = 0; - while (c > -1 && p < length) - { - los.write(c); - c = x.read(); - p++; - } - los.close(); - } - catch (IOException se) - { - throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, se); - } - // lob is closed by the stream so don't call lob.close() - setInt(parameterIndex, oid); - } - } - - - /* - * 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 - { - preparedParameters.clear(); - } - - // Helper method that extracts numeric values from an arbitary Object. - private String numericValueOf(Object x) - { - if (x instanceof Boolean) - return ((Boolean)x).booleanValue() ? "1" :"0"; - else if (x instanceof Integer || x instanceof Long || - x instanceof Double || x instanceof Short || - x instanceof Number || x instanceof Float) - return x.toString(); - else - //ensure the value is a valid numeric value to avoid - //sql injection attacks - return new BigDecimal(x.toString()).toString(); - } - - /* - * Set the value of a parameter using an object; use the java.lang - * equivalent objects for integral values. - * - * <P>The given Java object will be converted to the targetSqlType before - * being sent to the database. - * - * <P>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 - { - checkClosed(); - if (x == null) - { - setNull(parameterIndex, targetSqlType); - return ; - } - switch (targetSqlType) - { - case Types.INTEGER: - bindLiteral(parameterIndex, numericValueOf(x), Oid.INT4); - break; - case Types.TINYINT: - case Types.SMALLINT: - bindLiteral(parameterIndex, numericValueOf(x), Oid.INT2); - break; - case Types.BIGINT: - bindLiteral(parameterIndex, numericValueOf(x), Oid.INT8); - break; - case Types.REAL: - case Types.FLOAT: - bindLiteral(parameterIndex, numericValueOf(x), Oid.FLOAT4); - break; - case Types.DOUBLE: - bindLiteral(parameterIndex, numericValueOf(x), Oid.FLOAT8); - break; - case Types.DECIMAL: - case Types.NUMERIC: - bindLiteral(parameterIndex, numericValueOf(x), Oid.NUMERIC); - break; - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - setString(parameterIndex, x.toString()); - break; - case Types.DATE: - if (x instanceof java.sql.Date) - setDate(parameterIndex, (java.sql.Date)x); - else - { - java.sql.Date tmpd = (x instanceof java.util.Date) ? new java.sql.Date(((java.util.Date)x).getTime()) : dateFromString(x.toString()); - setDate(parameterIndex, tmpd); - } - break; - case Types.TIME: - if (x instanceof java.sql.Time) - setTime(parameterIndex, (java.sql.Time)x); - else - { - java.sql.Time tmpt = (x instanceof java.util.Date) ? new java.sql.Time(((java.util.Date)x).getTime()) : timeFromString(x.toString()); - setTime(parameterIndex, tmpt); - } - break; - case Types.TIMESTAMP: - if (x instanceof java.sql.Timestamp) - setTimestamp(parameterIndex ,(java.sql.Timestamp)x); - else - { - java.sql.Timestamp tmpts = (x instanceof java.util.Date) ? new java.sql.Timestamp(((java.util.Date)x).getTime()) : timestampFromString(x.toString()); - setTimestamp(parameterIndex, tmpts); - } - break; - case Types.BIT: - if (x instanceof Boolean) - { - bindString(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0", Oid.BOOL); - } - else if (x instanceof String) - { - bindString(parameterIndex, Boolean.valueOf(x.toString()).booleanValue() ? "1" : "0", Oid.BOOL); - } - else if (x instanceof Number) - { - bindString(parameterIndex, ((Number)x).intValue()!=0 ? "1" : "0", Oid.BOOL); - } - else - { - throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); - } - break; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - setObject(parameterIndex, x); - break; - case Types.OTHER: - if (x instanceof PGobject) - setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType( ((PGobject)x).getType() )); - else - throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); - break; - default: - throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); - } - } - - public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException - { - setObject(parameterIndex, x, targetSqlType, 0); - } - - /* - * This stores an Object into a parameter. - */ - public void setObject(int parameterIndex, Object x) throws SQLException - { - checkClosed(); - if (x == null) - { - setNull(parameterIndex, Types.OTHER); - return; - } - if (x instanceof String) - setString(parameterIndex, (String)x); - else if (x instanceof BigDecimal) - setBigDecimal(parameterIndex, (BigDecimal)x); - else if (x instanceof Short) - setShort(parameterIndex, ((Short)x).shortValue()); - 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 PGobject) - setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType(((PGobject)x).getType())); - else - // Try to store as a string in database - setString(parameterIndex, x.toString(), 0); - } - - /* - * Before executing a stored procedure call you must explicitly - * call registerOutParameter to register the java.sql.Type of each - * out parameter. - * - * <p>Note: When reading the value of an out parameter, you must use - * the getXXX method whose Java type XXX corresponds to the - * parameter's registered SQL type. - * - * ONLY 1 RETURN PARAMETER if {?= call ..} syntax is used - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @param sqlType SQL type code defined by java.sql.Types; for - * parameters of type Numeric or Decimal use the version of - * registerOutParameter that accepts a scale value - * @exception SQLException if a database-access error occurs. - */ - public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException - { - checkClosed(); - if (parameterIndex != 1) - throw new PSQLException ("postgresql.call.noinout", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); - if (!isFunction) - throw new PSQLException ("postgresql.call.procasfunc", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); - - // functionReturnType contains the user supplied value to check - // testReturn contains a modified version to make it easier to - // check the getXXX methods.. - functionReturnType = sqlType; - testReturn = sqlType; - if (functionReturnType == Types.CHAR || - functionReturnType == Types.LONGVARCHAR) - testReturn = Types.VARCHAR; - else if (functionReturnType == Types.FLOAT) - testReturn = Types.REAL; // changes to streamline later error checking - returnTypeSet = true; - } - - /* - * You must also specify the scale for numeric/decimal types: - * - * <p>Note: When reading the value of an out parameter, you must use - * the getXXX method whose Java type XXX corresponds to the - * parameter's registered SQL type. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @param sqlType use either java.sql.Type.NUMERIC or java.sql.Type.DECIMAL - * @param scale a value greater than or equal to zero representing the - * desired number of digits to the right of the decimal point - * @exception SQLException if a database-access error occurs. - */ - public void registerOutParameter(int parameterIndex, int sqlType, - int scale) throws SQLException - { - registerOutParameter (parameterIndex, sqlType); // ignore for now.. - } - - /* - * An OUT parameter may have the value of SQL NULL; wasNull - * reports whether the last value read has this special value. - * - * <p>Note: You must first call getXXX on a parameter to read its - * value and then call wasNull() to see if the value was SQL NULL. - * @return true if the last parameter read was SQL NULL - * @exception SQLException if a database-access error occurs. - */ - public boolean wasNull() throws SQLException - { - // check to see if the last access threw an exception - return (callResult == null); - } - - /* - * Get the value of a CHAR, VARCHAR, or LONGVARCHAR parameter as a - * Java String. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - */ - public String getString(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.VARCHAR, "String"); - return (String)callResult; - } - - - /* - * Get the value of a BIT parameter as a Java boolean. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is false - * @exception SQLException if a database-access error occurs. - */ - public boolean getBoolean(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.BIT, "Boolean"); - if (callResult == null) - return false; - return ((Boolean)callResult).booleanValue (); - } - - /* - * Get the value of a TINYINT parameter as a Java byte. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public byte getByte(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.TINYINT, "Byte"); - if (callResult == null) - return 0; - return (byte)((Integer)callResult).intValue (); - } - - /* - * Get the value of a SMALLINT parameter as a Java short. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public short getShort(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.SMALLINT, "Short"); - if (callResult == null) - return 0; - return (short)((Integer)callResult).intValue (); - } - - - /* - * Get the value of an INTEGER parameter as a Java int. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public int getInt(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.INTEGER, "Int"); - if (callResult == null) - return 0; - return ((Integer)callResult).intValue (); - } - - /* - * Get the value of a BIGINT parameter as a Java long. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public long getLong(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.BIGINT, "Long"); - if (callResult == null) - return 0; - return ((Long)callResult).longValue (); - } - - /* - * Get the value of a FLOAT parameter as a Java float. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public float getFloat(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.REAL, "Float"); - if (callResult == null) - return 0; - return ((Float)callResult).floatValue (); - } - - /* - * Get the value of a DOUBLE parameter as a Java double. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is 0 - * @exception SQLException if a database-access error occurs. - */ - public double getDouble(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.DOUBLE, "Double"); - if (callResult == null) - return 0; - return ((Double)callResult).doubleValue (); - } - - /* - * Get the value of a NUMERIC parameter as a java.math.BigDecimal - * object. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @param scale a value greater than or equal to zero representing the - * desired number of digits to the right of the decimal point - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - * @deprecated in Java2.0 - */ - public BigDecimal getBigDecimal(int parameterIndex, int scale) - throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.NUMERIC, "BigDecimal"); - return ((BigDecimal)callResult); - } - - /* - * Get the value of a SQL BINARY or VARBINARY parameter as a Java - * byte[] - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - */ - public byte[] getBytes(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.VARBINARY, Types.BINARY, "Bytes"); - return ((byte [])callResult); - } - - - /* - * Get the value of a SQL DATE parameter as a java.sql.Date object - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - */ - public java.sql.Date getDate(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.DATE, "Date"); - return (java.sql.Date)callResult; - } - - /* - * Get the value of a SQL TIME parameter as a java.sql.Time object. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - */ - public java.sql.Time getTime(int parameterIndex) throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.TIME, "Time"); - return (java.sql.Time)callResult; - } - - /* - * Get the value of a SQL TIMESTAMP parameter as a java.sql.Timestamp object. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return the parameter value; if the value is SQL NULL, the result is null - * @exception SQLException if a database-access error occurs. - */ - public java.sql.Timestamp getTimestamp(int parameterIndex) - throws SQLException - { - checkClosed(); - checkIndex (parameterIndex, Types.TIMESTAMP, "Timestamp"); - return (java.sql.Timestamp)callResult; - } - - // getObject returns a Java object for the parameter. - // See the JDBC spec's "Dynamic Programming" chapter for details. - /* - * Get the value of a parameter as a Java object. - * - * <p>This method returns a Java object whose type coresponds to the - * SQL type that was registered for this parameter using - * registerOutParameter. - * - * <P>Note that this method may be used to read datatabase-specific, - * abstract data types. This is done by specifying a targetSqlType - * of java.sql.types.OTHER, which allows the driver to return a - * database-specific Java type. - * - * <p>See the JDBC spec's "Dynamic Programming" chapter for details. - * - * @param parameterIndex the first parameter is 1, the second is 2,... - * @return A java.lang.Object holding the OUT parameter value. - * @exception SQLException if a database-access error occurs. - */ - public Object getObject(int parameterIndex) - throws SQLException - { - checkClosed(); - checkIndex (parameterIndex); - return callResult; - } - - /* - * Returns the SQL statement with the current template values - * substituted. - */ - public String toString() - { - if (preparedQuery == null) - return super.toString(); - - return preparedQuery.toString(preparedParameters); - } - - private int adjustParamIndex(int paramIndex) throws SQLException { - if (!isFunction) - return paramIndex; - - if (paramIndex == 1) // need to registerOut instead - throw new PSQLException ("postgresql.call.funcover"); - - return paramIndex - 1; - } - - /* - * Note if s is a String it should be escaped by the caller to avoid SQL - * injection attacks. It is not done here for efficency reasons as - * most calls to this method do not require escaping as the source - * of the string is known safe (i.e. Integer.toString()) - */ - private void bindLiteral(int paramIndex, String s, int oid) throws SQLException - { - preparedParameters.setLiteralParameter(adjustParamIndex(paramIndex), s, oid); - } - - /* - * This version is for values that should turn into strings - * e.g. setString directly calls bindString with no escaping; - * the per-protocol ParameterList does escaping as needed. - */ - private void bindString(int paramIndex, String s, int oid) throws SQLException - { - preparedParameters.setStringParameter(adjustParamIndex(paramIndex), s, oid); - } - - /** - * this method will turn a string of the form - * { [? =] call <some_function> [(?, [?,..])] } - * into the PostgreSQL format which is - * select <some_function> (?, [?, ...]) as result - * or select * from <some_function> (?, [?, ...]) as result (7.3) - */ - private String modifyJdbcCall(String p_sql) throws SQLException - { - checkClosed(); - - // Mini-parser for JDBC function-call syntax (only) - // TODO: Merge with escape processing (and parameter parsing?) - // so we only parse each query once. - - isFunction = false; - - int len = p_sql.length(); - int state = 1; - boolean inQuotes = false, inEscape = false; - - int startIndex = -1, endIndex = -1; - boolean syntaxError = false; - int i = 0; - - while (i < len && !syntaxError) { - char ch = p_sql.charAt(i); - - switch (state) { - case 1: // Looking for { at start of query - if (ch == '{') { - ++i; - ++state; - } else if (Character.isWhitespace(ch)) { - ++i; - } else { - // Not function-call syntax. Skip the rest of the string. - i = len; - } - break; - - case 2: // After {, looking for ? or =, skipping whitespace - if (ch == '?') { - isFunction = true; // { ? = call ... } -- function with one out parameter - ++i; - ++state; - } else if (ch == 'c') { // { call ... } -- proc with no out parameters - state += 3; // Don't increase 'i' - } else if (Character.isWhitespace(ch)) { - ++i; - } else { - // "{ foo ...", doesn't make sense, complain. - syntaxError = true; - } - break; - - case 3: // Looking for = after ?, skipping whitespace - if (ch == '=') { - ++i; - ++state; - } else if (Character.isWhitespace(ch)) { - ++i; - } else { - syntaxError = true; - } - break; - - case 4: // Looking for 'call' after '? =' skipping whitespace - if (ch == 'c' || ch == 'C') { - ++state; // Don't increase 'i'. - } else if (Character.isWhitespace(ch)) { - ++i; - } else { - syntaxError = true; - } - break; - - case 5: // Should be at 'call ' either at start of string or after ?= - if ((ch == 'c' || ch == 'C') && i+4 <= len && p_sql.substring(i, i+4).equalsIgnoreCase("call")) { - i += 4; - ++state; - } else if (Character.isWhitespace(ch)) { - ++i; - } else { - syntaxError = true; - } - break; - - case 6: // Looking for whitespace char after 'call' - if (Character.isWhitespace(ch)) { - // Ok, we found the start of the real call. - ++i; - ++state; - startIndex = i; - } else { - syntaxError = true; - } - break; - - case 7: // In "body" of the query (after "{ [? =] call ") - if (ch == '\'') { - inQuotes = !inQuotes; - ++i; - } else if (inQuotes && ch == '\\') { - // Backslash in string constant, skip next character. - i += 2; - } else if (!inQuotes && ch == '{') { - inEscape = !inEscape; - ++i; - } else if (!inQuotes && ch == '}') { - if (!inEscape) { - // Should be end of string. - endIndex = i; - ++i; - ++state; - } else { - inEscape = false; - } - } else if (!inQuotes && ch == ';') { - syntaxError = true; - } else { - // Everything else is ok. - ++i; - } - break; - - case 8: // At trailing end of query, eating whitespace - if (Character.isWhitespace(ch)) { - ++i; - } else { - syntaxError = true; - } - break; - - default: - throw new IllegalStateException("somehow got into bad state " + state); - } - } - - // We can only legally end in a couple of states here. - if (i == len && !syntaxError) { - if (state == 1) - return p_sql; // Not an escaped syntax. - if (state != 8) - syntaxError = true; // Ran out of query while still parsing - } - - if (syntaxError) - throw new PSQLException ("postgresql.call.malformed",PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL, - new Object[] { new Integer(i) }); - - if (connection.haveMinimumServerVersion("7.3")) { - return "select * from " + p_sql.substring(startIndex, endIndex) + " as result"; - } else { - return "select " + p_sql.substring(startIndex, endIndex) + " as result"; - } - } - - /** helperfunction for the getXXX calls to check isFunction and index == 1 - * Compare BOTH type fields against the return type. - */ - protected void checkIndex (int parameterIndex, int type1, int type2, String getName) - throws SQLException - { - checkIndex (parameterIndex); - if (type1 != this.testReturn && type2 != this.testReturn) - throw new PSQLException("postgresql.call.wrongget", PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH, - new Object[]{"java.sql.Types=" + testReturn, - getName, - "java.sql.Types=" + type1}); - } - - /** helperfunction for the getXXX calls to check isFunction and index == 1 - */ - protected void checkIndex (int parameterIndex, int type, String getName) - throws SQLException - { - checkIndex (parameterIndex); - if (type != this.testReturn) - throw new PSQLException("postgresql.call.wrongget", PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH, - new Object[]{"java.sql.Types=" + testReturn, - getName, - "java.sql.Types=" + type}); - } - - /** helperfunction for the getXXX calls to check isFunction and index == 1 - * @param parameterIndex index of getXXX (index) - * check to make sure is a function and index == 1 - */ - private void checkIndex (int parameterIndex) throws SQLException - { - if (!isFunction) - throw new PSQLException("postgresql.call.noreturntype", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); - if (parameterIndex != 1) - throw new PSQLException("postgresql.call.noinout", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); - } - - public void setPrepareThreshold(int newThreshold) throws SQLException { - checkClosed(); - - if (newThreshold < 0) - newThreshold = 0; - - this.m_prepareThreshold = newThreshold; - } - - public int getPrepareThreshold() { - return m_prepareThreshold; - } - - public void setUseServerPrepare(boolean flag) throws SQLException { - setPrepareThreshold(flag ? 1 : 0); - } - - public boolean isUseServerPrepare() { - return (preparedQuery != null && m_prepareThreshold != 0 && m_useCount+1 >= m_prepareThreshold); - } - - protected void checkClosed() throws SQLException - { - //need to add to errors.properties. - if (isClosed) - throw new PSQLException("postgresql.stmt.closed"); - } - - private java.sql.Date dateFromString (String s) throws SQLException - { - int timezone = 0; - long millis = 0; - long localoffset = 0; - int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); - //if the last index of '-' or '+' is past 8. we are guaranteed that it is a timezone marker - //shortest = yyyy-m-d - //longest = yyyy-mm-dd - try - { - timezone = (timezoneLocation>7) ? timezoneLocation : s.length(); - millis = java.sql.Date.valueOf(s.substring(0,timezone)).getTime(); - } - catch (Exception e) - { - throw new PSQLException("postgresql.format.baddate", PSQLState.BAD_DATETIME_FORMAT, e, - new Object[] { s , "yyyy-MM-dd[-tz]" }); - } - timezone = 0; - if (timezoneLocation>7 && timezoneLocation+3 == s.length()) - { - timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); - localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); - if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) - localoffset += 60*60*1000; - if (s.charAt(timezoneLocation)=='+') - timezone*=-1; - } - millis = millis + timezone*60*60*1000 + localoffset; - return new java.sql.Date(millis); - } - - private java.sql.Time timeFromString (String s) throws SQLException - { - int timezone = 0; - long millis = 0; - long localoffset = 0; - int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); - //if the index of the last '-' or '+' is greater than 0 that means this time has a timezone. - //everything earlier than that position, we treat as the time and parse it as such. - try - { - timezone = (timezoneLocation==-1) ? s.length() : timezoneLocation; - millis = java.sql.Time.valueOf(s.substring(0,timezone)).getTime(); - } - catch (Exception e) - { - throw new PSQLException("postgresql.format.badtime", PSQLState.BAD_DATETIME_FORMAT, e, - new Object[] { s, "HH:mm:ss[-tz]" }); - } - timezone = 0; - if (timezoneLocation != -1 && timezoneLocation+3 == s.length()) - { - timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); - localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); - if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) - localoffset += 60*60*1000; - if (s.charAt(timezoneLocation)=='+') - timezone*=-1; - } - millis = millis + timezone*60*60*1000 + localoffset; - return new java.sql.Time(millis); - } - - private java.sql.Timestamp timestampFromString (String s) throws SQLException - { - int timezone = 0; - long millis = 0; - long localoffset = 0; - int nanosVal = 0; - int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); - int nanospos = s.indexOf("."); - //if there is a '.', that means there are nanos info, and we take the timestamp up to that point - //if not, then we check to see if the last +/- (to indicate a timezone) is greater than 8 - //8 is because the shortest date, will have last '-' at position 7. e.g yyyy-x-x - try - { - if (nanospos != -1) - timezone = nanospos; - else if (timezoneLocation > 8) - timezone = timezoneLocation; - else - timezone = s.length(); - millis = java.sql.Timestamp.valueOf(s.substring(0,timezone)).getTime(); - } - catch (Exception e) - { - throw new PSQLException("postgresql.format.badtimestamp", PSQLState.BAD_DATETIME_FORMAT, e, - new Object[] { s, "yyyy-MM-dd HH:mm:ss[.xxxxxx][-tz]" }); - } - timezone = 0; - if (nanospos != -1) - { - int tmploc = (timezoneLocation > 8) ? timezoneLocation : s.length(); - nanosVal = Integer.parseInt(s.substring(nanospos+1,tmploc)); - int diff = 8-((tmploc-1)-(nanospos+1)); - for (int i=0;i<diff;i++) - nanosVal*=10; - } - if (timezoneLocation>8 && timezoneLocation+3 == s.length()) - { - timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); - localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); - if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) - localoffset += 60*60*1000; - if (s.charAt(timezoneLocation)=='+') - timezone*=-1; - } - millis = millis + timezone*60*60*1000 + localoffset; - java.sql.Timestamp tmpts = new java.sql.Timestamp(millis); - tmpts.setNanos(nanosVal); - return tmpts; - } -} diff --git a/org/postgresql/jdbc2/AbstractJdbc2Connection.java b/org/postgresql/jdbc2/AbstractJdbc2Connection.java index 9cb80bfc..201d200b 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2Connection.java +++ b/org/postgresql/jdbc2/AbstractJdbc2Connection.java @@ -1,18 +1,124 @@ package org.postgresql.jdbc2; +import java.io.IOException; import java.io.PrintWriter; import java.sql.*; -import java.util.Properties; +import java.util.*; +import org.postgresql.core.*; +import org.postgresql.Driver; +import org.postgresql.PGNotification; +import org.postgresql.fastpath.Fastpath; +import org.postgresql.largeobject.LargeObjectManager; +import org.postgresql.util.PSQLState; +import org.postgresql.util.PGobject; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLWarning; + /* $PostgreSQL: /cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Connection.java,v 1.6 2003/06/30 21:10:55 davec Exp $ - * This class defines methods of the jdbc2 specification. This class extends - * org.postgresql.jdbc1.AbstractJdbc1Connection which provides the jdbc1 - * methods. The real Connection class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Connection + * This class defines methods of the jdbc2 specification. + * The real Connection class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Connection */ -public abstract class AbstractJdbc2Connection extends org.postgresql.jdbc1.AbstractJdbc1Connection +public abstract class AbstractJdbc2Connection implements BaseConnection { - protected AbstractJdbc2Connection(String host, int port, String user, String database, Properties info, String url) throws SQLException { - super(host, port, user, database, info, url); + // + // Data initialized on construction: + // + + /* URL we were created via */ + private final String creatingURL; + + /* Actual network handler */ + private final ProtocolConnection protoConnection; + /* Compatibility version */ + private final String compatible; + /* Actual server version */ + private final String dbVersionNumber; + + /* Query that runs COMMIT */ + private final Query commitQuery; + /* Query that runs ROLLBACK */ + private final Query rollbackQuery; + + // These are used to cache the oids to PGType mappings. + private Hashtable oidTypeCache = new Hashtable(); // oid -> PGType + private Hashtable typeOidCache = new Hashtable(); // PGType -> oid + + // Default statement prepare threshold. + protected int prepareThreshold; + // Connection's autocommit state. + public boolean autoCommit = true; + // Connection's readonly state. + public boolean readOnly = false; + + // Current warnings; there might be more on protoConnection too. + public SQLWarning firstWarning = null; + + public abstract DatabaseMetaData getMetaData() throws SQLException; + + // + // Ctor. + // + protected AbstractJdbc2Connection(String host, int port, String user, String database, Properties info, String url) throws SQLException + { + this.creatingURL = url; + + //Read loglevel arg and set the loglevel based on this value + //in addition to setting the log level enable output to + //standard out if no other printwriter is set + + // XXX revisit: need a debug level *per connection*. + + int logLevel = 0; + try + { + logLevel = Integer.parseInt(info.getProperty("loglevel", "0")); + if (logLevel > Driver.DEBUG || logLevel < Driver.INFO) + { + logLevel = 0; + } + } + catch (Exception l_e) + { + // XXX revisit + // invalid value for loglevel; ignore it + } + + if (logLevel > 0) + { + Driver.setLogLevel(logLevel); + enableDriverManagerLogging(); + } + + prepareThreshold = 0; + try { + prepareThreshold = Integer.parseInt(info.getProperty("prepareThreshold", "0")); + } catch (Exception e) {} + + if (prepareThreshold < 0) + prepareThreshold = 0; + + //Print out the driver version number + if (Driver.logInfo) + Driver.info(Driver.getVersion()); + + // Now make the initial connection and set up local state + this.protoConnection = ConnectionFactory.openConnection(host, port, user, database, info); + this.dbVersionNumber = protoConnection.getServerVersion(); + this.compatible = info.getProperty("compatible", Driver.MAJORVERSION + "." + Driver.MINORVERSION); + + if (Driver.logDebug) { + Driver.debug(" compatible = " + compatible); + Driver.debug(" loglevel = " + logLevel); + Driver.debug(" prepare threshold = " + prepareThreshold); + } + + // Initialize common queries. + commitQuery = getQueryExecutor().createSimpleQuery("COMMIT"); + rollbackQuery = getQueryExecutor().createSimpleQuery("ROLLBACK"); + + // Initialize object handling + initObjectTypes(); } /* @@ -47,15 +153,191 @@ public abstract class AbstractJdbc2Connection extends org.postgresql.jdbc1.Abstr return typemap; } + // Query executor associated with this connection. + public QueryExecutor getQueryExecutor() { + return protoConnection.getQueryExecutor(); + } - public void setTypeMap(java.util.Map map) throws SQLException + /* + * This adds a warning to the warning chain. + * @param warn warning to add + */ + public void addWarning(SQLWarning warn) { - typemap = map; + // Add the warning to the chain + if (firstWarning != null) + firstWarning.setNextWarning(warn); + else + firstWarning = warn; + + } + + /** + * Simple query execution. + */ + public ResultSet execSQLQuery(String s) throws SQLException + { + Statement stat = (Statement) createStatement(); + boolean hasResultSet = stat.execute(s); + + // Transfer warnings to the connection, since the user never + // has a chance to see the statement itself. + SQLWarning warnings = stat.getWarnings(); + if (warnings != null) + addWarning(warnings); + + while (!hasResultSet && stat.getUpdateCount() != -1) + hasResultSet = stat.getMoreResults(); + + return stat.getResultSet(); + } + + public void execSQLUpdate(String s) throws SQLException + { + execSQLUpdate(s, 0); + } + + private void execSQLUpdate(String s, int flags) throws SQLException + { + BaseStatement stmt = (BaseStatement) createStatement(); + stmt.executeWithFlags(s, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | flags); + + // Transfer warnings to the connection, since the user never + // has a chance to see the statement itself. + SQLWarning warnings = stmt.getWarnings(); + if (warnings != null) + addWarning(warnings); + + stmt.close(); + } + + /* + * 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 do not support positioned update/delete, so this is a no-op. + * + * @param cursor the cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String cursor) throws SQLException + { + // No-op. + } + + /* + * getCursorName gets the cursor name. + * + * @return the current cursor name + * @exception SQLException if a database access error occurs + */ + public String getCursorName() throws SQLException + { + return null; + } + + /* + * 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 creatingURL; + } + + /* + * 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 protoConnection.getUser(); + } + + /* + * This returns the Fastpath API for the current connection. + * + * <p><b>NOTE:</b> This is not part of JDBC, but allows access to + * functions on the org.postgresql backend itself. + * + * <p>It is primarily used by the LargeObject API + * + * <p>The best way to use this is as follows: + * + * <p><pre> + * import org.postgresql.fastpath.*; + * ... + * Fastpath fp = ((org.postgresql.Connection)myconn).getFastpathAPI(); + * </pre> + * + * <p>where myconn is an open Connection to org.postgresql. + * + * @return Fastpath object allowing access to functions on the org.postgresql + * backend. + * @exception SQLException by Fastpath when initialising for first time + */ + public Fastpath getFastpathAPI() throws SQLException + { + if (fastpath == null) + fastpath = new Fastpath(this); + return fastpath; + } + + // This holds a reference to the Fastpath API if already open + private Fastpath fastpath = null; + + /* + * This returns the LargeObject API for the current connection. + * + * <p><b>NOTE:</b> This is not part of JDBC, but allows access to + * functions on the org.postgresql backend itself. + * + * <p>The best way to use this is as follows: + * + * <p><pre> + * import org.postgresql.largeobject.*; + * ... + * LargeObjectManager lo = ((org.postgresql.Connection)myconn).getLargeObjectAPI(); + * </pre> + * + * <p>where myconn is an open Connection to org.postgresql. + * + * @return LargeObject object that implements the API + * @exception SQLException by LargeObject when initialising for first time + */ + public LargeObjectManager getLargeObjectAPI() throws SQLException + { + if (largeobject == null) + largeobject = new LargeObjectManager(this); + return largeobject; } + // This holds a reference to the LargeObject API if already open + private LargeObjectManager largeobject = null; + /* - * This overides the standard internal getObject method so that we can - * check the jdbc2 type map first + * This method is used internally to return an object based around + * org.postgresql's more unique data types. + * + * <p>It uses an internal Hashtable to get the handling class. If the + * type is not supported, then an instance of org.postgresql.util.PGobject + * is returned. + * + * You can use the getValue() or setValue() methods to handle the returned + * object. Custom objects can have their own methods. + * + * @return PGobject for this type, and set to value + * @exception SQLException if value is not correct for this type */ public Object getObject(String type, String value) throws SQLException { @@ -69,10 +351,660 @@ public abstract class AbstractJdbc2Connection extends org.postgresql.jdbc1.Abstr } } - // Default to the original method - return super.getObject(type, value); + PGobject obj = null; + + if (Driver.logDebug) + Driver.debug("Constructing object from type=" + type + " value=<" + value + ">"); + + try + { + Class klass; + + synchronized (objectTypes) { + klass = (Class)objectTypes.get(type); + } + + // If className is not null, then try to instantiate it, + // It must be basetype PGobject + + // This is used to implement the org.postgresql unique types (like lseg, + // point, etc). + + if (klass != null) + { + obj = (PGobject) (klass.newInstance()); + obj.setType(type); + obj.setValue(value); + } + else + { + // If className is null, then the type is unknown. + // so return a PGobject with the type set, and the value set + obj = new PGobject(); + obj.setType( type ); + obj.setValue( value ); + } + + return obj; + } + catch (SQLException sx) + { + // rethrow the exception. Done because we capture any others next + throw sx; + } + catch (Exception ex) + { + throw new PSQLException("postgresql.con.creobj", PSQLState.CONNECTION_FAILURE, type, ex); + } + } + + public void addDataType(String type, String name) + { + try { + addDataType(type, Class.forName(name)); + } catch (Exception e) { + throw new RuntimeException("Cannot register new type: " + e); + } + } + + public void addDataType(String type, Class klass) throws SQLException + { + if (!org.postgresql.util.PGobject.class.isAssignableFrom(klass)) + throw new PSQLException("postgresql.adddatatype.notpgobject", PSQLState.INVALID_PARAMETER_TYPE, klass.toString()); + + synchronized (objectTypes) { + objectTypes.put(type, klass); + } + } + + // This holds the available types, a String to Class mapping. + private final HashMap objectTypes = new HashMap(); + + // This array contains the types that are supported as standard. + // + // The first entry is the types name on the database, the second + // the full class name of the handling class. + // + private static final Object[][] defaultObjectTypes = { + { "box", org.postgresql.geometric.PGbox.class }, + { "circle", org.postgresql.geometric.PGcircle.class }, + { "line", org.postgresql.geometric.PGline.class }, + { "lseg", org.postgresql.geometric.PGlseg.class }, + { "path", org.postgresql.geometric.PGpath.class }, + { "point", org.postgresql.geometric.PGpoint.class }, + { "polygon", org.postgresql.geometric.PGpolygon.class }, + { "money", org.postgresql.util.PGmoney.class }, + { "interval", org.postgresql.util.PGInterval.class } + }; + + // This initialises the objectTypes hashtable + private void initObjectTypes() throws SQLException + { + for (int i = 0; i < defaultObjectTypes.length; ++i) + addDataType((String) defaultObjectTypes[i][0], (Class) defaultObjectTypes[i][1]); + } + + /** + * 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. + * + * <B>Note:</B> 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() + { + protoConnection.close(); + } + + /* + * 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; + } + + /* + * The first warning reported by calls on this Connection is + * returned. + * + * <B>Note:</B> Sebsequent warnings will be changed to this + * SQLWarning + * + * @return the first SQLWarning or null + * @exception SQLException if a database access error occurs + */ + public synchronized SQLWarning getWarnings() throws SQLException + { + SQLWarning newWarnings = protoConnection.getWarnings(); // NB: also clears them. + if (firstWarning == null) + firstWarning = newWarnings; + else + firstWarning.setNextWarning(newWarnings); // Chain them on. + + return firstWarning; + } + + /* + * After this call, getWarnings returns null until a new warning + * is reported for this connection. + * + * @exception SQLException if a database access error occurs + */ + public synchronized void clearWarnings() throws SQLException + { + protoConnection.getWarnings(); // Clear and discard. + firstWarning = null; + } + + + /* + * You can put a connection in read-only mode as a hunt to enable + * database optimizations + * + * <B>Note:</B> 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 + { + if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) + throw new PSQLException("postgresql.con.changereadonly"); + + if (haveMinimumServerVersion("7.4") && readOnly != this.readOnly) { + String readOnlySql = "SET SESSION CHARACTERISTICS AS TRANSACTION " + (readOnly ? "READ ONLY" : "READ WRITE"); + execSQLUpdate(readOnlySql, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting + } + + this.readOnly = readOnly; + } + + /* + * Tests to see if the connection is in Read Only Mode. + * + * @return true if the connection is read only + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + return readOnly; } + /* + * 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 (!this.autoCommit) + commit(); + + this.autoCommit = autoCommit; + } + + /* + * gets the current auto-commit state + * + * @return Current state of the auto-commit mode + * @see setAutoCommit + */ + public boolean getAutoCommit() + { + return this.autoCommit; + } + + private void executeTransactionCommand(Query query) throws SQLException { + getQueryExecutor().execute(query, null, new TransactionCommandHandler(), + 0, 0, QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN); + } + + /* + * 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; + + if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) + executeTransactionCommand(commitQuery); + } + + /* + * 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; + + if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) + executeTransactionCommand(rollbackQuery); + } + + /* + * 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 + { + String level = null; + Statement stmt = createStatement(); + + if (haveMinimumServerVersion("7.3")) { + ResultSet rs = stmt.executeQuery("SHOW TRANSACTION ISOLATION LEVEL"); + if (rs.next()) + level = rs.getString(1); + + // Transfer warnings to the connection, since the user never + // has a chance to see the statement itself. + SQLWarning warnings = stmt.getWarnings(); + if (warnings != null) + addWarning(warnings); + } else { + stmt.executeUpdate("SHOW TRANSACTION ISOLATION LEVEL"); + SQLWarning warning = stmt.getWarnings(); + if (warning != null) + level = warning.getMessage(); + } + + stmt.close(); + + // XXX revisit: throw exception instead of silently eating the error in unkwon cases? + if (level == null) + return Connection.TRANSACTION_READ_COMMITTED; // Best guess. + + level = level.toUpperCase(); + if (level.indexOf("READ COMMITTED") != -1) + return Connection.TRANSACTION_READ_COMMITTED; + if (level.indexOf("READ UNCOMMITTED") != -1) + return Connection.TRANSACTION_READ_UNCOMMITTED; + if (level.indexOf("REPEATABLE READ") != -1) + return Connection.TRANSACTION_REPEATABLE_READ; + if (level.indexOf("SERIALIZABLE") != -1) + return Connection.TRANSACTION_SERIALIZABLE; + + return Connection.TRANSACTION_READ_COMMITTED; // Best guess. + } + + /* + * You can call this method to try to change the transaction + * isolation level using one of the TRANSACTION_* values. + * + * <B>Note:</B> 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 + { + if (protoConnection.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE) + throw new PSQLException("postgresql.con.changeisolevel"); + + String isolationLevelName = getIsolationLevelName(level); + if (isolationLevelName == null) + throw new PSQLException("postgresql.con.isolevel", PSQLState.TRANSACTION_STATE_INVALID, new Integer(level)); + + String isolationLevelSQL = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevelName; + execSQLUpdate(isolationLevelSQL, QueryExecutor.QUERY_SUPPRESS_BEGIN); // No BEGIN regardles of our autocommit setting + } + + protected String getIsolationLevelName(int level) + { + boolean pg75 = haveMinimumServerVersion("7.5"); + + if (level == Connection.TRANSACTION_READ_COMMITTED) { + return "READ COMMITTED"; + } else if (level == Connection.TRANSACTION_SERIALIZABLE) { + return "SERIALIZABLE"; + } else if (pg75 && level == Connection.TRANSACTION_READ_UNCOMMITTED) { + return "READ UNCOMMITTED"; + } else if (pg75 && level == Connection.TRANSACTION_REPEATABLE_READ) { + return "REPEATABLE READ"; + } + + return null; + } + + /* + * 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 protoConnection.getDatabase(); + } + + /* + * Overides finalize(). If called, it closes the connection. + * + * This was done at the request of Rachel Greenham + * <rachel@enlarion.demon.co.uk> who hit a problem where multiple + * clients didn't close the connection, and once a fortnight enough + * clients were open to kill the org.postgres server. + */ + public void finalize() throws Throwable + { + close(); + } + + /* + * Get server version number + */ + public String getDBVersionNumber() + { + return dbVersionNumber; + } + + // Parse a "dirty" integer surrounded by non-numeric characters + private static int integerPart(String dirtyString) + { + int start, end; + + for (start = 0; start < dirtyString.length() && !Character.isDigit(dirtyString.charAt(start)); ++start) + ; + + for (end = start; end < dirtyString.length() && Character.isDigit(dirtyString.charAt(end)); ++end) + ; + + if (start == end) + return 0; + + return Integer.parseInt(dirtyString.substring(start, end)); + } + + /* + * Get server major version + */ + public int getServerMajorVersion() + { + try + { + StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd + return integerPart(versionTokens.nextToken()); // return X + } + catch (NoSuchElementException e) + { + return 0; + } + } + + /* + * Get server minor version + */ + public int getServerMinorVersion() + { + try + { + StringTokenizer versionTokens = new StringTokenizer(dbVersionNumber, "."); // aaXbb.ccYdd + versionTokens.nextToken(); // Skip aaXbb + return integerPart(versionTokens.nextToken()); // return Y + } + catch (NoSuchElementException e) + { + return 0; + } + } + + /** + * Is the server we are connected to running at least this version? + * This comparison method will fail whenever a major or minor version + * goes to two digits (10.3.0) or (7.10.1). + */ + public boolean haveMinimumServerVersion(String ver) + { + return (dbVersionNumber.compareTo(ver) >= 0); + } + + /* + * This method returns true if the compatible level set in the connection + * (which can be passed into the connection or specified in the URL) + * is at least the value passed to this method. This is used to toggle + * between different functionality as it changes across different releases + * of the jdbc driver code. The values here are versions of the jdbc client + * and not server versions. For example in 7.1 get/setBytes worked on + * LargeObject values, in 7.2 these methods were changed to work on bytea + * values. This change in functionality could be disabled by setting the + * "compatible" level to be 7.1, in which case the driver will revert to + * the 7.1 functionality. + */ + public boolean haveMinimumCompatibleVersion(String ver) + { + return (compatible.compareTo(ver) >= 0); + } + + + public Encoding getEncoding() { + return protoConnection.getEncoding(); + } + + public byte[] encodeString(String str) throws SQLException { + try { + return getEncoding().encode(str); + } catch (IOException ioe) { + throw new PSQLException("postgresql.con.invalidchar", PSQLState.DATA_ERROR, ioe); + } + } + + /* + * This returns the java.sql.Types type for a PG type oid + * + * @param oid PostgreSQL type oid + * @return the java.sql.Types type + * @exception SQLException if a database access error occurs + */ + public int getSQLType(int oid) throws SQLException + { + return getSQLType(getPGType(oid)); + } + + /* + * This returns the oid for a given PG data type + * @param typeName PostgreSQL type name + * @return PostgreSQL oid value for a field of this type, or 0 if not found + */ + public int getPGType(String typeName) throws SQLException + { + if (typeName == null) + return 0; + + synchronized (this) { + Integer oidValue = (Integer) typeOidCache.get(typeName); + if (oidValue != null) + return oidValue.intValue(); + + // it's not in the cache, so perform a query, and add the result to the cache + int oid = 0; + + PreparedStatement query; + if (haveMinimumServerVersion("7.3")) + query = prepareStatement("SELECT oid FROM pg_catalog.pg_type WHERE typname=?"); + else + query = prepareStatement("SELECT oid FROM pg_type WHERE typname=?"); + + query.setString(1, typeName); + + BaseResultSet result = (BaseResultSet)query.executeQuery(); + if (result.next()) { + oid = result.getInt(1); + oidTypeCache.put(new Integer(oid), typeName); + } + + typeOidCache.put(typeName, new Integer(oid)); + result.close(); + return oid; + } + } + + /* + * We also need to get the PG type name as returned by the back end. + * + * @return the String representation of the type, or null if not fould + * @exception SQLException if a database access error occurs + */ + public String getPGType(int oid) throws SQLException + { + if (oid == 0) + return null; + + synchronized (this) { + String cachedValue = (String)oidTypeCache.get(new Integer(oid)); + if (cachedValue != null) + return cachedValue; + + // it's not in the cache, so perform a query, and add the result to the cache + String typeName = null; + + PreparedStatement query; + if (haveMinimumServerVersion("7.3")) + query = prepareStatement("SELECT typname FROM pg_catalog.pg_type WHERE oid=?"); + else + query = prepareStatement("SELECT typname FROM pg_type WHERE oid=?"); + + query.setInt(1, oid); + + ResultSet result = query.executeQuery(); + if (result.next()) { + typeName = result.getString(1); + typeOidCache.put(typeName, new Integer(oid)); + } + + oidTypeCache.put(new Integer(oid), typeName); + result.close(); + return typeName; + } + } + + // This is a cache of the DatabaseMetaData instance for this connection + protected java.sql.DatabaseMetaData metadata; + + /* + * Tests to see if a Connection is closed + * + * @return the status of the connection + * @exception SQLException (why?) + */ + public boolean isClosed() throws SQLException + { + return protoConnection.isClosed(); + } + + public void cancelQuery() throws SQLException + { + protoConnection.sendQueryCancel(); + } + + public PGNotification[] getNotifications() + { + // Backwards-compatibility hand-holding. + PGNotification[] notifications = protoConnection.getNotifications(); + return (notifications.length == 0 ? null : notifications); + } + + // + // Handler for transaction queries + // + private class TransactionCommandHandler implements ResultHandler { + private SQLException error; + + public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) {} + public void handleCommandStatus(String status, int updateCount, long insertOID) {} + + public void handleWarning(SQLWarning warning) { + AbstractJdbc2Connection.this.addWarning(warning); + } + + public void handleError(SQLException newError) { + if (error == null) + error = newError; + else + error.setNextException(newError); + } + + public void handleCompletion() throws SQLException { + if (error != null) + throw error; + } + } + + public int getPrepareThreshold() { + return prepareThreshold; + } + + public void setPrepareThreshold(int newThreshold) { + this.prepareThreshold = (newThreshold <= 0 ? 0 : newThreshold); + } + + + public void setTypeMap(java.util.Map map) throws SQLException + { + typemap = map; + } + + //Because the get/setLogStream methods are deprecated in JDBC2 //we use the get/setLogWriter methods here for JDBC2 by overriding @@ -88,8 +1020,7 @@ public abstract class AbstractJdbc2Connection extends org.postgresql.jdbc1.Abstr /* * This implemetation uses the jdbc2Types array to support the jdbc2 - * datatypes. Basically jdbc1 and jdbc2 are the same, except that - * jdbc2 adds the Array types. + * datatypes. */ public int getSQLType(String pgTypeName) { diff --git a/org/postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java b/org/postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java index 086c474f..6f9a1503 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java +++ b/org/postgresql/jdbc2/AbstractJdbc2DatabaseMetaData.java @@ -1,16 +1,3641 @@ package org.postgresql.jdbc2; -import java.sql.SQLException; +import java.sql.*; +import java.util.*; +import org.postgresql.core.*; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; +import org.postgresql.Driver; -public abstract class AbstractJdbc2DatabaseMetaData extends org.postgresql.jdbc1.AbstractJdbc1DatabaseMetaData +public abstract class AbstractJdbc2DatabaseMetaData { public AbstractJdbc2DatabaseMetaData(AbstractJdbc2Connection conn) { - super(conn); + this.connection = conn; } + private static final String keywords = "abort,acl,add,aggregate,append,archive," + + "arch_store,backward,binary,boolean,change,cluster," + + "copy,database,delimiter,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"; + + protected final AbstractJdbc2Connection connection; // The connection association + + protected static final int VARHDRSZ = 4; // length for int4 + + private int NAMEDATALEN = 0; // length for name datatype + private int INDEX_MAX_KEYS = 0; // maximum number of keys in an index. + + protected int getMaxIndexKeys() throws SQLException { + if (INDEX_MAX_KEYS == 0) { + String from; + if (connection.haveMinimumServerVersion("7.3")) { + from = "pg_catalog.pg_namespace n, pg_catalog.pg_type t1, pg_catalog.pg_type t2 WHERE t1.typnamespace=n.oid AND n.nspname='pg_catalog' AND "; + } else { + from = "pg_type t1, pg_type t2 WHERE "; + } + String sql = "SELECT t1.typlen/t2.typlen FROM "+from+" t1.typelem=t2.oid AND t1.typname='oidvector'"; + ResultSet rs = connection.createStatement().executeQuery(sql); + if (!rs.next()) { + throw new PSQLException("postgresql.unexpected", PSQLState.UNEXPECTED_ERROR); + } + INDEX_MAX_KEYS = rs.getInt(1); + rs.close(); + } + return INDEX_MAX_KEYS; + } + + protected int getMaxNameLength() throws SQLException { + if (NAMEDATALEN == 0) { + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT t.typlen FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE t.typnamespace=n.oid AND t.typname='name' AND n.nspname='pg_catalog'"; + } else { + sql = "SELECT typlen FROM pg_type WHERE typname='name'"; + } + ResultSet rs = connection.createStatement().executeQuery(sql); + if (!rs.next()) { + throw new PSQLException("postgresql.unexpected", PSQLState.UNEXPECTED_ERROR); + } + NAMEDATALEN = rs.getInt("typlen"); + rs.close(); + } + return NAMEDATALEN - 1; + } + + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("allProceduresAreCallable"); + 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 + { + if (Driver.logDebug) + Driver.debug("allTablesAreSelectable"); + 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 + { + String url = connection.getURL(); + if (Driver.logDebug) + Driver.debug("getURL " + url); + return url; + } + + /* + * 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 + { + String userName = connection.getUserName(); + if (Driver.logDebug) + Driver.debug("getUserName " + userName); + return userName; + } + + /* + * Is the database in read-only mode? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean isReadOnly() throws SQLException + { + boolean isReadOnly = connection.isReadOnly(); + if (Driver.logDebug) + Driver.debug("isReadOnly " + isReadOnly); + return isReadOnly; + } + + /* + * Are NULL values sorted high? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedHigh() throws SQLException + { + boolean nullSortedHigh = connection.haveMinimumServerVersion("7.2"); + if (Driver.logDebug) + Driver.debug("nullsAreSortedHigh " + nullSortedHigh); + return nullSortedHigh; + } + + /* + * Are NULL values sorted low? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean nullsAreSortedLow() throws SQLException + { + if (Driver.logDebug) + Driver.debug("nullsAreSortedLow false"); + 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 + { + if (Driver.logDebug) + Driver.debug("nullsAreSortedAtStart false"); + 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 + { + boolean nullsAreSortedAtEnd = ! connection.haveMinimumServerVersion("7.2"); + if (Driver.logDebug) + Driver.debug("nullsAreSortedAtEnd " + nullsAreSortedAtEnd); + return nullsAreSortedAtEnd; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("getDatabaseProductName PostgresSQL"); + return "PostgreSQL"; + } + + /* + * What is the version of this database product. + * + * @return the database version + * @exception SQLException if a database access error occurs + */ + public String getDatabaseProductVersion() throws SQLException + { + String versionNumber = connection.getDBVersionNumber(); + if (Driver.logDebug) + Driver.debug("getDatabaseProductVersion " + versionNumber); + return versionNumber; + } + + /* + * 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 + { + String driverName = "PostgreSQL Native Driver"; + if (Driver.logDebug) + Driver.debug("getDriverName" + driverName); + return driverName; + } + + /* + * 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 + { + String driverVersion = Driver.getVersion(); + if (Driver.logDebug) + Driver.debug("getDriverVersion " + driverVersion); + return driverVersion; + } + + /* + * What is this JDBC driver's major version number? + * + * @return the JDBC driver major version + */ + public int getDriverMajorVersion() + { + return Driver.MAJORVERSION; + } + + /* + * What is this JDBC driver's minor version number? + * + * @return the JDBC driver minor version + */ + public int getDriverMinorVersion() + { + return Driver.MINORVERSION; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("usesLocalFiles " + false); + 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 + { + if (Driver.logDebug) + Driver.debug("usesLocalFilePerTable " + false); + 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. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseIdentifiers() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsMixedCaseIdentifiers " + false); + return false; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("storesUpperCaseIdentifiers " + false); + 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 + { + if (Driver.logDebug) + Driver.debug("storesLowerCaseIdentifiers " + true); + return true; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("storesMixedCaseIdentifiers " + false); + 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. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsMixedCaseQuotedIdentifiers " + true); + 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 + { + if (Driver.logDebug) + Driver.debug("storesUpperCaseQuotedIdentifiers " + false); + 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 + { + if (Driver.logDebug) + Driver.debug("storesLowerCaseQuotedIdentifiers " + false); + 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 + { + if (Driver.logDebug) + Driver.debug("storesMixedCaseQuotedIdentifiers " + false); + 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. + * + * @return the quoting string + * @exception SQLException if a database access error occurs + */ + public String getIdentifierQuoteString() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getIdentifierQuoteString \"" ); + return "\""; + } + + /* + * Get a comma separated list of all a database's SQL keywords that + * are NOT also SQL92 keywords. + * + * <p>Within PostgreSQL, the keywords are found in + * src/backend/parser/keywords.c + * + * <p>For SQL Keywords, I took the list provided at + * <a href="http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt"> + * http://web.dementia.org/~shadow/sql/sql3bnf.sep93.txt</a> + * 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 keywords; + } + + public String getNumericFunctions() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getNumericFunctions"); + return ""; + } + + public String getStringFunctions() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getStringFunctions"); + return ""; + } + + public String getSystemFunctions() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getSystemFunctions"); + return ""; + } + + public String getTimeDateFunctions() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getTimeDateFunctions"); + return ""; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("getSearchStringEscape"); + return "\\"; + } + + /* + * Get all the "extra" characters that can be used in unquoted + * identifier names (those beyond a-zA-Z0-9 and _) + * + * <p>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 + { + if (Driver.logDebug) + Driver.debug("getExtraNameCharacters"); + return ""; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("supportsAlterTableWithAddColumn " + true); + return true; + } + + /* + * Is "ALTER TABLE" with a drop column supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsAlterTableWithDropColumn() throws SQLException + { + boolean dropColumn = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsAlterTableWithDropColumn " + dropColumn); + return dropColumn; + } + + /* + * Is column aliasing supported? + * + * <p>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. + * + * <p>e.g. + * + * <br><pre> + * select count(C) as C_COUNT from T group by C; + * + * </pre><br> + * 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 + { + if (Driver.logDebug) + Driver.debug("supportsColumnAliasing " + true); + 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 + { + if (Driver.logDebug) + Driver.debug("nullPlusNonNullIsNull " + true); + return true; + } + + public boolean supportsConvert() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsConvert " + false); + return false; + } + + public boolean supportsConvert(int fromType, int toType) throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsConvert " + false); + return false; + } + + /* + * Are table correlation names supported? A JDBC Compliant + * driver always returns true. + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsTableCorrelationNames() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsTableCorrelationNames " + true); + return true; + } + + /* + * If table correlation names are supported, are they restricted to + * be different from the names of the tables? + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsDifferentTableCorrelationNames() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsDifferentTableCorrelationNames " + false); + return false; + } + + /* + * Are expressions in "ORDER BY" lists supported? + * + * <br>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 + { + if (Driver.logDebug) + Driver.debug("supportsExpressionsInOrderBy " + true); + return true; + } + + /* + * Can an "ORDER BY" clause use columns not in the SELECT? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOrderByUnrelated() throws SQLException + { + boolean supportsOrderByUnrelated = connection.haveMinimumServerVersion("6.4"); + if (Driver.logDebug) + Driver.debug("supportsOrderByUnrelated " + supportsOrderByUnrelated); + return supportsOrderByUnrelated; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("supportsGroupBy " + true); + return true; + } + + /* + * Can a "GROUP BY" clause use columns not in the SELECT? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByUnrelated() throws SQLException + { + boolean supportsGroupByUnrelated = connection.haveMinimumServerVersion("6.4"); + if (Driver.logDebug) + Driver.debug("supportsGroupByUnrelated " + supportsGroupByUnrelated); + return supportsGroupByUnrelated; + } + + /* + * 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? + * + * (I think this is a subset of the previous function. -- petere) + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsGroupByBeyondSelect() throws SQLException + { + boolean supportsGroupByBeyondSelect = connection.haveMinimumServerVersion("6.4"); + if (Driver.logDebug) + Driver.debug("supportsGroupByUnrelated " + supportsGroupByBeyondSelect); + return supportsGroupByBeyondSelect; + } + + /* + * 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 + { + boolean supportsLikeEscapeClause = connection.haveMinimumServerVersion("7.1"); + if (Driver.logDebug) + Driver.debug("supportsLikeEscapeClause " + supportsLikeEscapeClause); + return supportsLikeEscapeClause; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("supportsMultipleResultSets " + false); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsMultipleTransactions " + true); + return true; + } + + /* + * Can columns be defined as non-nullable. A JDBC Compliant driver + * always returns true. + * + * <p>This changed from false to true in v6.2 of the driver, as this + * support was added to the backend. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsNonNullableColumns() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsNonNullableColumns true"); + return true; + } + + /* + * Does this driver support the minimum ODBC SQL grammar. This + * grammar is defined at: + * + * <p><a href="http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm">http://www.microsoft.com/msdn/sdk/platforms/doc/odbc/src/intropr.htm</a> + * + * <p>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 + { + if (Driver.logDebug) + Driver.debug("supportsMinimumSQLGrammar TRUE"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsCoreSQLGrammar FALSE "); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsExtendedSQLGrammar FALSE"); + return false; + } + + /* + * Does this driver support the ANSI-92 entry level SQL grammar? + * All JDBC Compliant drivers must return true. We currently + * report false until 'schema' support is added. Then this + * should be changed to return true, since we will be mostly + * compliant (probably more compliant than many other databases) + * And since this is a requirement for all JDBC drivers we + * need to get to the point where we can return true. + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92EntryLevelSQL() throws SQLException + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsANSI92EntryLevelSQL " + schemas); + return schemas; + } + + /* + * Does this driver support the ANSI-92 intermediate level SQL + * grammar? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsANSI92IntermediateSQL() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsANSI92IntermediateSQL false "); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsANSI92FullSQL false "); + return false; + } + + /* + * Is the SQL Integrity Enhancement Facility supported? + * Our best guess is that this means support for constraints + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsIntegrityEnhancementFacility() throws SQLException + { + if (Driver.logDebug) + Driver.debug("supportsIntegrityEnhancementFacility true "); + return true; + } + + /* + * Is some form of outer join supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsOuterJoins() throws SQLException + { + boolean supportsOuterJoins = connection.haveMinimumServerVersion("7.1"); + if (Driver.logDebug) + Driver.debug("supportsOuterJoins " + supportsOuterJoins); + return supportsOuterJoins; + } + + /* + * Are full nexted outer joins supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsFullOuterJoins() throws SQLException + { + boolean supportsFullOuterJoins = connection.haveMinimumServerVersion("7.1"); + if (Driver.logDebug) + Driver.debug("supportsFullOuterJoins " + supportsFullOuterJoins); + return supportsFullOuterJoins; + } + + /* + * Is there limited support for outer joins? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsLimitedOuterJoins() throws SQLException + { + boolean supportsLimitedOuterJoins = connection.haveMinimumServerVersion("7.1"); + if (Driver.logDebug) + Driver.debug("supportsFullOuterJoins " + supportsLimitedOuterJoins); + return supportsLimitedOuterJoins; + } + + /* + * What is the database vendor's preferred term for "schema"? + * PostgreSQL doesn't have schemas, but when it does, we'll use the + * term "schema". + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getSchemaTerm() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getSchemaTerm schema"); + return "schema"; + } + + /* + * What is the database vendor's preferred term for "procedure"? + * Traditionally, "function" has been used. + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getProcedureTerm() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getProcedureTerm function "); + return "function"; + } + + /* + * What is the database vendor's preferred term for "catalog"? + * + * @return the vendor term + * @exception SQLException if a database access error occurs + */ + public String getCatalogTerm() throws SQLException + { + if (Driver.logDebug) + Driver.debug("getCatalogTerm database "); + return "database"; + } + + /* + * 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 true here; we return false for every other catalog function + // so it won't matter what we return here D.C. + if (Driver.logDebug) + Driver.debug("isCatalogAtStart not implemented"); + return true; + } + + /* + * What is the Catalog separator. + * + * @return the catalog separator string + * @exception SQLException if a database access error occurs + */ + public String getCatalogSeparator() throws SQLException + { + // Give them something to work with here + // everything else returns false so it won't matter what we return here D.C. + if (Driver.logDebug) + Driver.debug("getCatalogSeparator not implemented "); + return "."; + } + + /* + * Can a schema name be used in a data manipulation statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInDataManipulation() throws SQLException + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsSchemasInDataManipulation "+schemas); + return schemas; + } + + /* + * Can a schema name be used in a procedure call statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInProcedureCalls() throws SQLException + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsSchemasInProcedureCalls "+schemas); + return schemas; + } + + /* + * Can a schema be used in a table definition statement? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsSchemasInTableDefinitions() throws SQLException + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + + if (Driver.logDebug) + Driver.debug("supportsSchemasInTableDefinitions " + schemas); + return schemas; + } + + /* + * 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 + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsSchemasInIndexDefinitions "+schemas); + return schemas; + } + + /* + * 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 + { + boolean schemas = connection.haveMinimumServerVersion("7.3"); + if (Driver.logDebug) + Driver.debug("supportsSchemasInPrivilegeDefinitions "+schemas); + return schemas; + } + + /* + * 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 + { + if (Driver.logDebug) + Driver.debug("supportsCatalogsInDataManipulation false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsCatalogsInDataManipulation false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsCatalogsInTableDefinitions false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsCatalogsInIndexDefinitions false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsCatalogsInPrivilegeDefinitions false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsPositionedDelete false"); + 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 + { + if (Driver.logDebug) + Driver.debug("supportsPositionedUpdate false"); + return false; // For now... + } + + /* + * Is SELECT for UPDATE supported? + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsSelectForUpdate() throws SQLException + { + return connection.haveMinimumServerVersion("6.5"); + } + + /* + * Are stored procedure calls using the stored procedure escape + * syntax supported? + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsStoredProcedures() throws SQLException + { + return false; + } + + /* + * Are subqueries in comparison expressions supported? A JDBC + * Compliant driver always returns true. + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsSubqueriesInComparisons() throws SQLException + { + return true; + } + + /* + * Are subqueries in 'exists' expressions supported? A JDBC + * Compliant driver always returns true. + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsSubqueriesInExists() throws SQLException + { + return true; + } + + /* + * Are subqueries in 'in' statements supported? A JDBC + * Compliant driver always returns true. + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsSubqueriesInIns() throws SQLException + { + return true; + } + + /* + * Are subqueries in quantified expressions supported? A JDBC + * Compliant driver always returns true. + * + * (No idea what this is, but we support a good deal of + * subquerying.) + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsSubqueriesInQuantifieds() throws SQLException + { + return true; + } + + /* + * Are correlated subqueries supported? A JDBC Compliant driver + * always returns true. + * + * (a.k.a. subselect in from?) + * + * @return true if so; false otherwise + * @exception SQLException - if a database access error occurs + */ + public boolean supportsCorrelatedSubqueries() throws SQLException + { + return connection.haveMinimumServerVersion("7.1"); + } + + /* + * Is SQL UNION supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnion() throws SQLException + { + return true; // since 6.3 + } + + /* + * Is SQL UNION ALL supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsUnionAll() throws SQLException + { + return connection.haveMinimumServerVersion("7.1"); + } + + /* + * 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 here, 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; // no limit + } + + /* + * 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 0; // no limit + } + + /* + * Whats the limit on column name length. + * + * @return the maximum column name length + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnNameLength() throws SQLException + { + return getMaxNameLength(); + } + + /* + * 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 0; // no limit + } + + /* + * What's the maximum number of columns allowed in an index? + * + * @return max number of columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInIndex() throws SQLException + { + return getMaxIndexKeys(); + } + + /* + * What's the maximum number of columns in an "ORDER BY clause? + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInOrderBy() throws SQLException + { + return 0; // no limit + } + + /* + * What is the maximum number of columns in a "SELECT" list? + * + * @return the max columns + * @exception SQLException if a database access error occurs + */ + public int getMaxColumnsInSelect() throws SQLException + { + return 0; // no limit + } + + /* + * What is the maximum number of columns in a table? From the + * CREATE TABLE reference page... + * + * <p>"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 + * + * @return max cursor name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxCursorNameLength() throws SQLException + { + return getMaxNameLength(); + } + + /* + * Retrieves the maximum number of bytes for an index, including all + * of the parts of the index. + * + * @return max index length in bytes, which includes the composite + * of all the constituent parts of the index; a result of zero means + * that there is no limit or the limit is not known + * @exception SQLException if a database access error occurs + */ + public int getMaxIndexLength() throws SQLException + { + return 0; // no limit (larger than an int anyway) + } + + public int getMaxSchemaNameLength() throws SQLException + { + return getMaxNameLength(); + } + + /* + * What is the maximum length of a procedure name + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxProcedureNameLength() throws SQLException + { + return getMaxNameLength(); + } + + public int getMaxCatalogNameLength() throws SQLException + { + return getMaxNameLength(); + } + + /* + * What is the maximum length of a single row? + * + * @return max row size in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxRowSize() throws SQLException + { + if (connection.haveMinimumServerVersion("7.1")) + return 1073741824; // 1 GB + else + return 8192; // XXX could be altered + } + + /* + * 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 + { + if (connection.haveMinimumServerVersion("7.0")) + return 0; // actually whatever fits in size_t + else + return 16384; + } + + /* + * 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 + * + * @return max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxTableNameLength() throws SQLException + { + return getMaxNameLength(); + } + + /* + * What is the maximum number of tables that can be specified + * in a SELECT? + * + * @return the maximum + * @exception SQLException if a database access error occurs + */ + public int getMaxTablesInSelect() throws SQLException + { + return 0; // no limit + } + + /* + * What is the maximum length of a user name + * + * @return the max name length in bytes + * @exception SQLException if a database access error occurs + */ + public int getMaxUserNameLength() throws SQLException + { + return getMaxNameLength(); + } + + + /* + * 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_READ_COMMITTED; + } + + /* + * 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 and TRANSACTION_READ_COMMITTED + * + * @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 || + level == Connection.TRANSACTION_READ_COMMITTED) + return true; + else if (connection.haveMinimumServerVersion("7.5") && (level == Connection.TRANSACTION_READ_UNCOMMITTED || level == Connection.TRANSACTION_REPEATABLE_READ)) + return true; + else + return false; + } + + /* + * Are both data definition and data manipulation transactions + * supported? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException + { + return true; + } + + /* + * 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 false; + } + + /* + * Does a data definition statement within a transaction force + * the transaction to commit? I think this means something like: + * + * <p><pre> + * 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; + * </pre><p> + * + * 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? + * + * @return true if so + * @exception SQLException if a database access error occurs + */ + public boolean dataDefinitionIgnoredInTransactions() throws SQLException + { + return false; + } + + /** + * Escape single quotes with another single quote. + */ + protected static String escapeQuotes(String s) { + StringBuffer sb = new StringBuffer(); + int length = s.length(); + char prevChar = ' '; + char prevPrevChar = ' '; + for (int i=0; i<length; i++) { + char c = s.charAt(i); + sb.append(c); + if (c == '\'' && (prevChar != '\\' || (prevChar == '\\' && prevPrevChar == '\\'))) { + sb.append("'"); + } + prevPrevChar = prevChar; + prevChar = c; + } + return sb.toString(); + } + + /* + * Get a description of stored procedures available in a catalog + * + * <p>Only procedure descriptions matching the schema and procedure + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME + * + * <p>Each procedure description has the following columns: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHEM</b> String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>Field 4</b> reserved (make it null) + * <li><b>Field 5</b> reserved (make it null) + * <li><b>Field 6</b> reserved (make it null) + * <li><b>REMARKS</b> String => explanatory comment on the procedure + * <li><b>PROCEDURE_TYPE</b> short => kind of procedure + * <ul> + * <li> procedureResultUnknown - May return a result + * <li> procedureNoResult - Does not return a result + * <li> procedureReturnsResult - Returns a result + * </ul> + * </ol> + * + * @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 + { + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT NULL AS PROCEDURE_CAT, n.nspname AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, d.description AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_proc p "+ + " LEFT JOIN pg_catalog.pg_description d ON (p.oid=d.objoid) "+ + " LEFT JOIN pg_catalog.pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') "+ + " LEFT JOIN pg_catalog.pg_namespace pn ON (c.relnamespace=pn.oid AND pn.nspname='pg_catalog') "+ + " WHERE p.pronamespace=n.oid "; + if (schemaPattern != null && !"".equals(schemaPattern)) { + sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; + } + if (procedureNamePattern != null) { + sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; + } + sql += " ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME "; + } else if (connection.haveMinimumServerVersion("7.1")) { + sql = "SELECT NULL AS PROCEDURE_CAT, NULL AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, d.description AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ + " FROM pg_proc p "+ + " LEFT JOIN pg_description d ON (p.oid=d.objoid) "; + if (connection.haveMinimumServerVersion("7.2")) { + sql += " LEFT JOIN pg_class c ON (d.classoid=c.oid AND c.relname='pg_proc') "; + } + if (procedureNamePattern != null) { + sql += " WHERE p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; + } + sql += " ORDER BY PROCEDURE_NAME "; + } else { + sql = "SELECT NULL AS PROCEDURE_CAT, NULL AS PROCEDURE_SCHEM, p.proname AS PROCEDURE_NAME, NULL, NULL, NULL, NULL AS REMARKS, "+java.sql.DatabaseMetaData.procedureReturnsResult+" AS PROCEDURE_TYPE "+ + " FROM pg_proc p "; + if (procedureNamePattern != null) { + sql += " WHERE p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; + } + sql += " ORDER BY PROCEDURE_NAME "; + } + return createMetaDataStatement().executeQuery(sql); + } + + /* + * Get a description of a catalog's stored procedure parameters + * and result columns. + * + * <p>Only descriptions matching the schema, procedure and parameter + * name criteria are returned. They are ordered by PROCEDURE_SCHEM + * and PROCEDURE_NAME. Within this, the return value, if any, is + * first. Next are the parameter descriptions in call order. The + * column descriptions follow in column number order. + * + * <p>Each row in the ResultSet is a parameter description or column + * description with the following fields: + * <ol> + * <li><b>PROCEDURE_CAT</b> String => procedure catalog (may be null) + * <li><b>PROCEDURE_SCHE</b>M String => procedure schema (may be null) + * <li><b>PROCEDURE_NAME</b> String => procedure name + * <li><b>COLUMN_NAME</b> String => column/parameter name + * <li><b>COLUMN_TYPE</b> Short => kind of column/parameter: + * <ul><li>procedureColumnUnknown - nobody knows + * <li>procedureColumnIn - IN parameter + * <li>procedureColumnInOut - INOUT parameter + * <li>procedureColumnOut - OUT parameter + * <li>procedureColumnReturn - procedure return value + * <li>procedureColumnResult - result column in ResultSet + * </ul> + * <li><b>DATA_TYPE</b> short => SQL type from java.sql.Types + * <li><b>TYPE_NAME</b> String => Data source specific type name + * <li><b>PRECISION</b> int => precision + * <li><b>LENGTH</b> int => length in bytes of data + * <li><b>SCALE</b> short => scale + * <li><b>RADIX</b> short => radix + * <li><b>NULLABLE</b> short => can it contain NULL? + * <ul><li>procedureNoNulls - does not allow NULL values + * <li>procedureNullable - allows NULL values + * <li>procedureNullableUnknown - nullability unknown + * <li><b>REMARKS</b> String => comment describing parameter/column + * </ol> + * @param catalog This is ignored in org.postgresql, advise this is set to null + * @param schemaPattern + * @param procedureNamePattern a procedure name pattern + * @param columnNamePattern a column name pattern, this is currently ignored because postgresql does not name procedure parameters. + * @return each row is a stored procedure parameter or column description + * @exception SQLException if a database-access error occurs + * @see #getSearchStringEscape + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException + { + Field f[] = new Field[13]; + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field("PROCEDURE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("PROCEDURE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("PROCEDURE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("COLUMN_TYPE", Oid.INT2, 2); + f[5] = new Field("DATA_TYPE", Oid.INT2, 2); + f[6] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[7] = new Field("PRECISION", Oid.INT4, 4); + f[8] = new Field("LENGTH", Oid.INT4, 4); + f[9] = new Field("SCALE", Oid.INT2, 2); + f[10] = new Field("RADIX", Oid.INT2, 2); + f[11] = new Field("NULLABLE", Oid.INT2, 2); + f[12] = new Field("REMARKS", Oid.VARCHAR, getMaxNameLength()); + + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT n.nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid "+ + " FROM pg_catalog.pg_proc p,pg_catalog.pg_namespace n, pg_catalog.pg_type t "+ + " WHERE p.pronamespace=n.oid AND p.prorettype=t.oid "; + if (schemaPattern != null && !"".equals(schemaPattern)) { + sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; + } + if (procedureNamePattern != null) { + sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; + } + sql += " ORDER BY n.nspname, p.proname "; + } else { + sql = "SELECT NULL AS nspname,p.proname,p.prorettype,p.proargtypes, t.typtype,t.typrelid "+ + " FROM pg_proc p,pg_type t "+ + " WHERE p.prorettype=t.oid "; + if (procedureNamePattern != null) { + sql += " AND p.proname LIKE '"+escapeQuotes(procedureNamePattern)+"' "; + } + sql += " ORDER BY p.proname "; + } + + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + byte schema[] = rs.getBytes("nspname"); + byte procedureName[] = rs.getBytes("proname"); + int returnType = rs.getInt("prorettype"); + String returnTypeType = rs.getString("typtype"); + int returnTypeRelid = rs.getInt("typrelid"); + String strArgTypes = rs.getString("proargtypes"); + StringTokenizer st = new StringTokenizer(strArgTypes); + Vector argTypes = new Vector(); + while (st.hasMoreTokens()) { + argTypes.addElement(new Integer(st.nextToken())); + } + + // decide if we are returning a single column result. + if (!returnTypeType.equals("c")) { + byte[][] tuple = new byte[13][]; + tuple[0] = null; + tuple[1] = schema; + tuple[2] = procedureName; + tuple[3] = connection.encodeString("returnValue"); + tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnReturn)); + tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(returnType))); + tuple[6] = connection.encodeString(connection.getPGType(returnType)); + tuple[7] = null; + tuple[8] = null; + tuple[9] = null; + tuple[10] = null; + tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); + tuple[12] = null; + v.addElement(tuple); + } + + // Add a row for each argument. + for (int i=0; i<argTypes.size(); i++) { + int argOid = ((Integer)argTypes.elementAt(i)).intValue(); + byte[][] tuple = new byte[13][]; + tuple[0] = null; + tuple[1] = schema; + tuple[2] = procedureName; + tuple[3] = connection.encodeString("$"+(i+1)); + tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnIn)); + tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(argOid))); + tuple[6] = connection.encodeString(connection.getPGType(argOid)); + tuple[7] = null; + tuple[8] = null; + tuple[9] = null; + tuple[10] = null; + tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); + tuple[12] = null; + v.addElement(tuple); + } + + // if we are returning a multi-column result. + if (returnTypeType.equals("c")) { + String columnsql = "SELECT a.attname,a.atttypid FROM pg_catalog.pg_attribute a WHERE a.attrelid = "+returnTypeRelid+" ORDER BY a.attnum "; + ResultSet columnrs = connection.createStatement().executeQuery(columnsql); + while (columnrs.next()) { + int columnTypeOid = columnrs.getInt("atttypid"); + byte[][] tuple = new byte[13][]; + tuple[0] = null; + tuple[1] = schema; + tuple[2] = procedureName; + tuple[3] = columnrs.getBytes("attname"); + tuple[4] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureColumnResult)); + tuple[5] = connection.encodeString(Integer.toString(connection.getSQLType(columnTypeOid))); + tuple[6] = connection.encodeString(connection.getPGType(columnTypeOid)); + tuple[7] = null; + tuple[8] = null; + tuple[9] = null; + tuple[10] = null; + tuple[11] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.procedureNullableUnknown)); + tuple[12] = null; + v.addElement(tuple); + } + } + } + rs.close(); + + return (ResultSet)((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of tables available in a catalog. + * + * <p>Only table descriptions matching the catalog, schema, table + * name and type criteria are returned. They are ordered by + * TABLE_TYPE, TABLE_SCHEM and TABLE_NAME. + * + * <p>Each table description has the following columns: + * + * <ol> + * <li><b>TABLE_CAT</b> String => table catalog (may be null) + * <li><b>TABLE_SCHEM</b> String => table schema (may be null) + * <li><b>TABLE_NAME</b> String => table name + * <li><b>TABLE_TYPE</b> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL + * TEMPORARY", "ALIAS", "SYNONYM". + * <li><b>REMARKS</b> String => explanatory comment on the table + * </ol> + * + * <p>The valid values for the types parameter are: + * "TABLE", "INDEX", "SEQUENCE", "VIEW", + * "SYSTEM TABLE", "SYSTEM INDEX", "SYSTEM VIEW", + * "SYSTEM TOAST TABLE", "SYSTEM TOAST INDEX", + * "TEMPORARY TABLE", and "TEMPORARY VIEW" + * + * @param catalog a catalog name; For org.postgresql, this is ignored, and + * should be set to null + * @param schemaPattern a schema name pattern + * @param tableNamePattern a table name pattern. For all tables this should be "%" + * @param types a list of table types to include; null returns + * all types + * @return each row is a table description + * @exception SQLException if a database-access error occurs. + */ + public java.sql.ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) throws SQLException + { + String select; + String orderby; + String useSchemas; + if (connection.haveMinimumServerVersion("7.3")) { + useSchemas = "SCHEMAS"; + select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+ + " CASE n.nspname LIKE 'pg\\\\_%' OR n.nspname = 'information_schema' "+ + " WHEN true THEN CASE "+ + " WHEN n.nspname = 'pg_catalog' OR n.nspname = 'information_schema' THEN CASE c.relkind "+ + " WHEN 'r' THEN 'SYSTEM TABLE' "+ + " WHEN 'v' THEN 'SYSTEM VIEW' "+ + " WHEN 'i' THEN 'SYSTEM INDEX' "+ + " ELSE NULL "+ + " END "+ + " WHEN n.nspname = 'pg_toast' THEN CASE c.relkind "+ + " WHEN 'r' THEN 'SYSTEM TOAST TABLE' "+ + " WHEN 'i' THEN 'SYSTEM TOAST INDEX' "+ + " ELSE NULL "+ + " END "+ + " ELSE CASE c.relkind "+ + " WHEN 'r' THEN 'TEMPORARY TABLE' "+ + " WHEN 'i' THEN 'TEMPORARY INDEX' "+ + " ELSE NULL "+ + " END "+ + " END "+ + " WHEN false THEN CASE c.relkind "+ + " WHEN 'r' THEN 'TABLE' "+ + " WHEN 'i' THEN 'INDEX' "+ + " WHEN 'S' THEN 'SEQUENCE' "+ + " WHEN 'v' THEN 'VIEW' "+ + " ELSE NULL "+ + " END "+ + " ELSE NULL "+ + " END "+ + " AS TABLE_TYPE, d.description AS REMARKS "+ + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c "+ + " LEFT JOIN pg_catalog.pg_description d ON (c.oid = d.objoid AND d.objsubid = 0) "+ + " LEFT JOIN pg_catalog.pg_class dc ON (d.classoid=dc.oid AND dc.relname='pg_class') "+ + " LEFT JOIN pg_catalog.pg_namespace dn ON (dn.oid=dc.relnamespace AND dn.nspname='pg_catalog') "+ + " WHERE c.relnamespace = n.oid "; + if (schemaPattern != null && !"".equals(schemaPattern)) { + select += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; + } + orderby = " ORDER BY TABLE_TYPE,TABLE_SCHEM,TABLE_NAME "; + } else { + useSchemas = "NOSCHEMAS"; + String tableType = ""+ + " CASE c.relname LIKE 'pg\\\\_%' "+ + " WHEN true THEN CASE c.relname LIKE 'pg\\\\_toast\\\\_%' "+ + " WHEN true THEN CASE c.relkind "+ + " WHEN 'r' THEN 'SYSTEM TOAST TABLE' "+ + " WHEN 'i' THEN 'SYSTEM TOAST INDEX' "+ + " ELSE NULL "+ + " END "+ + " WHEN false THEN CASE c.relname LIKE 'pg\\\\_temp\\\\_%' "+ + " WHEN true THEN CASE c.relkind "+ + " WHEN 'r' THEN 'TEMPORARY TABLE' "+ + " WHEN 'i' THEN 'TEMPORARY INDEX' "+ + " ELSE NULL "+ + " END "+ + " WHEN false THEN CASE c.relkind "+ + " WHEN 'r' THEN 'SYSTEM TABLE' "+ + " WHEN 'v' THEN 'SYSTEM VIEW' "+ + " WHEN 'i' THEN 'SYSTEM INDEX' "+ + " ELSE NULL "+ + " END "+ + " ELSE NULL "+ + " END "+ + " ELSE NULL "+ + " END "+ + " WHEN false THEN CASE c.relkind "+ + " WHEN 'r' THEN 'TABLE' "+ + " WHEN 'i' THEN 'INDEX' "+ + " WHEN 'S' THEN 'SEQUENCE' "+ + " WHEN 'v' THEN 'VIEW' "+ + " ELSE NULL "+ + " END "+ + " ELSE NULL "+ + " END "; + orderby = " ORDER BY TABLE_TYPE,TABLE_NAME "; + if (connection.haveMinimumServerVersion("7.2")) { + select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, d.description AS REMARKS "+ + " FROM pg_class c "+ + " LEFT JOIN pg_description d ON (c.oid=d.objoid AND d.objsubid = 0) "+ + " LEFT JOIN pg_class dc ON (d.classoid = dc.oid AND dc.relname='pg_class') "+ + " WHERE true "; + } else if (connection.haveMinimumServerVersion("7.1")) { + select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, d.description AS REMARKS "+ + " FROM pg_class c "+ + " LEFT JOIN pg_description d ON (c.oid=d.objoid) "+ + " WHERE true "; + } else { + select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, c.relname AS TABLE_NAME, "+tableType+" AS TABLE_TYPE, NULL AS REMARKS "+ + " FROM pg_class c "+ + " WHERE true "; + } + } + + if (types == null) { + types = defaultTableTypes; + } + if (tableNamePattern != null) { + select += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; + } + String sql = select; + sql += " AND (false "; + for (int i=0; i<types.length; i++) { + Hashtable clauses = (Hashtable)tableTypeClauses.get(types[i]); + if (clauses != null) { + String clause = (String)clauses.get(useSchemas); + sql += " OR ( "+clause+" ) "; + } + } + sql += ") "; + sql += orderby; + + return createMetaDataStatement().executeQuery(sql); + } + + private static final Hashtable tableTypeClauses; + static { + tableTypeClauses = new Hashtable(); + Hashtable ht = new Hashtable(); + tableTypeClauses.put("TABLE",ht); + ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname NOT LIKE 'pg\\\\_%' AND n.nspname <> 'information_schema'"); + ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname NOT LIKE 'pg\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("VIEW",ht); + ht.put("SCHEMAS","c.relkind = 'v' AND n.nspname <> 'pg_catalog' AND n.nspname <> 'information_schema'"); + ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname NOT LIKE 'pg\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("INDEX",ht); + ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname NOT LIKE 'pg\\\\_%' AND n.nspname <> 'information_schema'"); + ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname NOT LIKE 'pg\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("SEQUENCE",ht); + ht.put("SCHEMAS","c.relkind = 'S'"); + ht.put("NOSCHEMAS","c.relkind = 'S'"); + ht = new Hashtable(); + tableTypeClauses.put("SYSTEM TABLE",ht); + ht.put("SCHEMAS","c.relkind = 'r' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema')"); + ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_%' AND c.relname NOT LIKE 'pg\\\\_toast\\\\_%' AND c.relname NOT LIKE 'pg\\\\_temp\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("SYSTEM TOAST TABLE",ht); + ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname = 'pg_toast'"); + ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_toast\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("SYSTEM TOAST INDEX",ht); + ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname = 'pg_toast'"); + ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname LIKE 'pg\\\\_toast\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("SYSTEM VIEW",ht); + ht.put("SCHEMAS","c.relkind = 'v' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); + ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname LIKE 'pg\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("SYSTEM INDEX",ht); + ht.put("SCHEMAS","c.relkind = 'i' AND (n.nspname = 'pg_catalog' OR n.nspname = 'information_schema') "); + ht.put("NOSCHEMAS","c.relkind = 'v' AND c.relname LIKE 'pg\\\\_%' AND c.relname NOT LIKE 'pg\\\\_toast\\\\_%' AND c.relname NOT LIKE 'pg\\\\_temp\\\\_%'"); + ht = new Hashtable(); + tableTypeClauses.put("TEMPORARY TABLE",ht); + ht.put("SCHEMAS","c.relkind = 'r' AND n.nspname LIKE 'pg\\\\_temp\\\\_%' "); + ht.put("NOSCHEMAS","c.relkind = 'r' AND c.relname LIKE 'pg\\\\_temp\\\\_%' "); + ht = new Hashtable(); + tableTypeClauses.put("TEMPORARY INDEX",ht); + ht.put("SCHEMAS","c.relkind = 'i' AND n.nspname LIKE 'pg\\\\_temp\\\\_%' "); + ht.put("NOSCHEMAS","c.relkind = 'i' AND c.relname LIKE 'pg\\\\_temp\\\\_%' "); + } + + // These are the default tables, used when NULL is passed to getTables + // The choice of these provide the same behaviour as psql's \d + private static final String defaultTableTypes[] = { + "TABLE", "VIEW", "INDEX", "SEQUENCE", "TEMPORARY TABLE" + }; + + /* + * Get the schema names available in this database. The results + * are ordered by schema name. + * + * <P>The schema column is: + * <OL> + * <LI><B>TABLE_SCHEM</B> String => schema name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * schema name + */ + public java.sql.ResultSet getSchemas() throws SQLException + { + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT nspname AS TABLE_SCHEM FROM pg_catalog.pg_namespace WHERE nspname <> 'pg_toast' AND nspname NOT LIKE 'pg\\\\_temp\\\\_%' ORDER BY TABLE_SCHEM"; + } else { + sql = "SELECT ''::text AS TABLE_SCHEM ORDER BY TABLE_SCHEM"; + } + return createMetaDataStatement().executeQuery(sql); + } + + /* + * Get the catalog names available in this database. The results + * are ordered by catalog name. + * + * <P>The catalog column is: + * <OL> + * <LI><B>TABLE_CAT</B> String => catalog name + * </OL> + * + * @return ResultSet each row has a single String column that is a + * catalog name + */ + public java.sql.ResultSet getCatalogs() throws SQLException + { + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT datname AS TABLE_CAT FROM pg_catalog.pg_database ORDER BY TABLE_CAT"; + } else { + sql = "SELECT datname AS TABLE_CAT FROM pg_database ORDER BY TABLE_CAT"; + } + return createMetaDataStatement().executeQuery(sql); + } + + /* + * Get the table types available in this database. The results + * are ordered by table type. + * + * <P>The table type is: + * <OL> + * <LI><B>TABLE_TYPE</B> String => table type. Typical types are "TABLE", + * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", + * "LOCAL TEMPORARY", "ALIAS", "SYNONYM". + * </OL> + * + * @return ResultSet each row has a single String column that is a + * table type + */ + public java.sql.ResultSet getTableTypes() throws SQLException + { + String types[] = new String[tableTypeClauses.size()]; + Enumeration e = tableTypeClauses.keys(); + int i=0; + while (e.hasMoreElements()) { + types[i++] = (String)e.nextElement();} + sortStringArray(types); + + Field f[] = new Field[1]; + Vector v = new Vector(); + f[0] = new Field(new String("TABLE_TYPE"), Oid.VARCHAR, getMaxNameLength()); + for (i=0; i < types.length; i++) + { + byte[][] tuple = new byte[1][]; + tuple[0] = connection.encodeString(types[i]); + v.addElement(tuple); + } + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of table columns available in a catalog. + * + * <P>Only column descriptions matching the catalog, schema, table + * and column name criteria are returned. They are ordered by + * TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => column size. For char or date + * types this is the maximum number of characters, for numeric or + * decimal types this is precision. + * <LI><B>BUFFER_LENGTH</B> is not used. + * <LI><B>DECIMAL_DIGITS</B> int => the number of fractional digits + * <LI><B>NUM_PREC_RADIX</B> int => Radix (typically either 10 or 2) + * <LI><B>NULLABLE</B> int => is NULL allowed? + * <UL> + * <LI> columnNoNulls - might not allow NULL values + * <LI> columnNullable - definitely allows NULL values + * <LI> columnNullableUnknown - nullability unknown + * </UL> + * <LI><B>REMARKS</B> String => comment describing column (may be null) + * <LI><B>COLUMN_DEF</B> String => default value (may be null) + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>CHAR_OCTET_LENGTH</B> int => for char types the + * maximum number of bytes in the column + * <LI><B>ORDINAL_POSITION</B> int => index of column in table + * (starting at 1) + * <LI><B>IS_NULLABLE</B> String => "NO" means column definitely + * does not allow NULL values; "YES" means the column might + * allow NULL values. An empty string means nobody knows. + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException + { + Vector v = new Vector(); // The new ResultSet tuple stuff + Field f[] = new Field[18]; // The field descriptors for the new ResultSet + + f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("DATA_TYPE", Oid.INT2, 2); + f[5] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[6] = new Field("COLUMN_SIZE", Oid.INT4, 4); + f[7] = new Field("BUFFER_LENGTH", Oid.VARCHAR, getMaxNameLength()); + f[8] = new Field("DECIMAL_DIGITS", Oid.INT4, 4); + f[9] = new Field("NUM_PREC_RADIX", Oid.INT4, 4); + f[10] = new Field("NULLABLE", Oid.INT4, 4); + f[11] = new Field("REMARKS", Oid.VARCHAR, getMaxNameLength()); + f[12] = new Field("COLUMN_DEF", Oid.VARCHAR, getMaxNameLength()); + f[13] = new Field("SQL_DATA_TYPE", Oid.INT4, 4); + f[14] = new Field("SQL_DATETIME_SUB", Oid.INT4, 4); + f[15] = new Field("CHAR_OCTET_LENGTH", Oid.VARCHAR, getMaxNameLength()); + f[16] = new Field("ORDINAL_POSITION", Oid.INT4, 4); + f[17] = new Field("IS_NULLABLE", Oid.VARCHAR, getMaxNameLength()); + + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ + " FROM pg_catalog.pg_namespace n "+ + " JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) "+ + " JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) "+ + " LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ + " LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) "+ + " LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') "+ + " LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') "+ + " WHERE a.attnum > 0 AND NOT a.attisdropped "; + if (schemaPattern != null && !"".equals(schemaPattern)) { + sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; + } + } else if (connection.haveMinimumServerVersion("7.2")) { + sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ + " FROM pg_class c "+ + " JOIN pg_attribute a ON (a.attrelid=c.oid) "+ + " LEFT JOIN pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ + " LEFT JOIN pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) "+ + " LEFT JOIN pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') "+ + " WHERE a.attnum > 0 "; + } else if (connection.haveMinimumServerVersion("7.1")) { + sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,def.adsrc,dsc.description "+ + " FROM pg_class c "+ + " JOIN pg_attribute a ON (a.attrelid=c.oid) "+ + " LEFT JOIN pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) "+ + " LEFT JOIN pg_description dsc ON (a.oid=dsc.objoid) "+ + " WHERE a.attnum > 0 "; + } else { + // if < 7.1 then don't get defaults or descriptions. + sql = "SELECT NULL::text AS nspname,c.relname,a.attname,a.atttypid,a.attnotnull,a.atttypmod,a.attlen,a.attnum,NULL AS adsrc,NULL AS description "+ + " FROM pg_class c, pg_attribute a "+ + " WHERE a.attrelid=c.oid AND a.attnum > 0 "; + } + + if (tableNamePattern != null && !"".equals(tableNamePattern)) { + sql += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; + } + if (columnNamePattern != null && !"".equals(columnNamePattern)) { + sql += " AND a.attname LIKE '"+escapeQuotes(columnNamePattern)+"' "; + } + sql += " ORDER BY nspname,relname,attnum "; + + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) + { + byte[][] tuple = new byte[18][]; + int typeOid = rs.getInt("atttypid"); + + tuple[0] = null; // Catalog name, not supported + tuple[1] = rs.getBytes("nspname"); // Schema + tuple[2] = rs.getBytes("relname"); // Table name + tuple[3] = rs.getBytes("attname"); // Column name + tuple[4] = connection.encodeString(Integer.toString(connection.getSQLType(typeOid))); + String pgType = connection.getPGType(typeOid); + tuple[5] = connection.encodeString(pgType); // Type name + + // by default no decimal_digits + // if the type is numeric or decimal we will + // overwrite later. + tuple[8] = connection.encodeString("0"); + + if (pgType.equals("bpchar") || pgType.equals("varchar")) + { + int atttypmod = rs.getInt("atttypmod"); + tuple[6] = connection.encodeString(Integer.toString(atttypmod != -1 ? atttypmod - VARHDRSZ : 0)); + } + else if (pgType.equals("numeric") || pgType.equals("decimal")) + { + int attypmod = rs.getInt("atttypmod") - VARHDRSZ; + tuple[6] = connection.encodeString(Integer.toString( ( attypmod >> 16 ) & 0xffff )); + tuple[8] = connection.encodeString(Integer.toString(attypmod & 0xffff)); + tuple[9] = connection.encodeString("10"); + } + else if (pgType.equals("bit") || pgType.equals("varbit")) { + tuple[6] = rs.getBytes("atttypmod"); + tuple[9] = connection.encodeString("2"); + } + else { + tuple[6] = rs.getBytes("attlen"); + tuple[9] = connection.encodeString("10"); + } + + tuple[7] = null; // Buffer length + + tuple[10] = connection.encodeString(Integer.toString(rs.getBoolean("attnotnull") ? java.sql.DatabaseMetaData.columnNoNulls : java.sql.DatabaseMetaData.columnNullable)); // Nullable + tuple[11] = rs.getBytes("description"); // Description (if any) + tuple[12] = rs.getBytes("adsrc"); // Column default + tuple[13] = null; // sql data type (unused) + tuple[14] = null; // sql datetime sub (unused) + tuple[15] = tuple[6]; // char octet length + tuple[16] = rs.getBytes("attnum"); // ordinal position + tuple[17] = connection.encodeString(rs.getBoolean("attnotnull") ? "NO" : "YES"); // Is nullable + + v.addElement(tuple); + } + rs.close(); + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of the access rights for a table's columns. + * + * <P>Only privileges matching the column name criteria are + * returned. They are ordered by COLUMN_NAME and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param columnNamePattern a column name pattern + * @return ResultSet each row is a column privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException + { + Field f[] = new Field[8]; + Vector v = new Vector(); + + if (table == null) + table = "%"; + + if (columnNamePattern == null) + columnNamePattern = "%"; + + f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[3] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("GRANTOR", Oid.VARCHAR, getMaxNameLength()); + f[5] = new Field("GRANTEE", Oid.VARCHAR, getMaxNameLength()); + f[6] = new Field("PRIVILEGE", Oid.VARCHAR, getMaxNameLength()); + f[7] = new Field("IS_GRANTABLE", Oid.VARCHAR, getMaxNameLength()); + + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT n.nspname,c.relname,u.usename,c.relacl,a.attname "+ + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_attribute a "+ + " WHERE c.relnamespace = n.oid "+ + " AND u.usesysid = c.relowner "+ + " AND c.oid = a.attrelid "+ + " AND c.relkind = 'r' "+ + " AND a.attnum > 0 AND NOT a.attisdropped "; + if (schema != null && !"".equals(schema)) { + sql += " AND n.nspname = '"+escapeQuotes(schema)+"' "; + } + } else { + sql = "SELECT NULL::text AS nspname,c.relname,u.usename,c.relacl,a.attname "+ + "FROM pg_class c, pg_user u,pg_attribute a "+ + " WHERE u.usesysid = c.relowner "+ + " AND c.oid = a.attrelid "+ + " AND a.attnum > 0 "+ + " AND c.relkind = 'r' "; + } + + sql += " AND c.relname = '"+escapeQuotes(table)+"' "; + if (columnNamePattern != null && !"".equals(columnNamePattern)) { + sql += " AND a.attname LIKE '"+escapeQuotes(columnNamePattern)+"' "; + } + sql += " ORDER BY attname "; + + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + byte schemaName[] = rs.getBytes("nspname"); + byte tableName[] = rs.getBytes("relname"); + byte column[] = rs.getBytes("attname"); + String owner = rs.getString("usename"); + String acl = rs.getString("relacl"); + Hashtable permissions = parseACL(acl, owner); + String permNames[] = new String[permissions.size()]; + Enumeration e = permissions.keys(); + int i=0; + while (e.hasMoreElements()) { + permNames[i++] = (String)e.nextElement(); + } + sortStringArray(permNames); + for (i=0; i<permNames.length; i++) { + byte[] privilege = connection.encodeString(permNames[i]); + Vector grantees = (Vector)permissions.get(permNames[i]); + for (int j=0; j<grantees.size(); j++) { + String grantee = (String)grantees.elementAt(j); + String grantable = owner.equals(grantee) ? "YES" : "NO"; + byte[][] tuple = new byte[8][]; + tuple[0] = null; + tuple[1] = schemaName; + tuple[2] = tableName; + tuple[3] = column; + tuple[4] = connection.encodeString(owner); + tuple[5] = connection.encodeString(grantee); + tuple[6] = privilege; + tuple[7] = connection.encodeString(grantable); + v.addElement(tuple); + } + } + } + rs.close(); + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of the access rights for each table available + * in a catalog. + * + * This method is currently unimplemented. + * + * <P>Only privileges matching the schema and table name + * criteria are returned. They are ordered by TABLE_SCHEM, + * TABLE_NAME, and PRIVILEGE. + * + * <P>Each privilige description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>GRANTOR</B> => grantor of access (may be null) + * <LI><B>GRANTEE</B> String => grantee of access + * <LI><B>PRIVILEGE</B> String => name of access (SELECT, + * INSERT, UPDATE, REFRENCES, ...) + * <LI><B>IS_GRANTABLE</B> String => "YES" if grantee is permitted + * to grant to others; "NO" if not; null if unknown + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schemaPattern a schema name pattern; "" retrieves those + * without a schema + * @param tableNamePattern a table name pattern + * @return ResultSet each row is a table privilege description + * @see #getSearchStringEscape + */ + public java.sql.ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException + { + Field f[] = new Field[7]; + Vector v = new Vector(); + + f[0] = new Field("TABLE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("TABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("TABLE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[3] = new Field("GRANTOR", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("GRANTEE", Oid.VARCHAR, getMaxNameLength()); + f[5] = new Field("PRIVILEGE", Oid.VARCHAR, getMaxNameLength()); + f[6] = new Field("IS_GRANTABLE", Oid.VARCHAR, getMaxNameLength()); + + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT n.nspname,c.relname,u.usename,c.relacl "+ + " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class c, pg_catalog.pg_user u "+ + " WHERE c.relnamespace = n.oid "+ + " AND u.usesysid = c.relowner "+ + " AND c.relkind = 'r' "; + if (schemaPattern != null && !"".equals(schemaPattern)) { + sql += " AND n.nspname LIKE '"+escapeQuotes(schemaPattern)+"' "; + } + } else { + sql = "SELECT NULL::text AS nspname,c.relname,u.usename,c.relacl "+ + "FROM pg_class c, pg_user u "+ + " WHERE u.usesysid = c.relowner "+ + " AND c.relkind = 'r' "; + } + + if (tableNamePattern != null && !"".equals(tableNamePattern)) { + sql += " AND c.relname LIKE '"+escapeQuotes(tableNamePattern)+"' "; + } + sql += " ORDER BY nspname, relname "; + + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + byte schema[] = rs.getBytes("nspname"); + byte table[] = rs.getBytes("relname"); + String owner = rs.getString("usename"); + String acl = rs.getString("relacl"); + Hashtable permissions = parseACL(acl, owner); + String permNames[] = new String[permissions.size()]; + Enumeration e = permissions.keys(); + int i=0; + while (e.hasMoreElements()) { + permNames[i++] = (String)e.nextElement(); + } + sortStringArray(permNames); + for (i=0; i<permNames.length; i++) { + byte[] privilege = connection.encodeString(permNames[i]); + Vector grantees = (Vector)permissions.get(permNames[i]); + for (int j=0; j<grantees.size(); j++) { + String grantee = (String)grantees.elementAt(j); + String grantable = owner.equals(grantee) ? "YES" : "NO"; + byte[][] tuple = new byte[7][]; + tuple[0] = null; + tuple[1] = schema; + tuple[2] = table; + tuple[3] = connection.encodeString(owner); + tuple[4] = connection.encodeString(grantee); + tuple[5] = privilege; + tuple[6] = connection.encodeString(grantable); + v.addElement(tuple); + } + } + } + rs.close(); + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + private static void sortStringArray(String s[]) { + for (int i=0; i<s.length-1; i++) { + for (int j=i+1; j<s.length; j++) { + if (s[i].compareTo(s[j]) > 0) { + String tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + } + } + } + } + + /** + * Parse an String of ACLs into a Vector of ACLs. + */ + private static Vector parseACLArray(String aclString) { + Vector acls = new Vector(); + if (aclString == null || aclString.length() == 0) { + return acls; + } + boolean inQuotes = false; + // start at 1 because of leading "{" + int beginIndex = 1; + char prevChar = ' '; + for (int i=beginIndex; i<aclString.length(); i++) { + + char c = aclString.charAt(i); + if (c == '"' && prevChar != '\\') { + inQuotes = !inQuotes; + } else if (c == ',' && !inQuotes) { + acls.addElement(aclString.substring(beginIndex,i)); + beginIndex = i+1; + } + prevChar = c; + } + // add last element removing the trailing "}" + acls.addElement(aclString.substring(beginIndex,aclString.length()-1)); + + // Strip out enclosing quotes, if any. + for (int i=0; i<acls.size(); i++) { + String acl = (String)acls.elementAt(i); + if (acl.startsWith("\"") && acl.endsWith("\"")) { + acl = acl.substring(1,acl.length()-1); + acls.setElementAt(acl,i); + } + } + return acls; + } + + /** + * Add the user described by the given acl to the Vectors of users + * with the privileges described by the acl. + */ + private void addACLPrivileges(String acl, Hashtable privileges) { + int equalIndex = acl.lastIndexOf("="); + String name = acl.substring(0,equalIndex); + if (name.length() == 0) { + name = "PUBLIC"; + } + String privs = acl.substring(equalIndex+1); + for (int i=0; i<privs.length(); i++) { + char c = privs.charAt(i); + String sqlpriv; + switch (c) { + case 'a': sqlpriv = "INSERT"; break; + case 'r': sqlpriv = "SELECT"; break; + case 'w': sqlpriv = "UPDATE"; break; + case 'd': sqlpriv = "DELETE"; break; + case 'R': sqlpriv = "RULE"; break; + case 'x': sqlpriv = "REFERENCES"; break; + case 't': sqlpriv = "TRIGGER"; break; + // the folloowing can't be granted to a table, but + // we'll keep them for completeness. + case 'X': sqlpriv = "EXECUTE"; break; + case 'U': sqlpriv = "USAGE"; break; + case 'C': sqlpriv = "CREATE"; break; + case 'T': sqlpriv = "CREATE TEMP"; break; + default: sqlpriv = "UNKNOWN"; + } + Vector usersWithPermission = (Vector)privileges.get(sqlpriv); + if (usersWithPermission == null) { + usersWithPermission = new Vector(); + privileges.put(sqlpriv,usersWithPermission); + } + usersWithPermission.addElement(name); + } + } + + /** + * Take the a String representing an array of ACLs and return + * a Hashtable mapping the SQL permission name to a Vector of + * usernames who have that permission. + */ + protected Hashtable parseACL(String aclArray, String owner) { + if (aclArray == null || aclArray == "") { + //null acl is a shortcut for owner having full privs + aclArray = "{" + owner + "=arwdRxt}"; + } + Vector acls = parseACLArray(aclArray); + Hashtable privileges = new Hashtable(); + for (int i=0; i<acls.size(); i++) { + String acl = (String)acls.elementAt(i); + addACLPrivileges(acl,privileges); + } + return privileges; + } + + /* + * Get a description of a table's optimal set of columns that + * uniquely identifies a row. They are ordered by SCOPE. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => actual scope of result + * <UL> + * <LI> bestRowTemporary - very temporary, while using row + * <LI> bestRowTransaction - valid for remainder of current transaction + * <LI> bestRowSession - valid for remainder of current session + * </UL> + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => not used + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> bestRowUnknown - may or may not be pseudo column + * <LI> bestRowNotPseudo - is NOT a pseudo column + * <LI> bestRowPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @param scope the scope of interest; use same values as SCOPE + * @param nullable include columns that are nullable? + * @return ResultSet each row is a column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException + { + Field f[] = new Field[8]; + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field("SCOPE", Oid.INT2, 2); + f[1] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("DATA_TYPE", Oid.INT2, 2); + f[3] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("COLUMN_SIZE", Oid.INT4, 4); + f[5] = new Field("BUFFER_LENGTH", Oid.INT4, 4); + f[6] = new Field("DECIMAL_DIGITS", Oid.INT2, 2); + f[7] = new Field("PSEUDO_COLUMN", Oid.INT2, 2); + + /* At the moment this simply returns a table's primary key, + * if there is one. I believe other unique indexes, ctid, + * and oid should also be considered. -KJ + */ + + String from; + String where = ""; + if (connection.haveMinimumServerVersion("7.3")) { + from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_attribute a, pg_catalog.pg_index i "; + where = " AND ct.relnamespace = n.oid "; + if (schema != null && !"".equals(schema)) { + where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; + } + } else { + from = " FROM pg_class ct, pg_class ci, pg_attribute a, pg_index i "; + } + String sql = "SELECT a.attname, a.atttypid "+ + from+ + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid "+ + " AND a.attrelid=ci.oid AND i.indisprimary "+ + " AND ct.relname = '"+escapeQuotes(table)+"' "+ + where+ + " ORDER BY a.attnum "; + + ResultSet rs = connection.createStatement().executeQuery(sql); + while (rs.next()) { + byte tuple[][] = new byte[8][]; + int columnTypeOid = rs.getInt("atttypid"); + tuple[0] = connection.encodeString(Integer.toString(scope)); + tuple[1] = rs.getBytes("attname"); + tuple[2] = connection.encodeString(Integer.toString(connection.getSQLType(columnTypeOid))); + tuple[3] = connection.encodeString(connection.getPGType(columnTypeOid)); + tuple[4] = null; + tuple[5] = null; + tuple[6] = null; + tuple[7] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.bestRowNotPseudo)); + v.addElement(tuple); + } + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of a table's columns that are automatically + * updated when any value in a row is updated. They are + * unordered. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>SCOPE</B> short => is not used + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>TYPE_NAME</B> String => Data source dependent type name + * <LI><B>COLUMN_SIZE</B> int => precision + * <LI><B>BUFFER_LENGTH</B> int => length of column value in bytes + * <LI><B>DECIMAL_DIGITS</B> short => scale + * <LI><B>PSEUDO_COLUMN</B> short => is this a pseudo column + * like an Oracle ROWID + * <UL> + * <LI> versionColumnUnknown - may or may not be pseudo column + * <LI> versionColumnNotPseudo - is NOT a pseudo column + * <LI> versionColumnPseudo - is a pseudo column + * </UL> + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name; "" retrieves those without a schema + * @param table a table name + * @return ResultSet each row is a column description + */ + public java.sql.ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException + { + Field f[] = new Field[8]; + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field("SCOPE", Oid.INT2, 2); + f[1] = new Field("COLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("DATA_TYPE", Oid.INT2, 2); + f[3] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("COLUMN_SIZE", Oid.INT4, 4); + f[5] = new Field("BUFFER_LENGTH", Oid.INT4, 4); + f[6] = new Field("DECIMAL_DIGITS", Oid.INT2, 2); + f[7] = new Field("PSEUDO_COLUMN", Oid.INT2, 2); + + byte tuple[][] = new byte[8][]; + + /* Postgresql does not have any column types that are + * automatically updated like some databases' timestamp type. + * We can't tell what rules or triggers might be doing, so we + * are left with the system columns that change on an update. + * An update may change all of the following system columns: + * ctid, xmax, xmin, cmax, and cmin. Depending on if we are + * in a transaction and wether we roll it back or not the + * only guaranteed change is to ctid. -KJ + */ + + tuple[0] = null; + tuple[1] = connection.encodeString("ctid"); + tuple[2] = connection.encodeString(Integer.toString(connection.getSQLType("tid"))); + tuple[3] = connection.encodeString("tid"); + tuple[4] = null; + tuple[5] = null; + tuple[6] = null; + tuple[7] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.versionColumnPseudo)); + v.addElement(tuple); + + /* Perhaps we should check that the given + * catalog.schema.table actually exists. -KJ + */ + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of a table's primary key columns. They + * are ordered by COLUMN_NAME. + * + * <P>Each column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>COLUMN_NAME</B> String => column name + * <LI><B>KEY_SEQ</B> short => sequence number within primary key + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + */ + public java.sql.ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException + { + String select; + String from; + String where = ""; + if (connection.haveMinimumServerVersion("7.3")) { + select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "; + from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_attribute a, pg_catalog.pg_index i "; + where = " AND ct.relnamespace = n.oid "; + if (schema != null && !"".equals(schema)) { + where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; + } + } else { + select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, "; + from = " FROM pg_class ct, pg_class ci, pg_attribute a, pg_index i "; + } + String sql = select+ + " ct.relname AS TABLE_NAME, "+ + " a.attname AS COLUMN_NAME, "+ + " a.attnum AS KEY_SEQ, "+ + " ci.relname AS PK_NAME "+ + from+ + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid "+ + " AND a.attrelid=ci.oid AND i.indisprimary "; + if (table != null && !"".equals(table)) { + sql += " AND ct.relname = '"+escapeQuotes(table)+"' "; + } + sql += where+ + " ORDER BY table_name, pk_name, key_seq"; + return createMetaDataStatement().executeQuery(sql); + } + + /** + * + * @param primaryCatalog + * @param primarySchema + * @param primaryTable if provided will get the keys exported by this table + * @param foreignTable if provided will get the keys imported by this table + * @return ResultSet + * @throws SQLException + */ + + protected java.sql.ResultSet getImportedExportedKeys(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException + { + Field f[] = new Field[14]; + + f[0] = new Field("PKTABLE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("PKTABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[2] = new Field("PKTABLE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[3] = new Field("PKCOLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("FKTABLE_CAT", Oid.VARCHAR, getMaxNameLength()); + f[5] = new Field("FKTABLE_SCHEM", Oid.VARCHAR, getMaxNameLength()); + f[6] = new Field("FKTABLE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[7] = new Field("FKCOLUMN_NAME", Oid.VARCHAR, getMaxNameLength()); + f[8] = new Field("KEY_SEQ", Oid.INT2, 2); + f[9] = new Field("UPDATE_RULE", Oid.INT2, 2); + f[10] = new Field("DELETE_RULE", Oid.INT2, 2); + f[11] = new Field("FK_NAME", Oid.VARCHAR, getMaxNameLength()); + f[12] = new Field("PK_NAME", Oid.VARCHAR, getMaxNameLength()); + f[13] = new Field("DEFERRABILITY", Oid.INT2, 2); + + + String select; + String from; + String where = ""; + + /* + * The addition of the pg_constraint in 7.3 table should have really + * helped us out here, but it comes up just a bit short. + * - The conkey, confkey columns aren't really useful without + * contrib/array unless we want to issues separate queries. + * - Unique indexes that can support foreign keys are not necessarily + * added to pg_constraint. Also multiple unique indexes covering + * the same keys can be created which make it difficult to determine + * the PK_NAME field. + */ + + if (connection.haveMinimumServerVersion("7.4")) { + String sql = "SELECT NULL::text AS PKTABLE_CAT, pkn.nspname AS PKTABLE_SCHEM, pkc.relname AS PKTABLE_NAME, pka.attname AS PKCOLUMN_NAME, "+ + "NULL::text AS FKTABLE_CAT, fkn.nspname AS FKTABLE_SCHEM, fkc.relname AS FKTABLE_NAME, fka.attname AS FKCOLUMN_NAME, "+ + "pos.n AS KEY_SEQ, "+ + "CASE con.confupdtype "+ + " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + + " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + + " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + + " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + + " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + + " ELSE NULL END AS UPDATE_RULE, "+ + "CASE con.confdeltype "+ + " WHEN 'c' THEN " + DatabaseMetaData.importedKeyCascade + + " WHEN 'n' THEN " + DatabaseMetaData.importedKeySetNull + + " WHEN 'd' THEN " + DatabaseMetaData.importedKeySetDefault + + " WHEN 'r' THEN " + DatabaseMetaData.importedKeyRestrict + + " WHEN 'a' THEN " + DatabaseMetaData.importedKeyNoAction + + " ELSE NULL END AS DELETE_RULE, "+ + "con.conname AS FK_NAME, pkic.relname AS PK_NAME, "+ + "CASE "+ + " WHEN con.condeferrable AND con.condeferred THEN " + DatabaseMetaData.importedKeyInitiallyDeferred + + " WHEN con.condeferrable THEN " + DatabaseMetaData.importedKeyInitiallyImmediate + + " ELSE " + DatabaseMetaData.importedKeyNotDeferrable+ + " END AS DEFERRABILITY "+ + " FROM "+ + " pg_catalog.pg_namespace pkn, pg_catalog.pg_class pkc, pg_catalog.pg_attribute pka, "+ + " pg_catalog.pg_namespace fkn, pg_catalog.pg_class fkc, pg_catalog.pg_attribute fka, "+ + " pg_catalog.pg_constraint con, information_schema._pg_keypositions() pos(n), "+ + " pg_catalog.pg_depend dep, pg_catalog.pg_class pkic "+ + " WHERE pkn.oid = pkc.relnamespace AND pkc.oid = pka.attrelid AND pka.attnum = con.confkey[pos.n] AND con.confrelid = pkc.oid "+ + " AND fkn.oid = fkc.relnamespace AND fkc.oid = fka.attrelid AND fka.attnum = con.conkey[pos.n] AND con.conrelid = fkc.oid "+ + " AND con.contype = 'f' AND con.oid = dep.objid AND pkic.oid = dep.refobjid AND pkic.relkind = 'i' AND dep.classid = 'pg_constraint'::regclass::oid AND dep.refclassid = 'pg_class'::regclass::oid "; + if (primarySchema != null && !"".equals(primarySchema)) { + sql += " AND pkn.nspname = '"+escapeQuotes(primarySchema)+"' "; + } + if (foreignSchema != null && !"".equals(foreignSchema)) { + sql += " AND fkn.nspname = '"+escapeQuotes(foreignSchema)+"' "; + } + if (primaryTable != null && !"".equals(primaryTable)) { + sql += " AND pkc.relname = '"+escapeQuotes(primaryTable)+"' "; + } + if (foreignTable != null && !"".equals(foreignTable)) { + sql += " AND fkc.relname = '"+escapeQuotes(foreignTable)+"' "; + } + + if (primaryTable != null) { + sql += " ORDER BY fkn.nspname,fkc.relname,pos.n"; + } else { + sql += " ORDER BY pkn.nspname,pkc.relname,pos.n"; + } + + return createMetaDataStatement().executeQuery(sql); + } else if (connection.haveMinimumServerVersion("7.3")) { + select = "SELECT DISTINCT n1.nspname as pnspname,n2.nspname as fnspname, "; + from = " FROM pg_catalog.pg_namespace n1 "+ + " JOIN pg_catalog.pg_class c1 ON (c1.relnamespace = n1.oid) "+ + " JOIN pg_catalog.pg_index i ON (c1.oid=i.indrelid) "+ + " JOIN pg_catalog.pg_class ic ON (i.indexrelid=ic.oid) "+ + " JOIN pg_catalog.pg_attribute a ON (ic.oid=a.attrelid), "+ + " pg_catalog.pg_namespace n2 "+ + " JOIN pg_catalog.pg_class c2 ON (c2.relnamespace=n2.oid), "+ + " pg_catalog.pg_trigger t1 "+ + " JOIN pg_catalog.pg_proc p1 ON (t1.tgfoid=p1.oid), "+ + " pg_catalog.pg_trigger t2 "+ + " JOIN pg_catalog.pg_proc p2 ON (t2.tgfoid=p2.oid) "; + if (primarySchema != null && !"".equals(primarySchema)) { + where += " AND n1.nspname = '"+escapeQuotes(primarySchema)+"' "; + } + if (foreignSchema != null && !"".equals(foreignSchema)) { + where += " AND n2.nspname = '"+escapeQuotes(foreignSchema)+"' "; + } + } else { + select = "SELECT DISTINCT NULL::text as pnspname, NULL::text as fnspname, "; + from = " FROM pg_class c1 "+ + " JOIN pg_index i ON (c1.oid=i.indrelid) "+ + " JOIN pg_class ic ON (i.indexrelid=ic.oid) "+ + " JOIN pg_attribute a ON (ic.oid=a.attrelid), "+ + " pg_class c2, "+ + " pg_trigger t1 "+ + " JOIN pg_proc p1 ON (t1.tgfoid=p1.oid), "+ + " pg_trigger t2 "+ + " JOIN pg_proc p2 ON (t2.tgfoid=p2.oid) "; + } + + String sql = select + + "c1.relname as prelname, " + + "c2.relname as frelname, " + + "t1.tgconstrname, " + + "a.attnum as keyseq, " + + "ic.relname as fkeyname, " + + "t1.tgdeferrable, " + + "t1.tginitdeferred, " + + "t1.tgnargs,t1.tgargs, " + + "p1.proname as updaterule, " + + "p2.proname as deleterule " + + from + + "WHERE " + // isolate the update rule + + "(t1.tgrelid=c1.oid " + + "AND t1.tgisconstraint " + + "AND t1.tgconstrrelid=c2.oid " + + "AND p1.proname LIKE 'RI\\\\_FKey\\\\_%\\\\_upd') " + + + "AND " + // isolate the delete rule + + "(t2.tgrelid=c1.oid " + + "AND t2.tgisconstraint " + + "AND t2.tgconstrrelid=c2.oid " + + "AND p2.proname LIKE 'RI\\\\_FKey\\\\_%\\\\_del') " + + + "AND i.indisprimary " + + where; + + if (primaryTable != null) { + sql += "AND c1.relname='" + escapeQuotes(primaryTable) + "' "; + } + if (foreignTable != null) { + sql += "AND c2.relname='" + escapeQuotes(foreignTable) + "' "; + } + + sql += "ORDER BY "; + + // orderby is as follows getExported, orders by FKTABLE, + // getImported orders by PKTABLE + // getCrossReference orders by FKTABLE, so this should work for both, + // since when getting crossreference, primaryTable will be defined + + if (primaryTable != null) { + if (connection.haveMinimumServerVersion("7.3")) { + sql += "fnspname,"; + } + sql += "frelname"; + } else { + if (connection.haveMinimumServerVersion("7.3")) { + sql += "pnspname,"; + } + sql += "prelname"; + } + + sql += ",keyseq"; + + ResultSet rs = connection.createStatement().executeQuery(sql); + + // returns the following columns + // and some example data with a table defined as follows + + // create table people ( id int primary key); + // create table policy ( id int primary key); + // create table users ( id int primary key, people_id int references people(id), policy_id int references policy(id)) + + // prelname | frelname | tgconstrname | keyseq | fkeyName | tgdeferrable | tginitdeferred + // 1 | 2 | 3 | 4 | 5 | 6 | 7 + + // people | users | <unnamed> | 1 | people_pkey | f | f + + // | tgnargs | tgargs | updaterule | deleterule + // | 8 | 9 | 10 | 11 + // | 6 | <unnamed>\000users\000people\000UNSPECIFIED\000people_id\000id\000 | RI_FKey_noaction_upd | RI_FKey_noaction_del + + Vector tuples = new Vector(); + + while ( rs.next() ) + { + byte tuple[][] = new byte[14][]; + + tuple[1] = rs.getBytes(1); //PKTABLE_SCHEM + tuple[5] = rs.getBytes(2); //FKTABLE_SCHEM + tuple[2] = rs.getBytes(3); //PKTABLE_NAME + tuple[6] = rs.getBytes(4); //FKTABLE_NAME + String fKeyName = rs.getString(5); + String updateRule = rs.getString(12); + + if (updateRule != null ) + { + // Rules look like this RI_FKey_noaction_del so we want to pull out the part between the 'Key_' and the last '_' s + + String rule = updateRule.substring(8, updateRule.length() - 4); + + int action = java.sql.DatabaseMetaData.importedKeyNoAction; + + if ( rule == null || "noaction".equals(rule) ) + action = java.sql.DatabaseMetaData.importedKeyNoAction; + if ("cascade".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeyCascade; + else if ("setnull".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeySetNull; + else if ("setdefault".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeySetDefault; + else if ("restrict".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeyRestrict; + + tuple[9] = connection.encodeString(Integer.toString(action)); + + } + + String deleteRule = rs.getString(13); + + if ( deleteRule != null ) + { + + String rule = deleteRule.substring(8, deleteRule.length() - 4); + + int action = java.sql.DatabaseMetaData.importedKeyNoAction; + if ("cascade".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeyCascade; + else if ("setnull".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeySetNull; + else if ("setdefault".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeySetDefault; + else if ("restrict".equals(rule)) + action = java.sql.DatabaseMetaData.importedKeyRestrict; + tuple[10] = connection.encodeString(Integer.toString(action)); + } + + + int keySequence = rs.getInt(6); //KEY_SEQ + + // Parse the tgargs data + String fkeyColumn = ""; + String pkeyColumn = ""; + String fkName = ""; + // Note, I am guessing at most of this, but it should be close + // if not, please correct + // the keys are in pairs and start after the first four arguments + // the arguments are seperated by \000 + + String targs = rs.getString(11); + + // args look like this + //<unnamed>\000ww\000vv\000UNSPECIFIED\000m\000a\000n\000b\000 + // we are primarily interested in the column names which are the last items in the string + + Vector tokens = tokenize(targs, "\\000"); + if (tokens.size() > 0) { + fkName = (String)tokens.elementAt(0); + } + + if (fkName.startsWith("<unnamed>")) { + fkName = targs; + } + + int element = 4 + (keySequence - 1) * 2; + if (tokens.size() > element) { + fkeyColumn = (String)tokens.elementAt(element); + } + + element++; + if (tokens.size() > element) { + pkeyColumn = (String)tokens.elementAt(element); + } + + tuple[3] = connection.encodeString(pkeyColumn); //PKCOLUMN_NAME + tuple[7] = connection.encodeString(fkeyColumn); //FKCOLUMN_NAME + + tuple[8] = rs.getBytes(6); //KEY_SEQ + tuple[11] = connection.encodeString(fkName); //FK_NAME this will give us a unique name for the foreign key + tuple[12] = rs.getBytes(7); //PK_NAME + + // DEFERRABILITY + int deferrability = java.sql.DatabaseMetaData.importedKeyNotDeferrable; + boolean deferrable = rs.getBoolean(8); + boolean initiallyDeferred = rs.getBoolean(9); + if (deferrable) + { + if (initiallyDeferred) + deferrability = java.sql.DatabaseMetaData.importedKeyInitiallyDeferred; + else + deferrability = java.sql.DatabaseMetaData.importedKeyInitiallyImmediate; + } + tuple[13] = connection.encodeString(Integer.toString(deferrability)); + + tuples.addElement(tuple); + } + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, tuples); + } + + /* + * Get a description of the primary key columns that are + * referenced by a table's foreign key columns (the primary keys + * imported by a table). They are ordered by PKTABLE_CAT, + * PKTABLE_SCHEM, PKTABLE_NAME, and KEY_SEQ. + * + * <P>Each primary key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog + * being imported (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema + * being imported (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * being imported + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * being imported + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key name (may be null) + * <LI><B>PK_NAME</B> String => primary key name (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a primary key column description + * @see #getExportedKeys + */ + public java.sql.ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException + { + return getImportedExportedKeys(null,null,null,catalog, schema, table); + } + + /* + * Get a description of a foreign key columns that reference a + * table's primary key columns (the foreign keys exported by a + * table). They are ordered by FKTABLE_CAT, FKTABLE_SCHEM, + * FKTABLE_NAME, and KEY_SEQ. + * + * This method is currently unimplemented. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException + { + return getImportedExportedKeys(catalog, schema, table, null,null,null); + } + + /* + * Get a description of the foreign key columns in the foreign key + * table that reference the primary key columns of the primary key + * table (describe how one table imports another's key.) This + * should normally return a single foreign key/primary key pair + * (most tables only import a foreign key from a table once.) They + * are ordered by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, and + * KEY_SEQ. + * + * This method is currently unimplemented. + * + * <P>Each foreign key column description has the following columns: + * <OL> + * <LI><B>PKTABLE_CAT</B> String => primary key table catalog (may be null) + * <LI><B>PKTABLE_SCHEM</B> String => primary key table schema (may be null) + * <LI><B>PKTABLE_NAME</B> String => primary key table name + * <LI><B>PKCOLUMN_NAME</B> String => primary key column name + * <LI><B>FKTABLE_CAT</B> String => foreign key table catalog (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_SCHEM</B> String => foreign key table schema (may be null) + * being exported (may be null) + * <LI><B>FKTABLE_NAME</B> String => foreign key table name + * being exported + * <LI><B>FKCOLUMN_NAME</B> String => foreign key column name + * being exported + * <LI><B>KEY_SEQ</B> short => sequence number within foreign key + * <LI><B>UPDATE_RULE</B> short => What happens to + * foreign key when primary is updated: + * <UL> + * <LI> importedKeyCascade - change imported key to agree + * with primary key update + * <LI> importedKeyRestrict - do not allow update of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been updated + * </UL> + * <LI><B>DELETE_RULE</B> short => What happens to + * the foreign key when primary is deleted. + * <UL> + * <LI> importedKeyCascade - delete rows that import a deleted key + * <LI> importedKeyRestrict - do not allow delete of primary + * key if it has been imported + * <LI> importedKeySetNull - change imported key to NULL if + * its primary key has been deleted + * </UL> + * <LI><B>FK_NAME</B> String => foreign key identifier (may be null) + * <LI><B>PK_NAME</B> String => primary key identifier (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those + * without a schema + * @param table a table name + * @return ResultSet each row is a foreign key column description + * @see #getImportedKeys + */ + public java.sql.ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException + { + return getImportedExportedKeys(primaryCatalog, primarySchema, primaryTable, foreignCatalog, foreignSchema, foreignTable); + } + + /* + * Get a description of all the standard SQL types supported by + * this database. They are ordered by DATA_TYPE and then by how + * closely the data type maps to the corresponding JDBC SQL type. + * + * <P>Each type description has the following columns: + * <OL> + * <LI><B>TYPE_NAME</B> String => Type name + * <LI><B>DATA_TYPE</B> short => SQL data type from java.sql.Types + * <LI><B>PRECISION</B> int => maximum precision + * <LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal + * (may be null) + * <LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal + (may be null) + * <LI><B>CREATE_PARAMS</B> String => parameters used in creating + * the type (may be null) + * <LI><B>NULLABLE</B> short => can you use NULL for this type? + * <UL> + * <LI> typeNoNulls - does not allow NULL values + * <LI> typeNullable - allows NULL values + * <LI> typeNullableUnknown - nullability unknown + * </UL> + * <LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive? + * <LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type: + * <UL> + * <LI> typePredNone - No support + * <LI> typePredChar - Only supported with WHERE .. LIKE + * <LI> typePredBasic - Supported except for WHERE .. LIKE + * <LI> typeSearchable - Supported for all WHERE .. + * </UL> + * <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned? + * <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value? + * <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an + * auto-increment value? + * <LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name + * (may be null) + * <LI><B>MINIMUM_SCALE</B> short => minimum scale supported + * <LI><B>MAXIMUM_SCALE</B> short => maximum scale supported + * <LI><B>SQL_DATA_TYPE</B> int => unused + * <LI><B>SQL_DATETIME_SUB</B> int => unused + * <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10 + * </OL> + * + * @return ResultSet each row is a SQL type description + */ + public java.sql.ResultSet getTypeInfo() throws SQLException + { + + Field f[] = new Field[18]; + Vector v = new Vector(); // The new ResultSet tuple stuff + + f[0] = new Field("TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[1] = new Field("DATA_TYPE", Oid.INT2, 2); + f[2] = new Field("PRECISION", Oid.INT4, 4); + f[3] = new Field("LITERAL_PREFIX", Oid.VARCHAR, getMaxNameLength()); + f[4] = new Field("LITERAL_SUFFIX", Oid.VARCHAR, getMaxNameLength()); + f[5] = new Field("CREATE_PARAMS", Oid.VARCHAR, getMaxNameLength()); + f[6] = new Field("NULLABLE", Oid.INT2, 2); + f[7] = new Field("CASE_SENSITIVE", Oid.BOOL, 1); + f[8] = new Field("SEARCHABLE", Oid.INT2, 2); + f[9] = new Field("UNSIGNED_ATTRIBUTE", Oid.BOOL, 1); + f[10] = new Field("FIXED_PREC_SCALE", Oid.BOOL, 1); + f[11] = new Field("AUTO_INCREMENT", Oid.BOOL, 1); + f[12] = new Field("LOCAL_TYPE_NAME", Oid.VARCHAR, getMaxNameLength()); + f[13] = new Field("MINIMUM_SCALE", Oid.INT2, 2); + f[14] = new Field("MAXIMUM_SCALE", Oid.INT2, 2); + f[15] = new Field("SQL_DATA_TYPE", Oid.INT4, 4); + f[16] = new Field("SQL_DATETIME_SUB", Oid.INT4, 4); + f[17] = new Field("NUM_PREC_RADIX", Oid.INT4, 4); + + String sql; + if (connection.haveMinimumServerVersion("7.3")) { + sql = "SELECT typname FROM pg_catalog.pg_type"; + } else { + sql = "SELECT typname FROM pg_type"; + } + + ResultSet rs = connection.createStatement().executeQuery(sql); + // cache some results, this will keep memory useage down, and speed + // things up a little. + byte b9[] = connection.encodeString("9"); + byte b10[] = connection.encodeString("10"); + byte bf[] = connection.encodeString("f"); + byte bnn[] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeNoNulls)); + byte bts[] = connection.encodeString(Integer.toString(java.sql.DatabaseMetaData.typeSearchable)); + + while (rs.next()) + { + byte[][] tuple = new byte[18][]; + String typname = rs.getString(1); + tuple[0] = connection.encodeString(typname); + tuple[1] = connection.encodeString(Integer.toString(connection.getSQLType(typname))); + tuple[2] = b9; // for now + tuple[6] = bnn; // for now + tuple[7] = bf; // false for now - not case sensitive + tuple[8] = bts; + tuple[9] = bf; // false for now - it's signed + tuple[10] = bf; // false for now - must handle money + tuple[11] = bf; // false for now - handle autoincrement + // 12 - LOCAL_TYPE_NAME is null + // 13 & 14 ? + // 15 & 16 are unused so we return null + tuple[17] = b10; // everything is base 10 + v.addElement(tuple); + } + rs.close(); + + return (ResultSet) ((BaseStatement)createMetaDataStatement()).createDriverResultSet(f, v); + } + + /* + * Get a description of a table's indices and statistics. They are + * ordered by NON_UNIQUE, TYPE, INDEX_NAME, and ORDINAL_POSITION. + * + * <P>Each index column description has the following columns: + * <OL> + * <LI><B>TABLE_CAT</B> String => table catalog (may be null) + * <LI><B>TABLE_SCHEM</B> String => table schema (may be null) + * <LI><B>TABLE_NAME</B> String => table name + * <LI><B>NON_UNIQUE</B> boolean => Can index values be non-unique? + * false when TYPE is tableIndexStatistic + * <LI><B>INDEX_QUALIFIER</B> String => index catalog (may be null); + * null when TYPE is tableIndexStatistic + * <LI><B>INDEX_NAME</B> String => index name; null when TYPE is + * tableIndexStatistic + * <LI><B>TYPE</B> short => index type: + * <UL> + * <LI> tableIndexStatistic - this identifies table statistics that are + * returned in conjuction with a table's index descriptions + * <LI> tableIndexClustered - this is a clustered index + * <LI> tableIndexHashed - this is a hashed index + * <LI> tableIndexOther - this is some other style of index + * </UL> + * <LI><B>ORDINAL_POSITION</B> short => column sequence number + * within index; zero when TYPE is tableIndexStatistic + * <LI><B>COLUMN_NAME</B> String => column name; null when TYPE is + * tableIndexStatistic + * <LI><B>ASC_OR_DESC</B> String => column sort sequence, "A" => ascending + * "D" => descending, may be null if sort sequence is not supported; + * null when TYPE is tableIndexStatistic + * <LI><B>CARDINALITY</B> int => When TYPE is tableIndexStatisic then + * this is the number of rows in the table; otherwise it is the + * number of unique values in the index. + * <LI><B>PAGES</B> int => When TYPE is tableIndexStatisic then + * this is the number of pages used for the table, otherwise it + * is the number of pages used for the current index. + * <LI><B>FILTER_CONDITION</B> String => Filter condition, if any. + * (may be null) + * </OL> + * + * @param catalog a catalog name; "" retrieves those without a catalog + * @param schema a schema name pattern; "" retrieves those without a schema + * @param table a table name + * @param unique when true, return only indices for unique values; + * when false, return indices regardless of whether unique or not + * @param approximate when true, result is allowed to reflect approximate + * or out of data values; when false, results are requested to be + * accurate + * @return ResultSet each row is an index column description + */ + // Implementation note: This is required for Borland's JBuilder to work + public java.sql.ResultSet getIndexInfo(String catalog, String schema, String tableName, boolean unique, boolean approximate) throws SQLException + { + String select; + String from; + String where = ""; + if (connection.haveMinimumServerVersion("7.3")) { + select = "SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM, "; + from = " FROM pg_catalog.pg_namespace n, pg_catalog.pg_class ct, pg_catalog.pg_class ci, pg_catalog.pg_index i, pg_catalog.pg_attribute a, pg_catalog.pg_am am "; + where = " AND n.oid = ct.relnamespace "; + if (schema != null && ! "".equals(schema)) { + where += " AND n.nspname = '"+escapeQuotes(schema)+"' "; + } + } else { + select = "SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, "; + from = " FROM pg_class ct, pg_class ci, pg_index i, pg_attribute a, pg_am am "; + } + + String sql = select+ + " ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME, "+ + " CASE i.indisclustered "+ + " WHEN true THEN "+java.sql.DatabaseMetaData.tableIndexClustered+ + " ELSE CASE am.amname "+ + " WHEN 'hash' THEN "+java.sql.DatabaseMetaData.tableIndexHashed+ + " ELSE "+java.sql.DatabaseMetaData.tableIndexOther+ + " END "+ + " END AS TYPE, "+ + " a.attnum AS ORDINAL_POSITION, "+ + " a.attname AS COLUMN_NAME, "+ + " NULL AS ASC_OR_DESC, "+ + " ci.reltuples AS CARDINALITY, "+ + " ci.relpages AS PAGES, "+ + " NULL AS FILTER_CONDITION "+ + from+ + " WHERE ct.oid=i.indrelid AND ci.oid=i.indexrelid AND a.attrelid=ci.oid AND ci.relam=am.oid "+ + where+ + " AND ct.relname = '"+escapeQuotes(tableName)+"' "; + + if (unique) { + sql += " AND i.indisunique "; + } + sql += " ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION "; + return createMetaDataStatement().executeQuery(sql); + } + + /** + * Tokenize based on words not on single characters. + */ + private static Vector tokenize(String input, String delimiter) { + Vector result = new Vector(); + int start = 0; + int end = input.length(); + int delimiterSize = delimiter.length(); + + while (start < end) { + int delimiterIndex = input.indexOf(delimiter,start); + if (delimiterIndex < 0) { + result.addElement(input.substring(start)); + break; + } else { + String token = input.substring(start,delimiterIndex); + result.addElement(token); + start = delimiterIndex + delimiterSize; + } + } + return result; + } // ** JDBC 2 Extensions ** diff --git a/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java b/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java index 746e7a77..c7c2c567 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java +++ b/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java @@ -1,9 +1,8 @@ /*------------------------------------------------------------------------- * * AbstractJdbc2ResultSet.java - * This class defines methods of the jdbc2 specification. This class - * extends org.postgresql.jdbc1.AbstractJdbc1ResultSet which provides the - * jdbc1 methods. The real Statement class (for jdbc2) is + * This class defines methods of the jdbc2 specification. + * The real Statement class (for jdbc2) is * org.postgresql.jdbc2.Jdbc2ResultSet * * Copyright (c) 2003, PostgreSQL Global Development Group @@ -20,20 +19,26 @@ import java.io.InputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.io.ByteArrayInputStream; import java.math.BigDecimal; +import java.math.BigInteger; import java.sql.*; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.StringTokenizer; import java.util.Vector; +import java.text.SimpleDateFormat; import org.postgresql.Driver; import org.postgresql.core.*; +import org.postgresql.largeobject.*; +import org.postgresql.util.PGbytea; +import org.postgresql.util.PGtokenizer; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.AbstractJdbc1ResultSet +public abstract class AbstractJdbc2ResultSet implements BaseResultSet, org.postgresql.PGRefCursorResultSet { //needed for updateable result set support @@ -52,12 +57,47 @@ public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.Abstra private int resultsettype; private int resultsetconcurrency; private int fetchdirection = ResultSet.FETCH_UNKNOWN; + protected final BaseConnection connection; // the connection we belong to + protected final BaseStatement statement; // the statement we belong to + protected final Field fields[]; // Field metadata for this resultset. + protected final Query originalQuery; // Query we originated from + + protected final int maxRows; // Maximum rows in this resultset (might be 0). + protected final int maxFieldSize; // Maximum field size in this resultset (might be 0). + + protected Vector rows; // Current page of results. + protected int current_row = -1; // Index into 'rows' of our currrent row (0-based) + protected int row_offset; // Offset of row 0 in the actual resultset + protected byte[][] this_row; // copy of the current result row + protected SQLWarning warnings = null; // The warning chain + protected boolean wasNullFlag = false; // the flag for wasNull() + protected boolean onInsertRow = false; // are we on the insert row (for JDBC2 updatable resultsets)? + + private StringBuffer sbuf = null; + public byte[][] rowBuffer = null; // updateable rowbuffer + + private SimpleDateFormat m_tsFormat = null; + private SimpleDateFormat m_tstzFormat = null; + private SimpleDateFormat m_dateFormat = null; + + protected int fetchSize; // Current fetch size (might be 0). + protected ResultCursor cursor; // Cursor for fetching additional data. + + public abstract ResultSetMetaData getMetaData() throws SQLException; + public AbstractJdbc2ResultSet(Query originalQuery, BaseStatement statement, Field[] fields, Vector tuples, ResultCursor cursor, int maxRows, int maxFieldSize, int rsType, int rsConcurrency) throws SQLException { - super (originalQuery, statement, fields, tuples, cursor, maxRows, maxFieldSize); + this.originalQuery = originalQuery; + this.connection = (BaseConnection) statement.getConnection(); + this.statement = statement; + this.fields = fields; + this.rows = tuples; + this.cursor = cursor; + this.maxRows = maxRows; + this.maxFieldSize = maxFieldSize; this.resultsettype = rsType; this.resultsetconcurrency = rsConcurrency; } @@ -75,17 +115,56 @@ public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.Abstra protected Object internalGetObject(int columnIndex, Field field) throws SQLException { - Object result = super.internalGetObject(columnIndex, field); - if (result != null) - return result; // Handled by superclass. - - switch (getSQLType(columnIndex)) - { - case Types.ARRAY: - return getArray(columnIndex); + switch (getSQLType(columnIndex)) { + case Types.BIT: + return getBoolean(columnIndex) ? Boolean.TRUE : Boolean.FALSE; + case Types.SMALLINT: + return new Short(getShort(columnIndex)); + case Types.INTEGER: + return new Integer(getInt(columnIndex)); + case Types.BIGINT: + return new Long(getLong(columnIndex)); + case Types.NUMERIC: + return getBigDecimal + (columnIndex, (field.getMod() == -1) ? -1 : ((field.getMod() - 4) & 0xffff)); + 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); + case Types.BINARY: + case Types.VARBINARY: + return getBytes(columnIndex); + case Types.ARRAY: + return getArray(columnIndex); + + default: + String type = getPGType(columnIndex); + + // if the backend doesn't know the type then coerce to String + if (type.equals("unknown")) + return getString(columnIndex); + + // Specialized support for ref cursors is neater. + if (type.equals("refcursor")) { + // Fetch all results. + String cursorName = getString(columnIndex); + String fetchSql = "FETCH ALL IN \"" + cursorName + "\""; + ResultSet rs = connection.execSQLQuery(fetchSql); + ((AbstractJdbc2ResultSet)rs).setRefCursor(cursorName); + return rs; + } - default: - return null; + // Caller determines what to do (JDBC2 overrides in this case) + return null; } } @@ -1605,6 +1684,903 @@ public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.Abstra } } + public class CursorResultHandler implements ResultHandler { + private SQLException error; + + public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { + AbstractJdbc2ResultSet.this.rows = tuples; + AbstractJdbc2ResultSet.this.cursor = cursor; + } + + public void handleCommandStatus(String status, int updateCount, long insertOID) { + handleError(new SQLException("unexpected command status")); + } + + public void handleWarning(SQLWarning warning) { + AbstractJdbc2ResultSet.this.addWarning(warning); + } + + public void handleError(SQLException newError) { + if (error == null) + error = newError; + else + error.setNextException(newError); + } + + public void handleCompletion() throws SQLException { + if (error != null) + throw error; + } + }; + + + public BaseStatement getPGStatement() { + return statement; + } + + public StringBuffer getStringBuffer() { + return sbuf; + } + + // + // Backwards compatibility with PGRefCursorResultSet + // + + private String refCursorName; + + public String getRefCursor() { + return refCursorName; + } + + private void setRefCursor(String refCursorName) { + this.refCursorName = refCursorName; + } + + public void setFetchSize(int rows) throws SQLException + { + fetchSize = rows; + } + + public int getFetchSize() throws SQLException + { + return fetchSize; + } + + public boolean next() throws SQLException + { + if (rows == null) + throw new PSQLException("postgresql.con.closed", PSQLState.CONNECTION_DOES_NOT_EXIST); + + if (onInsertRow) + throw new PSQLException("postgresql.res.oninsertrow"); + + if (current_row+1 >= rows.size()) + { + if (cursor == null || (maxRows > 0 && row_offset + rows.size() >= maxRows)) { + current_row = rows.size(); + this_row = null; + rowBuffer = null; + return false; // End of the resultset. + } + + // Ask for some more data. + row_offset += rows.size(); // We are discarding some data. + + int fetchRows = fetchSize; + if (maxRows != 0) { + if (fetchRows == 0 || row_offset + fetchRows > maxRows) // Fetch would exceed maxRows, limit it. + fetchRows = maxRows - row_offset; + } + + // Execute the fetch and update this resultset. + connection.getQueryExecutor().fetch(cursor, new CursorResultHandler(), fetchRows); + + current_row = 0; + + // Test the new rows array. + if (rows.size() == 0) { + this_row = null; + rowBuffer = null; + return false; + } + } else { + current_row++; + } + + this_row = (byte [][])rows.elementAt(current_row); + + rowBuffer = new byte[this_row.length][]; + System.arraycopy(this_row, 0, rowBuffer, 0, this_row.length); + return true; + } + + public void close() throws SQLException + { + //release resources held (memory for tuples) + if (rows != null) + { + rows = null; + } + } + + public boolean wasNull() throws SQLException + { + return wasNullFlag; + } + + public String getString(int columnIndex) throws SQLException + { + checkResultSet( columnIndex ); + wasNullFlag = (this_row[columnIndex - 1] == null); + if (wasNullFlag) + return null; + + Encoding encoding = connection.getEncoding(); + try { + return trimString(columnIndex, encoding.decode(this_row[columnIndex-1])); + } catch (IOException ioe) { + throw new PSQLException("postgresql.con.invalidchar", PSQLState.DATA_ERROR, ioe); + } + } + + public boolean getBoolean(int columnIndex) throws SQLException + { + return toBoolean( getString(columnIndex) ); + } + + private static final BigInteger BYTEMAX = new BigInteger(Byte.toString(Byte.MAX_VALUE)); + private static final BigInteger BYTEMIN = new BigInteger(Byte.toString(Byte.MIN_VALUE)); + + public byte getByte(int columnIndex) throws SQLException + { + String s = getString(columnIndex); + + if (s != null ) + { + s= s.trim(); + if ( s.length() == 0 ) return 0; + try + { + // try the optimal parse + return Byte.parseByte(s); + } + catch (NumberFormatException e) + { + // didn't work, assume the column is not a byte + try + { + BigDecimal n = new BigDecimal(s); + BigInteger i = n.toBigInteger(); + + int gt = i.compareTo(BYTEMAX); + int lt = i.compareTo(BYTEMIN); + + if ( gt > 0 || lt < 0 ) + { + throw new PSQLException("postgresql.res.badbyte", + PSQLState. + NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + return i.byteValue(); + } + catch( NumberFormatException ex ) + { + throw new PSQLException("postgresql.res.badbyte", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + } + } + return 0; // SQL NULL + } + + private static final BigInteger SHORTMAX = new BigInteger(Short.toString(Short.MAX_VALUE)); + private static final BigInteger SHORTMIN = new BigInteger(Short.toString(Short.MIN_VALUE)); + + public short getShort(int columnIndex) throws SQLException + { + String s = getFixedString(columnIndex); + + if (s != null) + { + s = s.trim(); + try + { + return Short.parseShort(s); + } + catch (NumberFormatException e) + { + try + { + BigDecimal n = new BigDecimal(s); + BigInteger i = n.toBigInteger(); + int gt = i.compareTo(SHORTMAX); + int lt = i.compareTo(SHORTMIN); + + if ( gt > 0 || lt < 0 ) + { + throw new PSQLException("postgresql.res.badshort", + PSQLState. + NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + return i.shortValue(); + + } + catch ( NumberFormatException ne ) + { + throw new PSQLException("postgresql.res.badshort", + PSQLState. + NUMERIC_VALUE_OUT_OF_RANGE, s); + } + } + } + return 0; // SQL NULL + } + + public int getInt(int columnIndex) throws SQLException + { + return toInt( getFixedString(columnIndex) ); + } + + public long getLong(int columnIndex) throws SQLException + { + return toLong( getFixedString(columnIndex) ); + } + + public float getFloat(int columnIndex) throws SQLException + { + return toFloat( getFixedString(columnIndex) ); + } + + public double getDouble(int columnIndex) throws SQLException + { + return toDouble( getFixedString(columnIndex) ); + } + + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException + { + return toBigDecimal( getFixedString(columnIndex), scale ); + } + + /* + * Get the value of a column in the current row as a Java byte array. + * + * <p>In normal use, the bytes represent the raw values returned by the + * backend. However, if the column is an OID, then it is assumed to + * refer to a Large Object, and that object is returned as a byte array. + * + * <p><b>Be warned</b> If the large object is huge, then you may run out + * of memory. + * + * @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 + { + checkResultSet( columnIndex ); + wasNullFlag = (this_row[columnIndex - 1] == null); + if (!wasNullFlag) + { + if (fields[columnIndex -1].getFormat() == Field.BINARY_FORMAT) + { + //If the data is already binary then just return it + return this_row[columnIndex - 1]; + } + else if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports the bytea datatype for byte arrays + if (fields[columnIndex - 1].getOID() == Oid.BYTEA) + { + return trimBytes(columnIndex, PGbytea.toBytes(this_row[columnIndex - 1])); + } + else + { + return trimBytes(columnIndex, this_row[columnIndex - 1]); + } + } + else + { + //Version 7.1 and earlier supports LargeObjects for byte arrays + // Handle OID's as BLOBS + if ( fields[columnIndex - 1].getOID() == 26) + { + LargeObjectManager lom = connection.getLargeObjectAPI(); + LargeObject lob = lom.open(getInt(columnIndex)); + byte buf[] = lob.read(lob.size()); + lob.close(); + return trimBytes(columnIndex, buf); + } + else + { + return trimBytes(columnIndex, this_row[columnIndex - 1]); + } + } + } + return null; + } + + public java.sql.Date getDate(int columnIndex) throws SQLException + { + return toDate( getString(columnIndex) ); + } + + public Time getTime(int columnIndex) throws SQLException + { + checkResultSet(columnIndex); + return TimestampUtils.toTime( getString(columnIndex), getPGType(columnIndex) ); + } + + public Timestamp getTimestamp(int columnIndex) throws SQLException + { + this.checkResultSet(columnIndex); + int sqlType = getSQLType(columnIndex); + + if ( sqlType == Types.TIME ) + { + Time time = TimestampUtils.toTime(getString( columnIndex ), getPGType(columnIndex)); + return new Timestamp(time.getTime() ); + } + return TimestampUtils.toTimestamp(getString(columnIndex), getPGType(columnIndex)); + } + + public InputStream getAsciiStream(int columnIndex) throws SQLException + { + checkResultSet( columnIndex ); + wasNullFlag = (this_row[columnIndex - 1] == null); + if (wasNullFlag) + return null; + + if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports AsciiStream for all the PG text types + //As the spec/javadoc for this method indicate this is to be used for + //large text values (i.e. LONGVARCHAR) PG doesn't have a separate + //long string datatype, but with toast the text datatype is capable of + //handling very large values. Thus the implementation ends up calling + //getString() since there is no current way to stream the value from the server + try + { + return new ByteArrayInputStream(getString(columnIndex).getBytes("ASCII")); + } + catch (UnsupportedEncodingException l_uee) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); + } + } + else + { + // In 7.1 Handle as BLOBS so return the LargeObject input stream + return getBinaryStream(columnIndex); + } + } + + public InputStream getUnicodeStream(int columnIndex) throws SQLException + { + checkResultSet( columnIndex ); + wasNullFlag = (this_row[columnIndex - 1] == null); + if (wasNullFlag) + return null; + + if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports AsciiStream for all the PG text types + //As the spec/javadoc for this method indicate this is to be used for + //large text values (i.e. LONGVARCHAR) PG doesn't have a separate + //long string datatype, but with toast the text datatype is capable of + //handling very large values. Thus the implementation ends up calling + //getString() since there is no current way to stream the value from the server + try + { + return new ByteArrayInputStream(getString(columnIndex).getBytes("UTF-8")); + } + catch (UnsupportedEncodingException l_uee) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); + } + } + else + { + // In 7.1 Handle as BLOBS so return the LargeObject input stream + return getBinaryStream(columnIndex); + } + } + + public InputStream getBinaryStream(int columnIndex) throws SQLException + { + checkResultSet( columnIndex ); + wasNullFlag = (this_row[columnIndex - 1] == null); + if (wasNullFlag) + return null; + + if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports BinaryStream for all PG bytea type + //As the spec/javadoc for this method indicate this is to be used for + //large binary values (i.e. LONGVARBINARY) PG doesn't have a separate + //long binary datatype, but with toast the bytea datatype is capable of + //handling very large values. Thus the implementation ends up calling + //getBytes() since there is no current way to stream the value from the server + byte b[] = getBytes(columnIndex); + if (b != null) + return new ByteArrayInputStream(b); + } + else + { + // In 7.1 Handle as BLOBS so return the LargeObject input stream + if ( fields[columnIndex - 1].getOID() == 26) + { + LargeObjectManager lom = connection.getLargeObjectAPI(); + LargeObject lob = lom.open(getInt(columnIndex)); + return lob.getInputStream(); + } + } + return null; + } + + 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)); + } + + public SQLWarning getWarnings() throws SQLException + { + return warnings; + } + + public void clearWarnings() throws SQLException + { + warnings = null; + } + + protected void addWarning(SQLWarning warnings) + { + if (this.warnings != null) + this.warnings.setNextWarning(warnings); + else + this.warnings = warnings; + } + + public String getCursorName() throws SQLException + { + return null; + } + + /* + * Get the value of a column in the current row as a Java object + * + * <p>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. + * + * <p>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; + + checkResultSet(columnIndex); + + wasNullFlag = (this_row[columnIndex - 1] == null); + if (wasNullFlag) + return null; + + field = fields[columnIndex - 1]; + + // some fields can be null, mainly from those returned by MetaData methods + if (field == null) + { + wasNullFlag = true; + return null; + } + + Object result = internalGetObject(columnIndex, field); + if (result != null) + return result; + + return connection.getObject(getPGType(columnIndex), getString(columnIndex)); + } + + public Object getObject(String columnName) throws SQLException + { + return getObject(findColumn(columnName)); + } + + /* + * Map a ResultSet column name to a ResultSet column index + */ + public int findColumn(String columnName) throws SQLException + { + int i; + + final int flen = fields.length; + for (i = 0 ; i < flen; ++i) + if (fields[i].getColumnLabel().equalsIgnoreCase(columnName)) + return (i + 1); + throw new PSQLException ("postgresql.res.colname", null, columnName); + } + + /* + * returns the OID of a field.<p> + * It is used internally by the driver. + */ + public int getColumnOID(int field) + { + return fields[field -1].getOID(); + } + + /* + * This is used to fix get*() methods on Money fields. It should only be + * used by those methods! + * + * It converts ($##.##) to -##.## and $##.## to ##.## + */ + public String getFixedString(int col) throws SQLException + { + String s = getString(col); + + // Handle SQL Null + wasNullFlag = (this_row[col - 1] == null); + if (wasNullFlag) + return null; + + // if we don't have at least 2 characters it can't be money. + if (s.length() < 2) + return s; + + // Handle Money + if (s.charAt(0) == '(') + { + s = "-" + PGtokenizer.removePara(s).substring(1); + } + if (s.charAt(0) == '$') + { + s = s.substring(1); + } + else if (s.charAt(0) == '-' && s.charAt(1) == '$') + { + s = "-" + s.substring(2); + } + + return s; + } + + protected String getPGType( int column ) throws SQLException + { + return connection.getPGType(fields[column - 1].getOID()); + } + + protected int getSQLType( int column ) throws SQLException + { + return connection.getSQLType(fields[column - 1].getOID()); + } + + protected void checkResultSet( int column ) throws SQLException + { + if ( this_row == null ) + throw new PSQLException("postgresql.res.nextrequired"); + if ( column < 1 || column > fields.length ) + throw new PSQLException("postgresql.res.colrange", PSQLState.INVALID_PARAMETER_VALUE ); + } + + //----------------- Formatting Methods ------------------- + + public static boolean toBoolean(String s) + { + if (s != null) + { + s = s.trim(); + + if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t")) + return true; + + try + { + if (Double.valueOf(s).doubleValue()==1) + return true; + } + catch (NumberFormatException e) + { + } + } + return false; // SQL NULL + } + + private static final BigInteger INTMAX = new BigInteger(Integer.toString(Integer.MAX_VALUE)); + private static final BigInteger INTMIN = new BigInteger(Integer.toString(Integer.MIN_VALUE)); + + public static int toInt(String s) throws SQLException + { + if (s != null) + { + try + { + s = s.trim(); + return Integer.parseInt(s); + } + catch (NumberFormatException e) + { + try + { + BigDecimal n = new BigDecimal(s); + BigInteger i = n.toBigInteger(); + + int gt = i.compareTo(INTMAX); + int lt = i.compareTo(INTMIN); + + if (gt > 0 || lt < 0) + { + throw new PSQLException("postgresql.res.badint", + PSQLState. + NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + return i.intValue(); + + } + catch( NumberFormatException ne ) + { + throw new PSQLException("postgresql.res.badint", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + } + } + return 0; // SQL NULL + } + private final static BigInteger LONGMAX = new BigInteger(Long.toString(Long.MAX_VALUE)); + private final static BigInteger LONGMIN = new BigInteger(Long.toString(Long.MIN_VALUE)); + + public static long toLong(String s) throws SQLException + { + if (s != null) + { + try + { + s = s.trim(); + return Long.parseLong(s); + } + catch (NumberFormatException e) + { + try + { + BigDecimal n = new BigDecimal(s); + BigInteger i = n.toBigInteger(); + int gt = i.compareTo(LONGMAX); + int lt = i.compareTo(LONGMIN); + + if ( gt > 0 || lt < 0 ) + { + throw new PSQLException("postgresql.res.badint", + PSQLState. + NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + return i.longValue(); + } + catch( NumberFormatException ne ) + { + throw new PSQLException("postgresql.res.badint", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, + s); + } + } + } + return 0; // SQL NULL + } + + public static BigDecimal toBigDecimal(String s, int scale) throws SQLException + { + BigDecimal val; + if (s != null) + { + try + { + s = s.trim(); + val = new BigDecimal(s); + } + catch (NumberFormatException e) + { + throw new PSQLException("postgresql.res.badbigdec", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); + } + if (scale == -1) + return val; + try + { + return val.setScale(scale); + } + catch (ArithmeticException e) + { + throw new PSQLException("postgresql.res.badbigdec", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); + } + } + return null; // SQL NULL + } + + public static float toFloat(String s) throws SQLException + { + if (s != null) + { + try + { + s = s.trim(); + return Float.valueOf(s).floatValue(); + } + catch (NumberFormatException e) + { + throw new PSQLException("postgresql.res.badfloat", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); + } + } + return 0; // SQL NULL + } + + public static double toDouble(String s) throws SQLException + { + if (s != null) + { + try + { + s = s.trim(); + return Double.valueOf(s).doubleValue(); + } + catch (NumberFormatException e) + { + throw new PSQLException("postgresql.res.baddouble", + PSQLState.NUMERIC_VALUE_OUT_OF_RANGE, s); + } + } + return 0; // SQL NULL + } + + public static java.sql.Date toDate(String s) throws SQLException + { + if (s == null) + return null; + // length == 10: SQL Date + // length > 10: SQL Timestamp, assumes PGDATESTYLE=ISO + try + { + s = s.trim(); + return java.sql.Date.valueOf((s.length() == 10) ? s : s.substring(0, 10)); + } + catch (NumberFormatException e) + { + throw new PSQLException("postgresql.res.baddate",PSQLState.BAD_DATETIME_FORMAT, s); + } + } + + private boolean isColumnTrimmable(int columnIndex) throws SQLException + { + switch (getSQLType(columnIndex)) + { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + return true; + } + return false; + } + + private byte[] trimBytes(int p_columnIndex, byte[] p_bytes) throws SQLException + { + //we need to trim if maxsize is set and the length is greater than maxsize and the + //type of this column is a candidate for trimming + if (maxFieldSize > 0 && p_bytes.length > maxFieldSize && isColumnTrimmable(p_columnIndex)) + { + byte[] l_bytes = new byte[maxFieldSize]; + System.arraycopy (p_bytes, 0, l_bytes, 0, maxFieldSize); + return l_bytes; + } + else + { + return p_bytes; + } + } + + private String trimString(int p_columnIndex, String p_string) throws SQLException + { + //we need to trim if maxsize is set and the length is greater than maxsize and the + //type of this column is a candidate for trimming + if (maxFieldSize > 0 && p_string.length() > maxFieldSize && isColumnTrimmable(p_columnIndex)) + { + return p_string.substring(0, maxFieldSize); + } + else + { + return p_string; + } + } + protected void updateValue(int columnIndex, Object value) throws SQLException { if ( !isUpdateable() ) { diff --git a/org/postgresql/jdbc2/AbstractJdbc2ResultSetMetaData.java b/org/postgresql/jdbc2/AbstractJdbc2ResultSetMetaData.java index 895b950e..e5642330 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2ResultSetMetaData.java +++ b/org/postgresql/jdbc2/AbstractJdbc2ResultSetMetaData.java @@ -3,24 +3,587 @@ package org.postgresql.jdbc2; import org.postgresql.core.*; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import java.sql.SQLException; -import java.sql.Types; +import java.sql.*; import java.util.Vector; +import java.util.Hashtable; -public abstract class AbstractJdbc2ResultSetMetaData extends org.postgresql.jdbc1.AbstractJdbc1ResultSetMetaData +public abstract class AbstractJdbc2ResultSetMetaData { + protected final BaseConnection connection; + protected final Field[] fields; + + private Hashtable tableNameCache; + private Hashtable schemaNameCache; + /* * 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 AbstractJdbc2ResultSetMetaData(BaseConnection connection, Field[] fields) { - super(connection, fields); + this.connection = connection; + 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 = getSQLType(column); + + 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 = getSQLType(column); + + // 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 = getPGType(column); + + return type_name.equals("cash") || type_name.equals("money"); + } + + /* + * Indicates the nullability of values in the designated column. + * + * @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 + { + Field field = getField(column); + return field.getNullable(connection); + } + + /* + * 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 = getSQLType(column); + + 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 + { + Field f = getField(column); + String type_name = getPGType(column); + int typmod = f.getMod(); + + // I looked at other JDBC implementations and couldn't find a consistent + // interpretation of the "display size" for numeric values, so this is our's + // FIXME: currently, only types with a SQL92 or SQL3 pendant are implemented - jens@jens.de + + // fixed length data types + if (type_name.equals( "int2" )) + return 6; // -32768 to +32768 (5 digits and a sign) + if (type_name.equals( "int4" ) + || type_name.equals( "oid" )) + return 11; // -2147483648 to +2147483647 + if (type_name.equals( "int8" )) + return 20; // -9223372036854775808 to +9223372036854775807 + if (type_name.equals( "money" )) + return 12; // MONEY = DECIMAL(9,2) + if (type_name.equals( "float4" )) + return 11; // i checked it out ans wasn't able to produce more than 11 digits + if (type_name.equals( "float8" )) + return 20; // dito, 20 + if (type_name.equals( "char" )) + return 1; + if (type_name.equals( "bool" )) + return 1; + + int secondSize; + switch(typmod) { + case 0: + secondSize = 0; + break; + case -1: + // six digits plus the decimal point + secondSize = 7; + break; + default: + // with an odd scale an even number of digits + // are always show so timestamp(1) will print + // two fractional digits. + secondSize = typmod + (typmod%2) + 1; + break; + } + + if (type_name.equals( "date" )) + return 13; // "01/01/4713 BC" - "31/12/32767" + + // If we knew the timezone we could avoid having to possibly + // account for fractional hour offsets (which adds three chars). + // + // Also the range of timestamp types is not exactly clear. + // 4 digits is the common case for a year, but there are + // version/compilation dependencies on the exact date ranges, + // (notably --enable-integer-datetimes), but for now we'll + // just ignore them and assume that a year is four digits. + // + if (type_name.equals( "time" )) + return 8 + secondSize; // 00:00:00 + seconds + if (type_name.equals( "timetz" )) + return 8 + secondSize + 6; // 00:00.00 + .000000 + -00:00 + if (type_name.equals( "timestamp" )) + return 19 + secondSize; // 0000-00-00 00:00:00 + .000000; + if (type_name.equals( "timestamptz" )) + return 19 + secondSize + 6; // 0000-00-00 00:00:00 + .000000 + -00:00; + + // variable length fields + typmod -= 4; + if (type_name.equals( "bpchar" ) + || type_name.equals( "varchar" )) + return typmod; // VARHDRSZ=sizeof(int32)=4 + if (type_name.equals( "numeric" )) + return ( (typmod >> 16) & 0xffff ) + + 1 + ( typmod & 0xffff ); // DECIMAL(p,s) = (p digits).(s digits) + + // if we don't know better + return f.getLength(); + } + + /* + * @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 + { + Field f = getField(column); + if (f != null) + return f.getColumnLabel(); + return "field" + 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 database access error occurs + */ + public String getColumnName(int column) throws SQLException + { + Field field = getField(column); + return field.getColumnName(connection); + } + + /* + * @param column the first column is 1, the second is 2... + * @return the Schema Name + * @exception SQLException if a database access error occurs + */ + public String getSchemaName(int column) throws SQLException + { + Field field = getField(column); + if (field.getTableOid() == 0) + { + return ""; + } + Integer tableOid = new Integer(field.getTableOid()); + if (schemaNameCache == null) + { + schemaNameCache = new Hashtable(); + } + String schemaName = (String) schemaNameCache.get(tableOid); + if (schemaName != null) + { + return schemaName; + } else + { + ResultSet res = null; + PreparedStatement ps = null; + try + { + String sql = "SELECT n.nspname FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n WHERE n.oid = c.relnamespace AND c.oid = ?;"; + ps = ((Connection)connection).prepareStatement(sql); + ps.setInt(1, tableOid.intValue()); + res = ps.executeQuery(); + schemaName = ""; + if (res.next()) + { + schemaName = res.getString(1); + } + schemaNameCache.put(tableOid, schemaName); + return schemaName; + } finally + { + if (res != null) + res.close(); + if (ps != null) + ps.close(); + } + } + } + + /* + * 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 = getSQLType(column); + + 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; + case Types.VARCHAR: + return 0; + case Types.NUMERIC: + Field f = getField(column); + if (f != null) { + // no specified precision or scale + if (f.getMod() == -1) { + return 1000; + } + return ((0xFFFF0000)&f.getMod()) >> 16; + } else { + return 0; + } + default: + return 0; + } + } + + /* + * 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 = getSQLType(column); + + 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; + case Types.VARCHAR: + return 0; + case Types.NUMERIC: + Field f = getField(column); + if (f != null) { + // no specified precision or scale + if (f.getMod() == -1) { + return 1000; + } + return (((0x0000FFFF)&f.getMod()) - 4); + } else { + return 0; + } + case Types.TIME: + case Types.TIMESTAMP: + int typmod = -1; + + Field fld = getField(column); + if (fld != null) + typmod = fld.getMod(); + + if (typmod == -1) + return 6; + + return typmod; + default: + return 0; + } + } + + /* + * @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 + { + Field field = getField(column); + if (field.getTableOid() == 0) + { + return ""; + } + Integer tableOid = new Integer(field.getTableOid()); + if (tableNameCache == null) + { + tableNameCache = new Hashtable(); + } + String tableName = (String) tableNameCache.get(tableOid); + if (tableName != null) + { + return tableName; + } else + { + ResultSet res = null; + PreparedStatement ps = null; + try + { + ps = ((Connection)connection).prepareStatement("SELECT relname FROM pg_catalog.pg_class WHERE oid = ?"); + ps.setInt(1, tableOid.intValue()); + res = ps.executeQuery(); + tableName = ""; + if (res.next()) + { + tableName = res.getString(1); + } + tableNameCache.put(tableOid, tableName); + return tableName; + } finally + { + if (res != null) + res.close(); + if (ps != null) + ps.close(); + } + } + } + + /* + * 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 + { + return ""; + } + + /* + * 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 org.postgresql.Field#getSQLType + * @see java.sql.Types + */ + public int getColumnType(int column) throws SQLException + { + return getSQLType(column); + } + + /* + * 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 getPGType(column); + } + + /* + * 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 + { + return !isReadOnly(column); + } + + /* + * 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 false; + } + + // ******************************************************** + // 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 + */ + protected Field getField(int columnIndex) throws SQLException + { + if (columnIndex < 1 || columnIndex > fields.length) + throw new PSQLException("postgresql.res.colrange", PSQLState.INVALID_PARAMETER_VALUE); + return fields[columnIndex - 1]; + } + + protected String getPGType(int columnIndex) throws SQLException + { + return connection.getPGType(getField(columnIndex).getOID()); + } + + protected int getSQLType(int columnIndex) throws SQLException + { + return connection.getSQLType(getField(columnIndex).getOID()); + } + + // ** JDBC 2 Extensions ** // This can hook into our PG_Object mechanism @@ -41,38 +604,6 @@ public abstract class AbstractJdbc2ResultSetMetaData extends org.postgresql.jdbc */ public String getColumnClassName(int column) throws SQLException { - /* - The following data type mapping came from ../Field.java. - - "int2", - "int4","oid", - "int8", - "cash","money", - "numeric", - "float4", - "float8", - "bpchar","char","char2","char4","char8","char16", - "varchar","text","name","filename", - "bool", - "date", - "time", - "abstime","timestamp" - - Types.SMALLINT, - Types.INTEGER,Types.INTEGER, - Types.BIGINT, - Types.DOUBLE,Types.DOUBLE, - Types.NUMERIC, - Types.REAL, - Types.DOUBLE, - Types.CHAR,Types.CHAR,Types.CHAR,Types.CHAR,Types.CHAR,Types.CHAR, - Types.VARCHAR,Types.VARCHAR,Types.VARCHAR,Types.VARCHAR, - Types.BIT, - Types.DATE, - Types.TIME, - Types.TIMESTAMP,Types.TIMESTAMP - */ - Field field = getField(column); int sql_type = getSQLType(column); diff --git a/org/postgresql/jdbc2/AbstractJdbc2Statement.java b/org/postgresql/jdbc2/AbstractJdbc2Statement.java index 9b2c3d2c..3fcc51b7 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2Statement.java +++ b/org/postgresql/jdbc2/AbstractJdbc2Statement.java @@ -8,16 +8,17 @@ import java.util.ArrayList; import java.util.Vector; import org.postgresql.Driver; import org.postgresql.largeobject.*; +import org.postgresql.core.*; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.postgresql.core.*; +import org.postgresql.util.PGbytea; +import org.postgresql.util.PGobject; /* $PostgreSQL: /cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v 1.17 2003/09/09 10:49:16 barry Exp $ - * This class defines methods of the jdbc2 specification. This class extends - * org.postgresql.jdbc1.AbstractJdbc1Statement which provides the jdbc1 - * methods. The real Statement class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Statement + * This class defines methods of the jdbc2 specification. + * The real Statement class (for jdbc2) is org.postgresql.jdbc2.Jdbc2Statement */ -public abstract class AbstractJdbc2Statement extends org.postgresql.jdbc1.AbstractJdbc1Statement +public abstract class AbstractJdbc2Statement implements BaseStatement { protected ArrayList batchStatements = null; protected ArrayList batchParameters = null; @@ -25,25 +26,2184 @@ public abstract class AbstractJdbc2Statement extends org.postgresql.jdbc1.Abstra protected final int concurrency; // is it updateable or not? (ResultSet.CONCUR_xxx) protected int fetchdirection = ResultSet.FETCH_FORWARD; // fetch direction hint (currently ignored) + // The connection who created us + protected BaseConnection connection; + + /** The warnings chain. */ + protected SQLWarning warnings = null; + + /** Maximum number of rows to return, 0 = unlimited */ + protected int maxrows = 0; + + /** Number of rows to get in a batch. */ + protected int fetchSize = 0; + + /** Timeout (in seconds) for a query (not used) */ + protected int timeout = 0; + + protected boolean replaceProcessingEnabled = true; + + /** The current results. */ + protected ResultWrapper result = null; + + /** The first unclosed result. */ + protected ResultWrapper firstUnclosedResult = null; + + // Static variables for parsing SQL when replaceProcessing is true. + private static final short IN_SQLCODE = 0; + private static final short IN_STRING = 1; + private static final short BACKSLASH = 2; + private static final short ESC_TIMEDATE = 3; + + // Some performance caches + private StringBuffer sbuf = new StringBuffer(32); + + protected final Query preparedQuery; // Query fragments for prepared statement. + protected final ParameterList preparedParameters; // Parameter values for prepared statement. + + protected int m_prepareThreshold; // Reuse threshold to enable use of PREPARE + protected int m_useCount = 0; // Number of times this statement has been used + + //Used by the callablestatement style methods + private boolean isFunction; + // functionReturnType contains the user supplied value to check + // testReturn contains a modified version to make it easier to + // check the getXXX methods.. + private int functionReturnType; + private int testReturn; + // returnTypeSet is true when a proper call to registerOutParameter has been made + private boolean returnTypeSet; + protected Object callResult; + protected int maxfieldSize = 0; + + public ResultSet createDriverResultSet(Field[] fields, Vector tuples) + throws SQLException + { + return createResultSet(null,fields,tuples,null); + } + public AbstractJdbc2Statement (AbstractJdbc2Connection c, int rsType, int rsConcurrency) throws SQLException { - super(c); + this.connection = c; + this.preparedQuery = null; + this.preparedParameters = null; resultsettype = rsType; concurrency = rsConcurrency; } public AbstractJdbc2Statement(AbstractJdbc2Connection connection, String sql, boolean isCallable, int rsType, int rsConcurrency) throws SQLException { - super(connection, sql, isCallable); + this.connection = connection; + + String parsed_sql = replaceProcessing(sql); + if (isCallable) + parsed_sql = modifyJdbcCall(parsed_sql); + + this.preparedQuery = connection.getQueryExecutor().createParameterizedQuery(parsed_sql); + this.preparedParameters = preparedQuery.createParameterList(); + resultsettype = rsType; concurrency = rsConcurrency; } - // Overriddes JDBC1 implementation. + public abstract ResultSet createResultSet(Query originalQuery, Field[] fields, Vector tuples, ResultCursor cursor) + throws SQLException; + + + public BaseConnection getPGConnection() { + return connection; + } + + public String getFetchingCursorName() { + return null; + } + + public int getFetchSize() { + return fetchSize; + } + protected boolean wantsScrollableResultSet() { return resultsettype != ResultSet.TYPE_FORWARD_ONLY; } + // + // ResultHandler implementations for updates, queries, and either-or. + // + + public class StatementResultHandler implements ResultHandler { + private SQLException error; + private ResultWrapper results; + + ResultWrapper getResults() { return results; } + + private void append(ResultWrapper newResult) { + if (results == null) + results = newResult; + else + results.append(newResult); + } + + public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { + try { + ResultSet rs = AbstractJdbc2Statement.this.createResultSet(fromQuery, fields, tuples, cursor); + append(new ResultWrapper(rs)); + } catch (SQLException e) { + handleError(e); + } + } + + public void handleCommandStatus(String status, int updateCount, long insertOID) { + append(new ResultWrapper(updateCount, insertOID)); + } + + public void handleWarning(SQLWarning warning) { + AbstractJdbc2Statement.this.addWarning(warning); + } + + public void handleError(SQLException newError) { + if (error == null) + error = newError; + else + error.setNextException(newError); + } + + public void handleCompletion() throws SQLException { + if (error != null) + throw error; + } + } + + /* + * 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 p_sql) throws SQLException + { + if (preparedQuery != null) + throw new PSQLException("postgresql.stmt.wrongstatementtype"); + + if (!executeWithFlags(p_sql, 0)) + throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); + + if (result.getNext() != null) + throw new PSQLException("postgresql.stat.multi"); + + return (ResultSet)result.getResultSet(); + } + + /* + * 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 + { + if (!executeWithFlags(0)) + throw new PSQLException("postgresql.stat.noresult", PSQLState.NO_DATA); + + if (result.getNext() != null) + throw new PSQLException("postgresql.stat.multi"); + + return (ResultSet) result.getResultSet(); + } + + /* + * 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 p_sql) throws SQLException + { + if (preparedQuery != null) + throw new PSQLException("postgresql.stmt.wrongstatementtype"); + + if (executeWithFlags(p_sql, QueryExecutor.QUERY_NO_RESULTS)) + throw new PSQLException("postgresql.stat.result"); + + return getUpdateCount(); + } + + /* + * 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 + { + if (executeWithFlags(QueryExecutor.QUERY_NO_RESULTS)) + throw new PSQLException("postgresql.stat.result"); + + return getUpdateCount(); + } + + /* + * 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 p_sql) throws SQLException + { + if (preparedQuery != null) + throw new PSQLException("postgresql.stmt.wrongstatementtype"); + + return executeWithFlags(p_sql, 0); + } + + public boolean executeWithFlags(String p_sql, int flags) throws SQLException + { + checkClosed(); + p_sql = replaceProcessing(p_sql); + Query simpleQuery = connection.getQueryExecutor().createSimpleQuery(p_sql); + execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags); + return (result != null && result.getResultSet() != null); + } + + public boolean execute() throws SQLException + { + return executeWithFlags(0); + } + + private boolean executeWithFlags(int flags) throws SQLException + { + checkClosed(); + if (isFunction && !returnTypeSet) + throw new PSQLException("postgresql.call.noreturntype", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); + + execute(preparedQuery, preparedParameters, flags); + + // If we are executing a callable statement function set the return data + if (isFunction) + { + if (result == null || result.getResultSet() == null) + throw new PSQLException("postgresql.call.noreturnval", PSQLState.NO_DATA); + + ResultSet rs = result.getResultSet(); + if (!rs.next()) + throw new PSQLException ("postgresql.call.noreturnval", PSQLState.NO_DATA); + + callResult = rs.getObject(1); + int columnType = rs.getMetaData().getColumnType(1); + if (columnType != functionReturnType) + throw new PSQLException ("postgresql.call.wrongrtntype", PSQLState.DATA_TYPE_MISMATCH, null, + new Object[]{ + "java.sql.Types=" + columnType, "java.sql.Types=" + functionReturnType }); + + rs.close(); + result = null; + return false; + } + + return (result != null && result.getResultSet() != null); + } + + protected void execute(Query queryToExecute, ParameterList queryParameters, int flags) throws SQLException { + // Close any existing resultsets associated with this statement. + while (firstUnclosedResult != null) { + if (firstUnclosedResult.getResultSet() != null) + firstUnclosedResult.getResultSet().close(); + firstUnclosedResult = firstUnclosedResult.getNext(); + } + + + // Enable cursor-based resultset if possible. + if (fetchSize > 0 && !wantsScrollableResultSet()) + flags |= QueryExecutor.QUERY_FORWARD_CURSOR; + + // Only use named statements after we hit the threshold + if (preparedQuery != null) { + ++m_useCount; // We used this statement once more. + if (m_prepareThreshold == 0 || m_useCount < m_prepareThreshold) + flags |= QueryExecutor.QUERY_ONESHOT; + } + + if (connection.getAutoCommit()) + flags |= QueryExecutor.QUERY_SUPPRESS_BEGIN; + + StatementResultHandler handler = new StatementResultHandler(); + result = null; + connection.getQueryExecutor().execute(queryToExecute, + queryParameters, + handler, + maxrows, + fetchSize, + flags); + result = firstUnclosedResult = handler.getResults(); + } + + /* + * 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. + * + * <p><B>Note:</B> 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. + * + * @param name the new cursor name + * @exception SQLException if a database access error occurs + */ + public void setCursorName(String name) throws SQLException + { + checkClosed(); + // No-op. + } + + private boolean isClosed = false; + + /* + * 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 + { + checkClosed(); + if (result == null) + return -1; + + if (isFunction) + return 1; + + if (result.getResultSet() != null) + return -1; + + return result.getUpdateCount(); + } + + /* + * 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 + { + if (result == null) + return false; + + result = result.getNext(); + + // Close preceding resultsets. + while (firstUnclosedResult != result) { + if (firstUnclosedResult.getResultSet() != null) + firstUnclosedResult.getResultSet().close(); + firstUnclosedResult = firstUnclosedResult.getNext(); + } + + return (result != null && result.getResultSet() != null); + } + + /* + * 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 + { + checkClosed(); + 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 + { + checkClosed(); + if (max<0) throw new PSQLException("postgresql.input.rows.gt0"); + 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 + { + checkClosed(); + replaceProcessingEnabled = 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 + { + checkClosed(); + 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 + { + checkClosed(); + if (seconds<0) throw new PSQLException("postgresql.input.query.gt0"); + timeout = seconds; + } + + /** + * This adds a warning to the warning chain. + * @param warn warning to add + */ + public void addWarning(SQLWarning warn) + { + if (warnings != null) + warnings.setNextWarning(warn); + else + warnings = warn; + } + + /* + * 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. + * + * <p>The Warning chain is automatically cleared each time a statement + * is (re)executed. + * + * <p><B>Note:</B> 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; + } + + /* + * 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 maxfieldSize; + } + + /* + * Sets the maxFieldSize + * + * @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 + { + checkClosed(); + if (max < 0) throw new PSQLException("postgresql.input.field.gt0"); + maxfieldSize = max; + } + + /* + * After this call, getWarnings returns null until a new warning + * is reported for this Statement. + * + * @exception SQLException if a database access error occurs + */ + public void clearWarnings() throws SQLException + { + warnings = null; + } + + /* + * 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 + { + checkClosed(); + + if (result == null) + return null; + + return (ResultSet) result.getResultSet(); + } + + /* + * 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. + * + * <p><B>Note:</B> 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 + { + // closing an already closed Statement is a no-op. + if (isClosed) + return; + + // Force the ResultSet(s) to close + while (firstUnclosedResult != null) { + if (firstUnclosedResult.getResultSet() != null) + firstUnclosedResult.getResultSet().close(); + firstUnclosedResult = firstUnclosedResult.getNext(); + } + + + if (preparedQuery != null) + preparedQuery.close(); + + // Disasociate it from us + result = firstUnclosedResult = null; + isClosed = true; + } + + /** + * This finalizer ensures that statements that have allocated server-side + * resources free them when they become unreferenced. + */ + protected void finalize() { + try { close(); } + catch (SQLException e) {} + } + + /* + * Filter the SQL string of Java SQL Escape clauses. + * + * Currently implemented Escape clauses are those mentioned in 11.3 + * in the specification. Basically we look through the sql string for + * {d xxx}, {t xxx} or {ts xxx} in non-string sql code. When we find + * them, we just strip the escape part leaving only the xxx part. + * So, something like "select * from x where d={d '2001-10-09'}" would + * return "select * from x where d= '2001-10-09'". + */ + protected String replaceProcessing(String p_sql) + { + if (replaceProcessingEnabled) + { + // Since escape codes can only appear in SQL CODE, we keep track + // of if we enter a string or not. + StringBuffer newsql = new StringBuffer(p_sql.length()); + short state = IN_SQLCODE; + + int i = -1; + int len = p_sql.length(); + while (++i < len) + { + char c = p_sql.charAt(i); + switch (state) + { + case IN_SQLCODE: + if (c == '\'') // start of a string? + state = IN_STRING; + else if (c == '{') // start of an escape code? + if (i + 1 < len) + { + char next = p_sql.charAt(i + 1); + if (next == 'd') + { + state = ESC_TIMEDATE; + i++; + break; + } + else if (next == 't') + { + state = ESC_TIMEDATE; + i += (i + 2 < len && p_sql.charAt(i + 2) == 's') ? 2 : 1; + break; + } + } + newsql.append(c); + break; + + case IN_STRING: + if (c == '\'') // end of string? + state = IN_SQLCODE; + else if (c == '\\') // a backslash? + state = BACKSLASH; + + newsql.append(c); + break; + + case BACKSLASH: + state = IN_STRING; + + newsql.append(c); + break; + + case ESC_TIMEDATE: + if (c == '}') + state = IN_SQLCODE; // end of escape code. + else + newsql.append(c); + break; + } // end switch + } + + return newsql.toString(); + } + else + { + return p_sql; + } + } + + /* + * + * The following methods are postgres extensions and are defined + * in the interface BaseStatement + * + */ + + /* + * Returns the Last inserted/updated oid. Deprecated in 7.2 because + * range of OID values is greater than a java signed int. + * @deprecated Replaced by getLastOID in 7.2 + */ + public int getInsertedOID() throws SQLException + { + checkClosed(); + if (result == null) + return 0; + return (int) result.getInsertOID(); + } + + /* + * Returns the Last inserted/updated oid. + * @return OID of last insert + * @since 7.2 + */ + public long getLastOID() throws SQLException + { + checkClosed(); + if (result == null) + return 0; + return result.getInsertOID(); + } + + /* + * Set a parameter to SQL NULL + * + * <p><B>Note:</B> 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 + { + checkClosed(); + + int oid; + switch (sqlType) + { + case Types.INTEGER: + oid = Oid.INT4; + break; + case Types.TINYINT: + case Types.SMALLINT: + oid = Oid.INT2; + break; + case Types.BIGINT: + oid = Oid.INT8; + break; + case Types.REAL: + case Types.FLOAT: + oid = Oid.FLOAT4; + break; + case Types.DOUBLE: + oid = Oid.FLOAT8; + break; + case Types.DECIMAL: + case Types.NUMERIC: + oid = Oid.NUMERIC; + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + oid = Oid.TEXT; + break; + case Types.DATE: + oid = Oid.DATE; + break; + case Types.TIME: + oid = Oid.TIME; + break; + case Types.TIMESTAMP: + oid = Oid.TIMESTAMPTZ; + break; + case Types.BIT: + oid = Oid.BOOL; + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + oid = Oid.BYTEA; + break; + case Types.OTHER: + default: + oid = 0; + break; + } + + preparedParameters.setNull(adjustParamIndex(parameterIndex), oid); + } + + /* + * 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 + { + checkClosed(); + bindString(parameterIndex, x ? "1" : "0", Oid.BOOL); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Integer.toString(x), Oid.INT2); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Long.toString(x), Oid.INT8); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Float.toString(x), Oid.FLOAT4); + } + + /* + * 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 + { + checkClosed(); + bindLiteral(parameterIndex, Double.toString(x), Oid.FLOAT8); + } + + /* + * 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 + { + checkClosed(); + if (x == null) + setNull(parameterIndex, Types.DECIMAL); + else + { + bindLiteral(parameterIndex, x.toString(), Oid.NUMERIC); + } + } + + /* + * 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 + { + checkClosed(); + setString(parameterIndex, x, Oid.TEXT); + } + + protected void setString(int parameterIndex, String x, int oid) throws SQLException + { + // if the passed string is null, then set this column to null + checkClosed(); + if (x == null) + preparedParameters.setNull(adjustParamIndex(parameterIndex), oid); + else + bindString(parameterIndex, x, oid); + } + + /* + * 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. + * + * <p>Implementation note: + * <br>With org.postgresql, this creates a large object, and stores the + * objects oid in this column. + * + * @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 + { + checkClosed(); + + if (null == x) { + setNull(parameterIndex, Types.VARBINARY); + return; + } + + if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports the bytea datatype for byte arrays + byte[] copy = new byte[x.length]; + System.arraycopy(x, 0, copy, 0, x.length); + preparedParameters.setBytea(adjustParamIndex(parameterIndex), copy, 0, x.length); + } + else + { + //Version 7.1 and earlier support done as LargeObjects + LargeObjectManager lom = connection.getLargeObjectAPI(); + int oid = lom.create(); + LargeObject lob = lom.open(oid); + lob.write(x); + lob.close(); + setInt(parameterIndex, oid); + } + } + + /* + * 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 + { + checkClosed(); + if (null == x) + { + setNull(parameterIndex, Types.DATE); + } + else + { + bindString(parameterIndex, x.toString(), Oid.DATE); + } + } + + /* + * 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 + { + checkClosed(); + if (null == x) + { + setNull(parameterIndex, Types.TIME); + } + else + { + bindString(parameterIndex, x.toString(), Oid.TIME); + } + } + + /* + * 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 + { + checkClosed(); + if (null == x) + { + setNull(parameterIndex, Types.TIMESTAMP); + } + else + { + // Use the shared StringBuffer + synchronized (sbuf) + { + sbuf.setLength(0); + sbuf.ensureCapacity(32); + //format the timestamp + //we do our own formating so that we can get a format + //that works with both timestamp with time zone and + //timestamp without time zone datatypes. + //The format is '2002-01-01 23:59:59.123456-0130' + //we need to include the local time and timezone offset + //so that timestamp without time zone works correctly + int l_year = x.getYear() + 1900; + + // always use four digits for the year so very + // early years, like 2, don't get misinterpreted + int l_yearlen = String.valueOf(l_year).length(); + for (int i=4; i>l_yearlen; i--) { + sbuf.append("0"); + } + + sbuf.append(l_year); + sbuf.append('-'); + int l_month = x.getMonth() + 1; + if (l_month < 10) + sbuf.append('0'); + sbuf.append(l_month); + sbuf.append('-'); + int l_day = x.getDate(); + if (l_day < 10) + sbuf.append('0'); + sbuf.append(l_day); + sbuf.append(' '); + int l_hours = x.getHours(); + if (l_hours < 10) + sbuf.append('0'); + sbuf.append(l_hours); + sbuf.append(':'); + int l_minutes = x.getMinutes(); + if (l_minutes < 10) + sbuf.append('0'); + sbuf.append(l_minutes); + sbuf.append(':'); + int l_seconds = x.getSeconds(); + if (l_seconds < 10) + sbuf.append('0'); + sbuf.append(l_seconds); + // Make decimal from nanos. + char[] l_decimal = {'0', '0', '0', '0', '0', '0', '0', '0', '0'}; + char[] l_nanos = Integer.toString(x.getNanos()).toCharArray(); + System.arraycopy(l_nanos, 0, l_decimal, l_decimal.length - l_nanos.length, l_nanos.length); + sbuf.append('.'); + if (connection.haveMinimumServerVersion("7.2")) + { + sbuf.append(l_decimal, 0, 6); + } + else + { + // Because 7.1 include bug that "hh:mm:59.999" becomes "hh:mm:60.00". + sbuf.append(l_decimal, 0, 2); + } + //add timezone offset + int l_offset = -(x.getTimezoneOffset()); + int l_houros = l_offset / 60; + if (l_houros >= 0) + { + sbuf.append('+'); + } + else + { + sbuf.append('-'); + } + if (l_houros > -10 && l_houros < 10) + sbuf.append('0'); + if (l_houros >= 0) + { + sbuf.append(l_houros); + } + else + { + sbuf.append(-l_houros); + } + int l_minos = l_offset - (l_houros * 60); + if (l_minos != 0) + { + if (l_minos > -10 && l_minos < 10) + sbuf.append('0'); + if (l_minos >= 0) + { + sbuf.append(l_minos); + } + else + { + sbuf.append(-l_minos); + } + } + bindString(parameterIndex, sbuf.toString(), Oid.TIMESTAMPTZ); + } + + } + } + + private void setCharacterStreamPost71(int parameterIndex, InputStream x, int length, String encoding) throws SQLException + { + + if (x == null) + { + setNull(parameterIndex, Types.VARCHAR); + return; + } + + //Version 7.2 supports AsciiStream for all PG text types (char, varchar, text) + //As the spec/javadoc for this method indicate this is to be used for + //large String values (i.e. LONGVARCHAR) PG doesn't have a separate + //long varchar datatype, but with toast all text datatypes are capable of + //handling very large values. Thus the implementation ends up calling + //setString() since there is no current way to stream the value to the server + try + { + InputStreamReader l_inStream = new InputStreamReader(x, encoding); + char[] l_chars = new char[length]; + int l_charsRead = 0; + while (true) + { + int n = l_inStream.read(l_chars, l_charsRead, length - l_charsRead); + if (n == -1) + break; + + l_charsRead += n; + + if (l_charsRead == length) + break; + } + + setString(parameterIndex, new String(l_chars, 0, l_charsRead), Oid.TEXT); + } + catch (UnsupportedEncodingException l_uee) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_uee); + } + catch (IOException l_ioe) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, l_ioe); + } + } + + /* + * 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. + * + * <P><B>Note:</B> 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 + { + checkClosed(); + if (connection.haveMinimumCompatibleVersion("7.2")) + { + setCharacterStreamPost71(parameterIndex, x, length, "ASCII"); + } + else + { + //Version 7.1 supported only LargeObjects by treating everything + //as binary data + 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. + * + * <P><B>Note:</B> 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 + { + checkClosed(); + if (connection.haveMinimumCompatibleVersion("7.2")) + { + setCharacterStreamPost71(parameterIndex, x, length, "UTF-8"); + } + else + { + //Version 7.1 supported only LargeObjects by treating everything + //as binary data + 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. + * + * <P><B>Note:</B> 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 + { + checkClosed(); + + if (x == null) { + setNull(parameterIndex, Types.VARBINARY); + return; + } + + if (connection.haveMinimumCompatibleVersion("7.2")) + { + //Version 7.2 supports BinaryStream for for the PG bytea type + //As the spec/javadoc for this method indicate this is to be used for + //large binary values (i.e. LONGVARBINARY) PG doesn't have a separate + //long binary datatype, but with toast the bytea datatype is capable of + //handling very large values. + + preparedParameters.setBytea(adjustParamIndex(parameterIndex), x, length); + } + else + { + //Version 7.1 only supported streams for LargeObjects + //but the jdbc spec indicates that streams should be + //available for LONGVARBINARY instead + LargeObjectManager lom = connection.getLargeObjectAPI(); + int oid = lom.create(); + LargeObject lob = lom.open(oid); + OutputStream los = lob.getOutputStream(); + try + { + // could be buffered, but then the OutputStream returned by LargeObject + // is buffered internally anyhow, so there would be no performance + // boost gained, if anything it would be worse! + int c = x.read(); + int p = 0; + while (c > -1 && p < length) + { + los.write(c); + c = x.read(); + p++; + } + los.close(); + } + catch (IOException se) + { + throw new PSQLException("postgresql.unusual", PSQLState.UNEXPECTED_ERROR, se); + } + // lob is closed by the stream so don't call lob.close() + setInt(parameterIndex, oid); + } + } + + + /* + * 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 + { + preparedParameters.clear(); + } + + // Helper method that extracts numeric values from an arbitary Object. + private String numericValueOf(Object x) + { + if (x instanceof Boolean) + return ((Boolean)x).booleanValue() ? "1" :"0"; + else if (x instanceof Integer || x instanceof Long || + x instanceof Double || x instanceof Short || + x instanceof Number || x instanceof Float) + return x.toString(); + else + //ensure the value is a valid numeric value to avoid + //sql injection attacks + return new BigDecimal(x.toString()).toString(); + } + + /* + * Set the value of a parameter using an object; use the java.lang + * equivalent objects for integral values. + * + * <P>The given Java object will be converted to the targetSqlType before + * being sent to the database. + * + * <P>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 + { + checkClosed(); + if (x == null) + { + setNull(parameterIndex, targetSqlType); + return ; + } + switch (targetSqlType) + { + case Types.INTEGER: + bindLiteral(parameterIndex, numericValueOf(x), Oid.INT4); + break; + case Types.TINYINT: + case Types.SMALLINT: + bindLiteral(parameterIndex, numericValueOf(x), Oid.INT2); + break; + case Types.BIGINT: + bindLiteral(parameterIndex, numericValueOf(x), Oid.INT8); + break; + case Types.REAL: + case Types.FLOAT: + bindLiteral(parameterIndex, numericValueOf(x), Oid.FLOAT4); + break; + case Types.DOUBLE: + bindLiteral(parameterIndex, numericValueOf(x), Oid.FLOAT8); + break; + case Types.DECIMAL: + case Types.NUMERIC: + bindLiteral(parameterIndex, numericValueOf(x), Oid.NUMERIC); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + case Types.DATE: + if (x instanceof java.sql.Date) + setDate(parameterIndex, (java.sql.Date)x); + else + { + java.sql.Date tmpd = (x instanceof java.util.Date) ? new java.sql.Date(((java.util.Date)x).getTime()) : dateFromString(x.toString()); + setDate(parameterIndex, tmpd); + } + break; + case Types.TIME: + if (x instanceof java.sql.Time) + setTime(parameterIndex, (java.sql.Time)x); + else + { + java.sql.Time tmpt = (x instanceof java.util.Date) ? new java.sql.Time(((java.util.Date)x).getTime()) : timeFromString(x.toString()); + setTime(parameterIndex, tmpt); + } + break; + case Types.TIMESTAMP: + if (x instanceof java.sql.Timestamp) + setTimestamp(parameterIndex ,(java.sql.Timestamp)x); + else + { + java.sql.Timestamp tmpts = (x instanceof java.util.Date) ? new java.sql.Timestamp(((java.util.Date)x).getTime()) : timestampFromString(x.toString()); + setTimestamp(parameterIndex, tmpts); + } + break; + case Types.BIT: + if (x instanceof Boolean) + { + bindString(parameterIndex, ((Boolean)x).booleanValue() ? "1" : "0", Oid.BOOL); + } + else if (x instanceof String) + { + bindString(parameterIndex, Boolean.valueOf(x.toString()).booleanValue() ? "1" : "0", Oid.BOOL); + } + else if (x instanceof Number) + { + bindString(parameterIndex, ((Number)x).intValue()!=0 ? "1" : "0", Oid.BOOL); + } + else + { + throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); + } + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + setObject(parameterIndex, x); + break; + case Types.OTHER: + if (x instanceof PGobject) + setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType( ((PGobject)x).getType() )); + else + throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); + break; + default: + throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE); + } + } + + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException + { + setObject(parameterIndex, x, targetSqlType, 0); + } + + /* + * This stores an Object into a parameter. + */ + public void setObject(int parameterIndex, Object x) throws SQLException + { + checkClosed(); + if (x == null) + { + setNull(parameterIndex, Types.OTHER); + return; + } + if (x instanceof String) + setString(parameterIndex, (String)x); + else if (x instanceof BigDecimal) + setBigDecimal(parameterIndex, (BigDecimal)x); + else if (x instanceof Short) + setShort(parameterIndex, ((Short)x).shortValue()); + 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 PGobject) + setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType(((PGobject)x).getType())); + else + // Try to store as a string in database + setString(parameterIndex, x.toString(), 0); + } + + /* + * Before executing a stored procedure call you must explicitly + * call registerOutParameter to register the java.sql.Type of each + * out parameter. + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * ONLY 1 RETURN PARAMETER if {?= call ..} syntax is used + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType SQL type code defined by java.sql.Types; for + * parameters of type Numeric or Decimal use the version of + * registerOutParameter that accepts a scale value + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException + { + checkClosed(); + if (parameterIndex != 1) + throw new PSQLException ("postgresql.call.noinout", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); + if (!isFunction) + throw new PSQLException ("postgresql.call.procasfunc", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); + + // functionReturnType contains the user supplied value to check + // testReturn contains a modified version to make it easier to + // check the getXXX methods.. + functionReturnType = sqlType; + testReturn = sqlType; + if (functionReturnType == Types.CHAR || + functionReturnType == Types.LONGVARCHAR) + testReturn = Types.VARCHAR; + else if (functionReturnType == Types.FLOAT) + testReturn = Types.REAL; // changes to streamline later error checking + returnTypeSet = true; + } + + /* + * You must also specify the scale for numeric/decimal types: + * + * <p>Note: When reading the value of an out parameter, you must use + * the getXXX method whose Java type XXX corresponds to the + * parameter's registered SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param sqlType use either java.sql.Type.NUMERIC or java.sql.Type.DECIMAL + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @exception SQLException if a database-access error occurs. + */ + public void registerOutParameter(int parameterIndex, int sqlType, + int scale) throws SQLException + { + registerOutParameter (parameterIndex, sqlType); // ignore for now.. + } + + /* + * An OUT parameter may have the value of SQL NULL; wasNull + * reports whether the last value read has this special value. + * + * <p>Note: You must first call getXXX on a parameter to read its + * value and then call wasNull() to see if the value was SQL NULL. + * @return true if the last parameter read was SQL NULL + * @exception SQLException if a database-access error occurs. + */ + public boolean wasNull() throws SQLException + { + // check to see if the last access threw an exception + return (callResult == null); + } + + /* + * Get the value of a CHAR, VARCHAR, or LONGVARCHAR parameter as a + * Java String. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public String getString(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.VARCHAR, "String"); + return (String)callResult; + } + + + /* + * Get the value of a BIT parameter as a Java boolean. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is false + * @exception SQLException if a database-access error occurs. + */ + public boolean getBoolean(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.BIT, "Boolean"); + if (callResult == null) + return false; + return ((Boolean)callResult).booleanValue (); + } + + /* + * Get the value of a TINYINT parameter as a Java byte. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public byte getByte(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.TINYINT, "Byte"); + if (callResult == null) + return 0; + return (byte)((Integer)callResult).intValue (); + } + + /* + * Get the value of a SMALLINT parameter as a Java short. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public short getShort(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.SMALLINT, "Short"); + if (callResult == null) + return 0; + return (short)((Integer)callResult).intValue (); + } + + + /* + * Get the value of an INTEGER parameter as a Java int. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public int getInt(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.INTEGER, "Int"); + if (callResult == null) + return 0; + return ((Integer)callResult).intValue (); + } + + /* + * Get the value of a BIGINT parameter as a Java long. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public long getLong(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.BIGINT, "Long"); + if (callResult == null) + return 0; + return ((Long)callResult).longValue (); + } + + /* + * Get the value of a FLOAT parameter as a Java float. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public float getFloat(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.REAL, "Float"); + if (callResult == null) + return 0; + return ((Float)callResult).floatValue (); + } + + /* + * Get the value of a DOUBLE parameter as a Java double. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is 0 + * @exception SQLException if a database-access error occurs. + */ + public double getDouble(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.DOUBLE, "Double"); + if (callResult == null) + return 0; + return ((Double)callResult).doubleValue (); + } + + /* + * Get the value of a NUMERIC parameter as a java.math.BigDecimal + * object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @param scale a value greater than or equal to zero representing the + * desired number of digits to the right of the decimal point + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + * @deprecated in Java2.0 + */ + public BigDecimal getBigDecimal(int parameterIndex, int scale) + throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.NUMERIC, "BigDecimal"); + return ((BigDecimal)callResult); + } + + /* + * Get the value of a SQL BINARY or VARBINARY parameter as a Java + * byte[] + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public byte[] getBytes(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.VARBINARY, Types.BINARY, "Bytes"); + return ((byte [])callResult); + } + + + /* + * Get the value of a SQL DATE parameter as a java.sql.Date object + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Date getDate(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.DATE, "Date"); + return (java.sql.Date)callResult; + } + + /* + * Get the value of a SQL TIME parameter as a java.sql.Time object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Time getTime(int parameterIndex) throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.TIME, "Time"); + return (java.sql.Time)callResult; + } + + /* + * Get the value of a SQL TIMESTAMP parameter as a java.sql.Timestamp object. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return the parameter value; if the value is SQL NULL, the result is null + * @exception SQLException if a database-access error occurs. + */ + public java.sql.Timestamp getTimestamp(int parameterIndex) + throws SQLException + { + checkClosed(); + checkIndex (parameterIndex, Types.TIMESTAMP, "Timestamp"); + return (java.sql.Timestamp)callResult; + } + + // getObject returns a Java object for the parameter. + // See the JDBC spec's "Dynamic Programming" chapter for details. + /* + * Get the value of a parameter as a Java object. + * + * <p>This method returns a Java object whose type coresponds to the + * SQL type that was registered for this parameter using + * registerOutParameter. + * + * <P>Note that this method may be used to read datatabase-specific, + * abstract data types. This is done by specifying a targetSqlType + * of java.sql.types.OTHER, which allows the driver to return a + * database-specific Java type. + * + * <p>See the JDBC spec's "Dynamic Programming" chapter for details. + * + * @param parameterIndex the first parameter is 1, the second is 2,... + * @return A java.lang.Object holding the OUT parameter value. + * @exception SQLException if a database-access error occurs. + */ + public Object getObject(int parameterIndex) + throws SQLException + { + checkClosed(); + checkIndex (parameterIndex); + return callResult; + } + + /* + * Returns the SQL statement with the current template values + * substituted. + */ + public String toString() + { + if (preparedQuery == null) + return toString(); + + return preparedQuery.toString(preparedParameters); + } + + private int adjustParamIndex(int paramIndex) throws SQLException { + if (!isFunction) + return paramIndex; + + if (paramIndex == 1) // need to registerOut instead + throw new PSQLException ("postgresql.call.funcover"); + + return paramIndex - 1; + } + + /* + * Note if s is a String it should be escaped by the caller to avoid SQL + * injection attacks. It is not done here for efficency reasons as + * most calls to this method do not require escaping as the source + * of the string is known safe (i.e. Integer.toString()) + */ + private void bindLiteral(int paramIndex, String s, int oid) throws SQLException + { + preparedParameters.setLiteralParameter(adjustParamIndex(paramIndex), s, oid); + } + + /* + * This version is for values that should turn into strings + * e.g. setString directly calls bindString with no escaping; + * the per-protocol ParameterList does escaping as needed. + */ + private void bindString(int paramIndex, String s, int oid) throws SQLException + { + preparedParameters.setStringParameter(adjustParamIndex(paramIndex), s, oid); + } + + /** + * this method will turn a string of the form + * { [? =] call <some_function> [(?, [?,..])] } + * into the PostgreSQL format which is + * select <some_function> (?, [?, ...]) as result + * or select * from <some_function> (?, [?, ...]) as result (7.3) + */ + private String modifyJdbcCall(String p_sql) throws SQLException + { + checkClosed(); + + // Mini-parser for JDBC function-call syntax (only) + // TODO: Merge with escape processing (and parameter parsing?) + // so we only parse each query once. + + isFunction = false; + + int len = p_sql.length(); + int state = 1; + boolean inQuotes = false, inEscape = false; + + int startIndex = -1, endIndex = -1; + boolean syntaxError = false; + int i = 0; + + while (i < len && !syntaxError) { + char ch = p_sql.charAt(i); + + switch (state) { + case 1: // Looking for { at start of query + if (ch == '{') { + ++i; + ++state; + } else if (Character.isWhitespace(ch)) { + ++i; + } else { + // Not function-call syntax. Skip the rest of the string. + i = len; + } + break; + + case 2: // After {, looking for ? or =, skipping whitespace + if (ch == '?') { + isFunction = true; // { ? = call ... } -- function with one out parameter + ++i; + ++state; + } else if (ch == 'c') { // { call ... } -- proc with no out parameters + state += 3; // Don't increase 'i' + } else if (Character.isWhitespace(ch)) { + ++i; + } else { + // "{ foo ...", doesn't make sense, complain. + syntaxError = true; + } + break; + + case 3: // Looking for = after ?, skipping whitespace + if (ch == '=') { + ++i; + ++state; + } else if (Character.isWhitespace(ch)) { + ++i; + } else { + syntaxError = true; + } + break; + + case 4: // Looking for 'call' after '? =' skipping whitespace + if (ch == 'c' || ch == 'C') { + ++state; // Don't increase 'i'. + } else if (Character.isWhitespace(ch)) { + ++i; + } else { + syntaxError = true; + } + break; + + case 5: // Should be at 'call ' either at start of string or after ?= + if ((ch == 'c' || ch == 'C') && i+4 <= len && p_sql.substring(i, i+4).equalsIgnoreCase("call")) { + i += 4; + ++state; + } else if (Character.isWhitespace(ch)) { + ++i; + } else { + syntaxError = true; + } + break; + + case 6: // Looking for whitespace char after 'call' + if (Character.isWhitespace(ch)) { + // Ok, we found the start of the real call. + ++i; + ++state; + startIndex = i; + } else { + syntaxError = true; + } + break; + + case 7: // In "body" of the query (after "{ [? =] call ") + if (ch == '\'') { + inQuotes = !inQuotes; + ++i; + } else if (inQuotes && ch == '\\') { + // Backslash in string constant, skip next character. + i += 2; + } else if (!inQuotes && ch == '{') { + inEscape = !inEscape; + ++i; + } else if (!inQuotes && ch == '}') { + if (!inEscape) { + // Should be end of string. + endIndex = i; + ++i; + ++state; + } else { + inEscape = false; + } + } else if (!inQuotes && ch == ';') { + syntaxError = true; + } else { + // Everything else is ok. + ++i; + } + break; + + case 8: // At trailing end of query, eating whitespace + if (Character.isWhitespace(ch)) { + ++i; + } else { + syntaxError = true; + } + break; + + default: + throw new IllegalStateException("somehow got into bad state " + state); + } + } + + // We can only legally end in a couple of states here. + if (i == len && !syntaxError) { + if (state == 1) + return p_sql; // Not an escaped syntax. + if (state != 8) + syntaxError = true; // Ran out of query while still parsing + } + + if (syntaxError) + throw new PSQLException ("postgresql.call.malformed",PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL, + new Object[] { new Integer(i) }); + + if (connection.haveMinimumServerVersion("7.3")) { + return "select * from " + p_sql.substring(startIndex, endIndex) + " as result"; + } else { + return "select " + p_sql.substring(startIndex, endIndex) + " as result"; + } + } + + /** helperfunction for the getXXX calls to check isFunction and index == 1 + * Compare BOTH type fields against the return type. + */ + protected void checkIndex (int parameterIndex, int type1, int type2, String getName) + throws SQLException + { + checkIndex (parameterIndex); + if (type1 != this.testReturn && type2 != this.testReturn) + throw new PSQLException("postgresql.call.wrongget", PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH, + new Object[]{"java.sql.Types=" + testReturn, + getName, + "java.sql.Types=" + type1}); + } + + /** helperfunction for the getXXX calls to check isFunction and index == 1 + */ + protected void checkIndex (int parameterIndex, int type, String getName) + throws SQLException + { + checkIndex (parameterIndex); + if (type != this.testReturn) + throw new PSQLException("postgresql.call.wrongget", PSQLState.MOST_SPECIFIC_TYPE_DOES_NOT_MATCH, + new Object[]{"java.sql.Types=" + testReturn, + getName, + "java.sql.Types=" + type}); + } + + /** helperfunction for the getXXX calls to check isFunction and index == 1 + * @param parameterIndex index of getXXX (index) + * check to make sure is a function and index == 1 + */ + private void checkIndex (int parameterIndex) throws SQLException + { + if (!isFunction) + throw new PSQLException("postgresql.call.noreturntype", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); + if (parameterIndex != 1) + throw new PSQLException("postgresql.call.noinout", PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); + } + + public void setPrepareThreshold(int newThreshold) throws SQLException { + checkClosed(); + + if (newThreshold < 0) + newThreshold = 0; + + this.m_prepareThreshold = newThreshold; + } + + public int getPrepareThreshold() { + return m_prepareThreshold; + } + + public void setUseServerPrepare(boolean flag) throws SQLException { + setPrepareThreshold(flag ? 1 : 0); + } + + public boolean isUseServerPrepare() { + return (preparedQuery != null && m_prepareThreshold != 0 && m_useCount+1 >= m_prepareThreshold); + } + + protected void checkClosed() throws SQLException + { + //need to add to errors.properties. + if (isClosed) + throw new PSQLException("postgresql.stmt.closed"); + } + + private java.sql.Date dateFromString (String s) throws SQLException + { + int timezone = 0; + long millis = 0; + long localoffset = 0; + int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); + //if the last index of '-' or '+' is past 8. we are guaranteed that it is a timezone marker + //shortest = yyyy-m-d + //longest = yyyy-mm-dd + try + { + timezone = (timezoneLocation>7) ? timezoneLocation : s.length(); + millis = java.sql.Date.valueOf(s.substring(0,timezone)).getTime(); + } + catch (Exception e) + { + throw new PSQLException("postgresql.format.baddate", PSQLState.BAD_DATETIME_FORMAT, e, + new Object[] { s , "yyyy-MM-dd[-tz]" }); + } + timezone = 0; + if (timezoneLocation>7 && timezoneLocation+3 == s.length()) + { + timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); + localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); + if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) + localoffset += 60*60*1000; + if (s.charAt(timezoneLocation)=='+') + timezone*=-1; + } + millis = millis + timezone*60*60*1000 + localoffset; + return new java.sql.Date(millis); + } + + private java.sql.Time timeFromString (String s) throws SQLException + { + int timezone = 0; + long millis = 0; + long localoffset = 0; + int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); + //if the index of the last '-' or '+' is greater than 0 that means this time has a timezone. + //everything earlier than that position, we treat as the time and parse it as such. + try + { + timezone = (timezoneLocation==-1) ? s.length() : timezoneLocation; + millis = java.sql.Time.valueOf(s.substring(0,timezone)).getTime(); + } + catch (Exception e) + { + throw new PSQLException("postgresql.format.badtime", PSQLState.BAD_DATETIME_FORMAT, e, + new Object[] { s, "HH:mm:ss[-tz]" }); + } + timezone = 0; + if (timezoneLocation != -1 && timezoneLocation+3 == s.length()) + { + timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); + localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); + if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) + localoffset += 60*60*1000; + if (s.charAt(timezoneLocation)=='+') + timezone*=-1; + } + millis = millis + timezone*60*60*1000 + localoffset; + return new java.sql.Time(millis); + } + + private java.sql.Timestamp timestampFromString (String s) throws SQLException + { + int timezone = 0; + long millis = 0; + long localoffset = 0; + int nanosVal = 0; + int timezoneLocation = (s.indexOf('+') == -1) ? s.lastIndexOf("-") : s.indexOf('+'); + int nanospos = s.indexOf("."); + //if there is a '.', that means there are nanos info, and we take the timestamp up to that point + //if not, then we check to see if the last +/- (to indicate a timezone) is greater than 8 + //8 is because the shortest date, will have last '-' at position 7. e.g yyyy-x-x + try + { + if (nanospos != -1) + timezone = nanospos; + else if (timezoneLocation > 8) + timezone = timezoneLocation; + else + timezone = s.length(); + millis = java.sql.Timestamp.valueOf(s.substring(0,timezone)).getTime(); + } + catch (Exception e) + { + throw new PSQLException("postgresql.format.badtimestamp", PSQLState.BAD_DATETIME_FORMAT, e, + new Object[] { s, "yyyy-MM-dd HH:mm:ss[.xxxxxx][-tz]" }); + } + timezone = 0; + if (nanospos != -1) + { + int tmploc = (timezoneLocation > 8) ? timezoneLocation : s.length(); + nanosVal = Integer.parseInt(s.substring(nanospos+1,tmploc)); + int diff = 8-((tmploc-1)-(nanospos+1)); + for (int i=0;i<diff;i++) + nanosVal*=10; + } + if (timezoneLocation>8 && timezoneLocation+3 == s.length()) + { + timezone = Integer.parseInt(s.substring(timezoneLocation+1,s.length())); + localoffset = java.util.Calendar.getInstance().getTimeZone().getRawOffset(); + if (java.util.Calendar.getInstance().getTimeZone().inDaylightTime(new java.sql.Date(millis))) + localoffset += 60*60*1000; + if (s.charAt(timezoneLocation)=='+') + timezone*=-1; + } + millis = millis + timezone*60*60*1000 + localoffset; + java.sql.Timestamp tmpts = new java.sql.Timestamp(millis); + tmpts.setNanos(nanosVal); + return tmpts; + } + // ** JDBC 2 Extensions ** public void addBatch(String p_sql) throws SQLException @@ -182,6 +2342,13 @@ public abstract class AbstractJdbc2Statement extends org.postgresql.jdbc1.Abstra return updateCounts; } + /* + * Cancel can be used by one thread to cancel a statement that + * is being executed by another thread. + * <p> + * + * @exception SQLException only because thats the spec. + */ public void cancel() throws SQLException { connection.cancelQuery(); @@ -224,7 +2391,7 @@ public abstract class AbstractJdbc2Statement extends org.postgresql.jdbc1.Abstra { checkClosed(); if (rows<0) throw new PSQLException("postgresql.input.fetch.gt0"); - super.fetchSize = rows; + fetchSize = rows; } public void addBatch() throws SQLException diff --git a/org/postgresql/jdbc2/Array.java b/org/postgresql/jdbc2/Array.java index b43172a5..f54bdd31 100644 --- a/org/postgresql/jdbc2/Array.java +++ b/org/postgresql/jdbc2/Array.java @@ -3,7 +3,7 @@ package org.postgresql.jdbc2; import org.postgresql.core.*; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.postgresql.jdbc1.TimestampUtils; +import org.postgresql.jdbc2.TimestampUtils; import java.math.BigDecimal; import java.sql.ResultSet; diff --git a/org/postgresql/jdbc1/ResultWrapper.java b/org/postgresql/jdbc2/ResultWrapper.java similarity index 97% rename from org/postgresql/jdbc1/ResultWrapper.java rename to org/postgresql/jdbc2/ResultWrapper.java index 20048dc1..25685916 100644 --- a/org/postgresql/jdbc1/ResultWrapper.java +++ b/org/postgresql/jdbc2/ResultWrapper.java @@ -10,7 +10,7 @@ * *------------------------------------------------------------------------- */ -package org.postgresql.jdbc1; +package org.postgresql.jdbc2; import java.sql.*; diff --git a/org/postgresql/jdbc1/TimestampUtils.java b/org/postgresql/jdbc2/TimestampUtils.java similarity index 99% rename from org/postgresql/jdbc1/TimestampUtils.java rename to org/postgresql/jdbc2/TimestampUtils.java index 0b2d43d0..6b2a18c5 100644 --- a/org/postgresql/jdbc1/TimestampUtils.java +++ b/org/postgresql/jdbc2/TimestampUtils.java @@ -10,7 +10,7 @@ * *------------------------------------------------------------------------- */ -package org.postgresql.jdbc1; +package org.postgresql.jdbc2; import java.sql.*; import java.text.SimpleDateFormat; @@ -21,7 +21,7 @@ import org.postgresql.util.*; /** * Misc utils for handling time and date values. - * Extracted from AbstractJdbc1ResultSet. + * Extracted from AbstractJdbc2ResultSet. */ public class TimestampUtils { private static StringBuffer sbuf = new StringBuffer(); diff --git a/org/postgresql/test/TestUtil.java b/org/postgresql/test/TestUtil.java index 2a6407e2..fc76deb7 100644 --- a/org/postgresql/test/TestUtil.java +++ b/org/postgresql/test/TestUtil.java @@ -225,8 +225,8 @@ public class TestUtil * not an Postgres connection. */ public static boolean haveMinimumServerVersion(Connection con, String version) throws SQLException { - if (con instanceof org.postgresql.jdbc1.AbstractJdbc1Connection) { - return ((org.postgresql.jdbc1.AbstractJdbc1Connection)con).haveMinimumServerVersion(version); + if (con instanceof org.postgresql.jdbc2.AbstractJdbc2Connection) { + return ((org.postgresql.jdbc2.AbstractJdbc2Connection)con).haveMinimumServerVersion(version); } return false; } [parent: fda6a665cc4c]