Re: [JDBC] patch for cursor based querys in JDBC - Mailing list pgsql-patches

From Barry Lind
Subject Re: [JDBC] patch for cursor based querys in JDBC
Date
Msg-id 3E98E3D6.50103@xythos.com
Whole thread Raw
In response to patch for cursor based querys in JDBC  (Nic Ferrier <nferrier@tapsellferrier.co.uk>)
Responses Re: [JDBC] patch for cursor based querys in JDBC
List pgsql-patches
Nic,

Patch applied.

I have one questions about the patch though.  There is a comment that
states "Need to confirm this with Barry Lind."  I wasn't sure what you
wanted to confirm with me.

thanks,
--Barry


Nic Ferrier wrote:
> The attached patch cleans up the cursor based query code based on the
> discussions Barry Lind and I had some time ago when I provided the
> current code.
>
> Here's a sort of ChangeLog.
>
>
> All changes are against org.postgresql.jdbc1.AbstractJdbc1Statement
> unless specifically stated otherwise.
>
>
> - adds the cursor handling code to the execute() method
>
> - removes the method executeWithCursor
>
> - makes the cursor code only happen when the connection is already in
>   transactional state (ie: getAutoCommit() == false)
>
> - makes the cursor code fail calmly (log messages go out but the
>   query still works) when the supplied statement already uses a
>   cursor or there are multiple statements.
>
> - adds a member varible m_statementIsCursor to identify when to do a
>   DEALLOCATE of the current statement name and when not to.
>
> - fail when attempting to do cursor based querys and PS.
>
> - adds the getAutoCommit() / setAutoCommit() methods to BaseConnection
>   because all extenders must provide that.
>
>
> The unidiff is below. Enjoy.
>
>
> Barry, sorry this has not been done before now. Any chance we can get
> it in quickly so I can provide a patch for the CallableStatement
> stuff (you'll have noticed that people have started to ask for that
> now!).
>
>
>
> Nic
>
>
> Index: src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java
> ===================================================================
> RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java,v
> retrieving revision 1.18
> diff -p -u -r1.18 AbstractJdbc1Statement.java
> --- src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java    2003/03/07 18:39:44    1.18
> +++ src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java    2003/04/12 23:27:40
> @@ -20,8 +20,7 @@ import org.postgresql.util.*;
>   */
>  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 AbstractJdbc1State
>
>      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 AbstractJdbc1State
>          {
>              try
>              {
> -                connection.execSQL("DEALLOCATE " + m_statementName);
> +                                if (!m_statementIsCursor)
> +                                        connection.execSQL("DEALLOCATE " + m_statementName);
>              }
>              catch (Exception e)
>              {
> @@ -167,6 +168,7 @@ public abstract class AbstractJdbc1State
>              finally
>              {
>                  m_statementName = null;
> +                                m_statementIsCursor = false;
>                  m_origSqlFragments = null;
>                  m_executeSqlFragments = null;
>              }
> @@ -183,11 +185,8 @@ public abstract class AbstractJdbc1State
>       */
>      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 AbstractJdbc1State
>       * 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
> @@ -352,11 +355,76 @@ public abstract class AbstractJdbc1State
>                  m_sqlFragments = m_executeSqlFragments;
>              }
>          }
> +
> +                // 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
> +        // 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 AbstractJdbc1State
>              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
>
>
> ---------------------------(end of broadcast)---------------------------
> TIP 2: you can get off all lists at once with the unregister command
>     (send "unregister YourEmailAddressHere" to majordomo@postgresql.org)
>


pgsql-patches by date:

Previous
From: Nic Ferrier
Date:
Subject: patch for cursor based querys in JDBC
Next
From: Christopher Kings-Lynne
Date:
Subject: Resend of encoding docs patch