Index: java/client/org/apache/derby/client/net/NetConnection.java
===================================================================
--- java/client/org/apache/derby/client/net/NetConnection.java	(revision 641363)
+++ java/client/org/apache/derby/client/net/NetConnection.java	(working copy)
@@ -1768,6 +1768,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 were added in the same version as layber 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 641363)
+++ java/client/org/apache/derby/client/net/NetCursor.java	(working copy)
@@ -1058,7 +1058,7 @@
      * @return locator value, <code>Lob.INVALID_LOCATOR</code> 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, the value will be
@@ -1078,6 +1078,7 @@
         // Check for locator
         int locator = locator(column);
         if (locator > 0) { // Create locator-based LOB object
+            netResultSet_.keepLocatorColumnAlive(column);
             return new Blob(agent, locator);
         }
         
@@ -1112,6 +1113,7 @@
         // Check for locator
         int locator = locator(column);
         if (locator > 0) { // Create locator-based LOB object
+            netResultSet_.keepLocatorColumnAlive(column);
             return new Clob(agent, locator);
         }
         
Index: java/client/org/apache/derby/client/am/Connection.java
===================================================================
--- java/client/org/apache/derby/client/am/Connection.java	(revision 641363)
+++ java/client/org/apache/derby/client/am/Connection.java	(working copy)
@@ -1031,6 +1031,13 @@
      */
     protected abstract boolean supportsSessionDataCaching();
 
+    /**
+     * 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 641363)
+++ java/client/org/apache/derby/client/am/Statement.java	(working copy)
@@ -1489,6 +1489,8 @@
         }
         resultSet.resultSetMetaData_ = resultSetMetaData_;
         resultSet.resultSetMetaData_.resultSetConcurrency_ = resultSet.resultSetConcurrency_;
+        // Create tracker for locator LOB columns.
+        resultSet.createLOBColumnTracker();
 
         // only cache the Cursor object for a PreparedStatement and if a Cursor object is
         // not already cached.
Index: java/client/org/apache/derby/client/am/Cursor.java
===================================================================
--- java/client/org/apache/derby/client/am/Cursor.java	(revision 641363)
+++ java/client/org/apache/derby/client/am/Cursor.java	(working copy)
@@ -661,6 +661,27 @@
         return recyclableCalendar_;
     }
 
+    /**
+     * Returns a reference to the locator procedures.
+     * <p>
+     * These procedures are used to operate on large objects referenced on the
+     * server by locators.
+     *
+     * @return The locator procedures object.
+     */
+    CallableLocatorProcedures getLocatorProedures() {
+        return agent_.connection_.locatorProcedureCall();
+    }
+
+    /**
+     * Obtains the locator for the specified column.
+     *
+     * @param column 1-based column index
+     * @return A positive integer locator if valid, {@link Lob#INVALID_LOCATOR}
+     *      otherwise.
+     */
+    protected abstract int locator(int column);
+
     abstract public Blob getBlobColumn_(int column, Agent agent) throws SqlException;
 
     abstract public Clob getClobColumn_(int column, Agent agent) throws SqlException;
Index: java/client/org/apache/derby/client/am/NoOpLOBStateTracker.java
===================================================================
--- java/client/org/apache/derby/client/am/NoOpLOBStateTracker.java	(revision 0)
+++ java/client/org/apache/derby/client/am/NoOpLOBStateTracker.java	(revision 0)
@@ -0,0 +1,51 @@
+/*
+
+   Derby - Class org.apache.derby.client.am.NoOpLOBStateTracker
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+*/
+package org.apache.derby.client.am;
+
+import org.apache.derby.shared.common.sanity.SanityManager;
+
+/**
+ * A tracker implementation that does nothing, intended to be used when
+ * locators are not supported be the server.
+ */
+final class NoOpLOBStateTracker
+    extends LOBStateTracker {
+
+    public NoOpLOBStateTracker() {
+        super();
+    }
+
+    void checkCurrentRow(Cursor ignore) {
+        // Does nothing.
+    }
+
+    void noRelease(int ignore) {
+        // Does nothing.
+        if (SanityManager.DEBUG) {
+            SanityManager.THROWASSERT("NoOpLobStateTracker.noRelease should " +
+                    "not be called when locators are not supported.");
+        }
+    }
+
+    void discardState() {
+        // Does nothing.
+    }
+}

Property changes on: java/client/org/apache/derby/client/am/NoOpLOBStateTracker.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: java/client/org/apache/derby/client/am/LOBStateTracker.java
===================================================================
--- java/client/org/apache/derby/client/am/LOBStateTracker.java	(revision 0)
+++ java/client/org/apache/derby/client/am/LOBStateTracker.java	(revision 0)
@@ -0,0 +1,143 @@
+/*
+
+   Derby - Class org.apache.derby.client.am.LOBStateTracker
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+*/
+
+package org.apache.derby.client.am;
+
+import java.util.Arrays;
+
+/**
+ * Tracks the state of large objects (LOBs) represented by locators in a
+ * result set.
+ * <p>
+ * The state of LOBs must be tracked to ensure they are released on the server
+ * in a timely manner. If too many locators are kept upen, the assoicated
+ * resources on the server will consume too much memory. There are also other
+ * possible side effects, like holding on to locks for too long.
+ * <p>
+ * Note that it only makes sense to use this class if locators are used.
+ *
+ * @see NoOpLOBStateTracker
+ */
+//@NotThreadSafe
+class LOBStateTracker {
+
+    private static final int UNKNOWN = -17;
+    private static final int IGNORE = -18;
+
+    /** The columns indexes of the LOB columns. */
+    private final int[] columns;
+    /** Tells whether a LOB is a Blob or a Clob. */
+    private final boolean[] isBlob;
+    /** Status for the current row. */
+    private final int[] rowStatus;
+
+    LOBStateTracker(int[] lobColumns, boolean[] isBlob) {
+        this.columns = lobColumns;
+        this.isBlob = isBlob;
+        this.rowStatus = new int[lobColumns.length];
+        Arrays.fill(this.rowStatus, UNKNOWN);
+        if (lobColumns.length != isBlob.length) {
+            throw new IllegalArgumentException("Index array must have same " +
+                    "length as type array: " + lobColumns.length + " != " +
+                    isBlob.length);
+        }
+    }
+
+    /**
+     * Constructor used for no-op tracker implementations.
+     *
+     * @see NoOpLOBStateTracker
+     */
+    protected LOBStateTracker() {
+        this.columns = null;
+        this.isBlob = null;
+        this.rowStatus = null;
+    }
+
+    /**
+     * Marks the specified column of the current row as non-releasable.
+     * <p>
+     * Columns must be marked as non-releasable when a LOB object is created, to
+     * avoid releasing the corresponding locator too early.
+     *
+     * @param index 1-based column index
+     */
+    void noRelease(int index) {
+        // Locate the 1-based column index in the internal array and update
+        // the corresponding state entry.
+        for (int i=0; i < this.columns.length; i++) {
+            if (this.columns[i] == index) {
+                rowStatus[i] = IGNORE;
+                break;
+            }
+        }
+    }
+
+    /**
+     * Checks the next row, updating state and freeing locators on the
+     * server if required.
+     *
+     * @param cursor the cursor object to use for freeing the locators
+     * @throws SqlException if freeing the locators on the server fails
+     */
+    void checkCurrentRow(Cursor cursor)
+            throws SqlException {
+        final CallableLocatorProcedures procs = cursor.getLocatorProedures();
+        // IMPLEMENTATION NOTE / OPTIMALIZATION:
+        // The block of code below is very naive, and can cause a round trip
+        // for each LOB for each rs.next (or other navigational method).
+        // There are two possible optimizations:
+        //  (a) Implement a new stored procedure freeing a list of locators,
+        //      and free locators when a threshold is reached.
+        //  (b) Piggy-back list of locators on other requests.
+        //
+        // Optimization (a) is easy and will most likely be implemented right
+        // away. (b) is more complex, but frees resources optimally at almost no
+        // extra cost.
+        for (int i=0; i < this.columns.length; i++) {
+            if (this.rowStatus[i] == UNKNOWN) {
+                // Fetch locator so we can free it.
+                int locator = cursor.locator(this.columns[i]);
+                if (locator != Lob.INVALID_LOCATOR) {
+                    if (this.isBlob[i]) {
+                        procs.blobReleaseLocator(locator);
+                    } else {
+                        procs.clobReleaseLocator(locator);
+                    }
+                }
+            } else {
+                // Reset state to unknown.
+                this.rowStatus[i] = UNKNOWN;
+            }
+        }
+    }
+
+    /**
+     * Discards all recorded dynamic state about LOBs.
+     * <p>
+     * Typically called on connection commit or rollback, as this will release
+     * all locators on the server automatically. There is no need to release
+     * them from the client side.
+     */
+    void discardState() {
+        Arrays.fill(this.rowStatus, IGNORE);
+    }
+}

Property changes on: java/client/org/apache/derby/client/am/LOBStateTracker.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: java/client/org/apache/derby/client/am/ResultSet.java
===================================================================
--- java/client/org/apache/derby/client/am/ResultSet.java	(revision 641363)
+++ 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,7 @@
     public ColumnMetaData resultSetMetaData_; // As obtained from the SQLDA
     private SqlWarning warnings_;
     public Cursor cursor_;
+    private LOBStateTracker lobState = null;
     protected Agent agent_;
 
     public Section generatedSection_ = null;
@@ -426,6 +428,12 @@
         if (!openOnClient_) {
             return;
         }
+        // See if there are open locators on the current row, if valid.
+        if (isValidCursorPosition_) {
+            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_) {
@@ -3767,6 +3775,9 @@
             }
             isValidCursorPosition_ = true;
         }
+        if (isValidCursorPosition_) {
+            lobState.checkCurrentRow(cursor_);
+        }
     }
 
     /**
@@ -4338,6 +4349,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
@@ -4350,6 +4362,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();
@@ -6173,4 +6186,44 @@
             throw se.getSQLException();
         }
     }
+
+    public final void keepLocatorColumnAlive(int index) {
+        this.lobState.noRelease(index);
+    }
+
+    final void createLOBColumnTracker() {
+        if (this.lobState != null) {
+            if (SanityManager.DEBUG) {
+                SanityManager.THROWASSERT(
+                        "LOB state tracker already initialized.");
+            }
+        }
+        if (this.connection_.serverSupportsLocators() &&
+                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) {
+                    tmpIndexes[lobCount] = i +1; // Convert to 1-based index.
+                    tmpIsBlob[lobCount++] = true;
+                } else if (type == Types.CLOB) {
+                    tmpIndexes[lobCount] = i +1; // Convert to 1-based index.
+                    tmpIsBlob[lobCount++] = false;
+                }
+            }
+            // 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);
+        } else {
+            // Create a tracker that does nothing.
+            // Done to simplify code expecting a tracker object.
+            this.lobState = new NoOpLOBStateTracker();
+        }
+    }
 }
