import java.sql.Types;
import java.util.Vector;
-/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.40 2003/10/09 01:17:07 wieck Exp $
+/* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Statement.java,v 1.41 2003/10/29 02:39:09 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
// Some performance caches
private StringBuffer sbuf = new StringBuffer(32);
- //Used by the preparedstatement style methods
- protected String[] m_sqlFragments;
- private String[] m_origSqlFragments;
- private String[] m_executeSqlFragments;
- protected Object[] m_binds = new Object[0];
-
- protected String[] m_bindTypes = new String[0];
- protected String m_statementName = null;
- protected boolean m_statementIsCursor = false;
+ protected String[] m_sqlFragments; // Query fragments.
+ private String[] m_executeSqlFragments; // EXECUTE(...) if useServerPrepare
+ protected Object[] m_binds = new Object[0]; // Parameter values
+
+ protected String[] m_bindTypes = new String[0]; // Parameter types, for PREPARE(...)
+ protected String m_statementName = null; // Allocated PREPARE statement name for server-prepared statements
+ protected String m_cursorName = null; // Allocated DECLARE cursor name for cursor-based fetch
+
+ // Constants for allowXXX and m_isSingleStatement vars, below.
+ // The idea is to defer the cost of examining the query until we really need to know,
+ // but don't reexamine it every time thereafter.
+
+ private static final short UNKNOWN = 0; // Don't know yet, examine the query.
+ private static final short NO = 1; // Don't use feature
+ private static final short YES = 2; // Do use feature
+
+ private short m_isSingleDML = UNKNOWN; // Is the query a single SELECT/UPDATE/INSERT/DELETE?
+ private short m_isSingleSelect = UNKNOWN; // Is the query a single SELECT?
+ private short m_isSingleStatement = UNKNOWN; // Is the query a single statement?
private boolean m_useServerPrepare = false;
return connection;
}
- public String getStatementName() {
- return m_statementName;
+ public String getFetchingCursorName() {
+ return m_cursorName;
}
- public int getFetchSize() throws SQLException {
+ public int getFetchSize() {
return fetchSize;
}
boolean inQuotes = false;
int lastParmEnd = 0, i;
+ m_isSingleSelect = m_isSingleDML = UNKNOWN;
+ m_isSingleStatement = YES;
+
for (i = 0; i < l_sql.length(); ++i)
{
int c = l_sql.charAt(i);
v.addElement(l_sql.substring (lastParmEnd, i));
lastParmEnd = i + 1;
}
+ if (c == ';' && !inQuotes)
+ m_isSingleStatement = m_isSingleSelect = m_isSingleDML = NO;
}
v.addElement(l_sql.substring (lastParmEnd, l_sql.length()));
}
-
/*
- * 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
+ * Deallocate resources allocated for the current query
+ * in preparation for replacing it with a new query.
*/
- public java.sql.ResultSet executeQuery(String p_sql) throws SQLException
- {
- String l_sql = replaceProcessing(p_sql);
- m_sqlFragments = new String[] {l_sql};
- m_binds = new Object[0];
+ private void deallocateQuery()
+ {
//If we have already created a server prepared statement, we need
//to deallocate the existing one
if (m_statementName != null)
{
try
{
- if (!m_statementIsCursor)
- connection.execSQL("DEALLOCATE " + m_statementName);
+ connection.execSQL("DEALLOCATE " + m_statementName);
}
catch (Exception e)
{
}
- finally
- {
- m_statementName = null;
- m_statementIsCursor = false;
- m_origSqlFragments = null;
- m_executeSqlFragments = null;
- }
}
+
+ m_statementName = null;
+ m_cursorName = null; // automatically closed at end of txn anyway
+ m_executeSqlFragments = null;
+ m_isSingleStatement = m_isSingleSelect = m_isSingleDML = UNKNOWN;
+ }
+
+ /*
+ * 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
+ {
+ deallocateQuery();
+
+ String l_sql = replaceProcessing(p_sql);
+ m_sqlFragments = new String[] {l_sql};
+ m_binds = new Object[0];
+
return executeQuery();
}
*/
public int executeUpdate(String p_sql) throws SQLException
{
+ deallocateQuery();
+
String l_sql = replaceProcessing(p_sql);
m_sqlFragments = new String[] {l_sql};
m_binds = new Object[0];
- //If we have already created a server prepared statement, we need
- //to deallocate the existing one
- if (m_statementName != null) {
- connection.execSQL("DEALLOCATE " + m_statementName);
- m_statementName = null;
- m_origSqlFragments = null;
- m_executeSqlFragments = null;
- }
+
return executeUpdate();
}
*/
public boolean execute(String p_sql) throws SQLException
{
+ deallocateQuery();
+
String l_sql = replaceProcessing(p_sql);
m_sqlFragments = new String[] {l_sql};
m_binds = new Object[0];
- //If we have already created a server prepared statement, we need
- //to deallocate the existing one
- if (m_statementName != null) {
- connection.execSQL("DEALLOCATE " + m_statementName);
- m_statementName = null;
- m_origSqlFragments = null;
- m_executeSqlFragments = null;
- }
+
return execute();
}
+ /*
+ * Check if the current query is a single statement.
+ */
+ private boolean isSingleStatement()
+ {
+ if (m_isSingleStatement != UNKNOWN)
+ return m_isSingleStatement == YES;
+
+ // Crude detection of multiple statements. This could be
+ // improved by parsing the whole query for quotes, but is
+ // it worth it given that the only queries that get here are
+ // unparameterized queries?
+
+ for (int i = 0; i < m_sqlFragments.length; ++i) { // a bit redundant, but ..
+ if (m_sqlFragments[i].indexOf(';') != -1) {
+ m_isSingleStatement = NO;
+ return false;
+ }
+ }
+
+ m_isSingleStatement = YES;
+ return true;
+ }
+
+ /*
+ * Helper for isSingleSelect() and isSingleDML(): computes values
+ * of m_isSingleDML and m_isSingleSelect.
+ */
+ private void analyzeStatementType()
+ {
+ if (!isSingleStatement()) {
+ m_isSingleSelect = m_isSingleDML = NO;
+ return;
+ }
+
+ String compare = m_sqlFragments[0].trim().toLowerCase();
+ if (compare.startsWith("select")) {
+ m_isSingleSelect = m_isSingleDML = YES;
+ return;
+ }
+
+ m_isSingleSelect = NO;
+
+ if (!compare.startsWith("update") &&
+ !compare.startsWith("delete") &&
+ !compare.startsWith("insert")) {
+ m_isSingleDML = NO;
+ return;
+ }
+
+ m_isSingleDML = YES;
+ }
+
+ /*
+ * Check if the current query is a single SELECT.
+ */
+ private boolean isSingleSelect()
+ {
+ if (m_isSingleSelect == UNKNOWN)
+ analyzeStatementType();
+
+ return m_isSingleSelect == YES;
+ }
+
+ /*
+ * Check if the current query is a single SELECT/UPDATE/INSERT/DELETE.
+ */
+ private boolean isSingleDML()
+ {
+ if (m_isSingleDML == UNKNOWN)
+ analyzeStatementType();
+
+ return m_isSingleDML == YES;
+ }
+
+ /*
+ * Return the query fragments to use for a server-prepared statement.
+ * The first query executed will include a PREPARE and EXECUTE;
+ * subsequent queries will just be an EXECUTE.
+ */
+ private String[] transformToServerPrepare() {
+ if (m_statementName != null)
+ return m_executeSqlFragments;
+
+ // First time through.
+ m_statementName = "JDBC_STATEMENT_" + m_preparedCount++;
+
+ // Set up m_executeSqlFragments
+ m_executeSqlFragments = new String[m_sqlFragments.length];
+ m_executeSqlFragments[0] = "EXECUTE " + m_statementName;
+ if (m_sqlFragments.length > 1) {
+ m_executeSqlFragments[0] += "(";
+ for (int i = 1; i < m_bindTypes.length; i++)
+ m_executeSqlFragments[i] = ", ";
+ m_executeSqlFragments[m_bindTypes.length] = ")";
+ }
+
+ // Set up the PREPARE.
+ String[] prepareSqlFragments = new String[m_sqlFragments.length];
+ System.arraycopy(m_sqlFragments, 0, prepareSqlFragments, 0, m_sqlFragments.length);
+
+ synchronized (sbuf) {
+ sbuf.setLength(0);
+ sbuf.append("PREPARE ");
+ sbuf.append(m_statementName);
+ if (m_sqlFragments.length > 1) {
+ sbuf.append("(");
+ for (int i = 0; i < m_bindTypes.length; i++) {
+ if (i != 0) sbuf.append(", ");
+ sbuf.append(m_bindTypes[i]);
+ }
+ sbuf.append(")");
+ }
+ sbuf.append(" AS ");
+ sbuf.append(m_sqlFragments[0]);
+ for (int i = 1; i < m_sqlFragments.length; i++) {
+ sbuf.append(" $");
+ sbuf.append(i);
+ sbuf.append(" ");
+ sbuf.append(m_sqlFragments[i]);
+ }
+ sbuf.append("; ");
+ sbuf.append(m_executeSqlFragments[0]);
+
+ prepareSqlFragments[0] = sbuf.toString();
+ }
+
+ System.arraycopy(m_executeSqlFragments, 1, prepareSqlFragments, 1, prepareSqlFragments.length - 1);
+ return prepareSqlFragments;
+ }
+
+ /*
+ * Return the current query transformed into a cursor-based statement.
+ * This uses a new cursor on each query.
+ */
+ private String[] transformToCursorFetch()
+ {
+
+ // Pinch the prepared count for our own nefarious purposes.
+ m_cursorName = "JDBC_CURS_" + m_preparedCount++;
+
+ // Create a cursor declaration and initial fetch statement from the original query.
+ int len = m_sqlFragments.length;
+ String[] cursorBasedSql = new String[len];
+ System.arraycopy(m_sqlFragments, 0, cursorBasedSql, 0, len);
+ cursorBasedSql[0] = "DECLARE " + m_cursorName + " CURSOR FOR " + cursorBasedSql[0];
+ cursorBasedSql[len-1] += "; FETCH FORWARD " + fetchSize + " FROM " + m_cursorName;
+
+ // 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 " + m_cursorName);
+
+ return cursorBasedSql;
+ }
+
+ /**
+ * Do transformations to a query for server-side prepare or setFetchSize() cursor
+ * work.
+ * @return the query fragments to execute
+ */
+ private String[] getQueryFragments()
+ {
+ // nb: isSingleXXX() are relatively expensive, avoid calling them unless we must.
+
+ // We check the "mutable" bits of these conditions (which may change without
+ // a new query being created) here; isSingleXXX() only concern themselves with
+ // the query structure itself.
+
+ // We prefer cursor-based-fetch over server-side-prepare here.
+ // Eventually a v3 implementation should let us do both at once.
+ if (fetchSize > 0 && !connection.getAutoCommit() && isSingleSelect())
+ return transformToCursorFetch();
+
+ if (isUseServerPrepare() && isSingleDML())
+ return transformToServerPrepare();
+
+ // Not server-prepare or cursor-fetch, just return a plain query.
+ return m_sqlFragments;
+ }
+
/*
* 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
rs.close();
}
- //Use server prepared statements if directed
- if (m_useServerPrepare)
- {
- if (m_statementName == null)
- {
- m_statementName = "JDBC_STATEMENT_" + next_preparedCount();
- m_origSqlFragments = new String[m_sqlFragments.length];
- m_executeSqlFragments = new String[m_sqlFragments.length];
- System.arraycopy(m_sqlFragments, 0, m_origSqlFragments, 0, m_sqlFragments.length);
- m_executeSqlFragments[0] = "EXECUTE " + m_statementName;
- if (m_sqlFragments.length > 1)
- {
- m_executeSqlFragments[0] = m_executeSqlFragments[0] + "(";
- for (int i = 1; i < m_bindTypes.length; i++)
- {
- m_executeSqlFragments[i] = ", ";
- }
- m_executeSqlFragments[m_bindTypes.length] = ")";
- }
- synchronized (sbuf)
- {
- sbuf.setLength(0);
- sbuf.append("PREPARE ");
- sbuf.append(m_statementName);
- if (m_origSqlFragments.length > 1)
- {
- sbuf.append("(");
- for (int i = 0; i < m_bindTypes.length - 1; i++)
- {
- sbuf.append(m_bindTypes[i]);
- sbuf.append(", ");
- }
- sbuf.append(m_bindTypes[m_bindTypes.length - 1]);
- sbuf.append(")");
- }
- sbuf.append(" AS ");
- sbuf.append(m_origSqlFragments[0]);
- for (int i = 1; i < m_origSqlFragments.length; i++)
- {
- sbuf.append(" $");
- sbuf.append(i);
- sbuf.append(" ");
- sbuf.append(m_origSqlFragments[i]);
- }
- sbuf.append("; ");
-
- sbuf.append(m_executeSqlFragments[0]);
- m_sqlFragments[0] = sbuf.toString();
- System.arraycopy(m_executeSqlFragments, 1, m_sqlFragments, 1, m_sqlFragments.length - 1);
- }
-
- }
- else
- {
- 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_" + next_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());
- }
- }
+ // Get the actual query fragments to run (might be a transformed version of
+ // the original fragments)
+ String[] fragments = getQueryFragments();
// New in 7.1, pass Statement so that ExecSQL can customise to it
- result = QueryExecutor.execute(m_sqlFragments,
- m_binds,
- this);
+ result = QueryExecutor.execute(fragments,
+ m_binds,
+ this);
//If we are executing a callable statement function set the return data
if (isFunction)
if (rs != null)
rs.close();
- // If using server prepared statements deallocate them
- if (m_useServerPrepare && m_statementName != null) {
- connection.execSQL("DEALLOCATE " + m_statementName);
- }
+ deallocateQuery();
// Disasociate it from us (For Garbage Collection)
result = null;
public void setUseServerPrepare(boolean flag) throws SQLException {
//Server side prepared statements were introduced in 7.3
if (connection.haveMinimumServerVersion("7.3")) {
- //If turning server prepared statements off deallocate statement
- //and reset statement name
- if (m_useServerPrepare != flag && !flag && m_statementName != null)
- connection.execSQL("DEALLOCATE " + m_statementName);
- m_statementName = null;
+ if (m_useServerPrepare != flag)
+ deallocateQuery();
m_useServerPrepare = flag;
} else {
//This is a pre 7.3 server so no op this method