Patches from Oliver Jowett to fix CursorFetchTest, 7.4 now does not automatically delete cursors
This commit is contained in:
parent
15c6764bda
commit
7ecb6ede28
|
@ -6,7 +6,7 @@
|
||||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.3 2003/05/29 03:21:32 barry Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseConnection.java,v 1.4 2003/10/29 02:39:09 davec Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +26,7 @@ public interface BaseConnection extends PGConnection
|
||||||
public void cancelQuery() throws SQLException;
|
public void cancelQuery() throws SQLException;
|
||||||
public Statement createStatement() throws SQLException;
|
public Statement createStatement() throws SQLException;
|
||||||
public BaseResultSet execSQL(String s) throws SQLException;
|
public BaseResultSet execSQL(String s) throws SQLException;
|
||||||
public boolean getAutoCommit() throws SQLException;
|
public boolean getAutoCommit();
|
||||||
public String getCursorName() throws SQLException;
|
public String getCursorName() throws SQLException;
|
||||||
public Encoding getEncoding() throws SQLException;
|
public Encoding getEncoding() throws SQLException;
|
||||||
public DatabaseMetaData getMetaData() throws SQLException;
|
public DatabaseMetaData getMetaData() throws SQLException;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseStatement.java,v 1.5 2003/08/24 22:10:09 barry Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/core/Attic/BaseStatement.java,v 1.6 2003/10/29 02:39:09 davec Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
@ -30,11 +30,11 @@ public interface BaseStatement extends org.postgresql.PGStatement
|
||||||
*/
|
*/
|
||||||
public void addWarning(String p_warning) throws SQLException;
|
public void addWarning(String p_warning) throws SQLException;
|
||||||
public void close() throws SQLException;
|
public void close() throws SQLException;
|
||||||
public int getFetchSize() throws SQLException;
|
public int getFetchSize();
|
||||||
public int getMaxFieldSize() throws SQLException;
|
public int getMaxFieldSize() throws SQLException;
|
||||||
public int getMaxRows() throws SQLException;
|
public int getMaxRows() throws SQLException;
|
||||||
public int getResultSetConcurrency() throws SQLException;
|
public int getResultSetConcurrency() throws SQLException;
|
||||||
public String getStatementName();
|
public String getFetchingCursorName();
|
||||||
public SQLWarning getWarnings() throws SQLException;
|
public SQLWarning getWarnings() throws SQLException;
|
||||||
public void setMaxFieldSize(int max) throws SQLException;
|
public void setMaxFieldSize(int max) throws SQLException;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Connection.java,v 1.26 2003/09/13 04:02:15 barry Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1Connection.java,v 1.27 2003/10/29 02:39:09 davec Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
@ -1270,10 +1270,9 @@ public abstract class AbstractJdbc1Connection implements BaseConnection
|
||||||
* gets the current auto-commit state
|
* gets the current auto-commit state
|
||||||
*
|
*
|
||||||
* @return Current state of the auto-commit mode
|
* @return Current state of the auto-commit mode
|
||||||
* @exception SQLException (why?)
|
|
||||||
* @see setAutoCommit
|
* @see setAutoCommit
|
||||||
*/
|
*/
|
||||||
public boolean getAutoCommit() throws SQLException
|
public boolean getAutoCommit()
|
||||||
{
|
{
|
||||||
return this.autoCommit;
|
return this.autoCommit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1ResultSet.java,v 1.21 2003/09/22 04:54:59 barry Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc1/Attic/AbstractJdbc1ResultSet.java,v 1.22 2003/10/29 02:39:09 davec Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +61,9 @@ public abstract class AbstractJdbc1ResultSet implements BaseResultSet
|
||||||
private SimpleDateFormat m_tstzFormat = null;
|
private SimpleDateFormat m_tstzFormat = null;
|
||||||
private SimpleDateFormat m_dateFormat = null;
|
private SimpleDateFormat m_dateFormat = null;
|
||||||
|
|
||||||
|
private int fetchSize; // Fetch size for next read (might be 0).
|
||||||
|
private int lastFetchSize; // Fetch size of last read (might be 0).
|
||||||
|
|
||||||
public abstract ResultSetMetaData getMetaData() throws SQLException;
|
public abstract ResultSetMetaData getMetaData() throws SQLException;
|
||||||
|
|
||||||
public AbstractJdbc1ResultSet(BaseStatement statement,
|
public AbstractJdbc1ResultSet(BaseStatement statement,
|
||||||
|
@ -82,6 +85,8 @@ public abstract class AbstractJdbc1ResultSet implements BaseResultSet
|
||||||
this.this_row = null;
|
this.this_row = null;
|
||||||
this.current_row = -1;
|
this.current_row = -1;
|
||||||
this.binaryCursor = binaryCursor;
|
this.binaryCursor = binaryCursor;
|
||||||
|
|
||||||
|
this.lastFetchSize = this.fetchSize = (statement == null ? 0 : statement.getFetchSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseStatement getPGStatement() {
|
public BaseStatement getPGStatement() {
|
||||||
|
@ -111,7 +116,21 @@ public abstract class AbstractJdbc1ResultSet implements BaseResultSet
|
||||||
this.current_row = -1;
|
this.current_row = -1;
|
||||||
this.binaryCursor = binaryCursor;
|
this.binaryCursor = binaryCursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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
|
public boolean next() throws SQLException
|
||||||
{
|
{
|
||||||
|
@ -120,30 +139,32 @@ public abstract class AbstractJdbc1ResultSet implements BaseResultSet
|
||||||
|
|
||||||
if (++current_row >= rows.size())
|
if (++current_row >= rows.size())
|
||||||
{
|
{
|
||||||
int fetchSize = statement.getFetchSize();
|
String cursorName = statement.getFetchingCursorName();
|
||||||
// Must be false if we weren't batching.
|
if (cursorName == null || lastFetchSize == 0 || rows.size() < lastFetchSize)
|
||||||
if (fetchSize == 0)
|
return false; // Not doing a cursor-based fetch or the last fetch was the end of the query
|
||||||
return false;
|
|
||||||
// Use the ref to the statement to get
|
// Use the ref to the statement to get
|
||||||
// the details we need to do another cursor
|
// the details we need to do another cursor
|
||||||
// query - it will use reinit() to repopulate this
|
// query - it will use reinit() to repopulate this
|
||||||
// with the right data.
|
// with the right data.
|
||||||
String[] sql = new String[1];
|
|
||||||
String[] binds = new String[0];
|
// NB: We can reach this point with fetchSize == 0
|
||||||
// Is this the correct query???
|
// if the fetch size is changed halfway through reading results.
|
||||||
String cursorName = statement.getStatementName();
|
// Use "FETCH FORWARD ALL" in that case to complete the query.
|
||||||
//if cursorName is null, we are not batching (likely because the
|
String[] sql = new String[] {
|
||||||
//query itself can't be batched)
|
fetchSize == 0 ? ("FETCH FORWARD ALL FROM " + cursorName) :
|
||||||
if (cursorName == null)
|
("FETCH FORWARD " + fetchSize + " FROM " + cursorName)
|
||||||
return false;
|
};
|
||||||
sql[0] = "FETCH FORWARD " + fetchSize + " FROM " + cursorName;
|
|
||||||
QueryExecutor.execute(sql,
|
QueryExecutor.execute(sql,
|
||||||
binds,
|
new String[0],
|
||||||
this);
|
this);
|
||||||
|
|
||||||
|
// Test the new rows array.
|
||||||
|
lastFetchSize = fetchSize;
|
||||||
|
if (rows.size() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Test the new rows array.
|
|
||||||
if (rows.size() == 0)
|
|
||||||
return false;
|
|
||||||
// Otherwise reset the counter and let it go on...
|
// Otherwise reset the counter and let it go on...
|
||||||
current_row = 0;
|
current_row = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.sql.Timestamp;
|
||||||
import java.sql.Types;
|
import java.sql.Types;
|
||||||
import java.util.Vector;
|
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
|
* This class defines methods of the jdbc1 specification. This class is
|
||||||
* extended by org.postgresql.jdbc2.AbstractJdbc2Statement which adds the jdbc2
|
* extended by org.postgresql.jdbc2.AbstractJdbc2Statement which adds the jdbc2
|
||||||
* methods. The real Statement class (for jdbc1) is org.postgresql.jdbc1.Jdbc1Statement
|
* methods. The real Statement class (for jdbc1) is org.postgresql.jdbc1.Jdbc1Statement
|
||||||
|
@ -62,15 +62,25 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
// Some performance caches
|
// Some performance caches
|
||||||
private StringBuffer sbuf = new StringBuffer(32);
|
private StringBuffer sbuf = new StringBuffer(32);
|
||||||
|
|
||||||
//Used by the preparedstatement style methods
|
protected String[] m_sqlFragments; // Query fragments.
|
||||||
protected String[] m_sqlFragments;
|
private String[] m_executeSqlFragments; // EXECUTE(...) if useServerPrepare
|
||||||
private String[] m_origSqlFragments;
|
protected Object[] m_binds = new Object[0]; // Parameter values
|
||||||
private String[] m_executeSqlFragments;
|
|
||||||
protected Object[] m_binds = new Object[0];
|
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_bindTypes = new String[0];
|
protected String m_cursorName = null; // Allocated DECLARE cursor name for cursor-based fetch
|
||||||
protected String m_statementName = null;
|
|
||||||
protected boolean m_statementIsCursor = false;
|
// 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;
|
private boolean m_useServerPrepare = false;
|
||||||
|
|
||||||
|
@ -115,11 +125,11 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStatementName() {
|
public String getFetchingCursorName() {
|
||||||
return m_statementName;
|
return m_cursorName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFetchSize() throws SQLException {
|
public int getFetchSize() {
|
||||||
return fetchSize;
|
return fetchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +148,9 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
boolean inQuotes = false;
|
boolean inQuotes = false;
|
||||||
int lastParmEnd = 0, i;
|
int lastParmEnd = 0, i;
|
||||||
|
|
||||||
|
m_isSingleSelect = m_isSingleDML = UNKNOWN;
|
||||||
|
m_isSingleStatement = YES;
|
||||||
|
|
||||||
for (i = 0; i < l_sql.length(); ++i)
|
for (i = 0; i < l_sql.length(); ++i)
|
||||||
{
|
{
|
||||||
int c = l_sql.charAt(i);
|
int c = l_sql.charAt(i);
|
||||||
|
@ -149,6 +162,8 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
v.addElement(l_sql.substring (lastParmEnd, i));
|
v.addElement(l_sql.substring (lastParmEnd, i));
|
||||||
lastParmEnd = i + 1;
|
lastParmEnd = i + 1;
|
||||||
}
|
}
|
||||||
|
if (c == ';' && !inQuotes)
|
||||||
|
m_isSingleStatement = m_isSingleSelect = m_isSingleDML = NO;
|
||||||
}
|
}
|
||||||
v.addElement(l_sql.substring (lastParmEnd, l_sql.length()));
|
v.addElement(l_sql.substring (lastParmEnd, l_sql.length()));
|
||||||
|
|
||||||
|
@ -161,6 +176,30 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Deallocate resources allocated for the current query
|
||||||
|
* in preparation for replacing it with a new query.
|
||||||
|
*/
|
||||||
|
private void deallocateQuery()
|
||||||
|
{
|
||||||
|
//If we have already created a server prepared statement, we need
|
||||||
|
//to deallocate the existing one
|
||||||
|
if (m_statementName != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
connection.execSQL("DEALLOCATE " + m_statementName);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
* Execute a SQL statement that retruns a single ResultSet
|
||||||
|
@ -171,29 +210,12 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
*/
|
*/
|
||||||
public java.sql.ResultSet executeQuery(String p_sql) throws SQLException
|
public java.sql.ResultSet executeQuery(String p_sql) throws SQLException
|
||||||
{
|
{
|
||||||
|
deallocateQuery();
|
||||||
|
|
||||||
String l_sql = replaceProcessing(p_sql);
|
String l_sql = replaceProcessing(p_sql);
|
||||||
m_sqlFragments = new String[] {l_sql};
|
m_sqlFragments = new String[] {l_sql};
|
||||||
m_binds = new Object[0];
|
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)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!m_statementIsCursor)
|
|
||||||
connection.execSQL("DEALLOCATE " + m_statementName);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
m_statementName = null;
|
|
||||||
m_statementIsCursor = false;
|
|
||||||
m_origSqlFragments = null;
|
|
||||||
m_executeSqlFragments = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return executeQuery();
|
return executeQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,17 +248,12 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
*/
|
*/
|
||||||
public int executeUpdate(String p_sql) throws SQLException
|
public int executeUpdate(String p_sql) throws SQLException
|
||||||
{
|
{
|
||||||
|
deallocateQuery();
|
||||||
|
|
||||||
String l_sql = replaceProcessing(p_sql);
|
String l_sql = replaceProcessing(p_sql);
|
||||||
m_sqlFragments = new String[] {l_sql};
|
m_sqlFragments = new String[] {l_sql};
|
||||||
m_binds = new Object[0];
|
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();
|
return executeUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,28 +287,199 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
*/
|
*/
|
||||||
public boolean execute(String p_sql) throws SQLException
|
public boolean execute(String p_sql) throws SQLException
|
||||||
{
|
{
|
||||||
|
deallocateQuery();
|
||||||
|
|
||||||
String l_sql = replaceProcessing(p_sql);
|
String l_sql = replaceProcessing(p_sql);
|
||||||
m_sqlFragments = new String[] {l_sql};
|
m_sqlFragments = new String[] {l_sql};
|
||||||
m_binds = new Object[0];
|
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();
|
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
|
* Some prepared statements return multiple results; the execute method
|
||||||
* handles these complex statements as well as the simpler form of
|
* handles these complex statements as well as the simpler form of
|
||||||
* statements handled by executeQuery and executeUpdate
|
* 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
|
* @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
|
||||||
|
@ -319,133 +507,14 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
rs.close();
|
rs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Use server prepared statements if directed
|
// Get the actual query fragments to run (might be a transformed version of
|
||||||
if (m_useServerPrepare)
|
// the original fragments)
|
||||||
{
|
String[] fragments = getQueryFragments();
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
result = QueryExecutor.execute(fragments,
|
||||||
m_binds,
|
m_binds,
|
||||||
this);
|
this);
|
||||||
|
|
||||||
//If we are executing a callable statement function set the return data
|
//If we are executing a callable statement function set the return data
|
||||||
if (isFunction)
|
if (isFunction)
|
||||||
|
@ -721,10 +790,7 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
if (rs != null)
|
if (rs != null)
|
||||||
rs.close();
|
rs.close();
|
||||||
|
|
||||||
// If using server prepared statements deallocate them
|
deallocateQuery();
|
||||||
if (m_useServerPrepare && m_statementName != null) {
|
|
||||||
connection.execSQL("DEALLOCATE " + m_statementName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disasociate it from us (For Garbage Collection)
|
// Disasociate it from us (For Garbage Collection)
|
||||||
result = null;
|
result = null;
|
||||||
|
@ -2093,11 +2159,8 @@ public abstract class AbstractJdbc1Statement implements BaseStatement
|
||||||
public void setUseServerPrepare(boolean flag) throws SQLException {
|
public void setUseServerPrepare(boolean flag) throws SQLException {
|
||||||
//Server side prepared statements were introduced in 7.3
|
//Server side prepared statements were introduced in 7.3
|
||||||
if (connection.haveMinimumServerVersion("7.3")) {
|
if (connection.haveMinimumServerVersion("7.3")) {
|
||||||
//If turning server prepared statements off deallocate statement
|
if (m_useServerPrepare != flag)
|
||||||
//and reset statement name
|
deallocateQuery();
|
||||||
if (m_useServerPrepare != flag && !flag && m_statementName != null)
|
|
||||||
connection.execSQL("DEALLOCATE " + m_statementName);
|
|
||||||
m_statementName = null;
|
|
||||||
m_useServerPrepare = flag;
|
m_useServerPrepare = flag;
|
||||||
} else {
|
} else {
|
||||||
//This is a pre 7.3 server so no op this method
|
//This is a pre 7.3 server so no op this method
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/Attic/AbstractJdbc2ResultSet.java,v 1.24 2003/09/17 05:14:52 barry Exp $
|
* $Header: /cvsroot/pgsql/src/interfaces/jdbc/org/postgresql/jdbc2/Attic/AbstractJdbc2ResultSet.java,v 1.25 2003/10/29 02:39:09 davec Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
@ -389,13 +389,6 @@ public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.Abstra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getFetchSize() throws SQLException
|
|
||||||
{
|
|
||||||
// Returning the current batch size seems the right thing to do.
|
|
||||||
return rows.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Object getObject(String columnName, java.util.Map map) throws SQLException
|
public Object getObject(String columnName, java.util.Map map) throws SQLException
|
||||||
{
|
{
|
||||||
return getObject(findColumn(columnName), map);
|
return getObject(findColumn(columnName), map);
|
||||||
|
@ -518,13 +511,6 @@ public abstract class AbstractJdbc2ResultSet extends org.postgresql.jdbc1.Abstra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setFetchSize(int rows) throws SQLException
|
|
||||||
{
|
|
||||||
// Sub-classes should implement this as part of their cursor support
|
|
||||||
throw org.postgresql.Driver.notImplemented();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void cancelRowUpdates()
|
public synchronized void cancelRowUpdates()
|
||||||
throws SQLException
|
throws SQLException
|
||||||
{
|
{
|
||||||
|
|
|
@ -51,7 +51,10 @@ public class CursorFetchTest extends TestCase
|
||||||
int[] testSizes = { 0, 1, 49, 50, 51, 99, 100, 101 };
|
int[] testSizes = { 0, 1, 49, 50, 51, 99, 100, 101 };
|
||||||
for (int i = 0; i < testSizes.length; ++i) {
|
for (int i = 0; i < testSizes.length; ++i) {
|
||||||
stmt.setFetchSize(testSizes[i]);
|
stmt.setFetchSize(testSizes[i]);
|
||||||
|
assertEquals(testSizes[i], stmt.getFetchSize());
|
||||||
|
|
||||||
ResultSet rs = stmt.executeQuery();
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
assertEquals(testSizes[i], rs.getFetchSize());
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
|
@ -63,6 +66,115 @@ public class CursorFetchTest extends TestCase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tests for ResultSet.setFetchSize().
|
||||||
|
//
|
||||||
|
|
||||||
|
// test one:
|
||||||
|
// set fetchsize = 0
|
||||||
|
// run query (all rows should be fetched)
|
||||||
|
// set fetchsize = 50 (should have no effect)
|
||||||
|
// process results
|
||||||
|
public void testResultSetFetchSizeOne() throws Exception
|
||||||
|
{
|
||||||
|
createRows(100);
|
||||||
|
|
||||||
|
PreparedStatement stmt = con.prepareStatement("select * from test_fetch order by value");
|
||||||
|
stmt.setFetchSize(0);
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
stmt.setFetchSize(50); // Should have no effect.
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (rs.next()) {
|
||||||
|
assertEquals(count, rs.getInt(1));
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(100, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test two:
|
||||||
|
// set fetchsize = 25
|
||||||
|
// run query (25 rows fetched)
|
||||||
|
// set fetchsize = 0
|
||||||
|
// process results:
|
||||||
|
// process 25 rows
|
||||||
|
// should do a FETCH ALL to get more data
|
||||||
|
// process 75 rows
|
||||||
|
public void testResultSetFetchSizeTwo() throws Exception
|
||||||
|
{
|
||||||
|
createRows(100);
|
||||||
|
|
||||||
|
PreparedStatement stmt = con.prepareStatement("select * from test_fetch order by value");
|
||||||
|
stmt.setFetchSize(25);
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
stmt.setFetchSize(0);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (rs.next()) {
|
||||||
|
assertEquals(count, rs.getInt(1));
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(100, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test three:
|
||||||
|
// set fetchsize = 25
|
||||||
|
// run query (25 rows fetched)
|
||||||
|
// set fetchsize = 50
|
||||||
|
// process results:
|
||||||
|
// process 25 rows. should NOT hit end-of-results here.
|
||||||
|
// do a FETCH FORWARD 50
|
||||||
|
// process 50 rows
|
||||||
|
// do a FETCH FORWARD 50
|
||||||
|
// process 25 rows. end of results.
|
||||||
|
public void testResultSetFetchSizeThree() throws Exception
|
||||||
|
{
|
||||||
|
createRows(100);
|
||||||
|
|
||||||
|
PreparedStatement stmt = con.prepareStatement("select * from test_fetch order by value");
|
||||||
|
stmt.setFetchSize(25);
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
stmt.setFetchSize(50);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (rs.next()) {
|
||||||
|
assertEquals(count, rs.getInt(1));
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(100, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test four:
|
||||||
|
// set fetchsize = 50
|
||||||
|
// run query (50 rows fetched)
|
||||||
|
// set fetchsize = 25
|
||||||
|
// process results:
|
||||||
|
// process 50 rows.
|
||||||
|
// do a FETCH FORWARD 25
|
||||||
|
// process 25 rows
|
||||||
|
// do a FETCH FORWARD 25
|
||||||
|
// process 25 rows. end of results.
|
||||||
|
public void testResultSetFetchSizeFour() throws Exception
|
||||||
|
{
|
||||||
|
createRows(100);
|
||||||
|
|
||||||
|
PreparedStatement stmt = con.prepareStatement("select * from test_fetch order by value");
|
||||||
|
stmt.setFetchSize(50);
|
||||||
|
ResultSet rs = stmt.executeQuery();
|
||||||
|
stmt.setFetchSize(25);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (rs.next()) {
|
||||||
|
assertEquals(count, rs.getInt(1));
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(100, count);
|
||||||
|
}
|
||||||
|
|
||||||
// Test odd queries that should not be transformed into cursor-based fetches.
|
// Test odd queries that should not be transformed into cursor-based fetches.
|
||||||
public void TODO_FAILS_testInsert() throws Exception
|
public void TODO_FAILS_testInsert() throws Exception
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue