From 51acf972350dc1d25f9edf1c0990080ff8acd0e3 Mon Sep 17 00:00:00 2001 From: Barry Lind <barry@xythos.com> Date: Sun, 13 Apr 2003 04:10:07 +0000 Subject: [PATCH] Applied patch submitted by Nic Ferrier with some cleanups of his previous patch to add cursor based queries. Modified Files: jdbc/org/postgresql/core/BaseConnection.java jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java --- .../org/postgresql/core/BaseConnection.java | 4 +- .../jdbc1/AbstractJdbc1Statement.java | 190 ++++++++---------- 2 files changed, 84 insertions(+), 110 deletions(-) diff --git a/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java b/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java index d4ee0d4a858..d2c56ddb03b 100644 --- a/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java +++ b/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java @@ -6,7 +6,7 @@ * Copyright (c) 2003, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.1 2003/03/07 18:39:41 barry Exp $ + * $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.2 2003/04/13 04:10:07 barry Exp $ * *------------------------------------------------------------------------- */ @@ -27,6 +27,7 @@ public interface BaseConnection extends PGConnection public void cancelQuery() throws SQLException; public Statement createStatement() throws SQLException; public BaseResultSet execSQL(String s) throws SQLException; + public boolean getAutoCommit() throws SQLException; public String getCursorName() throws SQLException; public Encoding getEncoding() throws SQLException; public DatabaseMetaData getMetaData() throws SQLException; @@ -38,6 +39,7 @@ public interface BaseConnection extends PGConnection public int getSQLType(String pgTypeName) throws SQLException; public boolean haveMinimumCompatibleVersion(String ver) throws SQLException; public boolean haveMinimumServerVersion(String ver) throws SQLException; + public void setAutoCommit(boolean autoCommit) throws SQLException; public void setCursorName(String cursor) throws SQLException; } diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java index d66aa5e2240..e5b9fe780f0 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java @@ -13,15 +13,14 @@ import org.postgresql.core.QueryExecutor; import org.postgresql.largeobject.*; import org.postgresql.util.*; -/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.18 2003/03/07 18:39:44 barry Exp $ +/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.19 2003/04/13 04:10:07 barry 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 + // The connection who created us protected BaseConnection connection; /** The warnings chain. */ @@ -58,6 +57,7 @@ public abstract class AbstractJdbc1Statement implements BaseStatement protected String[] m_bindTypes = new String[0]; protected String m_statementName = null; + protected boolean m_statementIsCursor = false; private boolean m_useServerPrepare = false; private static int m_preparedCount = 1; @@ -159,7 +159,8 @@ public abstract class AbstractJdbc1Statement implements BaseStatement { try { - connection.execSQL("DEALLOCATE " + m_statementName); + if (!m_statementIsCursor) + connection.execSQL("DEALLOCATE " + m_statementName); } catch (Exception e) { @@ -167,6 +168,7 @@ public abstract class AbstractJdbc1Statement implements BaseStatement finally { m_statementName = null; + m_statementIsCursor = false; m_origSqlFragments = null; m_executeSqlFragments = null; } @@ -183,11 +185,8 @@ public abstract class AbstractJdbc1Statement implements BaseStatement */ public java.sql.ResultSet executeQuery() throws SQLException { - if (fetchSize > 0) - this.executeWithCursor(); - else - this.execute(); - + this.execute(); + while (result != null && !result.reallyResultSet()) result = (BaseResultSet) result.getNext(); if (result == null) @@ -268,9 +267,13 @@ public abstract class AbstractJdbc1Statement implements BaseStatement * Some prepared statements return multiple results; the execute method * handles these complex statements as well as the simpler form of * statements handled by executeQuery and executeUpdate + * + * This method also handles the translation of the query into a cursor based + * query if the user has specified a fetch size and set the connection + * into a non-auto commit state. * * @return true if the next result is a ResultSet; false if it is an - * * update count or there are no more results + * update count or there are no more results * @exception SQLException if a database access error occurs */ public boolean execute() throws SQLException @@ -353,10 +356,75 @@ public abstract class AbstractJdbc1Statement implements BaseStatement } } - // New in 7.1, pass Statement so that ExecSQL can customise to it + // Use a cursor if directed and in a transaction. + else if (fetchSize > 0 && !connection.getAutoCommit()) + { + // The first thing to do is transform the statement text into the cursor form. + String[] cursorBasedSql = new String[m_sqlFragments.length]; + // Pinch the prepared count for our own nefarious purposes. + String statementName = "JDBC_CURS_" + m_preparedCount++; + // Setup the cursor decleration. + // Note that we don't need a BEGIN because we've already + // made sure we're executing inside a transaction. + String cursDecl = "DECLARE " + statementName + " CURSOR FOR "; + String endCurs = " FETCH FORWARD " + fetchSize + " FROM " + statementName + ";"; + + // Copy the real query to the curs decleration. + try + { + // Need to confirm this with Barry Lind. + if (cursorBasedSql.length > 1) + throw new IllegalStateException("cursor fetches not supported with prepared statements."); + for (int i = 0; i < cursorBasedSql.length; i++) + { + if (i == 0) + { + if (m_sqlFragments[i].trim().toUpperCase().startsWith("DECLARE ")) + throw new IllegalStateException("statement is already cursor based."); + cursorBasedSql[i] = cursDecl; + } + + if (cursorBasedSql[i] != null) + cursorBasedSql[i] += m_sqlFragments[i]; + else + cursorBasedSql[i] = m_sqlFragments[i]; + + if (i == cursorBasedSql.length - 1) + { + // We have to be smart about adding the delimitting ";" + if (m_sqlFragments[i].endsWith(";")) + cursorBasedSql[i] += endCurs; + else + cursorBasedSql[i] += (";" + endCurs); + } + else if (m_sqlFragments[i].indexOf(";") > -1) + { + throw new IllegalStateException("multiple statements not " + + "allowed with cursor based querys."); + } + } + + // Make the cursor based query the one that will be used. + if (org.postgresql.Driver.logDebug) + org.postgresql.Driver.debug("using cursor based sql with cursor name " + statementName); + + // Do all of this after exceptions have been thrown. + m_statementName = statementName; + m_statementIsCursor = true; + m_sqlFragments = cursorBasedSql; + } + catch (IllegalStateException e) + { + // Something went wrong generating the cursor based statement. + if (org.postgresql.Driver.logDebug) + org.postgresql.Driver.debug(e.getMessage()); + } + } + + // New in 7.1, pass Statement so that ExecSQL can customise to it result = QueryExecutor.execute(m_sqlFragments, - m_binds, - this); + m_binds, + this); //If we are executing a callable statement function set the return data if (isFunction) @@ -379,102 +447,6 @@ public abstract class AbstractJdbc1Statement implements BaseStatement return (result != null && result.reallyResultSet()); } } - - /** version of execute which converts the query to a cursor. - */ - public boolean executeWithCursor() throws SQLException - { - if (isFunction && !returnTypeSet) - throw new PSQLException("postgresql.call.noreturntype"); - if (isFunction) - { // set entry 1 to dummy entry.. - m_binds[0] = ""; // dummy entry which ensured that no one overrode - m_bindTypes[0] = PG_TEXT; - // and calls to setXXX (2,..) really went to first arg in a function call.. - } - - // New in 7.1, if we have a previous resultset then force it to close - // This brings us nearer to compliance, and helps memory management. - // Internal stuff will call ExecSQL directly, bypassing this. - if (result != null) - { - java.sql.ResultSet rs = getResultSet(); - if (rs != null) - rs.close(); - } - - // I've pretty much ignored server prepared statements... can declare and prepare be - // used together? - // It's trivial to change this: you just have to resolve this issue - // of how to work out whether there's a function call. If there isn't then the first - // element of the array must be the bit that you extend to become the cursor - // decleration. - // The last thing that can go wrong is when the user supplies a cursor statement - // directly: the translation takes no account of that. I think we should just look - // for declare and stop the translation if we find it. - - // The first thing to do is transform the statement text into the cursor form. - String[] origSqlFragments = m_sqlFragments; - m_sqlFragments = new String[origSqlFragments.length]; - System.arraycopy(origSqlFragments, 0, m_sqlFragments, 0, origSqlFragments.length); - // Pinch the prepared count for our own nefarious purposes. - m_statementName = "JDBC_CURS_" + m_preparedCount++; - // The static bit to prepend to all querys. - String cursDecl = "BEGIN; DECLARE " + m_statementName + " CURSOR FOR "; - String endCurs = " FETCH FORWARD " + fetchSize + " FROM " + m_statementName + ";"; - - // Add the real query to the curs decleration. - // This is the bit that really makes the presumption about - // m_sqlFragments not being a function call. - if (m_sqlFragments.length < 1) - m_sqlFragments[0] = cursDecl + "SELECT NULL;"; - - else if (m_sqlFragments.length < 2) - { - if (m_sqlFragments[0].endsWith(";")) - m_sqlFragments[0] = cursDecl + m_sqlFragments[0] + endCurs; - else - m_sqlFragments[0] = cursDecl + m_sqlFragments[0] + ";" + endCurs; - } - else - { - m_sqlFragments[0] = cursDecl + m_sqlFragments[0]; - if (m_sqlFragments[m_sqlFragments.length - 1].endsWith(";")) - m_sqlFragments[m_sqlFragments.length - 1] += endCurs; - else - m_sqlFragments[m_sqlFragments.length - 1] += (";" + endCurs); - } - - result = QueryExecutor.execute(m_sqlFragments, - m_binds, - this); - - //If we are executing a callable statement function set the return data - if (isFunction) - { - if (!result.reallyResultSet()) - throw new PSQLException("postgresql.call.noreturnval"); - if (!result.next ()) - throw new PSQLException ("postgresql.call.noreturnval"); - callResult = result.getObject(1); - int columnType = result.getMetaData().getColumnType(1); - if (columnType != functionReturnType) - { - Object[] arr = - { "java.sql.Types=" + columnType, - "java.sql.Types=" + functionReturnType - }; - throw new PSQLException ("postgresql.call.wrongrtntype",arr); - } - result.close (); - return true; - } - else - { - return (result != null && result.reallyResultSet()); - } - } - /* * setCursorName defines the SQL cursor name that will be used by -- GitLab