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: