Index: java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/LargeDataLocksTest.java =================================================================== --- java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/LargeDataLocksTest.java (revision 574730) +++ java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/LargeDataLocksTest.java (working copy) @@ -196,8 +196,7 @@ public static Test suite() { TestSuite suite = new TestSuite("LargeDataLocksTest"); suite.addTest(baseSuite("LargeDataLocksTest:embedded")); - // Disable for client until DERBY-2892 is fixed - //suite.addTest(TestConfiguration.clientServerDecorator(baseSuite("LargeDataLocksTest:client"))); + suite.addTest(TestConfiguration.clientServerDecorator(baseSuite("LargeDataLocksTest:client"))); return suite; } Index: java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/_Suite.java =================================================================== --- java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/_Suite.java (revision 574730) +++ java/testing/org/apache/derbyTesting/functionTests/tests/derbynet/_Suite.java (working copy) @@ -55,6 +55,7 @@ suite.addTest(SecureServerTest.suite()); suite.addTest(SSLTest.suite()); suite.addTest(NetIjTest.suite()); + suite.addTest(LOBLocatorReleaseTest.suite()); // Disabled due to "java.sql.SQLSyntaxErrorException: The class // 'org.apache.derbyTesting.functionTests.tests.derbynet.checkSecMgr' Index: java/client/org/apache/derby/client/net/NetConnection.java =================================================================== --- java/client/org/apache/derby/client/net/NetConnection.java (revision 574730) +++ java/client/org/apache/derby/client/net/NetConnection.java (working copy) @@ -1782,6 +1782,17 @@ /** + * Checks whether the server supports locators for large objects. + * + * @return {@code true} if LOB locators are supported. + */ + protected final boolean serverSupportsLocators() { + // Support for locators was added in the same version as layer B + // streaming. + return serverSupportsLayerBStreaming(); + } + + /** * Returns if a transaction is in process * @return open */ Index: java/client/org/apache/derby/client/net/NetCursor.java =================================================================== --- java/client/org/apache/derby/client/net/NetCursor.java (revision 574730) +++ java/client/org/apache/derby/client/net/NetCursor.java (working copy) @@ -1052,11 +1052,14 @@ /** * Get locator for LOB of the designated column + *
+ * Note that this method cannot be invoked on a LOB column that is NULL.
+ *
* @param column column number, starts at 1
* @return locator value, Lob.INVALID_LOCATOR if LOB
* value was sent instead of locator
*/
- private int locator(int column)
+ protected int locator(int column)
{
int locator = get_INTEGER(column);
// If Lob value was sent instead of locator, highest bit will be set
@@ -1068,8 +1071,15 @@
}
}
- public Blob getBlobColumn_(int column, Agent agent) throws SqlException
- {
+ /**
+ * @see org.apache.derby.client.am.Cursor#getBlobColumn_
+ */
+ public Blob getBlobColumn_(int column, Agent agent, boolean toBePublished)
+ throws SqlException {
+ // Only inform the tracker if the Blob is published to the user.
+ if (toBePublished) {
+ netResultSet_.markLOBAsAccessed(column);
+ }
// Check for locator
int locator = locator(column);
if (locator > 0) { // Create locator-based LOB object
@@ -1103,7 +1113,15 @@
}
- public Clob getClobColumn_(int column, Agent agent) throws SqlException {
+ /**
+ * @see org.apache.derby.client.am.Cursor#getClobColumn_
+ */
+ public Clob getClobColumn_(int column, Agent agent, boolean toBePublished)
+ throws SqlException {
+ // Only inform the tracker if the Clob is published to the user.
+ if (toBePublished) {
+ netResultSet_.markLOBAsAccessed(column);
+ }
// Check for locator
int locator = locator(column);
if (locator > 0) { // Create locator-based LOB object
Index: java/client/org/apache/derby/client/am/Connection.java
===================================================================
--- java/client/org/apache/derby/client/am/Connection.java (revision 574730)
+++ java/client/org/apache/derby/client/am/Connection.java (working copy)
@@ -986,6 +986,13 @@
}
}
+ /**
+ * Checks whether the server supports locators for large objects.
+ *
+ * @return {@code true} if LOB locators are supported.
+ */
+ protected abstract boolean serverSupportsLocators();
+
public int getTransactionIsolation() throws SQLException {
// Store the current auto-commit value and use it to restore
Index: java/client/org/apache/derby/client/am/Statement.java
===================================================================
--- java/client/org/apache/derby/client/am/Statement.java (revision 574730)
+++ java/client/org/apache/derby/client/am/Statement.java (working copy)
@@ -1390,6 +1390,8 @@
}
resultSet.resultSetMetaData_ = resultSetMetaData_;
resultSet.resultSetMetaData_.resultSetConcurrency_ = resultSet.resultSetConcurrency_;
+ // Create tracker for LOB locator columns.
+ resultSet.createLOBColumnTracker();
// only cache the Cursor object for a PreparedStatement and if a Cursor object is
// not already cached.
@@ -1422,6 +1424,8 @@
resultSet.completeSqlca(sqlca);
// For CallableStatements we can't just clobber the resultSet_ here, must use setResultSetEvent() separately
resultSet.resultSetMetaData_ = resultSetMetaData;
+ // Create tracker for LOB locator columns.
+ resultSet.createLOBColumnTracker();
// The following two assignments should have already happened via prepareEvent(),
// but are included here for safety for the time being.
Index: java/client/org/apache/derby/client/am/Cursor.java
===================================================================
--- java/client/org/apache/derby/client/am/Cursor.java (revision 574730)
+++ java/client/org/apache/derby/client/am/Cursor.java (working copy)
@@ -23,6 +23,8 @@
import org.apache.derby.shared.common.reference.SQLState;
import java.sql.SQLException;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
// When we calculate column offsets make sure we calculate the correct offsets for double byte charactr5er data
@@ -661,10 +663,55 @@
return recyclableCalendar_;
}
- abstract public Blob getBlobColumn_(int column, Agent agent) throws SqlException;
+ /**
+ * Returns a reference to the locator procedures.
+ *
+ * These procedures are used to operate on large objects referenced on the + * server by locators. + * + * @return The locator procedures object. + */ + CallableLocatorProcedures getLocatorProcedures() { + return agent_.connection_.locatorProcedureCall(); + } - abstract public Clob getClobColumn_(int column, Agent agent) throws SqlException; + /** + * Obtains the locator for the specified LOB column. + *
+ * Note that this method cannot be invoked on a LOB column that is NULL.
+ *
+ * @param column 1-based column index
+ * @return A positive integer locator if valid, {@link Lob#INVALID_LOCATOR}
+ * otherwise.
+ */
+ protected abstract int locator(int column);
+ /**
+ * Returns a {@code Blob} object.
+ *
+ * @param column 1-based column index
+ * @param agent associated agent
+ * @param toBePublished whether the Blob will be published to the user
+ * @return A {@linkplain java.sql.Blob Blob} object.
+ * @throws SqlException if getting the {@code Blob} fails
+ */
+ public abstract Blob getBlobColumn_(int column, Agent agent,
+ boolean toBePublished)
+ throws SqlException;
+
+ /**
+ * Returns a {@code Clob} object.
+ *
+ * @param column 1-based column index
+ * @param agent associated agent
+ * @param toBePublished whether the Clob will be published to the user
+ * @return A {@linkplain java.sql.Clob Clob} object.
+ * @throws SqlException if getting the {@code Clob} fails
+ */
+ public abstract Clob getClobColumn_(int column, Agent agent,
+ boolean toBePublished)
+ throws SqlException;
+
// get the raw clob bytes, without translation. dataOffset must be int[1]
abstract public byte[] getClobBytes_(int column, int[] dataOffset /*output*/) throws SqlException;
@@ -979,11 +1026,14 @@
return (maxFieldSize_ == 0) ? tempString :
tempString.substring(0, java.lang.Math.min(maxFieldSize_, tempString.length()));
case java.sql.Types.BLOB:
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return agent_.crossConverters_.getStringFromBytes(b.getBytes(1, (int) b.length()));
+ Blob b = getBlobColumn_(column, agent_, false);
+ tempString = agent_.crossConverters_.
+ getStringFromBytes(b.getBytes(1, (int) b.length()));
+ return tempString;
case java.sql.Types.CLOB:
- Clob c = getClobColumn_(column, agent_);
- return c.getSubString(1, (int) c.length());
+ Clob c = getClobColumn_(column, agent_, false);
+ tempString = c.getSubString(1, (int) c.length());
+ return tempString;
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "String");
@@ -1002,8 +1052,9 @@
case java.sql.Types.LONGVARBINARY:
return get_VARCHAR_FOR_BIT_DATA(column);
case java.sql.Types.BLOB:
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return b.getBytes(1, (int) b.length());
+ Blob b = getBlobColumn_(column, agent_, false);
+ byte[] bytes = b.getBytes(1, (int) b.length());
+ return bytes;
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "byte[]");
@@ -1013,32 +1064,43 @@
}
}
- public final java.io.InputStream getBinaryStream(int column) throws SqlException {
- try {
- switch (jdbcTypes_[column - 1]) {
+ public final java.io.InputStream getBinaryStream(int column)
+ throws SqlException
+ {
+ switch (jdbcTypes_[column - 1]) {
case java.sql.Types.BINARY:
return new java.io.ByteArrayInputStream(get_CHAR_FOR_BIT_DATA(column));
case java.sql.Types.VARBINARY:
case java.sql.Types.LONGVARBINARY:
return new java.io.ByteArrayInputStream(get_VARCHAR_FOR_BIT_DATA(column));
case java.sql.Types.BLOB:
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return b.getBinaryStream();
+ Blob b = getBlobColumn_(column, agent_, false);
+ if (b.isLocator()) {
+ BlobLocatorInputStream is
+ = new BlobLocatorInputStream(agent_.connection_, b);
+ return new BufferedInputStream(is);
+ } else {
+ return b.getBinaryStreamX();
+ }
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "java.io.InputStream");
- }
- } catch ( SQLException se ) {
- throw new SqlException(se);
}
}
- public final java.io.InputStream getAsciiStream(int column) throws SqlException {
- try {
- switch (jdbcTypes_[column - 1]) {
+ public final java.io.InputStream getAsciiStream(int column)
+ throws SqlException
+ {
+ switch (jdbcTypes_[column - 1]) {
case java.sql.Types.CLOB:
- Clob c = getClobColumn_(column, agent_);
- return c.getAsciiStream();
+ Clob c = getClobColumn_(column, agent_, false);
+ if (c.isLocator()) {
+ ClobLocatorInputStream is
+ = new ClobLocatorInputStream(agent_.connection_, c);
+ return new BufferedInputStream(is);
+ } else {
+ return c.getAsciiStreamX();
+ }
case java.sql.Types.CHAR:
try {
return new java.io.ByteArrayInputStream(getCHAR(column).getBytes("ISO-8859-1"));
@@ -1062,24 +1124,19 @@
case java.sql.Types.LONGVARBINARY:
return new java.io.ByteArrayInputStream(get_VARCHAR_FOR_BIT_DATA(column));
case java.sql.Types.BLOB:
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return b.getBinaryStream();
+ return getBinaryStream(column);
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "java.io.InputStream");
- }
}
- catch ( SQLException se ) {
- throw new SqlException(se);
- }
}
-
+
public final java.io.InputStream getUnicodeStream(int column) throws SqlException {
try {
switch (jdbcTypes_[column - 1]) {
case java.sql.Types.CLOB:
{
- Clob c = getClobColumn_(column, agent_);
+ Clob c = getClobColumn_(column, agent_, false);
String s = c.getSubString(1L, (int) c.length());
try {
return new java.io.ByteArrayInputStream(s.getBytes("UTF-8"));
@@ -1114,8 +1171,7 @@
case java.sql.Types.LONGVARBINARY:
return new java.io.ByteArrayInputStream(get_VARCHAR_FOR_BIT_DATA(column));
case java.sql.Types.BLOB:
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return b.getBinaryStream();
+ return getBinaryStream(column);
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "UnicodeStream");
@@ -1125,12 +1181,19 @@
}
}
- public final java.io.Reader getCharacterStream(int column) throws SqlException {
- try {
- switch (jdbcTypes_[column - 1]) {
+ public final java.io.Reader getCharacterStream(int column)
+ throws SqlException
+ {
+ switch (jdbcTypes_[column - 1]) {
case java.sql.Types.CLOB:
- Clob c = getClobColumn_(column, agent_);
- return c.getCharacterStream();
+ Clob c = getClobColumn_(column, agent_, false);
+ if (c.isLocator()) {
+ ClobLocatorReader reader
+ = new ClobLocatorReader(agent_.connection_, c);
+ return new BufferedReader(reader);
+ } else {
+ return c.getCharacterStreamX();
+ }
case java.sql.Types.CHAR:
return new java.io.StringReader(getCHAR(column));
case java.sql.Types.VARCHAR:
@@ -1155,8 +1218,8 @@
}
case java.sql.Types.BLOB:
try {
- Blob b = (Blob) getBlobColumn_(column, agent_);
- return new java.io.InputStreamReader(b.getBinaryStream(), "UTF-16BE");
+ return new java.io.InputStreamReader(getBinaryStream(column),
+ "UTF-16BE");
} catch (java.io.UnsupportedEncodingException e) {
throw new SqlException(agent_.logWriter_,
new ClientMessageId (SQLState.UNSUPPORTED_ENCODING),
@@ -1166,15 +1229,12 @@
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "java.io.Reader");
}
- } catch ( SQLException se ) {
- throw new SqlException(se);
- }
}
public final java.sql.Blob getBlob(int column) throws SqlException {
switch (jdbcTypes_[column - 1]) {
case Types.BLOB:
- return getBlobColumn_(column, agent_);
+ return getBlobColumn_(column, agent_, true);
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "java.sql.Blob");
@@ -1184,7 +1244,7 @@
public final java.sql.Clob getClob(int column) throws SqlException {
switch (jdbcTypes_[column - 1]) {
case Types.CLOB:
- return getClobColumn_(column, agent_);
+ return getClobColumn_(column, agent_, true);
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "java.sql.Clob");
@@ -1233,9 +1293,9 @@
case java.sql.Types.LONGVARBINARY:
return get_VARCHAR_FOR_BIT_DATA(column);
case java.sql.Types.BLOB:
- return getBlobColumn_(column, agent_);
+ return getBlobColumn_(column, agent_, true);
case java.sql.Types.CLOB:
- return getClobColumn_(column, agent_);
+ return getClobColumn_(column, agent_, true);
default:
throw new ColumnTypeConversionException(agent_.logWriter_,
"java.sql.Types " + jdbcTypes_[column -1], "Object");
Index: java/client/org/apache/derby/client/am/Blob.java
===================================================================
--- java/client/org/apache/derby/client/am/Blob.java (revision 574730)
+++ java/client/org/apache/derby/client/am/Blob.java (working copy)
@@ -21,7 +21,6 @@
package org.apache.derby.client.am;
-import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -260,7 +259,7 @@
}
}
- private java.io.InputStream getBinaryStreamX() throws SqlException {
+ java.io.InputStream getBinaryStreamX() throws SqlException {
checkForClosedConnection();
if (isBinaryStream()) // this Lob is used for input
@@ -651,8 +650,6 @@
* @throws SQLException if pos is less than 1 or if pos is greater than
* the number of bytes in the Blob or if pos + length is
* greater than the number of bytes in the Blob
- *
- * @throws SQLException.
*/
public InputStream getBinaryStream(long pos, long length)
throws SQLException {
Index: java/client/org/apache/derby/client/am/Clob.java
===================================================================
--- java/client/org/apache/derby/client/am/Clob.java (revision 574730)
+++ java/client/org/apache/derby/client/am/Clob.java (working copy)
@@ -363,7 +363,7 @@
}
}
- private java.io.Reader getCharacterStreamX() throws SqlException {
+ java.io.Reader getCharacterStreamX() throws SqlException {
checkForClosedConnection();
//check is this Lob is locator enabled
@@ -409,7 +409,7 @@
}
}
- private java.io.InputStream getAsciiStreamX() throws SqlException {
+ java.io.InputStream getAsciiStreamX() throws SqlException {
checkForClosedConnection();
if (isAsciiStream()) // this Lob is used for input
Index: java/client/org/apache/derby/client/am/ResultSet.java
===================================================================
--- java/client/org/apache/derby/client/am/ResultSet.java (revision 574730)
+++ java/client/org/apache/derby/client/am/ResultSet.java (working copy)
@@ -28,6 +28,7 @@
import org.apache.derby.client.am.SQLExceptionFactory;
import org.apache.derby.shared.common.reference.SQLState;
import org.apache.derby.shared.common.i18n.MessageUtil;
+import org.apache.derby.shared.common.sanity.SanityManager;
public abstract class ResultSet implements java.sql.ResultSet,
ResultSetCallbackInterface {
@@ -37,6 +38,8 @@
public ColumnMetaData resultSetMetaData_; // As obtained from the SQLDA
private SqlWarning warnings_;
public Cursor cursor_;
+ /** Tracker object for LOB state, used to free locators on the server. */
+ private LOBStateTracker lobState = null;
protected Agent agent_;
public Section generatedSection_ = null;
@@ -426,6 +429,13 @@
if (!openOnClient_) {
return;
}
+ closeCloseFilterInputStream();
+ // See if there are open locators on the current row, if valid.
+ if (isValidCursorPosition_ && !isOnInsertRow_) {
+ lobState.checkCurrentRow(cursor_);
+ }
+ // NOTE: The preClose_ method must also check for locators if
+ // prefetching of data is enabled for result sets containing LOBs.
preClose_();
try {
if (openOnServer_) {
@@ -3753,6 +3763,12 @@
}
}
+ /**
+ * Moves off the insert row if positioned there, and checks the current row
+ * for releasable LOB locators if positioned on a valid data row.
+ *
+ * @throws SqlException if releasing a LOB locator fails
+ */
private void moveToCurrentRowX() throws SqlException {
if (isOnInsertRow_) {
resetUpdatedColumns();
@@ -3763,6 +3779,14 @@
}
isValidCursorPosition_ = true;
}
+ if (isValidCursorPosition_) {
+ // isOnInsertRow must be false here.
+ if (SanityManager.DEBUG) {
+ SanityManager.ASSERT(!isOnInsertRow_,
+ "Cannot check current row if positioned on insert row");
+ }
+ lobState.checkCurrentRow(cursor_);
+ }
}
/**
@@ -4329,6 +4353,7 @@
public void completeLocalCommit(java.util.Iterator listenerIterator) {
cursorUnpositionedOnServer_ = true;
+ lobState.discardState(); // Locators released on server side.
markAutoCommitted();
if (!cursorHold_) {
// only non-held cursors need to be closed at commit
@@ -4341,6 +4366,7 @@
}
public void completeLocalRollback(java.util.Iterator listenerIterator) {
+ lobState.discardState(); // Locators released on server side.
markAutoCommitted();
// all cursors need to be closed at rollback
markClosed();
@@ -6164,4 +6190,52 @@
throw se.getSQLException();
}
}
+
+ /**
+ * Marks the LOB at the specified column as accessed.
+ *
+ * When a LOB is marked as accessed, the release mechanism will not be + * invoked by the result set. It is expected that the code accessing the + * LOB releases the locator when it is done with the LOB. + * + * @param index 1-based column index + */ + public final void markLOBAsAccessed(int index) { + this.lobState.markAccessed(index); + } + + /** + * Initializes the LOB state tracker. + *
+ * The state tracker is used to free LOB locators on the server. + */ + final void createLOBColumnTracker() { + if (SanityManager.DEBUG) { + SanityManager.ASSERT(this.lobState == null, + "LOB state tracker already initialized."); + } + if (this.resultSetMetaData_.hasLobColumns()) { + final int columnCount = this.resultSetMetaData_.columns_; + int lobCount = 0; + int[] tmpIndexes = new int[columnCount]; + boolean[] tmpIsBlob = new boolean[columnCount]; + for (int i=0; i < columnCount; i++) { + int type = this.resultSetMetaData_.types_[i]; + if (type == Types.BLOB || type == Types.CLOB) { + tmpIndexes[lobCount] = i +1; // Convert to 1-based index. + tmpIsBlob[lobCount++] = (type == Types.BLOB); + } + } + // Create a tracker for the LOB columns found. + int[] lobIndexes = new int[lobCount]; + boolean[] isBlob = new boolean[lobCount]; + System.arraycopy(tmpIndexes, 0, lobIndexes, 0, lobCount); + System.arraycopy(tmpIsBlob, 0, isBlob, 0, lobCount); + this.lobState = new LOBStateTracker(lobIndexes, isBlob, + this.connection_.serverSupportsLocators()); + } else { + // Use a no-op state tracker to simplify code expecting a tracker. + this.lobState = LOBStateTracker.NO_OP_TRACKER; + } + } } Index: java/client/org/apache/derby/client/am/UpdateSensitiveLOBLocatorInputStream.java =================================================================== --- java/client/org/apache/derby/client/am/UpdateSensitiveLOBLocatorInputStream.java (revision 574730) +++ java/client/org/apache/derby/client/am/UpdateSensitiveLOBLocatorInputStream.java (working copy) @@ -160,6 +160,13 @@ currentPos += ret; return ret; } + + public void close() throws IOException + { + if (is != null) { + is.close(); + } + } /** * Verifies whether the current updateCount matches