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 da...@apache.org on 2008/01/22 01:35:40 UTC

svn commit: r614071 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/ engine/org/apache/derby/iapi/sql/conn/ engine/org/apache/derby/impl/sql/ engine/org/apache/derby/impl/sql/compile/ engine/org/apache/derby/impl/sql/conn/ engine/org/a...

Author: dag
Date: Mon Jan 21 16:35:38 2008
New Revision: 614071

URL: http://svn.apache.org/viewvc?rev=614071&view=rev
Log:
DERBY-3327 SQL roles: Implement authorization stack

Patch DERBY-3327-3 which implements the authorization stack for SQL roles.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/conn/LanguageConnectionContext.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SpecialFunctionNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/SetRoleConstantAction.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java Mon Jan 21 16:35:38 2008
@@ -585,4 +585,39 @@
 		this with a real implementation.
 	*/
 	public int getMaxDynamicResults();
+
+	/**
+	 * Set the current role name of the dynamic call context stemming
+	 * from this activation (which must be a stored
+	 * procedure/function) call.
+	 *
+	 * @arg role The name of the current role
+	 */
+	public void setNestedCurrentRole(String role);
+
+	/**
+	 * Get the current role name of the dynamic call context stemming
+	 * from this activation (which must be a stored
+	 * procedure/function) call.
+	 *
+	 * @return The name of the current role
+	 */
+    public String getNestedCurrentRole();
+
+	/**
+	 * This activation is created in a dynamic call context, remember
+	 * its caller's activation.
+	 *
+	 * @arg a The caller's activation
+	 */
+	public void setCallActivation(Activation a);
+
+	/**
+	 * This activation is created in a dynamic call context, get its
+	 * caller's activation.
+	 *
+	 * @return The caller's activation
+	 */
+	public Activation getCallActivation();
+
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/conn/LanguageConnectionContext.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/conn/LanguageConnectionContext.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/conn/LanguageConnectionContext.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/conn/LanguageConnectionContext.java Mon Jan 21 16:35:38 2008
@@ -424,22 +424,6 @@
 	public String getAuthorizationId();
 
 	/**
-	 *	Get the current role authorization identifier
-	 *
-	 * @return String	the role id
-	 */
-	public String getCurrentRoleId();
-
-
-    /**
-	 * Set the current role
-	 *
-	 * @param rd	the descriptor of the role to be set to current
-	 */
-    public void setCurrentRole(RoleDescriptor rd);
-
-
-	/**
 	 *	Get the current default schema for the connection.
 	 *
 	 * @return SchemaDescriptor	the current schema
@@ -1048,4 +1032,38 @@
 	 * Close any unused activations in this connection context.
 	 */
 	public void closeUnusedActivations() throws StandardException;
+
+	/**
+	 * Remember most recent (call stack top) caller's activation when
+	 * invoking a method, see CallStatementResultSet#open.
+	 */
+	public void pushCaller(Activation a);
+
+	/**
+	 * Companion of pushCaller. See usage in CallStatementResultSet#open.
+	 */
+	public void popCaller();
+
+	/**
+	 * Get most recent (call stack top) caller's activation
+	 */
+	public Activation getCaller();
+
+    /**
+	 * Set the current role
+	 *
+	 * @param activation activation of set role statement
+	 * @param role  the id of the role to be set to current
+	 */
+    public void setCurrentRole(Activation a, String role);
+
+	/**
+	 * Get the current role authorization identifier of the dynamic
+	 * call context associated with this activation.
+	 *
+	 * @param activation activation of statement needing current role
+	 * @return String	the role id
+	 */
+	public String getCurrentRoleId(Activation a);
+
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java Mon Jan 21 16:35:38 2008
@@ -564,6 +564,23 @@
 		return ac.getTargetVTI();
 	}
 
+	public void setNestedCurrentRole(String role) {
+		ac.setNestedCurrentRole(role);
+    }
+
+    public String getNestedCurrentRole() {
+		return ac.getNestedCurrentRole();
+    }
+
+	public void setCallActivation(Activation a) {
+		ac.setCallActivation(a);
+	}
+
+	public Activation getCallActivation() {
+		return ac.getCallActivation();
+	}
+
+
 	/* Class implementation */
 
 
@@ -667,5 +684,4 @@
 	public int getMaxDynamicResults() {
 		return ac.getMaxDynamicResults();
 	}
-
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java Mon Jan 21 16:35:38 2008
@@ -240,6 +240,8 @@
 		// deadlock.
 		lcc.closeUnusedActivations();
 
+		ac.setCallActivation(lcc.getCaller());
+
 		return ac;
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SpecialFunctionNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SpecialFunctionNode.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SpecialFunctionNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/SpecialFunctionNode.java Mon Jan 21 16:35:38 2008
@@ -236,8 +236,15 @@
 		mb.pushThis();
 		mb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Activation, "getLanguageConnectionContext",
 											 ClassName.LanguageConnectionContext, 0);
+		int argCount = 0;
 
-		mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, methodName, methodType, 0);
+		if (methodName.equals("getCurrentRoleId")) {
+			acb.pushThisAsActivation(mb);
+			argCount++;
+		}
+
+		mb.callMethod(VMOpcode.INVOKEINTERFACE,
+					  (String) null, methodName, methodType, argCount);
 
 		String fieldType = getTypeCompiler().interfaceName();
 		LocalField field = acb.newFieldDeclaration(Modifier.PRIVATE, fieldType);

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/conn/GenericLanguageConnectionContext.java Mon Jan 21 16:35:38 2008
@@ -163,6 +163,18 @@
 	 */
 	private int queryNestingDepth;
 
+	/**
+	 * 'callers' keeps track of which, if any, stored procedure
+	 * activations are active. This helps implement the "authorization
+	 * stack" of SQL 2003, vol 2, section 4.34.1.1 and 4.27.3.
+	 *
+	 * For the top level, the current role is kept here,
+	 * cf. 'currentRole'.  For dynamic call contexts, the current role
+	 * is kept in the activation of the calling statement,
+	 * cf. 'getCurrentRoleId'.
+	 */
+	private ArrayList callers = new ArrayList(); // used as a stack only
+
 	protected DataValueFactory dataFactory;
 	protected LanguageFactory langFactory;
 	protected TypeCompilerFactory tcf;
@@ -185,7 +197,7 @@
     protected Authorizer authorizer;
 	protected String userName = null; //The name the user connects with.
 	                                  //May still be quoted.
-	protected RoleDescriptor currentRole;
+	protected String currentRole;
 	protected SchemaDescriptor	sd;
 
 	// RESOLVE - How do we want to set the default.
@@ -1770,27 +1782,6 @@
 
 
 	/**
-	 * Get the current role authorization identifier
-	 *
-	 * @return String	the role id
-	 */
-	public String getCurrentRoleId() {
-		return currentRole != null ?
-			currentRole.getRoleName() : null;
-	}
-
-
-	/**
-	 * Set the current role
-	 *
-	 * @param rd	the descriptor of the role to be set to current
-	 */
-	public void setCurrentRole(RoleDescriptor rd) {
-		this.currentRole = rd;
-	}
-
-
-	/**
 	 *	Get the default schema
 	 *
 	 * @return SchemaDescriptor	the default schema
@@ -3082,5 +3073,76 @@
 		sb.append("), ");
 
 		return sb;
+	}
+
+	/**
+	 * Remember most recent (call stack top) caller's activation when
+	 * invoking a method, see CallStatementResultSet#open.
+	 */
+	public void pushCaller(Activation a) {
+		callers.add(a);
+	}
+
+	/**
+	 * Companion of pushCaller. See usage in CallStatementResultSet#open.
+	 */
+	public void popCaller() {
+		callers.remove(callers.size() - 1);
+	}
+
+
+	/**
+	 * Get most recent (call stack top) caller's activation
+	 * or null, if not in a call context.
+	 */
+	public Activation getCaller() {
+		if (callers.isEmpty()) {
+			return null;
+		} else {
+			return (Activation)callers.get(callers.size() - 1);
+		}
+	}
+
+
+	/**
+	 * Set the current role
+	 *
+	 * @param activation activation of set role statement
+	 * @param role	the id of the role to be set to current
+	 */
+	public void setCurrentRole(Activation a, String role) {
+		Activation caller = a.getCallActivation();
+
+		if (caller != null ) {
+			//inside a stored procedure context
+			caller.setNestedCurrentRole(role);
+		} else {
+			// top level
+			this.currentRole = role;
+		}
+	}
+
+
+	/**
+	 * Get the current role authorization identifier of the dynamic
+	 * call context associated with this activation.
+	 *
+	 * @param activation activation  of statement needing current role
+	 * @return String	the role id
+	 */
+	public String getCurrentRoleId(Activation a) {
+		Activation caller = a.getCallActivation();
+
+		if (caller != null ) {
+			// Want current role of stored procedure context
+			// Note that it may have returned at this point, but the
+			// activation still keeps track on what the current role
+			// was when we returned.
+			return caller.getNestedCurrentRole();
+		} else {
+			// Top level current role, no stored procedure call
+			// context.
+			return currentRole;
+		}
 	}
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java Mon Jan 21 16:35:38 2008
@@ -140,6 +140,34 @@
 	private int[] autoGeneratedKeysColumnIndexes ;
 	private String[] autoGeneratedKeysColumnNames ;
 
+	// Authorization stack frame, cf. SQL 2003 4.31.1.1 and 4.27.3 is
+	// implemented as follows: Statements at root connection level
+	// (not executed within a stored procedure), maintain the current
+	// role in the lcc.  In this case, 'callActivation' is null.  If
+	// we are executing SQL inside a stored procedure (nested
+	// connection), then 'callActivation' will be non-null, and we
+	// maintain the current role in the activation of the calling
+	// statement, see 'setNestedCurrentRole'. The current role of a call
+	// context is kept in the field 'nestedCurrentRole'.
+	//
+	// 'callActivation' is set when activation is created (see
+	// GenericPreparedStatement#getActivation based on the top of the
+	// dynamic call stack of activation, see
+	// GenericLanguageConnectionContext#getCaller.
+	//
+	// Corner case: When a dynamic result set references current role,
+	// the value retrieved will always be that of the current role
+	// when the statement is executed (inside), not the current value
+	// when the result set is accessed outside the stored procedure.
+	//
+	// Consequence of this implementation: If more than one nested
+	// connection is used inside a shared procedure, they will share
+	// the current role setting. Since the same dynamic call context
+	// is involved, this seems correct.
+	//
+	private Activation callActivation;
+	private String nestedCurrentRole;
+
 	//Following is the position of the session table names list in savedObjects in compiler context
 	//This is updated to be the correct value at cursor generate time if the cursor references any session table names.
 	//If the cursor does not reference any session table names, this will stay negative
@@ -1310,6 +1338,49 @@
 			row[resultSetNumber] = null;
 		}
 	}
+
+	/**
+	 * Set the current role name of the dynamic call context stemming
+	 * from this activation (which must be a stored
+	 * procedure/function call).
+	 *
+	 * @arg role The name of the current role
+	 */
+	public void setNestedCurrentRole(String role) {
+		nestedCurrentRole = role;
+	}
+
+	/**
+	 * Get the current role name of the dynamic call context stemming
+	 * from this activation (which must be a stored
+	 * procedure/function call).
+	 *
+	 * @return The name of the current role
+	 */
+	public String getNestedCurrentRole() {
+		return nestedCurrentRole;
+	}
+
+	/**
+	 * This activation is created in a dynamic call context, remember
+	 * its caller's activation.
+	 *
+	 * @arg a The caller's activation
+	 */
+	public void setCallActivation(Activation a) {
+		callActivation = a;
+	}
+
+	/**
+	 * This activation is created in a dynamic call context, get its
+	 * caller's activation.
+	 *
+	 * @return The caller's activation
+	 */
+	public Activation getCallActivation() {
+		return callActivation;
+	}
+
 
 	protected final DataValueDescriptor getColumnFromRow(int rsNumber, int colId)
 		throws StandardException {

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CallStatementResultSet.java Mon Jan 21 16:35:38 2008
@@ -28,6 +28,7 @@
 import org.apache.derby.iapi.jdbc.ConnectionContext;
 import org.apache.derby.iapi.services.loader.GeneratedMethod;
 import org.apache.derby.iapi.sql.Activation;
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
 
 /**
  * Call a Java procedure. This calls a generated method in the
@@ -71,9 +72,28 @@
 	public void open() throws StandardException
 	{
 		setup();
-		methodCall.invoke(activation);
+
+        LanguageConnectionContext lcc =
+            activation.getLanguageConnectionContext();
+
+        // Push the "authorization stack" of SQL 2003, vol 2, section
+        // 4.34.1.1 and 4.27.3.
+        lcc.pushCaller(activation);
+
+        // Copy the current role into top cell of stack. Activations
+        // inside nested connections look to this activation for
+        // keeping its current role rather than rely on what's in lcc
+        // (top level only).
+        activation.setNestedCurrentRole(lcc.getCurrentRoleId(activation));
+
+        try {
+            methodCall.invoke(activation);
+        }
+        finally {
+            activation.getLanguageConnectionContext().popCaller();
+        }
     }
-    
+
     /**
      * Need to explicitly close any dynamic result sets.
      * <BR>

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/SetRoleConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/SetRoleConstantAction.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/SetRoleConstantAction.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/SetRoleConstantAction.java Mon Jan 21 16:35:38 2008
@@ -135,15 +135,19 @@
                     rd = dd.getRoleGrantDescriptor(thisRoleName,
                                                    currentAuthId,
                                                    dbo);
-                    if (rd == null &&
-                        (dd.getRoleGrantDescriptor
-                         (thisRoleName,
-                          Authorizer.PUBLIC_AUTHORIZATION_ID,
-                          dbo) == null)) {
+                    if (rd == null) {
+                        // or if not, via PUBLIC?
+                        rd = dd.getRoleGrantDescriptor
+                            (thisRoleName,
+                             Authorizer.PUBLIC_AUTHORIZATION_ID,
+                             dbo);
 
-                        throw StandardException.newException
-                            (SQLState.ROLE_INVALID_SPECIFICATION_NOT_GRANTED,
-                             thisRoleName);
+                        // Nope, we can't set this role, so throw.
+                        if (rd == null) {
+                            throw StandardException.newException
+                              (SQLState. ROLE_INVALID_SPECIFICATION_NOT_GRANTED,
+                               thisRoleName);
+                        }
                     }
                 }
             } finally {
@@ -152,6 +156,6 @@
             }
         }
 
-        lcc.setCurrentRole(rd);
+        lcc.setCurrentRole(activation, rd != null ? thisRoleName : null);
     }
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java?rev=614071&r1=614070&r2=614071&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java Mon Jan 21 16:35:38 2008
@@ -27,6 +27,7 @@
 import java.sql.Statement;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.DriverManager;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.apache.derbyTesting.junit.BaseJDBCTestCase;
@@ -402,9 +403,14 @@
                sqlAuthorizationRequired, null , null /* through public */);
         doStmt("set role 'FOO'",
                sqlAuthorizationRequired, null, null);
+
+        doSetRoleInsideStoredProcedures("FOO");
+
         doStmt("set role none",
                sqlAuthorizationRequired, null , null);
+
         doDynamicSetRole(_conn);
+
         doStmt("set role bar",
                sqlAuthorizationRequired, null , null /* direct grant */);
         doStmt("set role role",
@@ -418,12 +424,14 @@
         _conn.commit();
         _conn.setAutoCommit(true);
 
+
+
         /*
          * CURRENT_ROLE
          */
         ResultSet rs = doQuery("values current_role",
                                sqlAuthorizationRequired, null , null);
-        assertCurrentRole(rs, "ROLE", "BAR");
+        assertRoleInRs(rs, "ROLE", "BAR");
 
         /*
          * REVOKE role
@@ -477,7 +485,7 @@
         doStmt("create function f1() returns int" +
                "  language java parameter style java" +
                "  external name 'org.apache.derbyTesting." +
-               "functionTests.tests.lang.RolesTest.t1'" +
+               "functionTests.tests.lang.RolesTest.f1'" +
                "  no sql called on null input",
                null, null, null);
         doStmt("grant execute on function f1 to admin",
@@ -758,7 +766,7 @@
                 int rowcnt = pstmt.executeUpdate();
                 assertEquals(rowcnt, 0, "rowcount from set role ? not 0");
                 ResultSet rs = doQuery("values current_role", n_a, null , n_a );
-                assertCurrentRole(rs, "NONE", n_a);
+                assertRoleInRs(rs, "NONE", n_a);
                 rs.close();
             } catch (SQLException e) {
                 fail("execute of set role ? failed: [NONE] " + e);
@@ -774,6 +782,60 @@
     }
 
 
+    /* Test that current role is handled correctly when inside a
+     * stored procedure.  The SQL standard requires we have an
+     * "authorization stack", see section 4.34.1.1. This implies that
+     * current role is popped at end of stored procedure.
+     * We test two levels deep.
+     */
+    private void doSetRoleInsideStoredProcedures(String currRole)
+            throws SQLException
+    {
+        if (_authLevel != NO_SQLAUTHORIZATION) {
+            String n_a = null; // auth level not used for this test
+
+            doStmt("create procedure p2(role varchar(255))" +
+                   "  dynamic result sets 1 language java parameter style java"+
+                   "  external name 'org.apache.derbyTesting." +
+                   "functionTests.tests.lang.RolesTest.p2'" +
+                   "  modifies sql data",
+                   n_a, null, null);
+            doStmt("create function f2(role varchar(255))" +
+                   "  returns int language java parameter style java" +
+                   "  external name 'org.apache.derbyTesting." +
+                   "functionTests.tests.lang.RolesTest.f2'" +
+                   "  reads sql data",
+                   n_a, null, null);
+            doStmt("call p2('" + currRole + "')",
+                   n_a , null , null );
+
+            // Dynamic result set: At what time should CURRENT_ROLE be
+            // evaluated?  Logically at the inside, so it should be
+            // "BAR" also when accessed on outside. I think. Anyway,
+            // that's what's implemented: the activation of the call
+            // is still live and holds the current role as it was
+            // inside the nested scope even when the procedure call
+            // has returned.
+            ResultSet prs = _stm.getResultSet();
+            assertRoleInRs(prs, "BAR", "BAR");
+            prs.close();
+
+            // check that role didn't get impacted by change inside p2
+            // too 'BAR':
+            ResultSet rs = doQuery("values current_role",
+                                   n_a , null , null );
+            assertRoleInRs(rs, currRole, currRole);
+            rs.close();
+
+            rs = doQuery("values f2('" + currRole + "')",
+                         n_a , null , null );
+            rs.close();
+
+            doStmt("drop procedure p2", n_a, null, null);
+            doStmt("drop function  f2", n_a, null, null);
+        }
+    }
+
     private void assertSystableRowCount(String table,
                                         int rcNoAuth,
                                         int rcDbo,
@@ -934,9 +996,9 @@
     }
 
 
-    private void assertCurrentRole(ResultSet rs,
-                                   String dboRole,
-                                   String notDboRole)
+    private void assertRoleInRs(ResultSet rs,
+                                String dboRole,
+                                String notDboRole)
         throws SQLException
     {
 
@@ -944,16 +1006,18 @@
             assertNull(rs);
         } else {
             assertTrue("result set empty", rs.next());
+            String actualRole = rs.getString(1);
 
             if (isDbo()) {
-                assertTrue(dboRole.equals(rs.getString(1)));
+                assertTrue("role is " + actualRole + ", expected " + dboRole,
+                           dboRole.equals(actualRole));
             } else {
-                assertTrue(notDboRole.equals(rs.getString(1)));
+                assertTrue("role is " + actualRole + ", expected " + notDboRole,
+                           notDboRole.equals(actualRole));
             }
 
             // cardinality should be 1
             assertFalse("result set not empty", rs.next());
-            rs.close();
         }
     }
 
@@ -964,6 +1028,22 @@
         }
     }
 
+    private static void assertRsSingleStringValue(ResultSet rs,
+                                                  String expectedValue)
+            throws SQLException
+    {
+
+        assertTrue("result set empty", rs.next());
+        String actualValue = rs.getString(1);
+
+        assertTrue("string is " + actualValue + ", expected " + expectedValue,
+                   actualValue.equals(expectedValue));
+
+        // cardinality should be 1
+        assertFalse("result set not empty", rs.next());
+    }
+
+
     /**
      * Utility function used to test auto-drop of grant routine
      * permission to a role
@@ -971,6 +1051,160 @@
      */
     public static int f1()
     {
+        return 1;
+    }
+
+
+    /**
+     * Utility procedure used to test that current role
+     * is stacked correctly according to dynamic scope.
+     */
+    public static void p2(String roleOutside, ResultSet[] rs1)
+            throws SQLException
+    {
+        Connection conn1 = null;
+        Connection conn2 = null;
+
+        try {
+            conn1 = DriverManager.getConnection("jdbc:default:connection");
+            PreparedStatement ps =
+                conn1.prepareStatement("values current_role");
+
+            // check that we inherit role correctly
+            ResultSet rs = ps.executeQuery();
+            assertRsSingleStringValue(rs, roleOutside);
+            rs.close();
+
+            // set the role to something else
+            Statement stm = conn1.createStatement();
+            stm.execute("set role bar");
+            rs = ps.executeQuery();
+
+            // check that role got set
+            assertRsSingleStringValue(rs, "BAR");
+
+            // another nesting level to test authorization stack even more
+            stm.execute(
+                "create procedure calledNestedFromP2(role varchar(255))" +
+                "  language java parameter style java" +
+                "  external name 'org.apache.derbyTesting." +
+                "functionTests.tests.lang.RolesTest.calledNestedFromP2'" +
+                "  modifies sql data");
+            conn1.commit(); // need to be idle
+            stm.execute("call calledNestedFromP2('BAR')");
+
+            rs = ps.executeQuery();
+
+            // check that role didn't get impacted by change inside
+            // calledNestedFromP2 too 'FOO':
+            assertRsSingleStringValue(rs, "BAR");
+            stm.execute("drop procedure calledNestedFromP2");
+
+            // Test that the role is shared by another nested
+            // connection also.
+            conn2 = DriverManager.getConnection("jdbc:default:connection");
+            PreparedStatement ps2 =
+                conn2.prepareStatement("values current_role");
+            ResultSet rs2 = ps2.executeQuery();
+            assertRsSingleStringValue(rs2, "BAR");
+
+            // Pass out CURRENT_ROLE in a dynamic result set.
+            rs = ps.executeQuery();
+            rs1[0] = rs;
+
+        } finally {
+
+            if (conn1 != null) {
+                try {
+                    conn1.close();
+                } catch (Exception e) {
+                }
+            }
+
+            if (conn2 != null) {
+                try {
+                    conn2.close();
+                } catch (Exception e) {
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Called from p2 so we get to test with a call stack 3 levels
+     * deep.
+     */
+    public static void calledNestedFromP2(String roleOutside)
+            throws SQLException
+    {
+        Connection conn1 = null;
+
+        try {
+            conn1 = DriverManager.getConnection("jdbc:default:connection");
+            PreparedStatement ps =
+                conn1.prepareStatement("values current_role");
+
+            // check that we inherit role correctly
+            ResultSet rs = ps.executeQuery();
+            assertRsSingleStringValue(rs, roleOutside);
+            rs.close();
+
+            // set the role to something else
+            Statement stm = conn1.createStatement();
+            stm.execute("set role foo");
+            rs = ps.executeQuery();
+
+            // check that role got set
+            assertRsSingleStringValue(rs, "FOO");
+
+        } finally {
+            if (conn1 != null) {
+                try {
+                    conn1.close();
+                } catch (Exception e) {
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Utility function used to test that current role
+     * is stacked correctly according to scope.
+     */
+    public static int f2(String roleOutside) throws SQLException
+    {
+        Connection conn1 = null;
+
+        try {
+            conn1 = DriverManager.getConnection("jdbc:default:connection");
+            PreparedStatement ps =
+                conn1.prepareStatement("values current_role");
+
+            // check that we inherit role correctly
+            ResultSet rs = ps.executeQuery();
+            assertRsSingleStringValue(rs, roleOutside);
+            rs.close();
+
+            // set the role to something else
+            Statement stm = conn1.createStatement();
+            stm.execute("set role bar");
+            rs = ps.executeQuery();
+
+            // check that role got set
+            assertRsSingleStringValue(rs, "BAR");
+
+        } finally {
+
+            if (conn1 != null) {
+                try {
+                    conn1.close();
+                } catch (Exception e) {
+                }
+            }
+
+        }
         return 1;
     }
 }