| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.lang.SQLSessionContextTest |
| |
| 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.derbyTesting.functionTests.tests.lang; |
| |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.DatabasePropertyTestSetup; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * SQLSessionContextTest tests the SQL session context stacking, |
| * cf. SQL 4.37.3. The testing of one of the SQL session state |
| * variable, the current role, relies on sqlAuthorization being set, |
| * so use sqlAuthorization=true. |
| * |
| * State variables tested: |
| * current role (SET ROLE <role>, CURRENT_ROLE) |
| * current default schema (SET SCHEMA <schema>, CURRENT SCHEMA) |
| * |
| * SET SCHEMA and SET ROLE are SQL SESSION statements and are not |
| * transaction-initiating, cf. SQL section 4.33. |
| * |
| * The tests are run in the cross product: |
| * |
| * {client/server, embedded} x |
| * {data base owner} |
| * |
| * See also RoutinesDefinersRightsTest, which tests the current user part of |
| * the SQLSessionContext. |
| */ |
| public class SQLSessionContextTest extends BaseJDBCTestCase |
| { |
| /* internal state */ |
| private Connection _conn; |
| private Statement _stm; |
| |
| private final static String pwSuffix = "pwSuffix"; |
| |
| /** |
| * SQL states used by test |
| */ |
| private final static String userException = "38000"; |
| |
| /** |
| * The TestConfiguration.sqlAuthorizationDecorator decorator presumes |
| * TEST_DBO as dbo, so add it to set of valid users. It uses a fresh db |
| * 'dbsqlauth', not 'wombat'. |
| */ |
| private final static String[] users = {"TEST_DBO"}; |
| |
| /** |
| * SQL keywords for session context state variables |
| */ |
| private final static String[] variableKeywords = |
| new String[]{"role", "schema"}; |
| |
| /** |
| * SQL prefix used to retrieve current value of a session context |
| * state variable. Cf. variableKeywords. |
| */ |
| private final static String[] currentPrefix = |
| new String[]{"current_", "current "}; |
| |
| /** |
| * Create a new instance of SQLSessionContextTest. |
| * |
| * @param name Fixture name |
| */ |
| |
| public SQLSessionContextTest(String name) |
| { |
| super(name); |
| } |
| |
| |
| /** |
| * Construct top level suite in this JUnit test |
| * |
| * @return A suite containing embedded and client suites. |
| * Client/server suite commented out to speed up this test as |
| * it does not add much value given the nature of the changes |
| * (SQL language only). |
| */ |
| public static Test suite() |
| { |
| BaseTestSuite suite = new BaseTestSuite("SQLSessionContextTest"); |
| |
| /* Positive tests */ |
| if (!JDBC.vmSupportsJSR169()) { |
| // JSR169 cannot run with tests with stored procedures |
| // that do database access - for they require a |
| // DriverManager connection to jdbc:default:connection; |
| // DriverManager is not supported with JSR169. |
| suite.addTest( |
| positiveSuite("suite: positive, embedded")); |
| |
| suite.addTest( |
| TestConfiguration.clientServerDecorator( |
| positiveSuite("suite: positive, client"))); |
| } |
| |
| return suite; |
| } |
| |
| /** |
| * Construct suite of positive tests |
| * |
| * @param framework Derby framework indication |
| * |
| * @return A suite containing the positive test cases incarnated only |
| * for security level sqlAuthorization. |
| */ |
| private static Test positiveSuite(String framework) |
| { |
| /* Tests running with sql authorization set. |
| * First decorate with users, then with authentication + |
| * sqlAuthorization. |
| */ |
| |
| // add decorator for different users authenticated |
| BaseTestSuite usersSuite = |
| new BaseTestSuite("suite: positiveSuite"); |
| |
| // First decorate with users, then with authorization |
| // decorator |
| for (int userNo = 0; userNo < users.length; userNo++) { |
| usersSuite.addTest |
| (TestConfiguration.changeUserDecorator |
| (new SQLSessionContextTest("testPositive"), |
| users[userNo], |
| users[userNo].concat(pwSuffix))); |
| } |
| |
| return TestConfiguration.sqlAuthorizationDecorator( |
| DatabasePropertyTestSetup.builtinAuthentication( |
| usersSuite, users, pwSuffix)); |
| } |
| |
| /** |
| * Positive tests for SQL session state. |
| * |
| * @throws SQLException |
| */ |
| public void testPositive() throws SQLException |
| { |
| PreparedStatement[]ps = new PreparedStatement[]{null, null}; |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| ps[i] = _conn.prepareStatement("values " + curr); |
| } |
| |
| println("testPositive user="+getTestConfiguration().getUserName()); |
| |
| /* |
| * SET SESSION STATE variables |
| */ |
| _stm.executeUpdate("set role outermost"); |
| _stm.executeUpdate("set schema outermost"); |
| |
| /** |
| * The SQL standard requires we have an SQL session context |
| * see section 4.27.3, which is stacked. The stack is pushed |
| * at entry to a stored procedure or function. The current |
| * SQL session state is popped at end of stored procedure. We |
| * test two levels deep. |
| */ |
| _stm.execute("call test_dbo.p2()"); |
| |
| // Dynamic result set: At what time should CURRENT_<state> be |
| // evaluated? Logically at the inside (inside the nested |
| // SQL session context), so it should be "LEVEL2" also when |
| // accessed on outside (outer SQL session context) I |
| // think. Anyway, that's what's implemented: the |
| // activation of the call is still live and holds (the |
| // final state of) the nested SQL session context |
| // even when the procedure call has |
| // returned. |
| ResultSet prs = _stm.getResultSet(); // role |
| assertCurrent("role", prs, "LEVEL2"); |
| |
| _stm.getMoreResults(Statement.CLOSE_CURRENT_RESULT); |
| prs = _stm.getResultSet(); |
| assertCurrent("schema", prs, "LEVEL2"); |
| prs.close(); |
| |
| // check that state didn't get impacted by change inside p2 |
| // |
| for (int i= 0; i < variableKeywords.length; i++) { |
| ResultSet rs = ps[i].executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| } |
| |
| ResultSet rs = _stm.executeQuery("values test_dbo.f2()"); |
| |
| |
| // Gotcha: we need to fetch first row for embedded driver |
| // to actually call f2. The client driver prefetches, so |
| // call to f2 will get performed even without the next(): |
| rs.next(); |
| rs.close(); |
| |
| // check that state didn't get impacted by change inside f2 |
| // |
| for (int i= 0; i < variableKeywords.length; i++) { |
| rs = ps[i].executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| } |
| |
| // check that f2 doesn't impact f22 |
| rs = _stm.executeQuery("values test_dbo.f2() + test_dbo.f22()"); |
| rs.next(); |
| rs.close(); |
| |
| // check that pushed SQL session context was popped also |
| // when the callee gets an exception |
| try { |
| rs = _stm.executeQuery("values test_dbo.f3()"); |
| rs.next(); |
| } catch (SQLException e) { |
| assertSQLState(userException, e); |
| } |
| |
| for (int i= 0; i < 1; i++) { |
| rs = ps[i].executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| } |
| |
| // Test that when a nested routine drops a role/schema the |
| // caller's stacked value is correctly reset. See also javadoc |
| // for dropper. |
| _stm.execute("call test_dbo.dropper()"); |
| |
| String[] expected = new String[]{null, "TEST_DBO"}; |
| for (int i= 0; i < 1; i++) { |
| rs = ps[i].executeQuery(); |
| assertCurrent(variableKeywords[i], rs, expected[i]); |
| rs.close(); |
| } |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| ps[i].close(); |
| } |
| |
| // DERBY-3897: See |
| // |
| // RolesConferredPrivilegesTest#testDefaultCurrentRole and |
| // RolesConferredPrivilegesTest#testCurrentRoleInWeirdContexts |
| // |
| // which are also relevant tests for SQLSessionContext. |
| } |
| |
| |
| protected void setUp() throws Exception |
| { |
| super.setUp(); |
| |
| _stm = createStatement(); |
| _conn = getConnection(); |
| |
| _stm.executeUpdate("create role outermost"); |
| _stm.executeUpdate("create role level2"); |
| _stm.executeUpdate("create role innermost"); |
| _stm.executeUpdate("create schema outermost"); |
| _stm.executeUpdate("create schema level2"); |
| _stm.executeUpdate("create schema innermost"); |
| |
| _stm.executeUpdate( |
| "create procedure p2()" + |
| " dynamic result sets 2 language java parameter style java"+ |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.p2'" + |
| " modifies sql data"); |
| _stm.executeUpdate( |
| "create function f2()" + |
| " returns int language java parameter style java" + |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.f2'" + |
| " reads sql data"); |
| _stm.executeUpdate( |
| "create function f22()" + |
| " returns int language java parameter style java" + |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.f22'" + |
| " reads sql data"); |
| _stm.executeUpdate( |
| "create function f3()" + |
| " returns int language java parameter style java" + |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.f3'" + |
| " reads sql data"); |
| _stm.executeUpdate( |
| "create procedure calledNestedFromP2(state VARCHAR(255))" + |
| " language java parameter style java" + |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.calledNestedFromP2'" |
| + |
| " modifies sql data"); |
| _stm.executeUpdate( |
| "create procedure dropper()" + |
| " language java parameter style java" + |
| " external name 'org.apache.derbyTesting." + |
| "functionTests.tests.lang.SQLSessionContextTest.dropper'" + |
| " modifies sql data"); |
| } |
| |
| |
| protected void tearDown() throws Exception |
| { |
| try { |
| _stm.executeUpdate("set schema test_dbo"); |
| // "OUTERMOST" role/schema is dropped as part of the test, |
| // cf. stored procedure 'dropper'. |
| _stm.executeUpdate("drop role level2"); |
| _stm.executeUpdate("drop role innermost"); |
| _stm.executeUpdate("drop schema level2 restrict"); |
| _stm.executeUpdate("drop schema innermost restrict"); |
| _stm.executeUpdate("drop procedure p2"); |
| _stm.executeUpdate("drop function f2"); |
| _stm.executeUpdate("drop function f22"); |
| _stm.executeUpdate("drop function f3"); |
| _stm.executeUpdate("drop procedure calledNestedFromP2"); |
| _stm.executeUpdate("drop procedure dropper"); |
| } |
| finally { |
| if (_stm != null) { |
| _stm = null; |
| } |
| |
| if (_conn != null) { |
| _conn = null; |
| } |
| |
| super.tearDown(); |
| } |
| |
| } |
| |
| |
| private static void assertCurrent(String sessionVar, |
| ResultSet rs, |
| String expected) |
| throws SQLException |
| { |
| assertTrue("result set empty", rs.next()); |
| String actualCurrent = rs.getString(1); |
| |
| if (sessionVar.equals("role") && expected != null) { |
| // returned current_role is a delimited identifer, which is SQL |
| // standard compliant. current_schema returns case normal form, |
| // which is not. |
| expected = "\"" + expected + "\""; |
| } |
| |
| if (expected != null) { |
| assertTrue(sessionVar + ": current is " + actualCurrent + |
| ", expected " + expected, |
| expected.equals(actualCurrent)); |
| } else { |
| assertTrue(sessionVar + ": current is " + actualCurrent + |
| ", expected null", |
| actualCurrent == null); |
| } |
| |
| |
| |
| // cardinality should be 1 |
| assertFalse("result set not empty", rs.next()); |
| } |
| |
| |
| /** |
| * Utility procedure used to test that current session state is |
| * stacked correctly in a nested SQL session context. |
| */ |
| public static void p2(ResultSet[] rs1, ResultSet[] rs2) |
| throws Throwable |
| { |
| Connection conn1 = null; |
| Connection conn2 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| |
| // check that we inherit context correctly |
| // current role, cf. SQL 4.34.1.1 and 4.27.3 |
| // current default schema, cf. SQL 4.37.3 and 4.37.5 |
| ResultSet rs = ps.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| |
| // set the state to "LEVEL2" |
| Statement stm = conn1.createStatement(); |
| stm.executeUpdate("set " + variableKeywords[i] + " level2"); |
| rs = ps.executeQuery(); |
| |
| // check that state got set |
| assertCurrent(variableKeywords[i], rs, "LEVEL2"); |
| |
| // another nesting level to test session context stack |
| // even more |
| conn1.commit(); // need to be idle |
| stm.executeUpdate("call test_dbo.calledNestedFromP2('" |
| + variableKeywords[i] + "')"); |
| |
| rs = ps.executeQuery(); |
| |
| // check that state didn't get impacted by change inside |
| // calledNestedFromP2 to 'INNERMOST': |
| assertCurrent(variableKeywords[i], rs, "LEVEL2"); |
| rs.close(); |
| |
| // Test that the state is shared by another nested |
| // connection also. |
| conn2 = DriverManager.getConnection("jdbc:default:connection"); |
| PreparedStatement ps2 = |
| conn2.prepareStatement("values " + curr); |
| rs = ps2.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "LEVEL2"); |
| rs.close(); |
| ps2.close(); |
| } |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| // Pass out CURRENT <state> in a dynamic result set. |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| ResultSet rs = ps.executeQuery(); |
| |
| if (variableKeywords[i].equals("role")) { |
| rs1[0] = rs; |
| } else { |
| rs2[0] = rs; |
| } |
| } |
| } catch (Throwable e) { |
| // Print here if debug, since on outside we only see 38000 |
| // (user error) |
| println("err: " + e); |
| |
| throw e; |
| |
| } 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 stateString) |
| throws SQLException |
| { |
| Connection conn1 = null; |
| Connection conn2 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| |
| // CURRENT_ROLE vs CURRENT SCHEMA syntax: |
| String curr = stateString.equals("role") |
| ? "current_role" |
| : "current schema"; |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| |
| // check that we inherit state correctly |
| ResultSet rs = ps.executeQuery(); |
| assertCurrent(stateString, rs, "LEVEL2"); |
| rs.close(); |
| |
| // set the state to "INNERMOST" |
| Statement stm = conn1.createStatement(); |
| stm.executeUpdate("set " + stateString + " innermost"); |
| rs = ps.executeQuery(); |
| |
| // check that state got set |
| assertCurrent(stateString, rs, "INNERMOST"); |
| rs.close(); |
| ps.close(); |
| |
| // Test that the session context is shared by another |
| // nested connection also. |
| conn2 = DriverManager.getConnection("jdbc:default:connection"); |
| ps = conn2.prepareStatement("values " + curr); |
| rs = ps.executeQuery(); |
| assertCurrent(stateString, rs, "INNERMOST"); |
| rs.close(); |
| ps.close(); |
| |
| } finally { |
| if (conn1 != null) { |
| try { |
| conn1.close(); |
| } catch (Exception e) { |
| } |
| } |
| if (conn2 != null) { |
| try { |
| conn2.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Utility function used to test that state variables are stacked |
| * correctly in a nested SQL session context. |
| */ |
| public static int f2() throws SQLException |
| { |
| Connection conn1 = null; |
| Connection conn2 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| |
| // check that we inherit context correctly |
| ResultSet rs = ps.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| |
| // set the state to "LEVEL2" |
| Statement stm = conn1.createStatement(); |
| stm.executeUpdate("set " + variableKeywords[i] + " level2"); |
| rs = ps.executeQuery(); |
| |
| // check that state got set |
| assertCurrent(variableKeywords[i], rs, "LEVEL2"); |
| |
| ps.close(); |
| |
| // Test that the state is shared by another nested |
| // connection also. |
| conn2 = DriverManager.getConnection("jdbc:default:connection"); |
| ps = conn2.prepareStatement("values " + curr); |
| rs = ps.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "LEVEL2"); |
| rs.close(); |
| ps.close(); |
| } |
| |
| } finally { |
| |
| if (conn1 != null) { |
| try { |
| conn1.close(); |
| } catch (Exception e) { |
| } |
| } |
| if (conn2 != null) { |
| try { |
| conn2.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| /** |
| * Used to test that a parallel (subsequent) function call in the |
| * same statement (called after f2 which changes state) is not |
| * affected by the preceding function (f2) call's changes. |
| * |
| */ |
| public static int f22() throws SQLException |
| { |
| Connection conn1 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| |
| for (int i= 0; i < variableKeywords.length; i++) { |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| |
| // check that we inherit context correctly |
| ResultSet rs = ps.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, "OUTERMOST"); |
| rs.close(); |
| ps.close(); |
| } |
| |
| } finally { |
| |
| if (conn1 != null) { |
| try { |
| conn1.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| /** |
| * Utility function used to test that current state is stacked |
| * correctly in a nested SQL session context. This particular |
| * function will throw and exception. This is used to test that |
| * the cleaning up pops the SQL session stack also. |
| */ |
| public static int f3() throws SQLException |
| { |
| Connection conn1 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| // set the state to something else |
| Statement stm = conn1.createStatement(); |
| stm.executeUpdate("set role level2"); |
| stm.executeUpdate("set schema level2"); |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values 42"); |
| ResultSet rs = ps.executeQuery(); |
| rs.next(); |
| // force an exception, rs. is empty, next should throw now |
| rs.next(); |
| String s = rs.getString(1); |
| } finally { |
| if (conn1 != null) { |
| try { |
| conn1.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| return 1; |
| } |
| |
| |
| /** |
| * Test that when a nested routine drops a role/schema, the |
| * current value is correctly reset. For roles, the current role |
| * is unchanged, since it is lazily checked (and potentially reset |
| * to NONE if it no longer exists or it is no longer granted to |
| * session user) only when it is attempted used for anything. For |
| * schema, the current schema should revert back to the session's |
| * default schema. This holds for all frames on the session |
| * context stack (see also caller's check). |
| */ |
| public static void dropper() throws SQLException |
| { |
| Connection conn1 = null; |
| |
| try { |
| conn1 = DriverManager.getConnection("jdbc:default:connection"); |
| |
| // Drop current contexts |
| Statement stm = conn1.createStatement(); |
| stm.executeUpdate("drop role outermost"); |
| stm.executeUpdate("drop schema outermost restrict"); |
| stm.close(); |
| |
| String[] expected = new String[]{null, "TEST_DBO"}; |
| |
| // check that we revert correctly |
| for (int i= 0; i < variableKeywords.length; i++) { |
| String curr = currentPrefix[i] + variableKeywords[i]; |
| |
| PreparedStatement ps = |
| conn1.prepareStatement("values " + curr); |
| |
| ResultSet rs = ps.executeQuery(); |
| assertCurrent(variableKeywords[i], rs, expected[i]); |
| rs.close(); |
| ps.close(); |
| } |
| } finally { |
| if (conn1 != null) { |
| try { |
| conn1.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| } |
| } |