You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by dy...@apache.org on 2008/02/27 14:53:11 UTC

svn commit: r631593 - in /db/derby/code/trunk/java: client/org/apache/derby/client/am/ client/org/apache/derby/client/net/ drda/org/apache/derby/impl/drda/ engine/org/apache/derby/iapi/jdbc/ engine/org/apache/derby/impl/jdbc/ testing/org/apache/derbyTe...

Author: dyre
Date: Wed Feb 27 05:52:59 2008
New Revision: 631593

URL: http://svn.apache.org/viewvc?rev=631593&view=rev
Log:
DERBY-3192: Cache session data in the client driver 
Piggy-backs the current isolation level and the current schema onto 
messages going back to the client. The client caches this information so that
it can be returned to a user (app) without an extra round-trip.

See also http://wiki.apache.org/db-derby/Derby3192Writeup

Patch file: derby-3192-mark2.v8.diff  

Added:
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java   (with props)
Modified:
    db/derby/code/trunk/java/client/org/apache/derby/client/am/Connection.java
    db/derby/code/trunk/java/client/org/apache/derby/client/am/ConnectionCallbackInterface.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePoint.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePointNameTable.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnectionReply.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetDatabaseMetaData.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetResultSetReply.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetStatementReply.java
    db/derby/code/trunk/java/client/org/apache/derby/client/net/NetXAConnectionReply.java
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/AppRequester.java
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePoint.java
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePointNameTable.java
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DRDAConnThread.java
    db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/Database.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/BrokeredConnection.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/EngineConnection.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/CacheSessionDataTest.java

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/am/Connection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/am/Connection.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/am/Connection.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/am/Connection.java Wed Feb 27 05:52:59 2008
@@ -27,6 +27,7 @@
 import org.apache.derby.shared.common.reference.SQLState;
 
 import java.sql.SQLException;
+import org.apache.derby.shared.common.sanity.SanityManager;
 
 public abstract class Connection implements java.sql.Connection,
         ConnectionCallbackInterface {
@@ -93,7 +94,24 @@
     protected boolean open_ = true;
     private boolean availableForReuse_ = false;
 
-    public int isolation_ = Configuration.defaultIsolation;
+    /**
+     * Constant indicating that isolation_ has not been updated through
+     * piggy-backing, (or that the previously stored value was invalidated,
+     * e.g. by an XA state change).
+     */
+    private static final int TRANSACTION_UNKNOWN = -1;
+    /**
+     * Cached copy of the isolation level. Kept in sync with server through
+     * piggy-backing.
+     */
+    private int isolation_ = TRANSACTION_UNKNOWN;
+
+    /**
+     * Cached copy of the schema name. Updated through piggy-backing and used
+     * to implement statement caching.
+     */
+    private String currentSchemaName_ = null;
+
     public boolean autoCommit_ = true;
     protected boolean inUnitOfWork_ = false; // This means a transaction is in progress.
 
@@ -274,7 +292,8 @@
             // property: open_
             // this should already be true
 
-            isolation_ = Configuration.defaultIsolation;
+            isolation_ = TRANSACTION_UNKNOWN;
+            currentSchemaName_ = null;
             autoCommit_ = true;
             inUnitOfWork_ = false;
 
@@ -311,7 +330,8 @@
         // property: open_
         // this should already be true
 
-        isolation_ = Configuration.defaultIsolation;
+        isolation_ = TRANSACTION_UNKNOWN;
+        currentSchemaName_ = null;
         autoCommit_ = true;
         inUnitOfWork_ = false;
 
@@ -975,7 +995,9 @@
             // transaction so we have to clean up locally.
             completeLocalCommit();
 
-            isolation_ = level;
+            if (SanityManager.DEBUG && supportsSessionDataCaching()) {
+                SanityManager.ASSERT(isolation_ == level);
+            }
         }
         catch ( SqlException se )
         {
@@ -983,6 +1005,13 @@
         }
     }
 
+    /**
+     * Finds out if the underlaying database connection supports session data
+     * caching.
+     * @return true if sessionData is supported
+     */
+    protected abstract boolean supportsSessionDataCaching();
+
     public int getTransactionIsolation() throws SQLException {
     	
     	// Store the current auto-commit value and use it to restore 
@@ -997,6 +1026,15 @@
                 agent_.logWriter_.traceExit(this, "getTransactionIsolation", isolation_);
             }
             
+            if (isolation_ != TRANSACTION_UNKNOWN) {
+                if (SanityManager.DEBUG) {
+                    SanityManager.ASSERT(supportsSessionDataCaching(),
+                            "Cannot return cached isolation when caching is " +
+                            "not supported!");
+                }
+                return isolation_;
+            }
+
             // Set auto-commit to false when executing the statement as we do not want to
             // cause an auto-commit from getTransactionIsolation() method. 
             autoCommit_ = false;
@@ -1007,6 +1045,14 @@
             // also happen when isolation is set using SQL instead of JDBC. So we try to get the
             // value from the server by calling the "current isolation" function. If we fail to 
             // get the value, return the value stored in the client's connection object.
+            // DERBY-3192 - Cache session data in the client driver allows
+            // the re-introduction of isolation level caching. Changes to the
+            // isolation level triggered from SQL are now handled by
+            // piggybacking the modified isolation level on messages going
+            // back to the client.
+            // The XA-problem is handled by letting XA state changes set the
+            // cached isolation level to TRANSACTION_UNKNOWN which will trigger
+            // a refresh from the server.
             if (getTransactionIsolationStmt == null  || 
             		!(getTransactionIsolationStmt.openOnClient_ &&
             				getTransactionIsolationStmt.openOnServer_)) {
@@ -1020,13 +1066,35 @@
             rs = getTransactionIsolationStmt.executeQuery("values current isolation");
             rs.next();
             String isolationStr = rs.getString(1);
-            isolation_ = translateIsolation(isolationStr);
+
+            int isolation = translateIsolation(isolationStr);
+            if (isolation_ == TRANSACTION_UNKNOWN &&
+                    supportsSessionDataCaching()) {
+                // isolation_ will be TRANSACTION_UNKNOWN if the connection has
+                // been reset on
+                // the client. The server will not observe a
+                // change in isolation level so no information is
+                // piggy-backed. Update the cached value here, rather than
+                // waiting for the isolation to change on the server.
+                isolation_ = isolation;
+            }
+            if (SanityManager.DEBUG) {
+                SanityManager.ASSERT(!supportsSessionDataCaching() ||
+                        (isolation_ == isolation),
+                        "Cached isolation_ not updated, (isolation_="+
+                        isolation_+")!=(isolation="+isolation+")");
+                SanityManager.ASSERT(supportsSessionDataCaching() ||
+                        (isolation_ == TRANSACTION_UNKNOWN),
+                        "isolation_ modified when caching is not supported");
+            }
             rs.close();	
             // So... of we did not have an active transaction before
             // the query, we pretend to still not have an open
             // transaction. The result set is closed, so this should
             // not be problematic. DERBY-2084
             inUnitOfWork_ = savedInUnitOfWork;
+
+            return isolation;
         }
         catch ( SqlException se )
         {
@@ -1038,11 +1106,44 @@
         	if(rs != null)
         		rs.close();
         }
-        
-        return isolation_;
     }
   
     /**
+     * Returns the current schema (the schema that would be used for
+     * compilation. This is not part of the java.sql.Connection interface, and
+     * is only intended for use with statement caching.
+     * @return the name of the current schema
+     * @throws java.sql.SQLException
+     */
+    public String getCurrentSchemaName() throws SQLException {
+        try {
+            checkForClosedConnection();
+        } catch (SqlException se) {
+            throw se.getSQLException();
+        }
+        if (currentSchemaName_ == null) {
+            if (agent_.loggingEnabled()) {
+               agent_.logWriter_.traceEntry(this,
+                  "getCurrentSchemaName() executes query");
+            }
+            java.sql.Statement s = createStatement();
+            java.sql.ResultSet rs = s.executeQuery("VALUES CURRENT SCHEMA");
+            rs.next();
+            String schema = rs.getString(1);
+            rs.close();
+            s.close();
+            return schema;
+        }
+        if (SanityManager.DEBUG) {
+            SanityManager.ASSERT(supportsSessionDataCaching(),
+                    "A cached schema name ("+currentSchemaName_+
+                    ") is not expected when session data caching is not" +
+                    "supported");
+        }
+        return currentSchemaName_;
+    }
+
+    /**
      * Translates the isolation level from a SQL string to the JDBC int value
      *  
      * @param isolationStr SQL isolation string
@@ -1969,6 +2070,20 @@
         } else if (sqlca.getSqlCode() < 0) {
             agent_.accumulateReadException(new SqlException(agent_.logWriter_, sqlca));
         }
+    }
+
+    public void completePiggyBackIsolation(int pbIsolation) {
+        if (SanityManager.DEBUG) {
+            SanityManager.ASSERT(supportsSessionDataCaching());
+        }
+        isolation_ = pbIsolation;
+    }
+
+    public void completePiggyBackSchema(String pbSchema) {
+        if (SanityManager.DEBUG) {
+            SanityManager.ASSERT(supportsSessionDataCaching());
+        }
+        currentSchemaName_ = pbSchema;
     }
 
     public abstract void addSpecialRegisters(String s);

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/am/ConnectionCallbackInterface.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/am/ConnectionCallbackInterface.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/am/ConnectionCallbackInterface.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/am/ConnectionCallbackInterface.java Wed Feb 27 05:52:59 2008
@@ -44,4 +44,18 @@
      *
      */
     public void completeAbnormalUnitOfWork(UnitOfWorkListener uwl);
+
+    /**
+     * Completes piggy-backing of the new current isolation level by
+     * updating the cached copy in am.Connection.
+     * @param pbIsolation new isolation level from the server
+     */
+    public void completePiggyBackIsolation(int pbIsolation);
+
+    /**
+     * Completes piggy-backing of the new current schema by updating
+     * the cached copy in am.Connection.
+     * @param pbSchema new current schema from the server
+     */
+    public void completePiggyBackSchema(String pbSchema);
 }

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePoint.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePoint.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePoint.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePoint.java Wed Feb 27 05:52:59 2008
@@ -599,6 +599,16 @@
     // FDOCA data
     static final int FDODTA = 0x147A;
 
+    // --- Product-specific 0xC000-0xFFFF ---
+    // Piggy-backed session data (product-specific)
+    static final int PBSD = 0xC000;
+
+    // Isolation level as a byte (product-specific)
+    static final int PBSD_ISO = 0xC001;
+
+    // Current schema as UTF8 String (product-specific)
+    static final int PBSD_SCHEMA = 0xC002;
+
     //--------------------------ddm error code points---------------------------------
     // Syntax Error Code.  DSS header length less than 6.
     static int SYNERRCD_DSS_LESS_THAN_6 = 0x01;

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePointNameTable.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePointNameTable.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePointNameTable.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/CodePointNameTable.java Wed Feb 27 05:52:59 2008
@@ -85,6 +85,9 @@
         put(new Integer(CodePoint.PRCCNVRM), "PRCCNVRM");
         put(new Integer(CodePoint.EXCSQLSET), "EXCSQLSET");
         put(new Integer(CodePoint.EXTDTA), "EXTDTA");
+        put(new Integer(CodePoint.PBSD), "PBSD");
+        put(new Integer(CodePoint.PBSD_ISO), "PBSD_ISO");
+        put(new Integer(CodePoint.PBSD_SCHEMA), "PBSD_SCHEMA");
     }
 
     String lookup(int codePoint) {

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnection.java Wed Feb 27 05:52:59 2008
@@ -1740,6 +1740,18 @@
     
     
     /**
+     * Check whether the server supports session data caching
+     * @return true session data caching is supported
+     */
+    protected final boolean supportsSessionDataCaching() {
+
+        NetDatabaseMetaData metadata =
+            (NetDatabaseMetaData) databaseMetaData_;
+
+        return metadata.serverSupportsSessionDataCaching();
+    }
+
+    /**
      * Returns if a transaction is in process
      * @return open
      */

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnectionReply.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnectionReply.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnectionReply.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetConnectionReply.java Wed Feb 27 05:52:59 2008
@@ -2130,7 +2130,11 @@
 
     // This method handles the parsing of all command replies and reply data
     // for the SYNCCTL command.
-    protected int parseSYNCCTLreply(ConnectionCallbackInterface connection) throws DisconnectException {
+    protected int parseSYNCCTLreply(ConnectionCallbackInterface connection) 
+        throws DisconnectException {
+        if (peekCodePoint() == CodePoint.PBSD) {
+            parsePBSD();
+        }
         return 0;
     }
 
@@ -3301,6 +3305,35 @@
         int count = ((rowsetSqlca == null) ? 0 : rowsetSqlca.length);
         for (int i = row; i < count; i++) {
             rowsetSqlca[i] = null;
+        }
+    }
+
+    /**
+     * Parse a PBSD - PiggyBackedSessionData code point. Can contain one or
+     * both of, a PBSD_ISO code point followed by a byte representing the jdbc
+     * isolation level, and a PBSD_SCHEMA code point followed by the name of the
+     * current schema as an UTF-8 String.
+     * @throws org.apache.derby.client.am.DisconnectException
+     */
+    protected void parsePBSD() throws DisconnectException {
+        parseLengthAndMatchCodePoint(CodePoint.PBSD);
+        int peekCP = peekCodePoint();
+        while (peekCP != END_OF_SAME_ID_CHAIN) {
+            parseLengthAndMatchCodePoint(peekCP);
+            switch (peekCP) {
+            case CodePoint.PBSD_ISO:
+                netAgent_.netConnection_.
+                    completePiggyBackIsolation(readUnsignedByte());
+                break;
+            case CodePoint.PBSD_SCHEMA:
+                netAgent_.netConnection_.
+                    completePiggyBackSchema
+                    (readString(getDdmLength(), "UTF-8"));
+                break;
+            default:
+                parseCommonError(peekCP);
+            }
+            peekCP = peekCodePoint();
         }
     }
 }

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetDatabaseMetaData.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetDatabaseMetaData.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetDatabaseMetaData.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetDatabaseMetaData.java Wed Feb 27 05:52:59 2008
@@ -31,6 +31,11 @@
     
     private boolean supportsLayerBStreaming_;
 
+    /**
+     * True if the server supports session data caching
+     */
+    private boolean supportsSessionDataCaching_;
+
     public NetDatabaseMetaData(NetAgent netAgent, NetConnection netConnection) {
         // Consider setting product level during parse
         super(netAgent, netConnection, new ProductLevel(netConnection.productID_,
@@ -86,6 +91,9 @@
         
         supportsLayerBStreaming_ = 
             productLevel_.greaterThanOrEqualTo(10, 3, 0);
+
+        supportsSessionDataCaching_ =
+                productLevel_.greaterThanOrEqualTo(10, 4, 0);
     }
 
     /**
@@ -102,4 +110,11 @@
         return supportsLayerBStreaming_;
     }
 
+    /**
+     * Check if server supports session data caching
+     * @return true if the server supports this
+     */
+    final boolean serverSupportsSessionDataCaching() {
+        return supportsSessionDataCaching_;
+    }
 }

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetResultSetReply.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetResultSetReply.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetResultSetReply.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetResultSetReply.java Wed Feb 27 05:52:59 2008
@@ -126,6 +126,9 @@
                     parseRDBUPDRM();
                     peekCP = peekCodePoint();
                 }
+                if (peekCP == CodePoint.PBSD) {
+                    parsePBSD();
+                }
                 return;
             }
             do {
@@ -175,10 +178,15 @@
         if (peekCP == CodePoint.RDBUPDRM) {
             found = true;
             parseRDBUPDRM();
+            peekCP = peekCodePoint();
         }
 
         if (!found) {
             parseFetchError(resultSetI);
+        }
+
+        if (peekCP == CodePoint.PBSD) {
+            parsePBSD();
         }
 
         if (longBufferForDecryption_ != null) {

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetStatementReply.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetStatementReply.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetStatementReply.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetStatementReply.java Wed Feb 27 05:52:59 2008
@@ -211,12 +211,21 @@
             break;
         }
 
+        peekCP = peekCodePoint();
+        if (peekCP == CodePoint.PBSD) {
+            parsePBSD();
+        }
     }
 
-    // Parse the reply for the Open Query Command.
-    // This method handles the parsing of all command replies and reply data for the opnqry command.
-    // will be replaced by parseOPNQRYreply (see parseOPNQRYreplyProto)
-    private void parseOPNQRYreply(StatementCallbackInterface statementI) throws DisconnectException {
+    /**
+     * Parse the reply for the Open Query Command. This method handles the
+     * parsing of all command replies and reply data for the opnqry command.
+     * will be replaced by parseOPNQRYreply (see parseOPNQRYreplyProto)
+     * @param statementI statement to invoke callbacks on
+     * @throws org.apache.derby.client.am.DisconnectException
+     */
+    private void parseOPNQRYreply(StatementCallbackInterface statementI)
+            throws DisconnectException {
         int peekCP = peekCodePoint();
 
         if (peekCP == CodePoint.OPNQRYRM) {
@@ -238,6 +247,9 @@
             peekCP = peekCodePoint();
         }
 
+        if (peekCP == CodePoint.PBSD) {
+            parsePBSD();
+        }
     }
 
     // Called by NETSetClientPiggybackCommand.read()
@@ -288,6 +300,11 @@
             peekCP = peekCodePoint();
             if (peekCP == CodePoint.RDBUPDRM) {
                 parseRDBUPDRM();
+                peekCP = peekCodePoint();
+            }
+
+            if (peekCP == CodePoint.PBSD) {
+                parsePBSD();
             }
             return;
         }
@@ -302,6 +319,7 @@
             NetSqlca netSqlca = parseSQLCARD(null);
 
             statementI.completeExecute(netSqlca);
+            peekCP = peekCodePoint();
         } else if (peekCP == CodePoint.SQLDTARD) {
             // in the case of singleton select or if a stored procedure was called which had
             // parameters but no result sets, an SQLSTARD may be returned
@@ -332,6 +350,10 @@
             parseExecuteError(statementI);
         }
 
+        if (peekCP == CodePoint.PBSD) {
+            parsePBSD();
+            peekCP = peekCodePoint();
+        }
     }
 
     protected void parseResultSetProcedure(StatementCallbackInterface statementI) throws DisconnectException {

Modified: db/derby/code/trunk/java/client/org/apache/derby/client/net/NetXAConnectionReply.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/client/org/apache/derby/client/net/NetXAConnectionReply.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/client/org/apache/derby/client/net/NetXAConnectionReply.java (original)
+++ db/derby/code/trunk/java/client/org/apache/derby/client/net/NetXAConnectionReply.java Wed Feb 27 05:52:59 2008
@@ -219,6 +219,9 @@
             netAgent_.netConnection_.xares_.addSpecialRegisters(s);
             peekCP = peekCodePoint();
         }
+        if (peekCP == CodePoint.PBSD) {
+            parsePBSD();
+        }
 
         return retval;
     }

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/AppRequester.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/AppRequester.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/AppRequester.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/AppRequester.java Wed Feb 27 05:52:59 2008
@@ -295,4 +295,7 @@
 		
 	}
 
+	protected boolean supportsSessionDataCaching() {
+		return (clientType == DNC_CLIENT && greaterThanOrEqualTo(10, 4, 0));
+	}
 }

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePoint.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePoint.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePoint.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePoint.java Wed Feb 27 05:52:59 2008
@@ -680,6 +680,16 @@
 	// FDOCA Triplet offset
 	static final int FDOTRPOFF = 0x212A;
 
+    // --- Product-specific 0xC000-0xFFFF ---
+    // Piggy-backed session data (product-specific)
+    static final int PBSD = 0xC000;
+
+    // Isolation level as a byte (product-specific)
+    static final int PBSD_ISO = 0xC001;
+
+    // Current schema as UTF8 String (product-specific)
+    static final int PBSD_SCHEMA = 0xC002;
+
 	//--------------------------ddm error code points---------------------------------
 	static final int SYNERRCD_DSS_LESS_THAN_6 = 0x01;
 	static final int SYNERRCD_DSS_LENGTH_BYTE_NUMBER_MISMATCH = 0x02;

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePointNameTable.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePointNameTable.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePointNameTable.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/CodePointNameTable.java Wed Feb 27 05:52:59 2008
@@ -157,6 +157,9 @@
     put (new Integer (CodePoint.UOWDSP), "UOWDSP");
     put (new Integer (CodePoint.USRID), "USRID");
     put (new Integer (CodePoint.VALNSPRM), "VALNSPRM");
+    put (new Integer (CodePoint.PBSD), "PBSD");
+    put (new Integer (CodePoint.PBSD_ISO), "PBSD_ISO");
+    put (new Integer (CodePoint.PBSD_SCHEMA), "PBSD_SCHEMA");
   }
 
   String lookup (int codePoint)

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DRDAConnThread.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DRDAConnThread.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DRDAConnThread.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/DRDAConnThread.java Wed Feb 27 05:52:59 2008
@@ -17,11 +17,7 @@
    limitations under the License.
 
 */
-/**
- * This class translates DRDA protocol from an application requester to JDBC
- * for Derby and then translates the results from Derby to DRDA
- * for return to the application requester.
- */
+
 package org.apache.derby.impl.drda;
 
 import java.io.ByteArrayInputStream;
@@ -34,7 +30,6 @@
 import java.math.BigDecimal;
 import java.sql.CallableStatement;
 import java.sql.Connection;
-import java.sql.Driver;
 import java.sql.ParameterMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -70,6 +65,11 @@
 import org.apache.derby.jdbc.InternalDriver;
 import org.apache.derby.iapi.jdbc.EnginePreparedStatement;
 
+/**
+ * This class translates DRDA protocol from an application requester to JDBC
+ * for Derby and then translates the results from Derby to DRDA
+ * for return to the application requester.
+ */
 class DRDAConnThread extends Thread {
 
     private static final String leftBrace = "{";
@@ -724,6 +724,7 @@
 							}
 							// Send any warnings if JCC can handle them
 							checkWarning(null, null, stmt.getResultSet(), 0, false, sendWarningsOnCNTQRY);
+                            writePBSD();
 						}
 					}
 					catch(SQLException e)
@@ -754,6 +755,7 @@
 						// we need to set update count in SQLCARD
 						checkWarning(null, database.getDefaultStatement().getStatement(),
 							null, updateCount, true, true);
+                        writePBSD();
 					} catch (SQLException e)
 					{
 						writer.clearDSSesBackToMark(writerMark);
@@ -851,6 +853,7 @@
 								}
 							}
 						}
+                        writePBSD();
 					}
 					catch (SQLException e)
 					{
@@ -991,6 +994,7 @@
 						DRDAStatement curStmt = database.getCurrentStatement();
 						if (curStmt != null)
 							curStmt.rsSuspend();
+                        writePBSD();
 					} catch (SQLException e)
 					{
 						skipRemainder(true);
@@ -1007,11 +1011,46 @@
 					if (xaProto == null)
 						xaProto = new DRDAXAProtocol(this);
 					xaProto.parseSYNCCTL();
+ 					try {
+ 						writePBSD();
+ 					} catch (SQLException se) {
+						server.consoleExceptionPrint(se);
+ 						errorInChain(se);
+ 					}
 					break;
 				default:
 					codePointNotSupported(codePoint);
 			}
 
+            if (SanityManager.DEBUG) {
+                String cpStr = new CodePointNameTable().lookup(codePoint);
+                try {
+                    PiggyBackedSessionData pbsd =
+                            database.getPiggyBackedSessionData(false);
+                    if (pbsd != null) {
+                        // Session data has already been piggy-backed. Refresh
+                        // the data from the connection, to make sure it has
+                        // not changed behind our back.
+                        pbsd.refresh();
+                        if (codePoint != CodePoint.SYNCCTL) {
+                            // We expect the session attributes to have changed
+                            // after SYNCCTL, but this is handled by the client
+                            // and is not a problem
+                            SanityManager.ASSERT(!pbsd.isModified(),
+                                "Unexpected PBSD modification: " + pbsd +
+                                " after codePoint " + cpStr);
+                        }
+                    }
+                    // Not having a pbsd here is ok. No data has been
+                    // piggy-backed and the client has no cached values.
+                    // If needed it will send an explicit request to get
+                    // session data
+                } catch (SQLException sqle) {
+                    SanityManager.THROWASSERT("Unexpected exception after " +
+                            "codePoint "+cpStr, sqle);
+                }
+            }
+
 			// Set the correct chaining bits for whatever
 			// reply DSS(es) we just wrote.  If we've reached
 			// the end of the chain, this method will send
@@ -2613,6 +2652,54 @@
 			stmt.setOutovr_drdaType(outovr_drdaType);
 		}
 	}
+
+    /**
+     * Piggy-back any modified session attributes on the current message. Writes
+     * a PBSD conataining one or both of PBSD_ISO and PBSD_SCHEMA. PBSD_ISO is
+     * followed by the jdbc isolation level as an unsigned byte. PBSD_SCHEMA is
+     * followed by the name of the current schema as an UTF-8 String.
+     * @throws java.sql.SQLException
+     * @throws org.apache.derby.impl.drda.DRDAProtocolException
+     */
+    private void writePBSD() throws SQLException, DRDAProtocolException
+    {
+        if (!appRequester.supportsSessionDataCaching()) {
+            return;
+        }
+        PiggyBackedSessionData pbsd = database.getPiggyBackedSessionData(true);
+        if (SanityManager.DEBUG) {
+            SanityManager.ASSERT(pbsd != null, "pbsd is not expected to be null");
+        }
+
+        pbsd.refresh();
+        if (pbsd.isModified()) {
+            writer.createDssReply();
+            writer.startDdm(CodePoint.PBSD);
+
+            if (pbsd.isIsoModified()) {
+                writer.writeScalar1Byte(CodePoint.PBSD_ISO, pbsd.getIso());
+            }
+
+            if (pbsd.isSchemaModified()) {
+                writer.startDdm(CodePoint.PBSD_SCHEMA);
+                writer.writeString(pbsd.getSchema());
+                writer.endDdm();
+            }
+            writer.endDdmAndDss();
+        }
+        pbsd.setUnmodified();
+        if (SanityManager.DEBUG) {
+            PiggyBackedSessionData pbsdNew =
+                database.getPiggyBackedSessionData(true);
+            SanityManager.ASSERT(pbsdNew == pbsd,
+                                 "pbsdNew and pbsd are expected to reference " +
+                                 "the same object");
+            pbsd.refresh();
+            SanityManager.ASSERT
+                (!pbsd.isModified(),
+                 "pbsd=("+pbsd+") is not expected to be modified");
+        }
+    }
 
 	/**
 	 * Write OPNQRYRM - Open Query Complete

Modified: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/Database.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/Database.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/Database.java (original)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/Database.java Wed Feb 27 05:52:59 2008
@@ -122,6 +122,10 @@
 	final void setConnection(EngineConnection conn)
 		throws SQLException
 	{
+        if (this.conn != conn) {
+            // Need to drop the pb session data when switching connections
+            pbsd_ = null;
+        }
 		this.conn = conn;
 		if(conn != null)
 			defaultStatement.setStatement(conn);
@@ -451,5 +455,27 @@
         userId = null;
         password = null;
         securityMechanism = 0;
+    }
+
+    /**
+     * Piggy-backed session data. Null if no piggy-backing
+     * has happened yet. Lazy initialization is acceptable since the client's
+     * cache initially is empty so that any request made prior to the first
+     * round of piggy-backing will trigger an actual request to the server.
+     */
+    private PiggyBackedSessionData pbsd_ = null;
+
+    /**
+     * Get a reference (handle) to the PiggyBackedSessionData object. Null will
+     * be returned either if Database.conn is not a valid connection, or if the
+     * create argument is false and no object has yet been created.
+     * @param createOnDemand if true create the PiggyBackedSessionData on demand
+     * @return a reference to the PBSD object or null
+     * @throws java.sql.SQLException
+     */
+    public PiggyBackedSessionData getPiggyBackedSessionData(
+            boolean createOnDemand) throws SQLException {
+        pbsd_ = PiggyBackedSessionData.getInstance(pbsd_, conn, createOnDemand);
+        return pbsd_;
     }
 }

Added: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java?rev=631593&view=auto
==============================================================================
--- db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java (added)
+++ db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java Wed Feb 27 05:52:59 2008
@@ -0,0 +1,148 @@
+/*
+
+   Derby - Class org.apache.derby.impl.drda.PiggyBackedSessionData
+
+   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.impl.drda;
+
+import java.sql.SQLException;
+import org.apache.derby.iapi.jdbc.EngineConnection;
+
+
+/**
+ * Tracks the most recently piggy-backed session attributes, and provides
+ * methods to determine if they have been modified and need to be re-sent
+ * to the client.
+ */
+class PiggyBackedSessionData {
+    private int iso_;
+    private boolean isoMod_;
+
+    private String schema_;
+    private boolean schemaMod_;
+
+    private final EngineConnection conn_;
+
+    /**
+     * Get a reference (handle) to the PiggyBackedSessionData object. Null will
+     * be returned either if the conn argument is not valid, or if the
+     * createOnDemand argument is false and the existing argument is null.
+     * @param existing the PBSD object from the previous piggybacking or null if
+     * none has yet taken place
+     * @param conn the current EngineConnection
+     * @param createOnDemand if true; create the instance when needed
+     * @return a reference to the PBSD object or null
+     * @throws java.sql.SQLException
+     */
+    public static PiggyBackedSessionData getInstance(
+            PiggyBackedSessionData existing, EngineConnection conn,
+            boolean createOnDemand) throws SQLException {
+        if (conn == null || conn.isClosed() ||
+                (existing != null && existing.conn_ != conn)) {
+            return null;
+        }
+        if (existing == null && createOnDemand) {
+            return new PiggyBackedSessionData(conn);
+        }
+        return existing;
+    }
+
+    /**
+     * Constructs a new instance with an associated EngineConnection.
+     * A newly constructed instance is invalid. refresh() must be called before
+     * the xModified() methods can be used.
+     * @param conn the connection to obtain data from
+     */
+    private PiggyBackedSessionData(EngineConnection conn) throws SQLException {
+        conn_ = conn;
+        iso_ = -1; // Initialize to an illegal value
+    }
+
+    /**
+     * Refresh with the latest session attribute values from
+     * the connection. Any changes will be reflected in the corresponding
+     * xModified() methods, until setUnmodified() is called.
+     */
+    public void refresh() throws SQLException {
+        setUnmodified();
+        int iso = conn_.getTransactionIsolation();
+        if (iso != iso_) {
+            isoMod_ = true;
+            iso_ = iso;
+        }
+        String schema = conn_.getCurrentSchemaName();
+        if (!schema.equals(schema_)) {
+            schemaMod_ = true;
+            schema_ = schema;
+        }
+    }
+
+    /**
+     * Clear the modified status. Called after session attributes have
+     * been sent to the client so that the xModified methods will
+     * return false.
+     */
+    public void setUnmodified() {
+        isoMod_ = false;
+        schemaMod_ = false;
+    }
+
+    /**
+     * @return true if the isolation level was modified by the last call
+     * to fetchLatest
+     */
+    public boolean isIsoModified() {
+        return isoMod_;
+    }
+
+    /**
+     * @return true if the current schema name was modified by the last
+     * call to fetchLatest
+     */
+    public boolean isSchemaModified() {
+        return schemaMod_;
+    }
+
+    /**
+     * @return true if any piggy-backed session attribute was modified by
+     * the last call to fetchLatest
+     */
+    public boolean isModified() {
+        return (isoMod_ || schemaMod_);
+    }
+
+    /**
+     * @return the saved jdbc isolation level
+     */
+    public int getIso() {
+        return iso_;
+    }
+
+    /**
+     * @return the saved schema name
+     */
+    public String getSchema() {
+        return schema_;
+    }
+
+    public String toString() {
+        return "iso:" + iso_ + (isoMod_ ? "(M)" : "") + " schema:" + schema_ +
+            (schemaMod_ ? "(M)" : "");
+    }
+}

Propchange: db/derby/code/trunk/java/drda/org/apache/derby/impl/drda/PiggyBackedSessionData.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/BrokeredConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/BrokeredConnection.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/BrokeredConnection.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/BrokeredConnection.java Wed Feb 27 05:52:59 2008
@@ -637,4 +637,22 @@
             //underlying EmbedConnection object. 
             return getRealConnection().getLOBMapping(key);
 	}
+
+    /**
+     * Obtain the name of the current schema. Not part of the
+     * java.sql.Connection interface, but is accessible through the
+     * EngineConnection interface, so that the NetworkServer can get at the
+     * current schema for piggy-backing
+     * @return the current schema name
+     * @throws java.sql.SQLException
+     */
+    public String getCurrentSchemaName() throws SQLException {
+        try {
+            return getRealConnection().getCurrentSchemaName();
+        }
+        catch (SQLException se) {
+            notifyException(se);
+            throw se;
+        }
+    }
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/EngineConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/EngineConnection.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/EngineConnection.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/EngineConnection.java Wed Feb 27 05:52:59 2008
@@ -92,4 +92,12 @@
     * @return the LOB Object corresponding to this locator.
     */
     public Object getLOBMapping(int key) throws SQLException;
+
+    /**
+     * Obtain the name of the current schema, so that the NetworkServer can
+     * use it for piggy-backing
+     * @return the current schema name
+     * @throws java.sql.SQLException
+     */
+    public String getCurrentSchemaName() throws SQLException;
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java Wed Feb 27 05:52:59 2008
@@ -2863,4 +2863,15 @@
     public void cancelRunningStatement() {
         getLanguageConnection().getStatementContext().cancel();
     }
+
+    /**
+     * Obtain the name of the current schema. Not part of the
+     * java.sql.Connection interface, but is accessible through the
+     * EngineConnection interface, so that the NetworkServer can get at the
+     * current schema for piggy-backing
+     * @return the current schema name
+     */
+    public String getCurrentSchemaName() {
+        return getLanguageConnection().getCurrentSchemaName();
+    }
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/CacheSessionDataTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/CacheSessionDataTest.java?rev=631593&r1=631592&r2=631593&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/CacheSessionDataTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/CacheSessionDataTest.java Wed Feb 27 05:52:59 2008
@@ -31,6 +31,7 @@
 import java.sql.Statement;
 import java.sql.Types;
 import java.sql.ResultSet;
+import java.util.Arrays;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.apache.derbyTesting.junit.BaseJDBCTestCase;
@@ -212,10 +213,38 @@
                         ".getCycleIsolationJDBC'");
  
                 s.execute("CREATE FUNCTION GET_CYCLE_ISOLATION_SQL " +
-                        "() RETURNS VARCHAR(2) READS SQL DATA LANGUAGE JAVA PARAMETER " +
-                        "STYLE JAVA EXTERNAL NAME '" +
+                        "() RETURNS VARCHAR(2) READS SQL DATA LANGUAGE JAVA " +
+                        "PARAMETER STYLE JAVA EXTERNAL NAME '" +
                         CacheSessionDataTest.class.getName() + 
                         ".getCycleIsolationSQL'");
+
+                // Schema testing
+                s.execute("CREATE SCHEMA FOO");
+                String unicodeschema = "\u00bbMY\u20ac\u00ab";
+                s.execute("CREATE SCHEMA \"" + unicodeschema + "\"");
+
+                s.execute("CREATE PROCEDURE APP.SET_SCHEMA (SCHEMANAME " +
+                        "VARCHAR(128)) MODIFIES SQL DATA LANGUAGE JAVA " +
+                        "PARAMETER STYLE JAVA EXTERNAL NAME '" +
+                        CacheSessionDataTest.class.getName() + ".setSchema'");
+
+                s.execute("CREATE FUNCTION APP.GET_SCHEMA_TRANSITION " +
+                        "(SCHEMANAME VARCHAR(128)) RETURNS VARCHAR(128) READS " +
+                        "SQL DATA LANGUAGE JAVA PARAMETER STYLE JAVA EXTERNAL " +
+                        "NAME '" + CacheSessionDataTest.class.getName() +
+                        ".getSchemaTransition'");
+
+                s.execute("CREATE TABLE APP.LARGE(X VARCHAR(32000), " +
+                        "SCHEMANAME VARCHAR(128), Y VARCHAR(32000))");
+
+                char[] carray = new char[32000];
+                Arrays.fill(carray, 'x');
+                String xs = new String(carray);
+                Arrays.fill(carray, 'y');
+                String ys = new String(carray);
+
+                s.execute("INSERT INTO APP.LARGE (SELECT '" + xs + "', " +
+                        "SCHEMANAME, " + " '" + ys + "' FROM SYS.SYSSCHEMAS)");
             }
         };
     } // End baseSuite
@@ -341,6 +370,41 @@
         return sqlName;
     }
 
+    /**
+     * Implementation of the SQL procedure SET_SCHEMA.
+     * Sets a different schema on the default Connection.
+     * @param schemaName name of the new schema
+     * @throws java.sql.SQLException
+     */
+    public static void setSchema(String schemaName)
+            throws SQLException {
+        Connection c = DriverManager.getConnection("jdbc:default:connection");
+        Statement s = c.createStatement();
+        s.execute("SET SCHEMA " + schemaName);
+        s.close();
+    }
+
+    /**
+     * Implementation of the SQL function GET_SCHEMA_TRANSITION.
+     * Sets the current schema to the name given as argument and returns the
+     * schema transition.
+     * @param nextSchema schema to transition to
+     * @return a string of the form oldSchema->newSchema
+     * @throws java.sql.SQLException
+     */
+    public static String getSchemaTransition(String nextSchema)
+            throws SQLException {
+        Connection c = DriverManager.getConnection("jdbc:default:connection");
+        Statement s = c.createStatement();
+        ResultSet rs = s.executeQuery("VALUES CURRENT SCHEMA");
+        rs.next();
+        String prevSchema = rs.getString(1);
+        rs.close();
+        s.execute("SET SCHEMA \"" + nextSchema + "\"");
+        s.close();
+        return (prevSchema + "->" + nextSchema);
+    }
+
     // Utilities
     private static IsoLevel[] isoLevels;    
     private static int isolationIndex = -1;
@@ -384,6 +448,21 @@
         assertEquals(serverJdbc, client);
     }
     
+    private void verifyCachedSchema(Connection c) throws SQLException {
+        if (c instanceof org.apache.derby.client.am.Connection) {
+            String cached =
+                    ((org.apache.derby.client.am.Connection) c).
+                    getCurrentSchemaName();
+            Statement s = c.createStatement();
+            ResultSet rs = s.executeQuery("VALUES CURRENT SCHEMA");
+            rs.next();
+            String reported = rs.getString(1);
+            assertEquals(reported, cached);
+        } else {
+            println("Cannot verify cached schema for "+c.getClass());
+        }
+    }
+
     // Test cases (fixtures) 
     // Change the isolation level using SQL
     public void testChangeIsoLevelStatementSQL() throws SQLException {
@@ -728,7 +807,8 @@
         preparedCursorTest("BIG", ResultSet.TYPE_SCROLL_SENSITIVE,
                 ResultSet.CONCUR_READ_ONLY);
     }
-    public void testLargePreparedScrollInsensitiveReadOnly() throws SQLException {
+    public void testLargePreparedScrollInsensitiveReadOnly()
+            throws SQLException {
         preparedCursorTest("BIG", ResultSet.TYPE_SCROLL_INSENSITIVE,
                 ResultSet.CONCUR_READ_ONLY);
     }
@@ -736,12 +816,90 @@
         preparedCursorTest("BIG", ResultSet.TYPE_FORWARD_ONLY,
                 ResultSet.CONCUR_UPDATABLE);
     }
-    public void testLargePreparedScrollSensitiveUpdatable() throws SQLException {
+    public void testLargePreparedScrollSensitiveUpdatable()
+            throws SQLException {
         preparedCursorTest("BIG", ResultSet.TYPE_SCROLL_SENSITIVE,
                 ResultSet.CONCUR_UPDATABLE);
     }
-    public void testLargePreparedScrollInsensitiveUpdatable() throws SQLException {
+    public void testLargePreparedScrollInsensitiveUpdatable()
+            throws SQLException {
         preparedCursorTest("BIG", ResultSet.TYPE_SCROLL_INSENSITIVE,
                 ResultSet.CONCUR_UPDATABLE);
+    }
+
+    // Test that the current schema is piggy-backed correctly
+    public void testSetSchema() throws SQLException {
+        Statement s = createStatement();
+        s.execute("SET SCHEMA FOO");
+        verifyCachedSchema(getConnection());
+        s.execute("SET SCHEMA \"\u00bbMY\u20ac\u00ab\"");
+        verifyCachedSchema(getConnection());
+    }
+    public void testPreparedSetSchema() throws SQLException {
+        PreparedStatement ps = prepareStatement("SET SCHEMA ?");
+        ps.setString(1, "FOO");
+        ps.execute();
+        verifyCachedSchema(getConnection());
+        ps.setString(1, "\u00bbMY\u20ac\u00ab");
+        ps.execute();
+        verifyCachedSchema(getConnection());
+    }
+    public void testSetSchemaProcedure() throws SQLException {
+        Statement s = createStatement();
+        s.execute("CALL APP.SET_SCHEMA('FOO')");
+        verifyCachedSchema(getConnection());
+        s.execute("CALL APP.SET_SCHEMA('\"\u00bbMY\u20ac\u00ab\"')");
+        verifyCachedSchema(getConnection());
+    }
+    public void testPreparedSetSchemaProcedure() throws SQLException {
+        CallableStatement cs = prepareCall("CALL APP.SET_SCHEMA(?)");
+        cs.setString(1, "FOO");
+        cs.execute();
+        verifyCachedSchema(getConnection());
+        cs.setString(1, "\"\u00bbMY\u20ac\u00ab\"");
+        cs.execute();
+        verifyCachedSchema(getConnection());
+    }
+
+    public void testSetSchemaFunction() throws SQLException {
+        Statement s = createStatement();
+        ResultSet rs = s.executeQuery("SELECT " +
+                "APP.GET_SCHEMA_TRANSITION(SCHEMANAME) FROM SYS.SYSSCHEMAS");
+        while (rs.next()) {
+            assertTrue(rs.getString(1).length() > 2);
+            verifyCachedSchema(getConnection());
+        }
+    }
+
+    public void testPreparedSetSchemaFunction() throws SQLException {
+        PreparedStatement ps = prepareStatement("SELECT " +
+                "APP.GET_SCHEMA_TRANSITION(SCHEMANAME) FROM SYS.SYSSCHEMAS");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            assertTrue(rs.getString(1).length() > 2);
+            verifyCachedSchema(getConnection());
+        }
+    }
+
+    public void testSetSchemaFunctionLarge() throws SQLException {
+        Statement s = createStatement();
+        ResultSet rs = s.executeQuery("SELECT X, " +
+                "APP.GET_SCHEMA_TRANSITION(SCHEMANAME), " +
+                "Y FROM APP.LARGE");
+        while (rs.next()) {
+            assertTrue(rs.getString(2).length() > 2);
+            verifyCachedSchema(getConnection());
+        }
+    }
+
+    public void testPreparedSetSchemaFunctionLarge() throws SQLException {
+        PreparedStatement ps = prepareStatement("SELECT X, " +
+                "APP.GET_SCHEMA_TRANSITION(SCHEMANAME), " +
+                "Y FROM APP.LARGE");
+        ResultSet rs = ps.executeQuery();
+        while (rs.next()) {
+            assertTrue(rs.getString(2).length() > 2);
+            verifyCachedSchema(getConnection());
+        }
     }
 }